binja 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -27,10 +27,11 @@
27
27
  |---------|-----------|------------------|
28
28
  | **Runtime Performance** | ✅ 2-4x faster | ❌ |
29
29
  | **AOT Compilation** | ✅ 160x faster | ❌ |
30
+ | **Multi-Engine** | ✅ Jinja2, Handlebars, Liquid | ❌ |
30
31
  | Django DTL Compatible | ✅ 100% | ❌ Partial |
31
32
  | Jinja2 Compatible | ✅ Full | ⚠️ Limited |
32
33
  | Template Inheritance | ✅ | ⚠️ |
33
- | 80+ Built-in Filters | ✅ | ❌ |
34
+ | 84 Built-in Filters | ✅ | ❌ |
34
35
  | 28 Built-in Tests | ✅ | ❌ |
35
36
  | Debug Panel | ✅ | ❌ |
36
37
  | CLI Tool | ✅ | ⚠️ |
@@ -418,6 +419,69 @@ console.log(Object.keys(builtinTests))
418
419
 
419
420
  ---
420
421
 
422
+ ## Multi-Engine Support
423
+
424
+ Binja supports multiple template engines through a unified API. All engines parse to a common AST and share the same runtime, filters, and optimizations.
425
+
426
+ ### Supported Engines
427
+
428
+ | Engine | Syntax | Use Case |
429
+ |--------|--------|----------|
430
+ | **Jinja2/DTL** | `{{ var }}` `{% if %}` | Default, Python/Django compatibility |
431
+ | **Handlebars** | `{{var}}` `{{#if}}` | JavaScript ecosystem, Ember.js |
432
+ | **Liquid** | `{{ var }}` `{% if %}` | Shopify, Jekyll, static sites |
433
+
434
+ ### Usage
435
+
436
+ ```typescript
437
+ // Direct engine imports
438
+ import * as handlebars from 'binja/engines/handlebars'
439
+ import * as liquid from 'binja/engines/liquid'
440
+
441
+ // Handlebars
442
+ await handlebars.render('Hello {{name}}!', { name: 'World' })
443
+ await handlebars.render('{{#each items}}{{this}}{{/each}}', { items: ['a', 'b'] })
444
+ await handlebars.render('{{{html}}}', { html: '<b>unescaped</b>' })
445
+
446
+ // Liquid (Shopify)
447
+ await liquid.render('Hello {{ name }}!', { name: 'World' })
448
+ await liquid.render('{% for item in items %}{{ item }}{% endfor %}', { items: ['a', 'b'] })
449
+ await liquid.render('{% assign x = "value" %}{{ x }}', {})
450
+ ```
451
+
452
+ ### MultiEngine API
453
+
454
+ ```typescript
455
+ import { MultiEngine } from 'binja/engines'
456
+
457
+ const engine = new MultiEngine()
458
+
459
+ // Render with any engine
460
+ await engine.render('Hello {{name}}!', { name: 'World' }, 'handlebars')
461
+ await engine.render('Hello {{ name }}!', { name: 'World' }, 'liquid')
462
+ await engine.render('Hello {{ name }}!', { name: 'World' }, 'jinja2')
463
+
464
+ // Auto-detect from file extension
465
+ import { detectEngine } from 'binja/engines'
466
+ const eng = detectEngine('template.hbs') // Returns Handlebars engine
467
+ const eng2 = detectEngine('page.liquid') // Returns Liquid engine
468
+ ```
469
+
470
+ ### Engine Feature Matrix
471
+
472
+ | Feature | Jinja2 | Handlebars | Liquid |
473
+ |---------|--------|------------|--------|
474
+ | Variables | `{{ x }}` | `{{x}}` | `{{ x }}` |
475
+ | Conditionals | `{% if %}` | `{{#if}}` | `{% if %}` |
476
+ | Loops | `{% for %}` | `{{#each}}` | `{% for %}` |
477
+ | Filters | `{{ x\|filter }}` | `{{ x }}` | `{{ x \| filter }}` |
478
+ | Raw output | `{% raw %}` | - | `{% raw %}` |
479
+ | Comments | `{# #}` | `{{! }}` | `{% comment %}` |
480
+ | Assignment | `{% set %}` | - | `{% assign %}` |
481
+ | Unescaped | `{{ x\|safe }}` | `{{{x}}}` | - |
482
+
483
+ ---
484
+
421
485
  ## Django Compatibility
422
486
 
423
487
  binja is designed to be a drop-in replacement for Django templates:
package/dist/cli.js CHANGED
@@ -1,17 +1,17 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
- import*as X from"fs";import*as Y from"path";var P={and:"AND",or:"OR",not:"NOT",true:"NAME",false:"NAME",True:"NAME",False:"NAME",None:"NAME",none:"NAME",is:"NAME",in:"NAME"};var b={red:"\x1B[31m",yellow:"\x1B[33m",cyan:"\x1B[36m",gray:"\x1B[90m",bold:"\x1B[1m",dim:"\x1B[2m",reset:"\x1B[0m"},p=process.stdout?.isTTY!==!1;function q(A,B){return p?`${b[A]}${B}${b.reset}`:B}class F extends Error{line;column;source;templateName;suggestion;constructor(A,B){let R=v("TemplateSyntaxError",A,B);super(R);this.name="TemplateSyntaxError",this.line=B.line,this.column=B.column,this.source=B.source,this.templateName=B.templateName,this.suggestion=B.suggestion}}function v(A,B,R){let K=[],O=R.templateName?`${R.templateName}:${R.line}:${R.column}`:`line ${R.line}, column ${R.column}`;if(K.push(`${q("red",q("bold",A))}: ${B} at ${q("cyan",O)}`),R.source)K.push(""),K.push(d(R.source,R.line,R.column));if(R.suggestion)K.push(""),K.push(`${q("yellow","Did you mean")}: ${q("cyan",R.suggestion)}?`);if(R.availableOptions&&R.availableOptions.length>0){K.push("");let Q=R.availableOptions.slice(0,8),U=R.availableOptions.length>8?` ${q("gray",`... and ${R.availableOptions.length-8} more`)}`:"";K.push(`${q("gray","Available")}: ${Q.join(", ")}${U}`)}return K.join(`
3
+ import*as X from"fs";import*as Y from"path";var P={and:"AND",or:"OR",not:"NOT",true:"NAME",false:"NAME",True:"NAME",False:"NAME",None:"NAME",none:"NAME",is:"NAME",in:"NAME"};var x={red:"\x1B[31m",yellow:"\x1B[33m",cyan:"\x1B[36m",gray:"\x1B[90m",bold:"\x1B[1m",dim:"\x1B[2m",reset:"\x1B[0m"},p=process.stdout?.isTTY!==!1;function q(A,B){return p?`${x[A]}${B}${x.reset}`:B}class H extends Error{line;column;source;templateName;suggestion;constructor(A,B){let R=v("TemplateSyntaxError",A,B);super(R);this.name="TemplateSyntaxError",this.line=B.line,this.column=B.column,this.source=B.source,this.templateName=B.templateName,this.suggestion=B.suggestion}}function v(A,B,R){let K=[],O=R.templateName?`${R.templateName}:${R.line}:${R.column}`:`line ${R.line}, column ${R.column}`;if(K.push(`${q("red",q("bold",A))}: ${B} at ${q("cyan",O)}`),R.source)K.push(""),K.push(d(R.source,R.line,R.column));if(R.suggestion)K.push(""),K.push(`${q("yellow","Did you mean")}: ${q("cyan",R.suggestion)}?`);if(R.availableOptions&&R.availableOptions.length>0){K.push("");let Q=R.availableOptions.slice(0,8),U=R.availableOptions.length>8?` ${q("gray",`... and ${R.availableOptions.length-8} more`)}`:"";K.push(`${q("gray","Available")}: ${Q.join(", ")}${U}`)}return K.join(`
4
4
  `)}function d(A,B,R){let K=A.split(`
5
- `),O=[],Q=Math.max(1,B-2),U=Math.min(K.length,B+1),M=String(U).length;for(let Z=Q;Z<=U;Z++){let $=K[Z-1]||"",G=String(Z).padStart(M," ");if(Z===B){O.push(`${q("red"," \u2192")} ${q("gray",G)} ${q("dim","\u2502")} ${$}`);let H=" ".repeat(M+4+Math.max(0,R-1)),E=q("red","^");O.push(`${H}${E}`)}else O.push(` ${q("gray",G)} ${q("dim","\u2502")} ${q("gray",$)}`)}return O.join(`
6
- `)}class D{state;variableStart;variableEnd;blockStart;blockEnd;commentStart;commentEnd;constructor(A,B={}){this.state={source:A,pos:0,line:1,column:1,tokens:[]},this.variableStart=B.variableStart??"{{",this.variableEnd=B.variableEnd??"}}",this.blockStart=B.blockStart??"{%",this.blockEnd=B.blockEnd??"%}",this.commentStart=B.commentStart??"{#",this.commentEnd=B.commentEnd??"#}"}tokenize(){while(!this.isAtEnd())this.scanToken();return this.addToken("EOF",""),this.state.tokens}scanToken(){if(this.match(this.variableStart)){this.addToken("VARIABLE_START",this.variableStart),this.scanExpression(this.variableEnd,"VARIABLE_END");return}if(this.match(this.blockStart)){let A=this.peek()==="-";if(A)this.advance();let B=this.state.pos;if(this.skipWhitespace(),this.checkWord("raw")||this.checkWord("verbatim")){let R=this.checkWord("raw")?"raw":"verbatim";this.scanRawBlock(R,A);return}this.state.pos=B,this.addToken("BLOCK_START",this.blockStart+(A?"-":"")),this.scanExpression(this.blockEnd,"BLOCK_END");return}if(this.match(this.commentStart)){this.scanComment();return}this.scanText()}checkWord(A){let B=this.state.pos;for(let K=0;K<A.length;K++)if(this.state.source[B+K]?.toLowerCase()!==A[K])return!1;let R=this.state.source[B+A.length];return!R||!this.isAlphaNumeric(R)}scanRawBlock(A,B){let R=this.state.line,K=this.state.column;for(let U=0;U<A.length;U++)this.advance();if(this.skipWhitespace(),this.peek()==="-")this.advance();if(!this.match(this.blockEnd))throw new F(`Expected '${this.blockEnd}' after '${A}'`,{line:this.state.line,column:this.state.column,source:this.state.source});let O=`end${A}`,Q=this.state.pos;while(!this.isAtEnd()){if(this.check(this.blockStart)){let U=this.state.pos,M=this.state.line,Z=this.state.column;if(this.match(this.blockStart),this.peek()==="-")this.advance();if(this.skipWhitespace(),this.checkWord(O)){let $=this.state.source.slice(Q,U);if($.length>0)this.state.tokens.push({type:"TEXT",value:$,line:R,column:K});for(let G=0;G<O.length;G++)this.advance();if(this.skipWhitespace(),this.peek()==="-")this.advance();if(!this.match(this.blockEnd))throw new F(`Expected '${this.blockEnd}' after '${O}'`,{line:this.state.line,column:this.state.column,source:this.state.source});return}this.state.pos=U,this.state.line=M,this.state.column=Z}if(this.peek()===`
7
- `)this.state.line++,this.state.column=0;this.advance()}throw new F(`Unclosed '${A}' block`,{line:R,column:K,source:this.state.source,suggestion:`Add {% end${A} %} to close the block`})}scanText(){let A=this.state.pos,B=this.state.line,R=this.state.column;while(!this.isAtEnd()){if(this.check(this.variableStart)||this.check(this.blockStart)||this.check(this.commentStart))break;if(this.peek()===`
8
- `)this.state.line++,this.state.column=0;this.advance()}if(this.state.pos>A){let K=this.state.source.slice(A,this.state.pos);this.state.tokens.push({type:"TEXT",value:K,line:B,column:R})}}scanExpression(A,B){this.skipWhitespace();while(!this.isAtEnd()){if(this.skipWhitespace(),this.peek()==="-"&&this.check(A,1))this.advance();if(this.match(A)){this.addToken(B,A);return}this.scanExpressionToken()}throw new F("Unclosed template tag",{line:this.state.line,column:this.state.column,source:this.state.source,suggestion:`Add closing delimiter '${A}'`})}scanExpressionToken(){if(this.skipWhitespace(),this.isAtEnd())return;let A=this.peek();if(A==='"'||A==="'"){this.scanString(A);return}if(this.isDigit(A)){this.scanNumber();return}if(this.isAlpha(A)||A==="_"){this.scanIdentifier();return}this.scanOperator()}scanString(A){this.advance();let B=this.state.pos;while(!this.isAtEnd()&&this.peek()!==A){if(this.peek()==="\\"&&this.peekNext()===A)this.advance();if(this.peek()===`
9
- `)this.state.line++,this.state.column=0;this.advance()}if(this.isAtEnd())throw new F("Unterminated string literal",{line:this.state.line,column:this.state.column,source:this.state.source,suggestion:`Add closing quote '${A}'`});let R=this.state.source.slice(B,this.state.pos);this.advance(),this.addToken("STRING",R)}scanNumber(){let A=this.state.pos;while(this.isDigit(this.peek()))this.advance();if(this.peek()==="."&&this.isDigit(this.peekNext())){this.advance();while(this.isDigit(this.peek()))this.advance()}let B=this.state.source.slice(A,this.state.pos);this.addToken("NUMBER",B)}scanIdentifier(){let A=this.state.pos;while(this.isAlphaNumeric(this.peek())||this.peek()==="_")this.advance();let B=this.state.source.slice(A,this.state.pos),R=P[B]??"NAME";this.addToken(R,B)}scanOperator(){let A=this.advance();switch(A){case".":this.addToken("DOT",A);break;case",":this.addToken("COMMA",A);break;case":":this.addToken("COLON",A);break;case"|":this.addToken("PIPE",A);break;case"(":this.addToken("LPAREN",A);break;case")":this.addToken("RPAREN",A);break;case"[":this.addToken("LBRACKET",A);break;case"]":this.addToken("RBRACKET",A);break;case"{":this.addToken("LBRACE",A);break;case"}":this.addToken("RBRACE",A);break;case"+":this.addToken("ADD",A);break;case"-":this.addToken("SUB",A);break;case"*":this.addToken("MUL",A);break;case"/":this.addToken("DIV",A);break;case"%":this.addToken("MOD",A);break;case"~":this.addToken("TILDE",A);break;case"=":if(this.match("="))this.addToken("EQ","==");else this.addToken("ASSIGN","=");break;case"!":if(this.match("="))this.addToken("NE","!=");else throw new F("Unexpected character '!'",{line:this.state.line,column:this.state.column-1,source:this.state.source,suggestion:"Use '!=' for not-equal comparison or 'not' for negation"});break;case"<":if(this.match("="))this.addToken("LE","<=");else this.addToken("LT","<");break;case">":if(this.match("="))this.addToken("GE",">=");else this.addToken("GT",">");break;default:if(!this.isWhitespace(A))throw new F(`Unexpected character '${A}'`,{line:this.state.line,column:this.state.column-1,source:this.state.source})}}scanComment(){while(!this.isAtEnd()&&!this.check(this.commentEnd)){if(this.peek()===`
5
+ `),O=[],Q=Math.max(1,B-2),U=Math.min(K.length,B+1),M=String(U).length;for(let Z=Q;Z<=U;Z++){let $=K[Z-1]||"",G=String(Z).padStart(M," ");if(Z===B){O.push(`${q("red"," \u2192")} ${q("gray",G)} ${q("dim","\u2502")} ${$}`);let z=" ".repeat(M+4+Math.max(0,R-1)),E=q("red","^");O.push(`${z}${E}`)}else O.push(` ${q("gray",G)} ${q("dim","\u2502")} ${q("gray",$)}`)}return O.join(`
6
+ `)}class D{state;variableStart;variableEnd;blockStart;blockEnd;commentStart;commentEnd;constructor(A,B={}){this.state={source:A,pos:0,line:1,column:1,tokens:[]},this.variableStart=B.variableStart??"{{",this.variableEnd=B.variableEnd??"}}",this.blockStart=B.blockStart??"{%",this.blockEnd=B.blockEnd??"%}",this.commentStart=B.commentStart??"{#",this.commentEnd=B.commentEnd??"#}"}tokenize(){while(!this.isAtEnd())this.scanToken();return this.addToken("EOF",""),this.state.tokens}scanToken(){if(this.match(this.variableStart)){this.addToken("VARIABLE_START",this.variableStart),this.scanExpression(this.variableEnd,"VARIABLE_END");return}if(this.match(this.blockStart)){let A=this.peek()==="-";if(A)this.advance();let B=this.state.pos;if(this.skipWhitespace(),this.checkWord("raw")||this.checkWord("verbatim")){let R=this.checkWord("raw")?"raw":"verbatim";this.scanRawBlock(R,A);return}this.state.pos=B,this.addToken("BLOCK_START",this.blockStart+(A?"-":"")),this.scanExpression(this.blockEnd,"BLOCK_END");return}if(this.match(this.commentStart)){this.scanComment();return}this.scanText()}checkWord(A){let B=this.state.pos;for(let K=0;K<A.length;K++)if(this.state.source[B+K]?.toLowerCase()!==A[K])return!1;let R=this.state.source[B+A.length];return!R||!this.isAlphaNumeric(R)}scanRawBlock(A,B){let R=this.state.line,K=this.state.column;for(let U=0;U<A.length;U++)this.advance();if(this.skipWhitespace(),this.peek()==="-")this.advance();if(!this.match(this.blockEnd))throw new H(`Expected '${this.blockEnd}' after '${A}'`,{line:this.state.line,column:this.state.column,source:this.state.source});let O=`end${A}`,Q=this.state.pos;while(!this.isAtEnd()){if(this.check(this.blockStart)){let U=this.state.pos,M=this.state.line,Z=this.state.column;if(this.match(this.blockStart),this.peek()==="-")this.advance();if(this.skipWhitespace(),this.checkWord(O)){let $=this.state.source.slice(Q,U);if($.length>0)this.state.tokens.push({type:"TEXT",value:$,line:R,column:K});for(let G=0;G<O.length;G++)this.advance();if(this.skipWhitespace(),this.peek()==="-")this.advance();if(!this.match(this.blockEnd))throw new H(`Expected '${this.blockEnd}' after '${O}'`,{line:this.state.line,column:this.state.column,source:this.state.source});return}this.state.pos=U,this.state.line=M,this.state.column=Z}if(this.peek()===`
7
+ `)this.state.line++,this.state.column=0;this.advance()}throw new H(`Unclosed '${A}' block`,{line:R,column:K,source:this.state.source,suggestion:`Add {% end${A} %} to close the block`})}scanText(){let A=this.state.pos,B=this.state.line,R=this.state.column;while(!this.isAtEnd()){if(this.check(this.variableStart)||this.check(this.blockStart)||this.check(this.commentStart))break;if(this.peek()===`
8
+ `)this.state.line++,this.state.column=0;this.advance()}if(this.state.pos>A){let K=this.state.source.slice(A,this.state.pos);this.state.tokens.push({type:"TEXT",value:K,line:B,column:R})}}scanExpression(A,B){this.skipWhitespace();while(!this.isAtEnd()){if(this.skipWhitespace(),this.peek()==="-"&&this.check(A,1))this.advance();if(this.match(A)){this.addToken(B,A);return}this.scanExpressionToken()}throw new H("Unclosed template tag",{line:this.state.line,column:this.state.column,source:this.state.source,suggestion:`Add closing delimiter '${A}'`})}scanExpressionToken(){if(this.skipWhitespace(),this.isAtEnd())return;let A=this.peek();if(A==='"'||A==="'"){this.scanString(A);return}if(this.isDigit(A)){this.scanNumber();return}if(this.isAlpha(A)||A==="_"){this.scanIdentifier();return}this.scanOperator()}scanString(A){this.advance();let B=this.state.pos;while(!this.isAtEnd()&&this.peek()!==A){if(this.peek()==="\\"&&this.peekNext()===A)this.advance();if(this.peek()===`
9
+ `)this.state.line++,this.state.column=0;this.advance()}if(this.isAtEnd())throw new H("Unterminated string literal",{line:this.state.line,column:this.state.column,source:this.state.source,suggestion:`Add closing quote '${A}'`});let R=this.state.source.slice(B,this.state.pos);this.advance(),this.addToken("STRING",R)}scanNumber(){let A=this.state.pos;while(this.isDigit(this.peek()))this.advance();if(this.peek()==="."&&this.isDigit(this.peekNext())){this.advance();while(this.isDigit(this.peek()))this.advance()}let B=this.state.source.slice(A,this.state.pos);this.addToken("NUMBER",B)}scanIdentifier(){let A=this.state.pos;while(this.isAlphaNumeric(this.peek())||this.peek()==="_")this.advance();let B=this.state.source.slice(A,this.state.pos),R=P[B]??"NAME";this.addToken(R,B)}scanOperator(){let A=this.advance();switch(A){case".":this.addToken("DOT",A);break;case",":this.addToken("COMMA",A);break;case":":this.addToken("COLON",A);break;case"|":this.addToken("PIPE",A);break;case"(":this.addToken("LPAREN",A);break;case")":this.addToken("RPAREN",A);break;case"[":this.addToken("LBRACKET",A);break;case"]":this.addToken("RBRACKET",A);break;case"{":this.addToken("LBRACE",A);break;case"}":this.addToken("RBRACE",A);break;case"+":this.addToken("ADD",A);break;case"-":this.addToken("SUB",A);break;case"*":this.addToken("MUL",A);break;case"/":this.addToken("DIV",A);break;case"%":this.addToken("MOD",A);break;case"~":this.addToken("TILDE",A);break;case"=":if(this.match("="))this.addToken("EQ","==");else this.addToken("ASSIGN","=");break;case"!":if(this.match("="))this.addToken("NE","!=");else throw new H("Unexpected character '!'",{line:this.state.line,column:this.state.column-1,source:this.state.source,suggestion:"Use '!=' for not-equal comparison or 'not' for negation"});break;case"<":if(this.match("="))this.addToken("LE","<=");else this.addToken("LT","<");break;case">":if(this.match("="))this.addToken("GE",">=");else this.addToken("GT",">");break;default:if(!this.isWhitespace(A))throw new H(`Unexpected character '${A}'`,{line:this.state.line,column:this.state.column-1,source:this.state.source})}}scanComment(){while(!this.isAtEnd()&&!this.check(this.commentEnd)){if(this.peek()===`
10
10
  `)this.state.line++,this.state.column=0;this.advance()}if(!this.isAtEnd())this.match(this.commentEnd)}isAtEnd(){return this.state.pos>=this.state.source.length}peek(){if(this.isAtEnd())return"\x00";return this.state.source[this.state.pos]}peekNext(){if(this.state.pos+1>=this.state.source.length)return"\x00";return this.state.source[this.state.pos+1]}advance(){let A=this.state.source[this.state.pos];return this.state.pos++,this.state.column++,A}match(A,B=0){let R=this.state.source,K=this.state.pos+B,O=A.length;if(K+O>R.length)return!1;for(let Q=0;Q<O;Q++)if(R[K+Q]!==A[Q])return!1;if(B===0)this.state.pos+=O,this.state.column+=O;return!0}check(A,B=0){let R=this.state.source,K=this.state.pos+B,O=A.length;if(K+O>R.length)return!1;for(let Q=0;Q<O;Q++)if(R[K+Q]!==A[Q])return!1;return!0}skipWhitespace(){while(!this.isAtEnd()&&this.isWhitespace(this.peek())){if(this.peek()===`
11
11
  `)this.state.line++,this.state.column=0;this.advance()}}isWhitespace(A){return A===" "||A==="\t"||A===`
12
- `||A==="\r"}isDigit(A){let B=A.charCodeAt(0);return B>=48&&B<=57}isAlpha(A){let B=A.charCodeAt(0);return B>=97&&B<=122||B>=65&&B<=90}isAlphaNumeric(A){let B=A.charCodeAt(0);return B>=48&&B<=57||B>=97&&B<=122||B>=65&&B<=90}addToken(A,B){this.state.tokens.push({type:A,value:B,line:this.state.line,column:this.state.column-B.length})}}class S{tokens;current=0;source;constructor(A,B){this.tokens=A,this.source=B}parse(){let A=[];while(!this.isAtEnd()){let B=this.parseStatement();if(B)A.push(B)}return{type:"Template",body:A,line:1,column:1}}parseStatement(){switch(this.peek().type){case"TEXT":return this.parseText();case"VARIABLE_START":return this.parseOutput();case"BLOCK_START":return this.parseBlock();case"EOF":return null;default:return this.advance(),null}}parseText(){let A=this.advance();return{type:"Text",value:A.value,line:A.line,column:A.column}}parseOutput(){let A=this.advance(),B=this.parseExpression();return this.expect("VARIABLE_END"),{type:"Output",expression:B,line:A.line,column:A.column}}parseBlock(){let A=this.advance(),B=this.expect("NAME");switch(B.value){case"if":return this.parseIf(A);case"for":return this.parseFor(A);case"block":return this.parseBlockTag(A);case"extends":return this.parseExtends(A);case"include":return this.parseInclude(A);case"set":return this.parseSet(A);case"with":return this.parseWith(A);case"load":return this.parseLoad(A);case"url":return this.parseUrl(A);case"static":return this.parseStatic(A);case"now":return this.parseNow(A);case"comment":return this.parseComment(A);case"spaceless":case"autoescape":case"verbatim":return this.parseSimpleBlock(A,B.value);case"cycle":return this.parseCycle(A);case"firstof":return this.parseFirstof(A);case"ifchanged":return this.parseIfchanged(A);case"regroup":return this.parseRegroup(A);case"widthratio":return this.parseWidthratio(A);case"lorem":return this.parseLorem(A);case"csrf_token":return this.parseCsrfToken(A);case"debug":return this.parseDebug(A);case"templatetag":return this.parseTemplatetag(A);case"ifequal":return this.parseIfequal(A,!1);case"ifnotequal":return this.parseIfequal(A,!0);default:return this.skipToBlockEnd(),null}}parseIf(A){let B=this.parseExpression();this.expect("BLOCK_END");let R=[],K=[],O=[];while(!this.isAtEnd()){if(this.checkBlockTag("elif")||this.checkBlockTag("else")||this.checkBlockTag("endif"))break;let Q=this.parseStatement();if(Q)R.push(Q)}while(this.checkBlockTag("elif")){this.advance(),this.advance();let Q=this.parseExpression();this.expect("BLOCK_END");let U=[];while(!this.isAtEnd()){if(this.checkBlockTag("elif")||this.checkBlockTag("else")||this.checkBlockTag("endif"))break;let M=this.parseStatement();if(M)U.push(M)}K.push({test:Q,body:U})}if(this.checkBlockTag("else")){this.advance(),this.advance(),this.expect("BLOCK_END");while(!this.isAtEnd()){if(this.checkBlockTag("endif"))break;let Q=this.parseStatement();if(Q)O.push(Q)}}return this.expectBlockTag("endif"),{type:"If",test:B,body:R,elifs:K,else_:O,line:A.line,column:A.column}}parseFor(A){let B,R=this.expect("NAME").value;if(this.check("COMMA")){let Z=[R];while(this.match("COMMA"))Z.push(this.expect("NAME").value);B=Z}else B=R;let K=this.expect("NAME");if(K.value!=="in")throw this.error(`Expected 'in' in for loop, got '${K.value}'`);let O=this.parseExpression(),Q=this.check("NAME")&&this.peek().value==="recursive";if(Q)this.advance();this.expect("BLOCK_END");let U=[],M=[];while(!this.isAtEnd()){if(this.checkBlockTag("empty")||this.checkBlockTag("else")||this.checkBlockTag("endfor"))break;let Z=this.parseStatement();if(Z)U.push(Z)}if(this.checkBlockTag("empty")||this.checkBlockTag("else")){this.advance(),this.advance(),this.expect("BLOCK_END");while(!this.isAtEnd()){if(this.checkBlockTag("endfor"))break;let Z=this.parseStatement();if(Z)M.push(Z)}}return this.expectBlockTag("endfor"),{type:"For",target:B,iter:O,body:U,else_:M,recursive:Q,line:A.line,column:A.column}}parseBlockTag(A){let B=this.expect("NAME").value,R=this.check("NAME")&&this.peek().value==="scoped";if(R)this.advance();this.expect("BLOCK_END");let K=[];while(!this.isAtEnd()){if(this.checkBlockTag("endblock"))break;let O=this.parseStatement();if(O)K.push(O)}if(this.advance(),this.advance(),this.check("NAME"))this.advance();return this.expect("BLOCK_END"),{type:"Block",name:B,body:K,scoped:R,line:A.line,column:A.column}}parseExtends(A){let B=this.parseExpression();return this.expect("BLOCK_END"),{type:"Extends",template:B,line:A.line,column:A.column}}parseInclude(A){let B=this.parseExpression(),R=null,K=!1,O=!1;while(this.check("NAME")){let Q=this.peek().value;if(Q==="ignore"&&this.peekNext()?.value==="missing")this.advance(),this.advance(),O=!0;else if(Q==="with")this.advance(),R=this.parseKeywordArgs();else if(Q==="only")this.advance(),K=!0;else if(Q==="without"){if(this.advance(),this.check("NAME")&&this.peek().value==="context")this.advance(),K=!0}else break}return this.expect("BLOCK_END"),{type:"Include",template:B,context:R,only:K,ignoreMissing:O,line:A.line,column:A.column}}parseSet(A){let B=this.expect("NAME").value;this.expect("ASSIGN");let R=this.parseExpression();return this.expect("BLOCK_END"),{type:"Set",target:B,value:R,line:A.line,column:A.column}}parseWith(A){let B=[];do{let K=this.expect("NAME").value;this.expect("ASSIGN");let O=this.parseExpression();B.push({target:K,value:O})}while(this.match("COMMA")||this.check("NAME")&&this.peekNext()?.type==="ASSIGN");this.expect("BLOCK_END");let R=[];while(!this.isAtEnd()){if(this.checkBlockTag("endwith"))break;let K=this.parseStatement();if(K)R.push(K)}return this.expectBlockTag("endwith"),{type:"With",assignments:B,body:R,line:A.line,column:A.column}}parseLoad(A){let B=[];while(this.check("NAME"))B.push(this.advance().value);return this.expect("BLOCK_END"),{type:"Load",names:B,line:A.line,column:A.column}}parseUrl(A){let B=this.parseExpression(),R=[],K={},O=null;while(!this.check("BLOCK_END")){if(this.check("NAME")&&this.peek().value==="as"){this.advance(),O=this.expect("NAME").value;break}if(this.check("NAME")&&this.peekNext()?.type==="ASSIGN"){let Q=this.advance().value;this.advance(),K[Q]=this.parseExpression()}else R.push(this.parseExpression())}return this.expect("BLOCK_END"),{type:"Url",name:B,args:R,kwargs:K,asVar:O,line:A.line,column:A.column}}parseStatic(A){let B=this.parseExpression(),R=null;if(this.check("NAME")&&this.peek().value==="as")this.advance(),R=this.expect("NAME").value;return this.expect("BLOCK_END"),{type:"Static",path:B,asVar:R,line:A.line,column:A.column}}parseNow(A){let B=this.parseExpression(),R=null;if(this.check("NAME")&&this.peek().value==="as")this.advance(),R=this.expect("NAME").value;return this.expect("BLOCK_END"),{type:"Now",format:B,asVar:R,line:A.line,column:A.column}}parseComment(A){this.expect("BLOCK_END");while(!this.isAtEnd()){if(this.checkBlockTag("endcomment"))break;this.advance()}return this.expectBlockTag("endcomment"),null}parseSimpleBlock(A,B){this.skipToBlockEnd();let R=`end${B}`;while(!this.isAtEnd()){if(this.checkBlockTag(R))break;this.advance()}if(this.checkBlockTag(R))this.advance(),this.advance(),this.expect("BLOCK_END");return null}parseCycle(A){let B=[],R=null,K=!1;while(!this.check("BLOCK_END")){if(this.check("NAME")&&this.peek().value==="as"){if(this.advance(),R=this.expect("NAME").value,this.check("NAME")&&this.peek().value==="silent")this.advance(),K=!0;break}B.push(this.parseExpression())}return this.expect("BLOCK_END"),{type:"Cycle",values:B,asVar:R,silent:K,line:A.line,column:A.column}}parseFirstof(A){let B=[],R=null,K=null;while(!this.check("BLOCK_END")){if(this.check("NAME")&&this.peek().value==="as"){this.advance(),K=this.expect("NAME").value;break}B.push(this.parseExpression())}if(B.length>0){let O=B[B.length-1];if(O.type==="Literal"&&typeof O.value==="string")R=B.pop()}return this.expect("BLOCK_END"),{type:"Firstof",values:B,fallback:R,asVar:K,line:A.line,column:A.column}}parseIfchanged(A){let B=[];while(!this.check("BLOCK_END"))B.push(this.parseExpression());this.expect("BLOCK_END");let R=[],K=[];while(!this.isAtEnd()){if(this.checkBlockTag("else")||this.checkBlockTag("endifchanged"))break;let O=this.parseStatement();if(O)R.push(O)}if(this.checkBlockTag("else")){this.advance(),this.advance(),this.expect("BLOCK_END");while(!this.isAtEnd()){if(this.checkBlockTag("endifchanged"))break;let O=this.parseStatement();if(O)K.push(O)}}return this.expectBlockTag("endifchanged"),{type:"Ifchanged",values:B,body:R,else_:K,line:A.line,column:A.column}}parseRegroup(A){let B=this.parseExpression();this.expectName("by");let R=this.expect("NAME").value;this.expectName("as");let K=this.expect("NAME").value;return this.expect("BLOCK_END"),{type:"Regroup",target:B,key:R,asVar:K,line:A.line,column:A.column}}parseWidthratio(A){let B=this.parseExpression(),R=this.parseExpression(),K=this.parseExpression(),O=null;if(this.check("NAME")&&this.peek().value==="as")this.advance(),O=this.expect("NAME").value;return this.expect("BLOCK_END"),{type:"Widthratio",value:B,maxValue:R,maxWidth:K,asVar:O,line:A.line,column:A.column}}parseLorem(A){let B=null,R="p",K=!1;if(this.check("NUMBER"))B={type:"Literal",value:parseInt(this.advance().value,10),line:A.line,column:A.column};if(this.check("NAME")){let O=this.peek().value.toLowerCase();if(O==="w"||O==="p"||O==="b")R=O,this.advance()}if(this.check("NAME")&&this.peek().value==="random")K=!0,this.advance();return this.expect("BLOCK_END"),{type:"Lorem",count:B,method:R,random:K,line:A.line,column:A.column}}parseCsrfToken(A){return this.expect("BLOCK_END"),{type:"CsrfToken",line:A.line,column:A.column}}parseDebug(A){return this.expect("BLOCK_END"),{type:"Debug",line:A.line,column:A.column}}parseTemplatetag(A){let B=this.expect("NAME").value;return this.expect("BLOCK_END"),{type:"Templatetag",tagType:B,line:A.line,column:A.column}}parseIfequal(A,B){let R=this.parseExpression(),K=this.parseExpression();this.expect("BLOCK_END");let O={type:"Compare",left:R,ops:[{operator:B?"!=":"==",right:K}],line:A.line,column:A.column},Q=[],U=[],M=B?"endifnotequal":"endifequal";while(!this.isAtEnd()){if(this.checkBlockTag("else")||this.checkBlockTag(M))break;let Z=this.parseStatement();if(Z)Q.push(Z)}if(this.checkBlockTag("else")){this.advance(),this.advance(),this.expect("BLOCK_END");while(!this.isAtEnd()){if(this.checkBlockTag(M))break;let Z=this.parseStatement();if(Z)U.push(Z)}}return this.expectBlockTag(M),{type:"If",test:O,body:Q,elifs:[],else_:U,line:A.line,column:A.column}}parseExpression(){return this.parseConditional()}parseConditional(){let A=this.parseOr();if(this.check("NAME")&&this.peek().value==="if"){this.advance();let B=this.parseOr();this.expectName("else");let R=this.parseConditional();A={type:"Conditional",test:B,trueExpr:A,falseExpr:R,line:A.line,column:A.column}}return A}parseOr(){let A=this.parseAnd();while(this.check("OR")||this.check("NAME")&&this.peek().value==="or"){this.advance();let B=this.parseAnd();A={type:"BinaryOp",operator:"or",left:A,right:B,line:A.line,column:A.column}}return A}parseAnd(){let A=this.parseNot();while(this.check("AND")||this.check("NAME")&&this.peek().value==="and"){this.advance();let B=this.parseNot();A={type:"BinaryOp",operator:"and",left:A,right:B,line:A.line,column:A.column}}return A}parseNot(){if(this.check("NOT")||this.check("NAME")&&this.peek().value==="not"){let A=this.advance();return{type:"UnaryOp",operator:"not",operand:this.parseNot(),line:A.line,column:A.column}}return this.parseCompare()}parseCompare(){let A=this.parseAddSub(),B=[];while(!0){let R=null;if(this.match("EQ"))R="==";else if(this.match("NE"))R="!=";else if(this.match("LT"))R="<";else if(this.match("GT"))R=">";else if(this.match("LE"))R="<=";else if(this.match("GE"))R=">=";else if(this.check("NAME")){let O=this.peek().value;if(O==="in")this.advance(),R="in";else if(O==="not"&&this.peekNext()?.value==="in")this.advance(),this.advance(),R="not in";else if(O==="is"){this.advance();let Q=this.check("NOT");if(Q)this.advance();let M=this.expect("NAME").value,Z=[];if(this.match("LPAREN")){while(!this.check("RPAREN"))if(Z.push(this.parseExpression()),!this.check("RPAREN"))this.expect("COMMA");this.expect("RPAREN")}A={type:"TestExpr",node:A,test:M,args:Z,negated:Q,line:A.line,column:A.column};continue}}if(!R)break;let K=this.parseAddSub();B.push({operator:R,right:K})}if(B.length===0)return A;return{type:"Compare",left:A,ops:B,line:A.line,column:A.column}}parseAddSub(){let A=this.parseMulDiv();while(this.check("ADD")||this.check("SUB")||this.check("TILDE")){let B=this.advance(),R=this.parseMulDiv();A={type:"BinaryOp",operator:B.value,left:A,right:R,line:A.line,column:A.column}}return A}parseMulDiv(){let A=this.parseUnary();while(this.check("MUL")||this.check("DIV")||this.check("MOD")){let B=this.advance(),R=this.parseUnary();A={type:"BinaryOp",operator:B.value,left:A,right:R,line:A.line,column:A.column}}return A}parseUnary(){if(this.check("SUB")||this.check("ADD")){let A=this.advance(),B=this.parseUnary();return{type:"UnaryOp",operator:A.value,operand:B,line:A.line,column:A.column}}return this.parseFilter()}parseFilter(){let A=this.parsePostfix();while(this.match("PIPE")){let B=this.expect("NAME").value,R=[],K={};if(this.match("COLON"))if(this.check("SUB")||this.check("ADD")){let O=this.advance(),Q=this.parsePostfix();R.push({type:"UnaryOp",operator:O.value,operand:Q,line:O.line,column:O.column})}else R.push(this.parsePostfix());else if(this.match("LPAREN")){while(!this.check("RPAREN")){if(this.check("NAME")&&this.peekNext()?.type==="ASSIGN"){let O=this.advance().value;this.advance(),K[O]=this.parseExpression()}else R.push(this.parseExpression());if(!this.check("RPAREN"))this.expect("COMMA")}this.expect("RPAREN")}A={type:"FilterExpr",node:A,filter:B,args:R,kwargs:K,line:A.line,column:A.column}}return A}parsePostfix(){let A=this.parsePrimary();while(!0)if(this.match("DOT")){let B;if(this.check("NUMBER"))B=this.advance().value;else B=this.expect("NAME").value;A={type:"GetAttr",object:A,attribute:B,line:A.line,column:A.column}}else if(this.match("LBRACKET")){let B=this.parseExpression();this.expect("RBRACKET"),A={type:"GetItem",object:A,index:B,line:A.line,column:A.column}}else if(this.match("LPAREN")){let B=[],R={};while(!this.check("RPAREN")){if(this.check("NAME")&&this.peekNext()?.type==="ASSIGN"){let K=this.advance().value;this.advance(),R[K]=this.parseExpression()}else B.push(this.parseExpression());if(!this.check("RPAREN"))this.expect("COMMA")}this.expect("RPAREN"),A={type:"FunctionCall",callee:A,args:B,kwargs:R,line:A.line,column:A.column}}else break;return A}parsePrimary(){let A=this.peek();if(this.match("STRING"))return{type:"Literal",value:A.value,line:A.line,column:A.column};if(this.match("NUMBER"))return{type:"Literal",value:A.value.includes(".")?parseFloat(A.value):parseInt(A.value,10),line:A.line,column:A.column};if(this.check("NAME")){let B=this.advance().value;if(B==="true"||B==="True")return{type:"Literal",value:!0,line:A.line,column:A.column};if(B==="false"||B==="False")return{type:"Literal",value:!1,line:A.line,column:A.column};if(B==="none"||B==="None"||B==="null")return{type:"Literal",value:null,line:A.line,column:A.column};return{type:"Name",name:B,line:A.line,column:A.column}}if(this.match("LPAREN")){let B=this.parseExpression();return this.expect("RPAREN"),B}if(this.match("LBRACKET")){let B=[];while(!this.check("RBRACKET"))if(B.push(this.parseExpression()),!this.check("RBRACKET"))this.expect("COMMA");return this.expect("RBRACKET"),{type:"Array",elements:B,line:A.line,column:A.column}}if(this.match("LBRACE")){let B=[];while(!this.check("RBRACE")){let R=this.parseExpression();this.expect("COLON");let K=this.parseExpression();if(B.push({key:R,value:K}),!this.check("RBRACE"))this.expect("COMMA")}return this.expect("RBRACE"),{type:"Object",pairs:B,line:A.line,column:A.column}}throw this.error(`Unexpected token: ${A.type} (${A.value})`)}parseKeywordArgs(){let A={};while(this.check("NAME")&&this.peekNext()?.type==="ASSIGN"){let B=this.advance().value;this.advance(),A[B]=this.parseExpression()}return A}checkBlockTag(A){if(this.peek().type!=="BLOCK_START")return!1;let B=this.current;this.advance();let R=this.check("NAME")&&this.peek().value===A;return this.current=B,R}expectBlockTag(A){this.advance();let B=this.expect("NAME");if(B.value!==A)throw this.error(`Expected '${A}', got '${B.value}'`);this.expect("BLOCK_END")}expectName(A){let B=this.expect("NAME");if(B.value!==A)throw this.error(`Expected '${A}', got '${B.value}'`)}skipToBlockEnd(){while(!this.isAtEnd()&&!this.check("BLOCK_END"))this.advance();if(this.check("BLOCK_END"))this.advance()}isAtEnd(){return this.peek().type==="EOF"}peek(){return this.tokens[this.current]}peekNext(){if(this.current+1>=this.tokens.length)return null;return this.tokens[this.current+1]}advance(){if(!this.isAtEnd())this.current++;return this.tokens[this.current-1]}check(A){if(this.isAtEnd())return!1;return this.peek().type===A}match(A){if(this.check(A))return this.advance(),!0;return!1}expect(A){if(this.check(A))return this.advance();let B=this.peek();throw this.error(`Expected ${A}, got ${B.type} (${B.value})`)}error(A){let B=this.peek();return new F(A,{line:B.line,column:B.column,source:this.source})}}function f(A,B={}){return new k(B).compile(A)}class k{options;indent=0;varCounter=0;loopStack=[];localVars=[];constructor(A={}){this.options={functionName:A.functionName??"render",inlineHelpers:A.inlineHelpers??!0,minify:A.minify??!1,autoescape:A.autoescape??!0}}pushScope(){this.localVars.push(new Set)}popScope(){this.localVars.pop()}addLocalVar(A){if(this.localVars.length>0)this.localVars[this.localVars.length-1].add(A)}isLocalVar(A){for(let B=this.localVars.length-1;B>=0;B--)if(this.localVars[B].has(A))return!0;return!1}compile(A){let B=this.compileNodes(A.body),R=this.options.minify?"":`
12
+ `||A==="\r"}isDigit(A){let B=A.charCodeAt(0);return B>=48&&B<=57}isAlpha(A){let B=A.charCodeAt(0);return B>=97&&B<=122||B>=65&&B<=90}isAlphaNumeric(A){let B=A.charCodeAt(0);return B>=48&&B<=57||B>=97&&B<=122||B>=65&&B<=90}addToken(A,B){this.state.tokens.push({type:A,value:B,line:this.state.line,column:this.state.column-B.length})}}class S{tokens;current=0;source;constructor(A,B){this.tokens=A,this.source=B}parse(){let A=[];while(!this.isAtEnd()){let B=this.parseStatement();if(B)A.push(B)}return{type:"Template",body:A,line:1,column:1}}parseStatement(){switch(this.peek().type){case"TEXT":return this.parseText();case"VARIABLE_START":return this.parseOutput();case"BLOCK_START":return this.parseBlock();case"EOF":return null;default:return this.advance(),null}}parseText(){let A=this.advance();return{type:"Text",value:A.value,line:A.line,column:A.column}}parseOutput(){let A=this.advance(),B=this.parseExpression();return this.expect("VARIABLE_END"),{type:"Output",expression:B,line:A.line,column:A.column}}parseBlock(){let A=this.advance(),B=this.expect("NAME");switch(B.value){case"if":return this.parseIf(A);case"for":return this.parseFor(A);case"block":return this.parseBlockTag(A);case"extends":return this.parseExtends(A);case"include":return this.parseInclude(A);case"set":return this.parseSet(A);case"with":return this.parseWith(A);case"load":return this.parseLoad(A);case"url":return this.parseUrl(A);case"static":return this.parseStatic(A);case"now":return this.parseNow(A);case"comment":return this.parseComment(A);case"spaceless":case"autoescape":case"verbatim":return this.parseSimpleBlock(A,B.value);case"cycle":return this.parseCycle(A);case"firstof":return this.parseFirstof(A);case"ifchanged":return this.parseIfchanged(A);case"regroup":return this.parseRegroup(A);case"widthratio":return this.parseWidthratio(A);case"lorem":return this.parseLorem(A);case"csrf_token":return this.parseCsrfToken(A);case"debug":return this.parseDebug(A);case"templatetag":return this.parseTemplatetag(A);case"ifequal":return this.parseIfequal(A,!1);case"ifnotequal":return this.parseIfequal(A,!0);default:return this.skipToBlockEnd(),null}}parseIf(A){let B=this.parseExpression();this.expect("BLOCK_END");let R=[],K=[],O=[];while(!this.isAtEnd()){if(this.checkBlockTag("elif")||this.checkBlockTag("else")||this.checkBlockTag("endif"))break;let Q=this.parseStatement();if(Q)R.push(Q)}while(this.checkBlockTag("elif")){this.advance(),this.advance();let Q=this.parseExpression();this.expect("BLOCK_END");let U=[];while(!this.isAtEnd()){if(this.checkBlockTag("elif")||this.checkBlockTag("else")||this.checkBlockTag("endif"))break;let M=this.parseStatement();if(M)U.push(M)}K.push({test:Q,body:U})}if(this.checkBlockTag("else")){this.advance(),this.advance(),this.expect("BLOCK_END");while(!this.isAtEnd()){if(this.checkBlockTag("endif"))break;let Q=this.parseStatement();if(Q)O.push(Q)}}return this.expectBlockTag("endif"),{type:"If",test:B,body:R,elifs:K,else_:O,line:A.line,column:A.column}}parseFor(A){let B,R=this.expect("NAME").value;if(this.check("COMMA")){let Z=[R];while(this.match("COMMA"))Z.push(this.expect("NAME").value);B=Z}else B=R;let K=this.expect("NAME");if(K.value!=="in")throw this.error(`Expected 'in' in for loop, got '${K.value}'`);let O=this.parseExpression(),Q=this.check("NAME")&&this.peek().value==="recursive";if(Q)this.advance();this.expect("BLOCK_END");let U=[],M=[];while(!this.isAtEnd()){if(this.checkBlockTag("empty")||this.checkBlockTag("else")||this.checkBlockTag("endfor"))break;let Z=this.parseStatement();if(Z)U.push(Z)}if(this.checkBlockTag("empty")||this.checkBlockTag("else")){this.advance(),this.advance(),this.expect("BLOCK_END");while(!this.isAtEnd()){if(this.checkBlockTag("endfor"))break;let Z=this.parseStatement();if(Z)M.push(Z)}}return this.expectBlockTag("endfor"),{type:"For",target:B,iter:O,body:U,else_:M,recursive:Q,line:A.line,column:A.column}}parseBlockTag(A){let B=this.expect("NAME").value,R=this.check("NAME")&&this.peek().value==="scoped";if(R)this.advance();this.expect("BLOCK_END");let K=[];while(!this.isAtEnd()){if(this.checkBlockTag("endblock"))break;let O=this.parseStatement();if(O)K.push(O)}if(this.advance(),this.advance(),this.check("NAME"))this.advance();return this.expect("BLOCK_END"),{type:"Block",name:B,body:K,scoped:R,line:A.line,column:A.column}}parseExtends(A){let B=this.parseExpression();return this.expect("BLOCK_END"),{type:"Extends",template:B,line:A.line,column:A.column}}parseInclude(A){let B=this.parseExpression(),R=null,K=!1,O=!1;while(this.check("NAME")){let Q=this.peek().value;if(Q==="ignore"&&this.peekNext()?.value==="missing")this.advance(),this.advance(),O=!0;else if(Q==="with")this.advance(),R=this.parseKeywordArgs();else if(Q==="only")this.advance(),K=!0;else if(Q==="without"){if(this.advance(),this.check("NAME")&&this.peek().value==="context")this.advance(),K=!0}else break}return this.expect("BLOCK_END"),{type:"Include",template:B,context:R,only:K,ignoreMissing:O,line:A.line,column:A.column}}parseSet(A){let B=this.expect("NAME").value;this.expect("ASSIGN");let R=this.parseExpression();return this.expect("BLOCK_END"),{type:"Set",target:B,value:R,line:A.line,column:A.column}}parseWith(A){let B=[];do{let K=this.expect("NAME").value;this.expect("ASSIGN");let O=this.parseExpression();B.push({target:K,value:O})}while(this.match("COMMA")||this.check("NAME")&&this.peekNext()?.type==="ASSIGN");this.expect("BLOCK_END");let R=[];while(!this.isAtEnd()){if(this.checkBlockTag("endwith"))break;let K=this.parseStatement();if(K)R.push(K)}return this.expectBlockTag("endwith"),{type:"With",assignments:B,body:R,line:A.line,column:A.column}}parseLoad(A){let B=[];while(this.check("NAME"))B.push(this.advance().value);return this.expect("BLOCK_END"),{type:"Load",names:B,line:A.line,column:A.column}}parseUrl(A){let B=this.parseExpression(),R=[],K={},O=null;while(!this.check("BLOCK_END")){if(this.check("NAME")&&this.peek().value==="as"){this.advance(),O=this.expect("NAME").value;break}if(this.check("NAME")&&this.peekNext()?.type==="ASSIGN"){let Q=this.advance().value;this.advance(),K[Q]=this.parseExpression()}else R.push(this.parseExpression())}return this.expect("BLOCK_END"),{type:"Url",name:B,args:R,kwargs:K,asVar:O,line:A.line,column:A.column}}parseStatic(A){let B=this.parseExpression(),R=null;if(this.check("NAME")&&this.peek().value==="as")this.advance(),R=this.expect("NAME").value;return this.expect("BLOCK_END"),{type:"Static",path:B,asVar:R,line:A.line,column:A.column}}parseNow(A){let B=this.parseExpression(),R=null;if(this.check("NAME")&&this.peek().value==="as")this.advance(),R=this.expect("NAME").value;return this.expect("BLOCK_END"),{type:"Now",format:B,asVar:R,line:A.line,column:A.column}}parseComment(A){this.expect("BLOCK_END");while(!this.isAtEnd()){if(this.checkBlockTag("endcomment"))break;this.advance()}return this.expectBlockTag("endcomment"),null}parseSimpleBlock(A,B){this.skipToBlockEnd();let R=`end${B}`;while(!this.isAtEnd()){if(this.checkBlockTag(R))break;this.advance()}if(this.checkBlockTag(R))this.advance(),this.advance(),this.expect("BLOCK_END");return null}parseCycle(A){let B=[],R=null,K=!1;while(!this.check("BLOCK_END")){if(this.check("NAME")&&this.peek().value==="as"){if(this.advance(),R=this.expect("NAME").value,this.check("NAME")&&this.peek().value==="silent")this.advance(),K=!0;break}B.push(this.parseExpression())}return this.expect("BLOCK_END"),{type:"Cycle",values:B,asVar:R,silent:K,line:A.line,column:A.column}}parseFirstof(A){let B=[],R=null,K=null;while(!this.check("BLOCK_END")){if(this.check("NAME")&&this.peek().value==="as"){this.advance(),K=this.expect("NAME").value;break}B.push(this.parseExpression())}if(B.length>0){let O=B[B.length-1];if(O.type==="Literal"&&typeof O.value==="string")R=B.pop()}return this.expect("BLOCK_END"),{type:"Firstof",values:B,fallback:R,asVar:K,line:A.line,column:A.column}}parseIfchanged(A){let B=[];while(!this.check("BLOCK_END"))B.push(this.parseExpression());this.expect("BLOCK_END");let R=[],K=[];while(!this.isAtEnd()){if(this.checkBlockTag("else")||this.checkBlockTag("endifchanged"))break;let O=this.parseStatement();if(O)R.push(O)}if(this.checkBlockTag("else")){this.advance(),this.advance(),this.expect("BLOCK_END");while(!this.isAtEnd()){if(this.checkBlockTag("endifchanged"))break;let O=this.parseStatement();if(O)K.push(O)}}return this.expectBlockTag("endifchanged"),{type:"Ifchanged",values:B,body:R,else_:K,line:A.line,column:A.column}}parseRegroup(A){let B=this.parseExpression();this.expectName("by");let R=this.expect("NAME").value;this.expectName("as");let K=this.expect("NAME").value;return this.expect("BLOCK_END"),{type:"Regroup",target:B,key:R,asVar:K,line:A.line,column:A.column}}parseWidthratio(A){let B=this.parseExpression(),R=this.parseExpression(),K=this.parseExpression(),O=null;if(this.check("NAME")&&this.peek().value==="as")this.advance(),O=this.expect("NAME").value;return this.expect("BLOCK_END"),{type:"Widthratio",value:B,maxValue:R,maxWidth:K,asVar:O,line:A.line,column:A.column}}parseLorem(A){let B=null,R="p",K=!1;if(this.check("NUMBER"))B={type:"Literal",value:parseInt(this.advance().value,10),line:A.line,column:A.column};if(this.check("NAME")){let O=this.peek().value.toLowerCase();if(O==="w"||O==="p"||O==="b")R=O,this.advance()}if(this.check("NAME")&&this.peek().value==="random")K=!0,this.advance();return this.expect("BLOCK_END"),{type:"Lorem",count:B,method:R,random:K,line:A.line,column:A.column}}parseCsrfToken(A){return this.expect("BLOCK_END"),{type:"CsrfToken",line:A.line,column:A.column}}parseDebug(A){return this.expect("BLOCK_END"),{type:"Debug",line:A.line,column:A.column}}parseTemplatetag(A){let B=this.expect("NAME").value;return this.expect("BLOCK_END"),{type:"Templatetag",tagType:B,line:A.line,column:A.column}}parseIfequal(A,B){let R=this.parseExpression(),K=this.parseExpression();this.expect("BLOCK_END");let O={type:"Compare",left:R,ops:[{operator:B?"!=":"==",right:K}],line:A.line,column:A.column},Q=[],U=[],M=B?"endifnotequal":"endifequal";while(!this.isAtEnd()){if(this.checkBlockTag("else")||this.checkBlockTag(M))break;let Z=this.parseStatement();if(Z)Q.push(Z)}if(this.checkBlockTag("else")){this.advance(),this.advance(),this.expect("BLOCK_END");while(!this.isAtEnd()){if(this.checkBlockTag(M))break;let Z=this.parseStatement();if(Z)U.push(Z)}}return this.expectBlockTag(M),{type:"If",test:O,body:Q,elifs:[],else_:U,line:A.line,column:A.column}}parseExpression(){return this.parseConditional()}parseConditional(){let A=this.parseOr();if(this.check("NAME")&&this.peek().value==="if"){this.advance();let B=this.parseOr();this.expectName("else");let R=this.parseConditional();A={type:"Conditional",test:B,trueExpr:A,falseExpr:R,line:A.line,column:A.column}}return A}parseOr(){let A=this.parseAnd();while(this.check("OR")||this.check("NAME")&&this.peek().value==="or"){this.advance();let B=this.parseAnd();A={type:"BinaryOp",operator:"or",left:A,right:B,line:A.line,column:A.column}}return A}parseAnd(){let A=this.parseNot();while(this.check("AND")||this.check("NAME")&&this.peek().value==="and"){this.advance();let B=this.parseNot();A={type:"BinaryOp",operator:"and",left:A,right:B,line:A.line,column:A.column}}return A}parseNot(){if(this.check("NOT")||this.check("NAME")&&this.peek().value==="not"){let A=this.advance();return{type:"UnaryOp",operator:"not",operand:this.parseNot(),line:A.line,column:A.column}}return this.parseCompare()}parseCompare(){let A=this.parseAddSub(),B=[];while(!0){let R=null;if(this.match("EQ"))R="==";else if(this.match("NE"))R="!=";else if(this.match("LT"))R="<";else if(this.match("GT"))R=">";else if(this.match("LE"))R="<=";else if(this.match("GE"))R=">=";else if(this.check("NAME")){let O=this.peek().value;if(O==="in")this.advance(),R="in";else if(O==="not"&&this.peekNext()?.value==="in")this.advance(),this.advance(),R="not in";else if(O==="is"){this.advance();let Q=this.check("NOT");if(Q)this.advance();let M=this.expect("NAME").value,Z=[];if(this.match("LPAREN")){while(!this.check("RPAREN"))if(Z.push(this.parseExpression()),!this.check("RPAREN"))this.expect("COMMA");this.expect("RPAREN")}A={type:"TestExpr",node:A,test:M,args:Z,negated:Q,line:A.line,column:A.column};continue}}if(!R)break;let K=this.parseAddSub();B.push({operator:R,right:K})}if(B.length===0)return A;return{type:"Compare",left:A,ops:B,line:A.line,column:A.column}}parseAddSub(){let A=this.parseMulDiv();while(this.check("ADD")||this.check("SUB")||this.check("TILDE")){let B=this.advance(),R=this.parseMulDiv();A={type:"BinaryOp",operator:B.value,left:A,right:R,line:A.line,column:A.column}}return A}parseMulDiv(){let A=this.parseUnary();while(this.check("MUL")||this.check("DIV")||this.check("MOD")){let B=this.advance(),R=this.parseUnary();A={type:"BinaryOp",operator:B.value,left:A,right:R,line:A.line,column:A.column}}return A}parseUnary(){if(this.check("SUB")||this.check("ADD")){let A=this.advance(),B=this.parseUnary();return{type:"UnaryOp",operator:A.value,operand:B,line:A.line,column:A.column}}return this.parseFilter()}parseFilter(){let A=this.parsePostfix();while(this.match("PIPE")){let B=this.expect("NAME").value,R=[],K={};if(this.match("COLON"))if(this.check("SUB")||this.check("ADD")){let O=this.advance(),Q=this.parsePostfix();R.push({type:"UnaryOp",operator:O.value,operand:Q,line:O.line,column:O.column})}else R.push(this.parsePostfix());else if(this.match("LPAREN")){while(!this.check("RPAREN")){if(this.check("NAME")&&this.peekNext()?.type==="ASSIGN"){let O=this.advance().value;this.advance(),K[O]=this.parseExpression()}else R.push(this.parseExpression());if(!this.check("RPAREN"))this.expect("COMMA")}this.expect("RPAREN")}A={type:"FilterExpr",node:A,filter:B,args:R,kwargs:K,line:A.line,column:A.column}}return A}parsePostfix(){let A=this.parsePrimary();while(!0)if(this.match("DOT")){let B;if(this.check("NUMBER"))B=this.advance().value;else B=this.expect("NAME").value;A={type:"GetAttr",object:A,attribute:B,line:A.line,column:A.column}}else if(this.match("LBRACKET")){let B=this.parseExpression();this.expect("RBRACKET"),A={type:"GetItem",object:A,index:B,line:A.line,column:A.column}}else if(this.match("LPAREN")){let B=[],R={};while(!this.check("RPAREN")){if(this.check("NAME")&&this.peekNext()?.type==="ASSIGN"){let K=this.advance().value;this.advance(),R[K]=this.parseExpression()}else B.push(this.parseExpression());if(!this.check("RPAREN"))this.expect("COMMA")}this.expect("RPAREN"),A={type:"FunctionCall",callee:A,args:B,kwargs:R,line:A.line,column:A.column}}else break;return A}parsePrimary(){let A=this.peek();if(this.match("STRING"))return{type:"Literal",value:A.value,line:A.line,column:A.column};if(this.match("NUMBER"))return{type:"Literal",value:A.value.includes(".")?parseFloat(A.value):parseInt(A.value,10),line:A.line,column:A.column};if(this.check("NAME")){let B=this.advance().value;if(B==="true"||B==="True")return{type:"Literal",value:!0,line:A.line,column:A.column};if(B==="false"||B==="False")return{type:"Literal",value:!1,line:A.line,column:A.column};if(B==="none"||B==="None"||B==="null")return{type:"Literal",value:null,line:A.line,column:A.column};return{type:"Name",name:B,line:A.line,column:A.column}}if(this.match("LPAREN")){let B=this.parseExpression();return this.expect("RPAREN"),B}if(this.match("LBRACKET")){let B=[];while(!this.check("RBRACKET"))if(B.push(this.parseExpression()),!this.check("RBRACKET"))this.expect("COMMA");return this.expect("RBRACKET"),{type:"Array",elements:B,line:A.line,column:A.column}}if(this.match("LBRACE")){let B=[];while(!this.check("RBRACE")){let R=this.parseExpression();this.expect("COLON");let K=this.parseExpression();if(B.push({key:R,value:K}),!this.check("RBRACE"))this.expect("COMMA")}return this.expect("RBRACE"),{type:"Object",pairs:B,line:A.line,column:A.column}}throw this.error(`Unexpected token: ${A.type} (${A.value})`)}parseKeywordArgs(){let A={};while(this.check("NAME")&&this.peekNext()?.type==="ASSIGN"){let B=this.advance().value;this.advance(),A[B]=this.parseExpression()}return A}checkBlockTag(A){if(this.peek().type!=="BLOCK_START")return!1;let B=this.current+1;if(B>=this.tokens.length)return!1;let R=this.tokens[B];return R.type==="NAME"&&R.value===A}expectBlockTag(A){this.advance();let B=this.expect("NAME");if(B.value!==A)throw this.error(`Expected '${A}', got '${B.value}'`);this.expect("BLOCK_END")}expectName(A){let B=this.expect("NAME");if(B.value!==A)throw this.error(`Expected '${A}', got '${B.value}'`)}skipToBlockEnd(){while(!this.isAtEnd()&&!this.check("BLOCK_END"))this.advance();if(this.check("BLOCK_END"))this.advance()}isAtEnd(){return this.peek().type==="EOF"}peek(){return this.tokens[this.current]}peekNext(){if(this.current+1>=this.tokens.length)return null;return this.tokens[this.current+1]}advance(){if(!this.isAtEnd())this.current++;return this.tokens[this.current-1]}check(A){if(this.isAtEnd())return!1;return this.peek().type===A}match(A){if(this.check(A))return this.advance(),!0;return!1}expect(A){if(this.check(A))return this.advance();let B=this.peek();throw this.error(`Expected ${A}, got ${B.type} (${B.value})`)}error(A){let B=this.peek();return new H(A,{line:B.line,column:B.column,source:this.source})}}function f(A,B={}){return new y(B).compile(A)}class y{options;indent=0;varCounter=0;loopStack=[];localVars=[];constructor(A={}){this.options={functionName:A.functionName??"render",inlineHelpers:A.inlineHelpers??!0,minify:A.minify??!1,autoescape:A.autoescape??!0}}pushScope(){this.localVars.push(new Set)}popScope(){this.localVars.pop()}addLocalVar(A){if(this.localVars.length>0)this.localVars[this.localVars.length-1].add(A)}isLocalVar(A){for(let B=this.localVars.length-1;B>=0;B--)if(this.localVars[B].has(A))return!0;return!1}compile(A){let B=this.compileNodes(A.body),R=this.options.minify?"":`
13
13
  `;return`function ${this.options.functionName}(__ctx) {${R} let __out = '';${R}`+B+` return __out;${R}}`}compileNodes(A){return A.map((B)=>this.compileNode(B)).join("")}compileNode(A){switch(A.type){case"Text":return this.compileText(A);case"Output":return this.compileOutput(A);case"If":return this.compileIf(A);case"For":return this.compileFor(A);case"Set":return this.compileSet(A);case"With":return this.compileWith(A);case"Comment":return"";case"Extends":case"Block":case"Include":throw Error(`AOT compilation does not support '${A.type}' - use Environment.render() for templates with inheritance`);case"Url":case"Static":throw Error(`AOT compilation does not support '${A.type}' tag - use Environment.render() with urlResolver/staticResolver`);default:throw Error(`Unknown node type in AOT compiler: ${A.type}`)}}compileText(A){return` __out += ${JSON.stringify(A.value)};${this.nl()}`}compileOutput(A){let B=this.compileExpr(A.expression);if(this.options.autoescape&&!this.isMarkedSafe(A.expression))return` __out += escape(${B});${this.nl()}`;return` __out += (${B}) ?? '';${this.nl()}`}compileIf(A){let B="",R=this.compileExpr(A.test);B+=` if (isTruthy(${R})) {${this.nl()}`,B+=this.compileNodes(A.body),B+=" }";for(let K of A.elifs){let O=this.compileExpr(K.test);B+=` else if (isTruthy(${O})) {${this.nl()}`,B+=this.compileNodes(K.body),B+=" }"}if(A.else_.length>0)B+=` else {${this.nl()}`,B+=this.compileNodes(A.else_),B+=" }";return B+=this.nl(),B}compileFor(A){let B=this.genVar("iter"),R=this.genVar("i"),K=this.genVar("len"),O=this.genVar("loop"),Q=Array.isArray(A.target)?A.target[0]:A.target,U=Array.isArray(A.target)&&A.target[1]?A.target[1]:null,M=this.loopStack.length>0?this.loopStack[this.loopStack.length-1]:null,Z=this.compileExpr(A.iter),$="";if($+=` const ${B} = toArray(${Z});${this.nl()}`,$+=` const ${K} = ${B}.length;${this.nl()}`,A.else_.length>0)$+=` if (${K} === 0) {${this.nl()}`,$+=this.compileNodes(A.else_),$+=` } else {${this.nl()}`;if($+=` for (let ${R} = 0; ${R} < ${K}; ${R}++) {${this.nl()}`,U)$+=` const ${Q} = ${B}[${R}][0];${this.nl()}`,$+=` const ${U} = ${B}[${R}][1];${this.nl()}`;else $+=` const ${Q} = ${B}[${R}];${this.nl()}`;if($+=` const ${O} = {${this.nl()}`,$+=` counter: ${R} + 1,${this.nl()}`,$+=` counter0: ${R},${this.nl()}`,$+=` revcounter: ${K} - ${R},${this.nl()}`,$+=` revcounter0: ${K} - ${R} - 1,${this.nl()}`,$+=` first: ${R} === 0,${this.nl()}`,$+=` last: ${R} === ${K} - 1,${this.nl()}`,$+=` length: ${K},${this.nl()}`,$+=` index: ${R} + 1,${this.nl()}`,$+=` index0: ${R},${this.nl()}`,M)$+=` parentloop: ${M},${this.nl()}`,$+=` parent: ${M}${this.nl()}`;else $+=` parentloop: null,${this.nl()}`,$+=` parent: null${this.nl()}`;$+=` };${this.nl()}`,$+=` const forloop = ${O};${this.nl()}`,$+=` const loop = ${O};${this.nl()}`,this.loopStack.push(O);let G=this.compileNodes(A.body);if($+=G.replace(new RegExp(`__ctx\\.${Q}`,"g"),Q),this.loopStack.pop(),$+=` }${this.nl()}`,A.else_.length>0)$+=` }${this.nl()}`;return $}compileSet(A){let B=this.compileExpr(A.value);return` const ${A.target} = ${B};${this.nl()}`}compileWith(A){let B=` {${this.nl()}`;this.pushScope();for(let{target:R,value:K}of A.assignments){let O=this.compileExpr(K);B+=` const ${R} = ${O};${this.nl()}`,this.addLocalVar(R)}return B+=this.compileNodes(A.body),B+=` }${this.nl()}`,this.popScope(),B}compileExpr(A){switch(A.type){case"Name":return this.compileName(A);case"Literal":return this.compileLiteral(A);case"Array":return this.compileArray(A);case"Object":return this.compileObject(A);case"BinaryOp":return this.compileBinaryOp(A);case"UnaryOp":return this.compileUnaryOp(A);case"Compare":return this.compileCompare(A);case"GetAttr":return this.compileGetAttr(A);case"GetItem":return this.compileGetItem(A);case"FilterExpr":return this.compileFilter(A);case"TestExpr":return this.compileTest(A);case"Conditional":return this.compileConditional(A);default:return"undefined"}}compileName(A){if(A.name==="true"||A.name==="True")return"true";if(A.name==="false"||A.name==="False")return"false";if(A.name==="none"||A.name==="None"||A.name==="null")return"null";if(A.name==="forloop"||A.name==="loop")return A.name;if(this.isLocalVar(A.name))return A.name;return`__ctx.${A.name}`}compileLiteral(A){if(typeof A.value==="string")return JSON.stringify(A.value);return String(A.value)}compileArray(A){return`[${A.elements.map((R)=>this.compileExpr(R)).join(", ")}]`}compileObject(A){return`{${A.pairs.map(({key:R,value:K})=>{let O=this.compileExpr(R),Q=this.compileExpr(K);return`[${O}]: ${Q}`}).join(", ")}}`}compileBinaryOp(A){let B=this.compileExpr(A.left),R=this.compileExpr(A.right);switch(A.operator){case"and":return`(${B} && ${R})`;case"or":return`(${B} || ${R})`;case"~":return`(String(${B}) + String(${R}))`;case"in":return`(Array.isArray(${R}) ? ${R}.includes(${B}) : String(${R}).includes(String(${B})))`;case"not in":return`!(Array.isArray(${R}) ? ${R}.includes(${B}) : String(${R}).includes(String(${B})))`;default:return`(${B} ${A.operator} ${R})`}}compileUnaryOp(A){let B=this.compileExpr(A.operand);switch(A.operator){case"not":return`!isTruthy(${B})`;case"-":return`-(${B})`;case"+":return`+(${B})`;default:return B}}compileCompare(A){let B=this.compileExpr(A.left);for(let{operator:R,right:K}of A.ops){let O=this.compileExpr(K);switch(R){case"==":case"===":B=`(${B} === ${O})`;break;case"!=":case"!==":B=`(${B} !== ${O})`;break;default:B=`(${B} ${R} ${O})`}}return B}compileGetAttr(A){return`${this.compileExpr(A.object)}?.${A.attribute}`}compileGetItem(A){let B=this.compileExpr(A.object),R=this.compileExpr(A.index);return`${B}?.[${R}]`}compileFilter(A){let B=this.compileExpr(A.node),R=A.args.map((K)=>this.compileExpr(K));switch(A.filter){case"upper":return`String(${B}).toUpperCase()`;case"lower":return`String(${B}).toLowerCase()`;case"title":return`String(${B}).replace(/\\b\\w/g, c => c.toUpperCase())`;case"trim":return`String(${B}).trim()`;case"length":return`(${B}?.length ?? Object.keys(${B} ?? {}).length)`;case"first":return`(${B})?.[0]`;case"last":return`(${B})?.[(${B})?.length - 1]`;case"default":return`((${B}) ?? ${R[0]??'""'})`;case"safe":return`{ __safe: true, value: String(${B}) }`;case"escape":case"e":return`escape(${B})`;case"join":return`(${B} ?? []).join(${R[0]??'""'})`;case"abs":return`Math.abs(${B})`;case"round":return R.length?`Number(${B}).toFixed(${R[0]})`:`Math.round(${B})`;case"int":return`parseInt(${B}, 10)`;case"float":return`parseFloat(${B})`;case"floatformat":return`Number(${B}).toFixed(${R[0]??1})`;case"filesizeformat":return`applyFilter('filesizeformat', ${B})`;default:let K=R.length?", "+R.join(", "):"";return`applyFilter('${A.filter}', ${B}${K})`}}compileTest(A){let B=this.compileExpr(A.node),R=A.args.map((O)=>this.compileExpr(O)),K=A.negated?"!":"";switch(A.test){case"defined":return`${K}(${B} !== undefined)`;case"undefined":return`${K}(${B} === undefined)`;case"none":return`${K}(${B} === null)`;case"even":return`${K}(${B} % 2 === 0)`;case"odd":return`${K}(${B} % 2 !== 0)`;case"divisibleby":return`${K}(${B} % ${R[0]} === 0)`;case"empty":return`${K}((${B} == null) || (${B}.length === 0) || (Object.keys(${B}).length === 0))`;case"iterable":return`${K}(Array.isArray(${B}) || typeof ${B} === 'string')`;case"number":return`${K}(typeof ${B} === 'number' && !isNaN(${B}))`;case"string":return`${K}(typeof ${B} === 'string')`;default:let O=R.length?", "+R.join(", "):"";return`${K}applyTest('${A.test}', ${B}${O})`}}compileConditional(A){let B=this.compileExpr(A.test),R=this.compileExpr(A.trueExpr),K=this.compileExpr(A.falseExpr);return`(isTruthy(${B}) ? ${R} : ${K})`}isMarkedSafe(A){if(A.type==="FilterExpr")return A.filter==="safe";return!1}genVar(A){return`__${A}${this.varCounter++}`}nl(){return this.options.minify?"":`
14
- `}}function y(A,B){return new g(B).flatten(A)}function _(A){return new m().check(A)}class g{loader;maxDepth;blocks=new Map;depth=0;constructor(A){this.loader=A.loader,this.maxDepth=A.maxDepth??10}flatten(A){return this.blocks.clear(),this.depth=0,this.processTemplate(A)}processTemplate(A,B=!0){if(this.depth>this.maxDepth)throw Error(`Maximum template inheritance depth (${this.maxDepth}) exceeded`);this.collectBlocks(A.body,B);let R=this.findExtends(A.body);if(R){let K=this.getStaticTemplateName(R.template),O=this.loader.load(K),Q=this.loader.parse(O);this.depth++;let U=this.processTemplate(Q,!1);return this.depth--,{type:"Template",body:this.replaceBlocks(U.body),line:A.line,column:A.column}}return{type:"Template",body:this.processNodes(A.body),line:A.line,column:A.column}}collectBlocks(A,B=!0){for(let R of A){if(R.type==="Block"){let K=R;if(B||!this.blocks.has(K.name))this.blocks.set(K.name,K)}this.collectBlocksFromNode(R,B)}}collectBlocksFromNode(A,B=!0){switch(A.type){case"If":{let R=A;this.collectBlocks(R.body,B);for(let K of R.elifs)this.collectBlocks(K.body,B);this.collectBlocks(R.else_,B);break}case"For":{let R=A;this.collectBlocks(R.body,B),this.collectBlocks(R.else_,B);break}case"With":{let R=A;this.collectBlocks(R.body,B);break}case"Block":{let R=A;this.collectBlocks(R.body,B);break}}}findExtends(A){for(let B of A)if(B.type==="Extends")return B;return null}processNodes(A){let B=[];for(let R of A){if(R.type==="Extends")continue;if(R.type==="Include"){let O=R,Q=this.inlineInclude(O);B.push(...Q);continue}if(R.type==="Block"){let O=R,Q=this.blocks.get(O.name);if(Q&&Q!==O)B.push(...this.processNodes(Q.body));else B.push(...this.processNodes(O.body));continue}let K=this.processNode(R);if(K)B.push(K)}return B}processNode(A){switch(A.type){case"If":{let B=A;return{...B,body:this.processNodes(B.body),elifs:B.elifs.map((R)=>({test:R.test,body:this.processNodes(R.body)})),else_:this.processNodes(B.else_)}}case"For":{let B=A;return{...B,body:this.processNodes(B.body),else_:this.processNodes(B.else_)}}case"With":{let B=A;return{...B,body:this.processNodes(B.body)}}default:return A}}replaceBlocks(A){return this.processNodes(A)}inlineInclude(A){let B=this.getStaticTemplateName(A.template);try{let R=this.loader.load(B),K=this.loader.parse(R);this.depth++;let O=this.processTemplate(K);if(this.depth--,A.context&&Object.keys(A.context).length>0)return[{type:"With",assignments:Object.entries(A.context).map(([U,M])=>({target:U,value:M})),body:O.body,line:A.line,column:A.column}];return O.body}catch(R){if(A.ignoreMissing)return[];throw R}}getStaticTemplateName(A){if(A.type==="Literal"){let B=A;if(typeof B.value==="string")return B.value}throw Error(`AOT compilation requires static template names. Found dynamic expression at line ${A.line}. Use Environment.render() for dynamic template names.`)}}class m{check(A){return this.checkNodes(A.body)}checkNodes(A){for(let B of A){let R=this.checkNode(B);if(!R.canFlatten)return R}return{canFlatten:!0}}checkNode(A){switch(A.type){case"Extends":{let B=A;if(!this.isStaticName(B.template))return{canFlatten:!1,reason:`Dynamic extends at line ${A.line} - use static string literal`};break}case"Include":{let B=A;if(!this.isStaticName(B.template))return{canFlatten:!1,reason:`Dynamic include at line ${A.line} - use static string literal`};break}case"If":{let B=A,R=this.checkNodes(B.body);if(!R.canFlatten)return R;for(let K of B.elifs)if(R=this.checkNodes(K.body),!R.canFlatten)return R;if(R=this.checkNodes(B.else_),!R.canFlatten)return R;break}case"For":{let B=A,R=this.checkNodes(B.body);if(!R.canFlatten)return R;if(R=this.checkNodes(B.else_),!R.canFlatten)return R;break}case"With":{let B=A,R=this.checkNodes(B.body);if(!R.canFlatten)return R;break}case"Block":{let B=A,R=this.checkNodes(B.body);if(!R.canFlatten)return R;break}}return{canFlatten:!0}}isStaticName(A){return A.type==="Literal"&&typeof A.value==="string"}}var i="0.1.1",J={reset:"\x1B[0m",green:"\x1B[32m",yellow:"\x1B[33m",red:"\x1B[31m",cyan:"\x1B[36m",dim:"\x1B[2m"};function I(A){console.log(A)}function C(A){console.log(`${J.green}\u2713${J.reset} ${A}`)}function N(A){console.log(`${J.yellow}\u26A0${J.reset} ${A}`)}function z(A){console.error(`${J.red}\u2717${J.reset} ${A}`)}function w(){console.log(`
14
+ `}}function k(A,B){return new g(B).flatten(A)}function N(A){return new m().check(A)}class g{loader;maxDepth;blocks=new Map;depth=0;constructor(A){this.loader=A.loader,this.maxDepth=A.maxDepth??10}flatten(A){return this.blocks.clear(),this.depth=0,this.processTemplate(A)}processTemplate(A,B=!0){if(this.depth>this.maxDepth)throw Error(`Maximum template inheritance depth (${this.maxDepth}) exceeded`);this.collectBlocks(A.body,B);let R=this.findExtends(A.body);if(R){let K=this.getStaticTemplateName(R.template),O=this.loader.load(K),Q=this.loader.parse(O);this.depth++;let U=this.processTemplate(Q,!1);return this.depth--,{type:"Template",body:this.replaceBlocks(U.body),line:A.line,column:A.column}}return{type:"Template",body:this.processNodes(A.body),line:A.line,column:A.column}}collectBlocks(A,B=!0){for(let R of A){if(R.type==="Block"){let K=R;if(B||!this.blocks.has(K.name))this.blocks.set(K.name,K)}this.collectBlocksFromNode(R,B)}}collectBlocksFromNode(A,B=!0){switch(A.type){case"If":{let R=A;this.collectBlocks(R.body,B);for(let K of R.elifs)this.collectBlocks(K.body,B);this.collectBlocks(R.else_,B);break}case"For":{let R=A;this.collectBlocks(R.body,B),this.collectBlocks(R.else_,B);break}case"With":{let R=A;this.collectBlocks(R.body,B);break}case"Block":{let R=A;this.collectBlocks(R.body,B);break}}}findExtends(A){for(let B of A)if(B.type==="Extends")return B;return null}processNodes(A){let B=[];for(let R of A){if(R.type==="Extends")continue;if(R.type==="Include"){let O=R,Q=this.inlineInclude(O);B.push(...Q);continue}if(R.type==="Block"){let O=R,Q=this.blocks.get(O.name);if(Q&&Q!==O)B.push(...this.processNodes(Q.body));else B.push(...this.processNodes(O.body));continue}let K=this.processNode(R);if(K)B.push(K)}return B}processNode(A){switch(A.type){case"If":{let B=A;return{...B,body:this.processNodes(B.body),elifs:B.elifs.map((R)=>({test:R.test,body:this.processNodes(R.body)})),else_:this.processNodes(B.else_)}}case"For":{let B=A;return{...B,body:this.processNodes(B.body),else_:this.processNodes(B.else_)}}case"With":{let B=A;return{...B,body:this.processNodes(B.body)}}default:return A}}replaceBlocks(A){return this.processNodes(A)}inlineInclude(A){let B=this.getStaticTemplateName(A.template);try{let R=this.loader.load(B),K=this.loader.parse(R);this.depth++;let O=this.processTemplate(K);if(this.depth--,A.context&&Object.keys(A.context).length>0)return[{type:"With",assignments:Object.entries(A.context).map(([U,M])=>({target:U,value:M})),body:O.body,line:A.line,column:A.column}];return O.body}catch(R){if(A.ignoreMissing)return[];throw R}}getStaticTemplateName(A){if(A.type==="Literal"){let B=A;if(typeof B.value==="string")return B.value}throw Error(`AOT compilation requires static template names. Found dynamic expression at line ${A.line}. Use Environment.render() for dynamic template names.`)}}class m{check(A){return this.checkNodes(A.body)}checkNodes(A){for(let B of A){let R=this.checkNode(B);if(!R.canFlatten)return R}return{canFlatten:!0}}checkNode(A){switch(A.type){case"Extends":{let B=A;if(!this.isStaticName(B.template))return{canFlatten:!1,reason:`Dynamic extends at line ${A.line} - use static string literal`};break}case"Include":{let B=A;if(!this.isStaticName(B.template))return{canFlatten:!1,reason:`Dynamic include at line ${A.line} - use static string literal`};break}case"If":{let B=A,R=this.checkNodes(B.body);if(!R.canFlatten)return R;for(let K of B.elifs)if(R=this.checkNodes(K.body),!R.canFlatten)return R;if(R=this.checkNodes(B.else_),!R.canFlatten)return R;break}case"For":{let B=A,R=this.checkNodes(B.body);if(!R.canFlatten)return R;if(R=this.checkNodes(B.else_),!R.canFlatten)return R;break}case"With":{let B=A,R=this.checkNodes(B.body);if(!R.canFlatten)return R;break}case"Block":{let B=A,R=this.checkNodes(B.body);if(!R.canFlatten)return R;break}}return{canFlatten:!0}}isStaticName(A){return A.type==="Literal"&&typeof A.value==="string"}}var i="0.1.1",J={reset:"\x1B[0m",green:"\x1B[32m",yellow:"\x1B[33m",red:"\x1B[31m",cyan:"\x1B[36m",dim:"\x1B[2m"};function F(A){console.log(A)}function C(A){console.log(`${J.green}\u2713${J.reset} ${A}`)}function _(A){console.log(`${J.yellow}\u26A0${J.reset} ${A}`)}function I(A){console.error(`${J.red}\u2717${J.reset} ${A}`)}function w(){console.log(`
15
15
  ${J.cyan}binja${J.reset} - High-performance template compiler
16
16
 
17
17
  ${J.yellow}Usage:${J.reset}
@@ -94,5 +94,5 @@ ${A}
94
94
 
95
95
  export { ${B} as render };
96
96
  export default ${B};
97
- `}async function x(A,B,R,K){try{let O=X.readFileSync(A,"utf-8"),Q=V(R,K.extensions),U=Q.parse(O),M=_(U),Z=U;if(!M.canFlatten){if(K.verbose)N(`${Y.basename(A)}: ${M.reason} - compiling without inheritance resolution`)}else Z=y(U,{loader:Q});let $=Y.relative(R,A),G=K.name||"render"+$.replace(/\.[^.]+$/,"").replace(/[^a-zA-Z0-9]/g,"_").replace(/^_+|_+$/g,"").replace(/_([a-z])/g,(o,c)=>c.toUpperCase()),W=f(Z,{functionName:G,minify:K.minify}),H=$.replace(/\.[^.]+$/,".js"),E=Y.join(B,H),L=Y.dirname(E);if(!X.existsSync(L))X.mkdirSync(L,{recursive:!0});let j=s(W,G);return X.writeFileSync(E,j),{success:!0,outputPath:E}}catch(O){return{success:!1,error:O.message}}}async function T(A,B,R){let K=0,O=0,Q=[];function U(M){let Z=X.readdirSync(M,{withFileTypes:!0});for(let $ of Z){let G=Y.join(M,$.name);if($.isDirectory())U(G);else if($.isFile()){let W=Y.extname($.name);if(R.extensions.includes(W))Q.push(G)}}}U(A);for(let M of Q){let Z=await x(M,B,A,R);if(Z.success){if(K++,R.verbose)C(`${Y.relative(A,M)} \u2192 ${Y.relative(process.cwd(),Z.outputPath)}`)}else O++,z(`${Y.relative(A,M)}: ${Z.error}`)}return{compiled:K,failed:O}}async function r(A,B){let R=V(A,B.extensions),K=0,O=0,Q=0;function U(M){let Z=X.readdirSync(M,{withFileTypes:!0});for(let $ of Z){let G=Y.join(M,$.name);if($.isDirectory())U(G);else if($.isFile()){let W=Y.extname($.name);if(B.extensions.includes(W)){K++;try{let H=X.readFileSync(G,"utf-8"),E=R.parse(H),L=_(E),j=Y.relative(A,G);if(L.canFlatten)O++,C(`${j}`);else Q++,N(`${j}: ${L.reason}`)}catch(H){Q++,z(`${Y.relative(A,G)}: ${H.message}`)}}}}}if(U(A),I(""),I(`Total: ${K} templates`),I(`${J.green}AOT compatible: ${O}${J.reset}`),Q>0)I(`${J.yellow}Require runtime: ${Q}${J.reset}`)}async function l(A,B,R){I(`${J.cyan}Watching${J.reset} ${A} for changes...`),I(`${J.dim}Press Ctrl+C to stop${J.reset}`),I("");let{compiled:K,failed:O}=await T(A,B,{...R,verbose:!0});I(""),I(`Compiled ${K} templates${O>0?`, ${O} failed`:""}`),I("");let Q=X.watch(A,{recursive:!0},async(U,M)=>{if(!M)return;let Z=Y.extname(M);if(!R.extensions.includes(Z))return;let $=Y.join(A,M);if(!X.existsSync($))return;I(`${J.dim}[${new Date().toLocaleTimeString()}]${J.reset} ${M} changed`);let G=await x($,B,A,R);if(G.success)C(`Compiled ${M}`);else z(`${M}: ${G.error}`)});process.on("SIGINT",()=>{Q.close(),I(`
98
- Stopped watching.`),process.exit(0)})}async function n(){let A=process.argv.slice(2);if(A.length===0)w(),process.exit(0);let{command:B,source:R,options:K}=u(A);if(!B)z('No command specified. Use "compile" or "check".'),w(),process.exit(1);if(!R)z("No source path specified."),process.exit(1);let O=Y.resolve(R);if(!X.existsSync(O))z(`Source not found: ${R}`),process.exit(1);let Q=X.statSync(O).isDirectory();if(B==="check")if(Q)await r(O,K);else{let U=V(Y.dirname(O),K.extensions),M=X.readFileSync(O,"utf-8"),Z=U.parse(M),$=_(Z);if($.canFlatten)C(`${R} can be AOT compiled`);else N(`${R}: ${$.reason}`)}else if(B==="compile"){if(!K.output)z("Output directory required. Use -o <dir>"),process.exit(1);let U=Y.resolve(K.output);if(K.watch){if(!Q)z("Watch mode requires a directory, not a single file."),process.exit(1);await l(O,U,K)}else if(Q){let M=Date.now(),{compiled:Z,failed:$}=await T(O,U,K),G=Date.now()-M;if(I(""),$===0)C(`Compiled ${Z} templates in ${G}ms`);else N(`Compiled ${Z} templates, ${$} failed (${G}ms)`)}else{let M=await x(O,U,Y.dirname(O),K);if(M.success)C(`Compiled to ${M.outputPath}`);else z(M.error),process.exit(1)}}}n().catch((A)=>{z(A.message),process.exit(1)});
97
+ `}async function b(A,B,R,K){try{let O=X.readFileSync(A,"utf-8"),Q=V(R,K.extensions),U=Q.parse(O),M=N(U),Z=U;if(!M.canFlatten){if(K.verbose)_(`${Y.basename(A)}: ${M.reason} - compiling without inheritance resolution`)}else Z=k(U,{loader:Q});let $=Y.relative(R,A),G=K.name||"render"+$.replace(/\.[^.]+$/,"").replace(/[^a-zA-Z0-9]/g,"_").replace(/^_+|_+$/g,"").replace(/_([a-z])/g,(o,c)=>c.toUpperCase()),W=f(Z,{functionName:G,minify:K.minify}),z=$.replace(/\.[^.]+$/,".js"),E=Y.join(B,z),L=Y.dirname(E);if(!X.existsSync(L))X.mkdirSync(L,{recursive:!0});let j=s(W,G);return X.writeFileSync(E,j),{success:!0,outputPath:E}}catch(O){return{success:!1,error:O.message}}}async function T(A,B,R){let K=0,O=0,Q=[];function U(M){let Z=X.readdirSync(M,{withFileTypes:!0});for(let $ of Z){let G=Y.join(M,$.name);if($.isDirectory())U(G);else if($.isFile()){let W=Y.extname($.name);if(R.extensions.includes(W))Q.push(G)}}}U(A);for(let M of Q){let Z=await b(M,B,A,R);if(Z.success){if(K++,R.verbose)C(`${Y.relative(A,M)} \u2192 ${Y.relative(process.cwd(),Z.outputPath)}`)}else O++,I(`${Y.relative(A,M)}: ${Z.error}`)}return{compiled:K,failed:O}}async function r(A,B){let R=V(A,B.extensions),K=0,O=0,Q=0;function U(M){let Z=X.readdirSync(M,{withFileTypes:!0});for(let $ of Z){let G=Y.join(M,$.name);if($.isDirectory())U(G);else if($.isFile()){let W=Y.extname($.name);if(B.extensions.includes(W)){K++;try{let z=X.readFileSync(G,"utf-8"),E=R.parse(z),L=N(E),j=Y.relative(A,G);if(L.canFlatten)O++,C(`${j}`);else Q++,_(`${j}: ${L.reason}`)}catch(z){Q++,I(`${Y.relative(A,G)}: ${z.message}`)}}}}}if(U(A),F(""),F(`Total: ${K} templates`),F(`${J.green}AOT compatible: ${O}${J.reset}`),Q>0)F(`${J.yellow}Require runtime: ${Q}${J.reset}`)}async function l(A,B,R){F(`${J.cyan}Watching${J.reset} ${A} for changes...`),F(`${J.dim}Press Ctrl+C to stop${J.reset}`),F("");let{compiled:K,failed:O}=await T(A,B,{...R,verbose:!0});F(""),F(`Compiled ${K} templates${O>0?`, ${O} failed`:""}`),F("");let Q=X.watch(A,{recursive:!0},async(U,M)=>{if(!M)return;let Z=Y.extname(M);if(!R.extensions.includes(Z))return;let $=Y.join(A,M);if(!X.existsSync($))return;F(`${J.dim}[${new Date().toLocaleTimeString()}]${J.reset} ${M} changed`);let G=await b($,B,A,R);if(G.success)C(`Compiled ${M}`);else I(`${M}: ${G.error}`)});process.on("SIGINT",()=>{Q.close(),F(`
98
+ Stopped watching.`),process.exit(0)})}async function n(){let A=process.argv.slice(2);if(A.length===0)w(),process.exit(0);let{command:B,source:R,options:K}=u(A);if(!B)I('No command specified. Use "compile" or "check".'),w(),process.exit(1);if(!R)I("No source path specified."),process.exit(1);let O=Y.resolve(R);if(!X.existsSync(O))I(`Source not found: ${R}`),process.exit(1);let Q=X.statSync(O).isDirectory();if(B==="check")if(Q)await r(O,K);else{let U=V(Y.dirname(O),K.extensions),M=X.readFileSync(O,"utf-8"),Z=U.parse(M),$=N(Z);if($.canFlatten)C(`${R} can be AOT compiled`);else _(`${R}: ${$.reason}`)}else if(B==="compile"){if(!K.output)I("Output directory required. Use -o <dir>"),process.exit(1);let U=Y.resolve(K.output);if(K.watch){if(!Q)I("Watch mode requires a directory, not a single file."),process.exit(1);await l(O,U,K)}else if(Q){let M=Date.now(),{compiled:Z,failed:$}=await T(O,U,K),G=Date.now()-M;if(F(""),$===0)C(`Compiled ${Z} templates in ${G}ms`);else _(`Compiled ${Z} templates, ${$} failed (${G}ms)`)}else{let M=await b(O,U,Y.dirname(O),K);if(M.success)C(`Compiled to ${M.outputPath}`);else I(M.error),process.exit(1)}}}n().catch((A)=>{I(A.message),process.exit(1)});
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Handlebars Engine
3
+ * Converts Handlebars templates to binja's common AST format
4
+ */
5
+ import type { TemplateNode } from '../../parser/nodes';
6
+ export { HandlebarsLexer, HbsTokenType, type HbsToken } from './lexer';
7
+ export { HandlebarsParser } from './parser';
8
+ /**
9
+ * Parse a Handlebars template string into an AST
10
+ */
11
+ export declare function parse(source: string): TemplateNode;
12
+ /**
13
+ * Compile a Handlebars template to a render function
14
+ */
15
+ export declare function compile(source: string): (context: Record<string, any>) => Promise<string>;
16
+ /**
17
+ * Render a Handlebars template with context
18
+ */
19
+ export declare function render(source: string, context?: Record<string, any>): Promise<string>;
20
+ /**
21
+ * Engine interface for multi-engine support
22
+ */
23
+ export declare const engine: {
24
+ name: string;
25
+ extensions: string[];
26
+ parse: typeof parse;
27
+ compile: typeof compile;
28
+ render: typeof render;
29
+ };
30
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Handlebars Lexer
3
+ * Tokenizes Handlebars template syntax: {{expr}}, {{#if}}, {{/if}}, {{>partial}}
4
+ */
5
+ export declare enum HbsTokenType {
6
+ TEXT = "TEXT",
7
+ OPEN = "OPEN",// {{
8
+ OPEN_BLOCK = "OPEN_BLOCK",// {{#
9
+ OPEN_END = "OPEN_END",// {{/
10
+ OPEN_PARTIAL = "OPEN_PARTIAL",// {{>
11
+ OPEN_UNESCAPED = "OPEN_UNESCAPED",// {{{
12
+ OPEN_COMMENT = "OPEN_COMMENT",// {{!
13
+ CLOSE = "CLOSE",// }}
14
+ CLOSE_UNESCAPED = "CLOSE_UNESCAPED",// }}}
15
+ ID = "ID",// identifier
16
+ STRING = "STRING",
17
+ NUMBER = "NUMBER",
18
+ BOOLEAN = "BOOLEAN",
19
+ DOT = "DOT",
20
+ DOTDOT = "DOTDOT",// ..
21
+ SLASH = "SLASH",
22
+ EQUALS = "EQUALS",
23
+ PIPE = "PIPE",
24
+ EOF = "EOF"
25
+ }
26
+ export interface HbsToken {
27
+ type: HbsTokenType;
28
+ value: string;
29
+ line: number;
30
+ column: number;
31
+ }
32
+ export declare class HandlebarsLexer {
33
+ private source;
34
+ private pos;
35
+ private line;
36
+ private column;
37
+ private tokens;
38
+ constructor(source: string);
39
+ tokenize(): HbsToken[];
40
+ private scanToken;
41
+ private scanText;
42
+ private scanExpression;
43
+ private scanExpressionToken;
44
+ private scanString;
45
+ private scanNumber;
46
+ private scanIdentifier;
47
+ private scanComment;
48
+ private scanBlockComment;
49
+ private isAtEnd;
50
+ private peek;
51
+ private peekNext;
52
+ private advance;
53
+ private check;
54
+ private match;
55
+ private skipWhitespace;
56
+ private isDigit;
57
+ private isAlpha;
58
+ private isAlphaNumeric;
59
+ private addToken;
60
+ }
61
+ //# sourceMappingURL=lexer.d.ts.map
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Handlebars Parser
3
+ * Converts Handlebars tokens to a common AST format
4
+ */
5
+ import { HbsToken } from './lexer';
6
+ import type { TemplateNode } from '../../parser/nodes';
7
+ export declare class HandlebarsParser {
8
+ private tokens;
9
+ private current;
10
+ private source;
11
+ constructor(tokens: HbsToken[], source?: string);
12
+ parse(): TemplateNode;
13
+ private parseNodes;
14
+ private parseNode;
15
+ private parseText;
16
+ private parseOutput;
17
+ private parseBlock;
18
+ private parseIfBlock;
19
+ private parseUnlessBlock;
20
+ private parseEachBlock;
21
+ private parseWithBlock;
22
+ private parseCustomBlock;
23
+ private parsePartial;
24
+ private parseExpression;
25
+ private parseExpressionAtom;
26
+ private parsePath;
27
+ private checkOpenBlock;
28
+ private consumeElse;
29
+ private consumeEndBlock;
30
+ private skipComment;
31
+ private isAtEnd;
32
+ private peek;
33
+ private advance;
34
+ private check;
35
+ private expect;
36
+ }
37
+ //# sourceMappingURL=parser.d.ts.map
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Multi-Engine Support
3
+ * Unified interface for multiple template engines
4
+ */
5
+ import * as handlebars from './handlebars';
6
+ import * as liquid from './liquid';
7
+ export { handlebars, liquid };
8
+ /**
9
+ * Engine interface
10
+ */
11
+ export interface TemplateEngine {
12
+ name: string;
13
+ extensions: string[];
14
+ parse: (source: string) => any;
15
+ compile: (source: string) => (context: Record<string, any>) => Promise<string>;
16
+ render: (source: string, context?: Record<string, any>) => Promise<string>;
17
+ }
18
+ /**
19
+ * Registry of all available engines
20
+ */
21
+ export declare const engines: Record<string, TemplateEngine>;
22
+ /**
23
+ * Get engine by name or file extension
24
+ */
25
+ export declare function getEngine(nameOrExt: string): TemplateEngine | undefined;
26
+ /**
27
+ * Detect engine from file path
28
+ */
29
+ export declare function detectEngine(filePath: string): TemplateEngine | undefined;
30
+ /**
31
+ * Render a template with auto-detected engine
32
+ */
33
+ export declare function render(source: string, context?: Record<string, any>, engineName?: string): Promise<string>;
34
+ /**
35
+ * Multi-engine environment for API service
36
+ */
37
+ export declare class MultiEngine {
38
+ private defaultEngine;
39
+ constructor(defaultEngine?: string);
40
+ /**
41
+ * Render with specified engine
42
+ */
43
+ render(source: string, context?: Record<string, any>, engineName?: string): Promise<string>;
44
+ /**
45
+ * Compile template with specified engine
46
+ */
47
+ compile(source: string, engineName?: string): (context: Record<string, any>) => Promise<string>;
48
+ /**
49
+ * List all available engines
50
+ */
51
+ listEngines(): string[];
52
+ }
53
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Liquid Engine
3
+ * Converts Liquid (Shopify) templates to binja's common AST format
4
+ */
5
+ import type { TemplateNode } from '../../parser/nodes';
6
+ export { LiquidLexer, LiquidTokenType, type LiquidToken } from './lexer';
7
+ export { LiquidParser } from './parser';
8
+ /**
9
+ * Parse a Liquid template string into an AST
10
+ */
11
+ export declare function parse(source: string): TemplateNode;
12
+ /**
13
+ * Compile a Liquid template to a render function
14
+ */
15
+ export declare function compile(source: string): (context: Record<string, any>) => Promise<string>;
16
+ /**
17
+ * Render a Liquid template with context
18
+ */
19
+ export declare function render(source: string, context?: Record<string, any>): Promise<string>;
20
+ /**
21
+ * Engine interface for multi-engine support
22
+ */
23
+ export declare const engine: {
24
+ name: string;
25
+ extensions: string[];
26
+ parse: typeof parse;
27
+ compile: typeof compile;
28
+ render: typeof render;
29
+ };
30
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Liquid Lexer
3
+ * Tokenizes Liquid template syntax: {{ output }}, {% tags %}
4
+ * Shopify-compatible implementation
5
+ */
6
+ export declare enum LiquidTokenType {
7
+ TEXT = "TEXT",
8
+ VAR_START = "VAR_START",// {{
9
+ VAR_END = "VAR_END",// }}
10
+ TAG_START = "TAG_START",// {%
11
+ TAG_END = "TAG_END",// %}
12
+ ID = "ID",
13
+ STRING = "STRING",
14
+ NUMBER = "NUMBER",
15
+ DOT = "DOT",
16
+ PIPE = "PIPE",
17
+ COLON = "COLON",
18
+ COMMA = "COMMA",
19
+ LBRACKET = "LBRACKET",
20
+ RBRACKET = "RBRACKET",
21
+ RANGE = "RANGE",// ..
22
+ EQUALS = "EQUALS",// =
23
+ EQ = "EQ",// ==
24
+ NE = "NE",// != or <>
25
+ LT = "LT",
26
+ LE = "LE",
27
+ GT = "GT",
28
+ GE = "GE",
29
+ CONTAINS = "CONTAINS",
30
+ AND = "AND",
31
+ OR = "OR",
32
+ EOF = "EOF"
33
+ }
34
+ export interface LiquidToken {
35
+ type: LiquidTokenType;
36
+ value: string;
37
+ line: number;
38
+ column: number;
39
+ }
40
+ export declare class LiquidLexer {
41
+ private source;
42
+ private pos;
43
+ private line;
44
+ private column;
45
+ private tokens;
46
+ constructor(source: string);
47
+ tokenize(): LiquidToken[];
48
+ private scanToken;
49
+ private checkRawTag;
50
+ private scanRawBlock;
51
+ private scanText;
52
+ private scanExpression;
53
+ private scanExpressionToken;
54
+ private scanString;
55
+ private scanNumber;
56
+ private scanIdentifier;
57
+ private isAtEnd;
58
+ private peek;
59
+ private peekNext;
60
+ private advance;
61
+ private check;
62
+ private match;
63
+ private skipWhitespace;
64
+ private isDigit;
65
+ private isAlpha;
66
+ private isAlphaNumeric;
67
+ private addToken;
68
+ }
69
+ //# sourceMappingURL=lexer.d.ts.map
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Liquid Parser
3
+ * Converts Liquid tokens to a common AST format
4
+ * Shopify-compatible implementation
5
+ */
6
+ import { LiquidToken } from './lexer';
7
+ import type { TemplateNode } from '../../parser/nodes';
8
+ export declare class LiquidParser {
9
+ private tokens;
10
+ private current;
11
+ private source;
12
+ constructor(tokens: LiquidToken[], source?: string);
13
+ parse(): TemplateNode;
14
+ private parseNodes;
15
+ private parseNode;
16
+ private parseText;
17
+ private parseOutput;
18
+ private parseTag;
19
+ private parseIfTag;
20
+ private parseUnlessTag;
21
+ private parseCaseTag;
22
+ private parseForTag;
23
+ private parseForIterable;
24
+ private parseAssignTag;
25
+ private parseCaptureTag;
26
+ private parseIncrementTag;
27
+ private parseIncludeTag;
28
+ private parseCommentTag;
29
+ private parseRawTag;
30
+ private parseCondition;
31
+ private parseOr;
32
+ private parseAnd;
33
+ private parseComparison;
34
+ private parseExpression;
35
+ private parseExpressionAtom;
36
+ private parsePath;
37
+ private checkTag;
38
+ private consumeTag;
39
+ private checkKeyword;
40
+ private expectKeyword;
41
+ private isAtEnd;
42
+ private peek;
43
+ private advance;
44
+ private check;
45
+ private expect;
46
+ }
47
+ //# sourceMappingURL=parser.d.ts.map