binja 0.7.2 → 0.8.1

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 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
- `)}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 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
- `)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()===`
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 I 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 O=[],K=R.templateName?`${R.templateName}:${R.line}:${R.column}`:`line ${R.line}, column ${R.column}`;if(O.push(`${q("red",q("bold",A))}: ${B} at ${q("cyan",K)}`),R.source)O.push(""),O.push(d(R.source,R.line,R.column));if(R.suggestion)O.push(""),O.push(`${q("yellow","Did you mean")}: ${q("cyan",R.suggestion)}?`);if(R.availableOptions&&R.availableOptions.length>0){O.push("");let Q=R.availableOptions.slice(0,8),M=R.availableOptions.length>8?` ${q("gray",`... and ${R.availableOptions.length-8} more`)}`:"";O.push(`${q("gray","Available")}: ${Q.join(", ")}${M}`)}return O.join(`
4
+ `)}function d(A,B,R){let O=A.split(`
5
+ `),K=[],Q=Math.max(1,B-2),M=Math.min(O.length,B+1),$=String(M).length;for(let Z=Q;Z<=M;Z++){let U=O[Z-1]||"",G=String(Z).padStart($," ");if(Z===B){K.push(`${q("red"," \u2192")} ${q("gray",G)} ${q("dim","\u2502")} ${U}`);let H=" ".repeat($+4+Math.max(0,R-1)),z=q("red","^");K.push(`${H}${z}`)}else K.push(` ${q("gray",G)} ${q("dim","\u2502")} ${q("gray",U)}`)}return K.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 O=0;O<A.length;O++)if(this.state.source[B+O]?.toLowerCase()!==A[O])return!1;let R=this.state.source[B+A.length];return!R||!this.isAlphaNumeric(R)}scanRawBlock(A,B){let R=this.state.line,O=this.state.column;for(let M=0;M<A.length;M++)this.advance();if(this.skipWhitespace(),this.peek()==="-")this.advance();if(!this.match(this.blockEnd))throw new I(`Expected '${this.blockEnd}' after '${A}'`,{line:this.state.line,column:this.state.column,source:this.state.source});let K=`end${A}`,Q=this.state.pos;while(!this.isAtEnd()){if(this.check(this.blockStart)){let M=this.state.pos,$=this.state.line,Z=this.state.column;if(this.match(this.blockStart),this.peek()==="-")this.advance();if(this.skipWhitespace(),this.checkWord(K)){let U=this.state.source.slice(Q,M);if(U.length>0)this.state.tokens.push({type:"TEXT",value:U,line:R,column:O});for(let G=0;G<K.length;G++)this.advance();if(this.skipWhitespace(),this.peek()==="-")this.advance();if(!this.match(this.blockEnd))throw new I(`Expected '${this.blockEnd}' after '${K}'`,{line:this.state.line,column:this.state.column,source:this.state.source});return}this.state.pos=M,this.state.line=$,this.state.column=Z}if(this.peek()===`
7
+ `)this.state.line++,this.state.column=0;this.advance()}throw new I(`Unclosed '${A}' block`,{line:R,column:O,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 O=this.state.source.slice(A,this.state.pos);this.state.tokens.push({type:"TEXT",value:O,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 I("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 I("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 I("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;case"?":if(this.match("?"))this.addToken("NULLCOALESCE","??");else this.addToken("QUESTION","?");break;default:if(!this.isWhitespace(A))throw new I(`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
+ `)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,O=this.state.pos+B,K=A.length;if(O+K>R.length)return!1;for(let Q=0;Q<K;Q++)if(R[O+Q]!==A[Q])return!1;if(B===0)this.state.pos+=K,this.state.column+=K;return!0}check(A,B=0){let R=this.state.source,O=this.state.pos+B,K=A.length;if(O+K>R.length)return!1;for(let Q=0;Q<K;Q++)if(R[O+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+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
- `;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 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(`
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=[],O=[],K=[];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 M=[];while(!this.isAtEnd()){if(this.checkBlockTag("elif")||this.checkBlockTag("else")||this.checkBlockTag("endif"))break;let $=this.parseStatement();if($)M.push($)}O.push({test:Q,body:M})}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)K.push(Q)}}return this.expectBlockTag("endif"),{type:"If",test:B,body:R,elifs:O,else_:K,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 O=this.expect("NAME");if(O.value!=="in")throw this.error(`Expected 'in' in for loop, got '${O.value}'`);let K=this.parseExpression(),Q=this.check("NAME")&&this.peek().value==="recursive";if(Q)this.advance();this.expect("BLOCK_END");let M=[],$=[];while(!this.isAtEnd()){if(this.checkBlockTag("empty")||this.checkBlockTag("else")||this.checkBlockTag("endfor"))break;let Z=this.parseStatement();if(Z)M.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)$.push(Z)}}return this.expectBlockTag("endfor"),{type:"For",target:B,iter:K,body:M,else_:$,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 O=[];while(!this.isAtEnd()){if(this.checkBlockTag("endblock"))break;let K=this.parseStatement();if(K)O.push(K)}if(this.advance(),this.advance(),this.check("NAME"))this.advance();return this.expect("BLOCK_END"),{type:"Block",name:B,body:O,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,O=!1,K=!1;while(this.check("NAME")){let Q=this.peek().value;if(Q==="ignore"&&this.peekNext()?.value==="missing")this.advance(),this.advance(),K=!0;else if(Q==="with")this.advance(),R=this.parseKeywordArgs();else if(Q==="only")this.advance(),O=!0;else if(Q==="without"){if(this.advance(),this.check("NAME")&&this.peek().value==="context")this.advance(),O=!0}else break}return this.expect("BLOCK_END"),{type:"Include",template:B,context:R,only:O,ignoreMissing:K,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 O=this.expect("NAME").value;this.expect("ASSIGN");let K=this.parseExpression();B.push({target:O,value:K})}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 O=this.parseStatement();if(O)R.push(O)}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=[],O={},K=null;while(!this.check("BLOCK_END")){if(this.check("NAME")&&this.peek().value==="as"){this.advance(),K=this.expect("NAME").value;break}if(this.check("NAME")&&this.peekNext()?.type==="ASSIGN"){let Q=this.advance().value;this.advance(),O[Q]=this.parseExpression()}else R.push(this.parseExpression())}return this.expect("BLOCK_END"),{type:"Url",name:B,args:R,kwargs:O,asVar:K,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,O=!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(),O=!0;break}B.push(this.parseExpression())}return this.expect("BLOCK_END"),{type:"Cycle",values:B,asVar:R,silent:O,line:A.line,column:A.column}}parseFirstof(A){let B=[],R=null,O=null;while(!this.check("BLOCK_END")){if(this.check("NAME")&&this.peek().value==="as"){this.advance(),O=this.expect("NAME").value;break}B.push(this.parseExpression())}if(B.length>0){let K=B[B.length-1];if(K.type==="Literal"&&typeof K.value==="string")R=B.pop()}return this.expect("BLOCK_END"),{type:"Firstof",values:B,fallback:R,asVar:O,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=[],O=[];while(!this.isAtEnd()){if(this.checkBlockTag("else")||this.checkBlockTag("endifchanged"))break;let K=this.parseStatement();if(K)R.push(K)}if(this.checkBlockTag("else")){this.advance(),this.advance(),this.expect("BLOCK_END");while(!this.isAtEnd()){if(this.checkBlockTag("endifchanged"))break;let K=this.parseStatement();if(K)O.push(K)}}return this.expectBlockTag("endifchanged"),{type:"Ifchanged",values:B,body:R,else_:O,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 O=this.expect("NAME").value;return this.expect("BLOCK_END"),{type:"Regroup",target:B,key:R,asVar:O,line:A.line,column:A.column}}parseWidthratio(A){let B=this.parseExpression(),R=this.parseExpression(),O=this.parseExpression(),K=null;if(this.check("NAME")&&this.peek().value==="as")this.advance(),K=this.expect("NAME").value;return this.expect("BLOCK_END"),{type:"Widthratio",value:B,maxValue:R,maxWidth:O,asVar:K,line:A.line,column:A.column}}parseLorem(A){let B=null,R="p",O=!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 K=this.peek().value.toLowerCase();if(K==="w"||K==="p"||K==="b")R=K,this.advance()}if(this.check("NAME")&&this.peek().value==="random")O=!0,this.advance();return this.expect("BLOCK_END"),{type:"Lorem",count:B,method:R,random:O,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(),O=this.parseExpression();this.expect("BLOCK_END");let K={type:"Compare",left:R,ops:[{operator:B?"!=":"==",right:O}],line:A.line,column:A.column},Q=[],M=[],$=B?"endifnotequal":"endifequal";while(!this.isAtEnd()){if(this.checkBlockTag("else")||this.checkBlockTag($))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($))break;let Z=this.parseStatement();if(Z)M.push(Z)}}return this.expectBlockTag($),{type:"If",test:K,body:Q,elifs:[],else_:M,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 K=this.peek().value;if(K==="in")this.advance(),R="in";else if(K==="not"&&this.peekNext()?.value==="in")this.advance(),this.advance(),R="not in";else if(K==="is"){this.advance();let Q=this.check("NOT");if(Q)this.advance();let $=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:$,args:Z,negated:Q,line:A.line,column:A.column};continue}}if(!R)break;let O=this.parseAddSub();B.push({operator:R,right:O})}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=[],O={};if(this.match("COLON"))if(this.check("SUB")||this.check("ADD")){let K=this.advance(),Q=this.parsePostfix();R.push({type:"UnaryOp",operator:K.value,operand:Q,line:K.line,column:K.column})}else R.push(this.parsePostfix());else if(this.match("LPAREN")){while(!this.check("RPAREN")){if(this.check("NAME")&&this.peekNext()?.type==="ASSIGN"){let K=this.advance().value;this.advance(),O[K]=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:O,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 O=this.advance().value;this.advance(),R[O]=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 O=this.parseExpression();if(B.push({key:R,value:O}),!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 I(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
+ `;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 O of A.elifs){let K=this.compileExpr(O.test);B+=` else if (isTruthy(${K})) {${this.nl()}`,B+=this.compileNodes(O.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"),O=this.genVar("len"),K=this.genVar("loop"),Q=Array.isArray(A.target)?A.target[0]:A.target,M=Array.isArray(A.target)&&A.target[1]?A.target[1]:null,$=this.loopStack.length>0?this.loopStack[this.loopStack.length-1]:null,Z=this.compileExpr(A.iter),U="";if(U+=` const ${B} = toArray(${Z});${this.nl()}`,U+=` const ${O} = ${B}.length;${this.nl()}`,A.else_.length>0)U+=` if (${O} === 0) {${this.nl()}`,U+=this.compileNodes(A.else_),U+=` } else {${this.nl()}`;if(U+=` for (let ${R} = 0; ${R} < ${O}; ${R}++) {${this.nl()}`,M)U+=` const ${Q} = ${B}[${R}][0];${this.nl()}`,U+=` const ${M} = ${B}[${R}][1];${this.nl()}`;else U+=` const ${Q} = ${B}[${R}];${this.nl()}`;if(U+=` const ${K} = {${this.nl()}`,U+=` counter: ${R} + 1,${this.nl()}`,U+=` counter0: ${R},${this.nl()}`,U+=` revcounter: ${O} - ${R},${this.nl()}`,U+=` revcounter0: ${O} - ${R} - 1,${this.nl()}`,U+=` first: ${R} === 0,${this.nl()}`,U+=` last: ${R} === ${O} - 1,${this.nl()}`,U+=` length: ${O},${this.nl()}`,U+=` index: ${R} + 1,${this.nl()}`,U+=` index0: ${R},${this.nl()}`,$)U+=` parentloop: ${$},${this.nl()}`,U+=` parent: ${$}${this.nl()}`;else U+=` parentloop: null,${this.nl()}`,U+=` parent: null${this.nl()}`;U+=` };${this.nl()}`,U+=` const forloop = ${K};${this.nl()}`,U+=` const loop = ${K};${this.nl()}`,this.loopStack.push(K);let G=this.compileNodes(A.body);if(U+=G.replace(new RegExp(`__ctx\\.${Q}`,"g"),Q),this.loopStack.pop(),U+=` }${this.nl()}`,A.else_.length>0)U+=` }${this.nl()}`;return U}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:O}of A.assignments){let K=this.compileExpr(O);B+=` const ${R} = ${K};${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:O})=>{let K=this.compileExpr(R),Q=this.compileExpr(O);return`[${K}]: ${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:O}of A.ops){let K=this.compileExpr(O);switch(R){case"==":case"===":B=`(${B} === ${K})`;break;case"!=":case"!==":B=`(${B} !== ${K})`;break;default:B=`(${B} ${R} ${K})`}}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((O)=>this.compileExpr(O));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 O=R.length?", "+R.join(", "):"";return`applyFilter('${A.filter}', ${B}${O})`}}compileTest(A){let B=this.compileExpr(A.node),R=A.args.map((K)=>this.compileExpr(K)),O=A.negated?"!":"";switch(A.test){case"defined":return`${O}(${B} !== undefined)`;case"undefined":return`${O}(${B} === undefined)`;case"none":return`${O}(${B} === null)`;case"even":return`${O}(${B} % 2 === 0)`;case"odd":return`${O}(${B} % 2 !== 0)`;case"divisibleby":return`${O}(${B} % ${R[0]} === 0)`;case"empty":return`${O}((${B} == null) || (${B}.length === 0) || (Object.keys(${B}).length === 0))`;case"iterable":return`${O}(Array.isArray(${B}) || typeof ${B} === 'string')`;case"number":return`${O}(typeof ${B} === 'number' && !isNaN(${B}))`;case"string":return`${O}(typeof ${B} === 'string')`;default:let K=R.length?", "+R.join(", "):"";return`${O}applyTest('${A.test}', ${B}${K})`}}compileConditional(A){let B=this.compileExpr(A.test),R=this.compileExpr(A.trueExpr),O=this.compileExpr(A.falseExpr);return`(isTruthy(${B}) ? ${R} : ${O})`}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 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 O=this.getStaticTemplateName(R.template),K=this.loader.load(O),Q=this.loader.parse(K);this.depth++;let M=this.processTemplate(Q,!1);return this.depth--,{type:"Template",body:this.replaceBlocks(M.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 O=R;if(B||!this.blocks.has(O.name))this.blocks.set(O.name,O)}this.collectBlocksFromNode(R,B)}}collectBlocksFromNode(A,B=!0){switch(A.type){case"If":{let R=A;this.collectBlocks(R.body,B);for(let O of R.elifs)this.collectBlocks(O.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 K=R,Q=this.inlineInclude(K);B.push(...Q);continue}if(R.type==="Block"){let K=R,Q=this.blocks.get(K.name);if(Q&&Q!==K)B.push(...this.processNodes(Q.body));else B.push(...this.processNodes(K.body));continue}let O=this.processNode(R);if(O)B.push(O)}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),O=this.loader.parse(R);this.depth++;let K=this.processTemplate(O);if(this.depth--,A.context&&Object.keys(A.context).length>0)return[{type:"With",assignments:Object.entries(A.context).map(([M,$])=>({target:M,value:$})),body:K.body,line:A.line,column:A.column}];return K.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 O of B.elifs)if(R=this.checkNodes(O.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 E(A){console.log(A)}function L(A){console.log(`${J.green}\u2713${J.reset} ${A}`)}function _(A){console.log(`${J.yellow}\u26A0${J.reset} ${A}`)}function F(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}
@@ -50,7 +50,7 @@ ${J.yellow}Output:${J.reset}
50
50
  ${J.dim}// Usage${J.reset}
51
51
  import { render } from './dist/templates/home.js'
52
52
  const html = render({ title: 'Hello' })
53
- `)}function u(A){let B={output:"",minify:!1,watch:!1,extensions:[".html",".jinja",".jinja2"],verbose:!1},R="",K="";for(let O=0;O<A.length;O++){let Q=A[O];if(Q==="compile"||Q==="check"||Q==="watch"){if(R=Q,Q==="watch")B.watch=!0,R="compile"}else if(Q==="-o"||Q==="--output")B.output=A[++O];else if(Q==="-n"||Q==="--name")B.name=A[++O];else if(Q==="-m"||Q==="--minify")B.minify=!0;else if(Q==="-w"||Q==="--watch")B.watch=!0;else if(Q==="-v"||Q==="--verbose")B.verbose=!0;else if(Q==="-e"||Q==="--ext")B.extensions=A[++O].split(",").map((U)=>U.startsWith(".")?U:`.${U}`);else if(Q==="--help"||Q==="-h")w(),process.exit(0);else if(Q==="--version"||Q==="-V")console.log(`binja v${i}`),process.exit(0);else if(!Q.startsWith("-")&&!K)K=Q}return{command:R,source:K,options:B}}function V(A,B){return{load(R){let K=Y.resolve(A,R);for(let O of[...B,""]){let Q=K+O;if(X.existsSync(Q))return X.readFileSync(Q,"utf-8")}throw Error(`Template not found: ${R}`)},parse(R){let O=new D(R).tokenize();return new S(O).parse()}}}function s(A,B){return`// Generated by binja - DO NOT EDIT
53
+ `)}function u(A){let B={output:"",minify:!1,watch:!1,extensions:[".html",".jinja",".jinja2"],verbose:!1},R="",O="";for(let K=0;K<A.length;K++){let Q=A[K];if(Q==="compile"||Q==="check"||Q==="watch"){if(R=Q,Q==="watch")B.watch=!0,R="compile"}else if(Q==="-o"||Q==="--output")B.output=A[++K];else if(Q==="-n"||Q==="--name")B.name=A[++K];else if(Q==="-m"||Q==="--minify")B.minify=!0;else if(Q==="-w"||Q==="--watch")B.watch=!0;else if(Q==="-v"||Q==="--verbose")B.verbose=!0;else if(Q==="-e"||Q==="--ext")B.extensions=A[++K].split(",").map((M)=>M.startsWith(".")?M:`.${M}`);else if(Q==="--help"||Q==="-h")w(),process.exit(0);else if(Q==="--version"||Q==="-V")console.log(`binja v${i}`),process.exit(0);else if(!Q.startsWith("-")&&!O)O=Q}return{command:R,source:O,options:B}}function V(A,B){return{load(R){let O=Y.resolve(A,R);for(let K of[...B,""]){let Q=O+K;if(X.existsSync(Q))return X.readFileSync(Q,"utf-8")}throw Error(`Template not found: ${R}`)},parse(R){let K=new D(R).tokenize();return new S(K).parse()}}}function s(A,B){return`// Generated by binja - DO NOT EDIT
54
54
  // Source: binja compile
55
55
 
56
56
  const escape = (v) => {
@@ -94,5 +94,5 @@ ${A}
94
94
 
95
95
  export { ${B} as render };
96
96
  export default ${B};
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)});
97
+ `}async function b(A,B,R,O){try{let K=X.readFileSync(A,"utf-8"),Q=V(R,O.extensions),M=Q.parse(K),$=N(M),Z=M;if(!$.canFlatten){if(O.verbose)_(`${Y.basename(A)}: ${$.reason} - compiling without inheritance resolution`)}else Z=k(M,{loader:Q});let U=Y.relative(R,A),G=O.name||"render"+U.replace(/\.[^.]+$/,"").replace(/[^a-zA-Z0-9]/g,"_").replace(/^_+|_+$/g,"").replace(/_([a-z])/g,(o,c)=>c.toUpperCase()),W=f(Z,{functionName:G,minify:O.minify}),H=U.replace(/\.[^.]+$/,".js"),z=Y.join(B,H),C=Y.dirname(z);if(!X.existsSync(C))X.mkdirSync(C,{recursive:!0});let j=s(W,G);return X.writeFileSync(z,j),{success:!0,outputPath:z}}catch(K){return{success:!1,error:K.message}}}async function T(A,B,R){let O=0,K=0,Q=[];function M($){let Z=X.readdirSync($,{withFileTypes:!0});for(let U of Z){let G=Y.join($,U.name);if(U.isDirectory())M(G);else if(U.isFile()){let W=Y.extname(U.name);if(R.extensions.includes(W))Q.push(G)}}}M(A);for(let $ of Q){let Z=await b($,B,A,R);if(Z.success){if(O++,R.verbose)L(`${Y.relative(A,$)} \u2192 ${Y.relative(process.cwd(),Z.outputPath)}`)}else K++,F(`${Y.relative(A,$)}: ${Z.error}`)}return{compiled:O,failed:K}}async function r(A,B){let R=V(A,B.extensions),O=0,K=0,Q=0;function M($){let Z=X.readdirSync($,{withFileTypes:!0});for(let U of Z){let G=Y.join($,U.name);if(U.isDirectory())M(G);else if(U.isFile()){let W=Y.extname(U.name);if(B.extensions.includes(W)){O++;try{let H=X.readFileSync(G,"utf-8"),z=R.parse(H),C=N(z),j=Y.relative(A,G);if(C.canFlatten)K++,L(`${j}`);else Q++,_(`${j}: ${C.reason}`)}catch(H){Q++,F(`${Y.relative(A,G)}: ${H.message}`)}}}}}if(M(A),E(""),E(`Total: ${O} templates`),E(`${J.green}AOT compatible: ${K}${J.reset}`),Q>0)E(`${J.yellow}Require runtime: ${Q}${J.reset}`)}async function l(A,B,R){E(`${J.cyan}Watching${J.reset} ${A} for changes...`),E(`${J.dim}Press Ctrl+C to stop${J.reset}`),E("");let{compiled:O,failed:K}=await T(A,B,{...R,verbose:!0});E(""),E(`Compiled ${O} templates${K>0?`, ${K} failed`:""}`),E("");let Q=X.watch(A,{recursive:!0},async(M,$)=>{if(!$)return;let Z=Y.extname($);if(!R.extensions.includes(Z))return;let U=Y.join(A,$);if(!X.existsSync(U))return;E(`${J.dim}[${new Date().toLocaleTimeString()}]${J.reset} ${$} changed`);let G=await b(U,B,A,R);if(G.success)L(`Compiled ${$}`);else F(`${$}: ${G.error}`)});process.on("SIGINT",()=>{Q.close(),E(`
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:O}=u(A);if(!B)F('No command specified. Use "compile" or "check".'),w(),process.exit(1);if(!R)F("No source path specified."),process.exit(1);let K=Y.resolve(R);if(!X.existsSync(K))F(`Source not found: ${R}`),process.exit(1);let Q=X.statSync(K).isDirectory();if(B==="check")if(Q)await r(K,O);else{let M=V(Y.dirname(K),O.extensions),$=X.readFileSync(K,"utf-8"),Z=M.parse($),U=N(Z);if(U.canFlatten)L(`${R} can be AOT compiled`);else _(`${R}: ${U.reason}`)}else if(B==="compile"){if(!O.output)F("Output directory required. Use -o <dir>"),process.exit(1);let M=Y.resolve(O.output);if(O.watch){if(!Q)F("Watch mode requires a directory, not a single file."),process.exit(1);await l(K,M,O)}else if(Q){let $=Date.now(),{compiled:Z,failed:U}=await T(K,M,O),G=Date.now()-$;if(E(""),U===0)L(`Compiled ${Z} templates in ${G}ms`);else _(`Compiled ${Z} templates, ${U} failed (${G}ms)`)}else{let $=await b(K,M,Y.dirname(K),O);if($.success)L(`Compiled to ${$.outputPath}`);else F($.error),process.exit(1)}}}n().catch((A)=>{F(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,54 @@
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
+ import * as twig from './twig';
8
+ export { handlebars, liquid, twig };
9
+ /**
10
+ * Engine interface
11
+ */
12
+ export interface TemplateEngine {
13
+ name: string;
14
+ extensions: string[];
15
+ parse: (source: string) => any;
16
+ compile: (source: string) => (context: Record<string, any>) => Promise<string>;
17
+ render: (source: string, context?: Record<string, any>) => Promise<string>;
18
+ }
19
+ /**
20
+ * Registry of all available engines
21
+ */
22
+ export declare const engines: Record<string, TemplateEngine>;
23
+ /**
24
+ * Get engine by name or file extension
25
+ */
26
+ export declare function getEngine(nameOrExt: string): TemplateEngine | undefined;
27
+ /**
28
+ * Detect engine from file path
29
+ */
30
+ export declare function detectEngine(filePath: string): TemplateEngine | undefined;
31
+ /**
32
+ * Render a template with auto-detected engine
33
+ */
34
+ export declare function render(source: string, context?: Record<string, any>, engineName?: string): Promise<string>;
35
+ /**
36
+ * Multi-engine environment for API service
37
+ */
38
+ export declare class MultiEngine {
39
+ private defaultEngine;
40
+ constructor(defaultEngine?: string);
41
+ /**
42
+ * Render with specified engine
43
+ */
44
+ render(source: string, context?: Record<string, any>, engineName?: string): Promise<string>;
45
+ /**
46
+ * Compile template with specified engine
47
+ */
48
+ compile(source: string, engineName?: string): (context: Record<string, any>) => Promise<string>;
49
+ /**
50
+ * List all available engines
51
+ */
52
+ listEngines(): string[];
53
+ }
54
+ //# 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