1+ /**
2+ * @callback RenderFunction
3+ * @param {Engine } engine
4+ * @param {Context } context
5+ */
6+
17function todo ( ) {
28 throw new Error ( "Not implemented" )
39}
@@ -6,33 +12,37 @@ const None = Symbol.for("None")
612const True = true
713const False = false
814
15+ const VarNotFound = ""
916
1017class Context {
18+ #entries = new Map ( )
19+ /** @type {Map<string,any>|Context }*/
20+ #parentContext = new Map ( Object . entries ( { None, True, False} ) )
21+
1122 constructor ( dict , parentContext = undefined ) {
12- this . _entries = new Map ( Object . entries ( dict ) )
13-
14- this . parentContext = (
15- parentContext instanceof Context
16- ? parentContext
17- : new Proxy ( new Map ( Object . entries ( { None, True, False} ) ) , {
18- get ( target , prop ) {
19- const value = target . get ( prop )
20- return value !== undefined ? value : ""
21- }
22- } )
23- )
24-
25- return new Proxy ( this , {
26- get ( target , prop ) {
27- const value = target . _entries . get ( prop )
28- return value !== undefined ? value : target . parentContext [ prop ]
29- }
30- } )
23+ this . #entries = new Map ( Object . entries ( dict ) )
24+ if ( parentContext instanceof Context ) {
25+ this . #parentContext = parentContext
26+ }
3127 }
3228
3329 extend ( dict ) {
3430 return new Context ( dict , this )
3531 }
32+
33+ has ( key ) {
34+ return this . #entries. has ( key ) || this . #parentContext. has ( key )
35+ }
36+
37+ get ( key ) {
38+ if ( this . #entries. has ( key ) ) {
39+ return this . #entries. get ( key )
40+ }
41+ if ( this . #parentContext. has ( key ) ) {
42+ return this . #parentContext. get ( key )
43+ }
44+ return VarNotFound
45+ }
3646}
3747
3848
@@ -64,10 +74,6 @@ function escape(value) {
6474 return SafeString . isSafe ( value ) ? value : new Option ( String ( value ) ) . innerHTML
6575}
6676
67- function toNumber ( value ) {
68- return Number . isFinite ( value ) ? value : parseFloat ( String ( value ) )
69- }
70-
7177function toInteger ( value ) {
7278 const result = parseInt ( value , 10 )
7379 if ( ! Number . isFinite ( result ) ) {
@@ -77,7 +83,8 @@ function toInteger(value) {
7783}
7884
7985class Filters {
80- constructor ( ) {
86+ constructor ( engine ) {
87+ this . engine = engine
8188 return new Proxy ( Object . freeze ( this ) , {
8289 get ( target , prop ) {
8390 const result = target [ prop ]
@@ -347,18 +354,93 @@ class Filters {
347354 }
348355}
349356
357+
358+ class Tags {
359+ #engine
360+
361+ constructor ( engine ) {
362+ this . #engine = engine
363+
364+ return new Proxy ( Object . freeze ( this ) , {
365+ get ( target , prop , receiver ) {
366+ if ( Reflect . has ( target , prop ) ) {
367+ return Reflect . get ( target , prop , receiver )
368+ }
369+ if ( Reflect . has ( target , `do_${ prop } ` ) ) {
370+ return Reflect . get ( target , `do_${ prop } ` , receiver ) . bind ( target )
371+ }
372+ throw new Error ( `Unknown tag: ${ prop } ` )
373+ }
374+ } )
375+ }
376+
377+ /**
378+ *
379+ * @param params
380+ * @param {Context } params.context
381+ * @param {Iterable } params.iter
382+ * @param {function(any): Object<string, any> } params.forloopVariables
383+ * @param {boolean } params.reversed
384+ * @param {RenderFunction } params.render
385+ * @param {RenderFunction | null } params.emptyRender
386+ * @returns {string }
387+ */
388+ do_for ( { context, iter, forloopVariables, render, emptyRender = null , reversed = false } ) {
389+ const parentloop = context . get ( "forloop" ) || None
390+ const container = Array . from ( iter )
391+
392+ function getContext ( counter , value ) {
393+ return context . extend ( {
394+ ...forloopVariables ( value ) ,
395+ forloop : {
396+ get counter ( ) {
397+ return counter + 1
398+ } ,
399+ get counter0 ( ) {
400+ return counter
401+ } ,
402+ get revcounter ( ) {
403+ return container . length - counter
404+ } ,
405+ get revcounter0 ( ) {
406+ return container . length - counter - 1
407+ } ,
408+ get first ( ) {
409+ return counter === 0
410+ } ,
411+ get last ( ) {
412+ return counter === container . length - 1
413+ } ,
414+ get length ( ) {
415+ return container . length
416+ } ,
417+ parentloop,
418+ }
419+ } )
420+ }
421+
422+ if ( container . length === 0 ) {
423+ return emptyRender ?. call ( null , this . #engine, context ) ?? VarNotFound
424+ }
425+ const reducer = reversed ? container . reduceRight . bind ( container ) : container . reduce . bind ( container )
426+ return reducer ( ( acc , value , idx ) => acc + render ( this . #engine, getContext ( idx , value ) ) , "" )
427+ }
428+ }
429+
350430const utils = Object . freeze ( {
351431 contains ( first , second ) {
352432 return Array . from ( first ) . contains ( second )
353433 } ,
354434 do_is ( first , second ) {
355435 return first === second
356- }
436+ } ,
357437} )
358438
359439class Engine {
440+ #tags = new Tags ( this )
441+ #filters = new Filters ( this )
442+
360443 constructor ( ) {
361- this . _filters = new Filters ( )
362444 this . _ = utils
363445 }
364446
@@ -371,25 +453,56 @@ class Engine {
371453 }
372454
373455 /**
374- * @param {string } varName
375- * @param {any } literal
456+ * @param {string[]|undefined } varNames
457+ * @param {any|undefined } literal
376458 * @param {Context } context
377- * @param {Filters } filters
378- * @returns {string }
459+ * @param {string[] } filters
460+ * @returns {any }
379461 */
380- variable ( { varName, literal, context, filters} ) {
381- let value = literal || context [ varName ]
462+ variable ( { varNames = [ ] , literal = undefined , context, filters} ) {
463+ if ( literal !== undefined ) {
464+ return `${ literal } `
465+ }
466+
467+ let target = context
468+ for ( let varName of varNames ) {
469+ const orig = target
470+ if ( target instanceof Context || target instanceof Map ) {
471+ target = target . get ( varName )
472+ } else if ( ( target instanceof Set && target . has ( varName ) ) || Object . hasOwn ( target , varName ) ) {
473+ target = target [ varName ]
474+ } else {
475+ target = VarNotFound
476+ }
477+
478+ if ( typeof target === "function" ) {
479+ target = target . call ( orig )
480+ } else if ( target === null || target === undefined ) {
481+ target = VarNotFound
482+ }
483+ }
484+
382485 for ( const { filterName, argument} of filters ) {
383- value = escape ( this . _filters [ filterName ] ( value , argument ) )
486+ target = this . #filters [ filterName ] ( target , argument )
384487 }
385- return value
488+
489+ return target
490+ }
491+
492+ escape ( value ) {
493+ return escape ( `${ value } ` )
494+ }
495+
496+ tag ( { tagName, args, context} ) {
497+ return this . #tags[ tagName ] ( { ...args , context} )
386498 }
387499
388500 translate ( value ) {
389501 console . warn ( "Translation not implemented" )
390502 return value
391503 }
392504
505+
393506}
394507
395508export default Engine
0 commit comments