kriti-lang
Version:
A TypeScript implementation of the Kriti templating language
3 lines (2 loc) • 29 kB
JavaScript
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).KritiLang={})}(this,function(e){"use strict";var t,s;!function(e){e.STRING="STRING",e.NUMBER="NUMBER",e.INTEGER="INTEGER",e.BOOLEAN="BOOLEAN",e.NULL="NULL",e.IDENTIFIER="IDENTIFIER",e.IF="IF",e.ELIF="ELIF",e.ELSE="ELSE",e.END="END",e.RANGE="RANGE",e.IN="IN",e.NOT="NOT",e.TRUE="TRUE",e.FALSE="FALSE",e.EQ="EQ",e.NE="NE",e.GT="GT",e.LT="LT",e.GTE="GTE",e.LTE="LTE",e.AND="AND",e.OR="OR",e.DEFAULT="DEFAULT",e.ASSIGN="ASSIGN",e.COLON="COLON",e.DOT="DOT",e.COMMA="COMMA",e.QUESTION="QUESTION",e.SQUOTE="SQUOTE",e.UNDERSCORE="UNDERSCORE",e.LPAREN="LPAREN",e.RPAREN="RPAREN",e.LBRACE="LBRACE",e.RBRACE="RBRACE",e.LBRACKET="LBRACKET",e.RBRACKET="RBRACKET",e.DQUOTE="DQUOTE",e.TEMPLATE_START="TEMPLATE_START",e.TEMPLATE_END="TEMPLATE_END",e.STRING_LITERAL="STRING_LITERAL",e.EOF="EOF",e.NEWLINE="NEWLINE",e.WHITESPACE="WHITESPACE",e.COMMENT="COMMENT"}(t||(t={}));class r{static isDigit(e){const t=e.charCodeAt(0);return t>=48&&t<=57}static isLetter(e){const t=e.charCodeAt(0);return t>=65&&t<=90||t>=97&&t<=122}static isAlphaNumeric(e){return this.isLetter(e)||this.isDigit(e)}static isIdentifierStart(e){return this.isLetter(e)||"$"===e}static isIdentifierChar(e){return this.isAlphaNumeric(e)||"_"===e||"$"===e||"-"===e}static isHexDigit(e){const t=e.charCodeAt(0);return t>=48&&t<=57||t>=65&&t<=70||t>=97&&t<=102}static isWhitespace(e){return" "===e||"\t"===e||"\r"===e}static isLineTerminator(e){return"\n"===e}static isQuote(e){return'"'===e||"'"===e}static hexToDecimal(e){let t=0;for(let s=0;s<e.length;s++){const r=e[s].charCodeAt(0);let n;if(r>=48&&r<=57)n=r-48;else if(r>=65&&r<=70)n=r-65+10;else{if(!(r>=97&&r<=102))return-1;n=r-97+10}t=16*t+n}return t}}class n extends Error{constructor(e,t){super(`Lexer error at line ${t.line}, column ${t.column}: ${e}`),this.position=t,this.name="LexerError"}}class i{constructor(e){this.position=0,this.line=1,this.column=1,this.tokens=[],this.inStringTemplate=!1,this.templateDepth=0,this.input=e}getCurrentPosition(){return{line:this.line,column:this.column,offset:this.position}}peek(e=0){const t=this.position+e;return t<this.input.length?this.input[t]:""}advance(){if(this.position>=this.input.length)return"";const e=this.input[this.position];return this.position++,"\n"===e?(this.line++,this.column=1):this.column++,e}skipWhitespace(){for(;this.position<this.input.length&&r.isWhitespace(this.peek());)this.advance()}skipComment(){if("#"===this.peek())for(;this.position<this.input.length&&!r.isLineTerminator(this.peek());)this.advance()}readEscapeSequence(){const e=this.advance();switch(e){case"n":return"\n";case"t":return"\t";case"r":return"\r";case"b":return"\b";case"f":return"\f";case"\\":return"\\";case'"':return'"';case"/":return"/";case"{":return"{";case"u":{let e="";for(let t=0;t<4;t++){const t=this.peek();if(!r.isHexDigit(t))throw new n("Invalid unicode escape sequence",this.getCurrentPosition());e+=this.advance()}const t=r.hexToDecimal(e);return String.fromCharCode(t)}default:return e}}readString(){let e="";for(this.advance();this.position<this.input.length;){const t=this.peek();if('"'===t){this.advance();break}if("\\"===t)this.advance(),e+=this.readEscapeSequence();else{if("{"===t&&"{"===this.peek(1))break;e+=this.advance()}}return e}readNumber(){let e="",t=!1;for("-"===this.peek()&&(e+=this.advance());r.isDigit(this.peek());)e+=this.advance();if("."===this.peek()&&r.isDigit(this.peek(1)))for(t=!0,e+=this.advance();r.isDigit(this.peek());)e+=this.advance();const s=this.peek();if("e"===s||"E"===s){t=!0,e+=this.advance();const s=this.peek();for("+"!==s&&"-"!==s||(e+=this.advance());r.isDigit(this.peek());)e+=this.advance()}return{value:e,isDecimal:t}}readIdentifier(){let e="";for("$"===this.peek()&&(e+=this.advance()),r.isLetter(this.peek())&&(e+=this.advance());r.isIdentifierChar(this.peek());)e+=this.advance();return e}readStringTemplatePart(){let e="";for(;this.position<this.input.length;){const t=this.peek();if('"'===t)break;if("{"===t&&"{"===this.peek(1))break;"\\"===t?(this.advance(),e+=this.readEscapeSequence()):e+=this.advance()}return e}createToken(e,t,s){return{type:e,value:t,start:s,end:this.getCurrentPosition()}}getKeywordType(e){switch(e){case"if":return t.IF;case"elif":return t.ELIF;case"else":return t.ELSE;case"end":return t.END;case"null":return t.NULL;case"range":return t.RANGE;case"in":return t.IN;case"not":return t.NOT;case"true":return t.TRUE;case"false":return t.FALSE;default:return null}}matchTwoCharOperator(){switch(this.peek()+this.peek(1)){case"==":return{type:t.EQ,value:"=="};case"!=":return{type:t.NE,value:"!="};case">=":return{type:t.GTE,value:">="};case"<=":return{type:t.LTE,value:"<="};case"&&":return{type:t.AND,value:"&&"};case"||":return{type:t.OR,value:"||"};case"??":return{type:t.DEFAULT,value:"??"};case":=":return{type:t.ASSIGN,value:":="};case"{{":return{type:t.TEMPLATE_START,value:"{{"};case"}}":return{type:t.TEMPLATE_END,value:"}}"};default:return null}}getSingleCharTokenType(e){switch(e){case":":return t.COLON;case".":return t.DOT;case",":return t.COMMA;case"?":return t.QUESTION;case"'":return t.SQUOTE;case"_":return t.UNDERSCORE;case"(":return t.LPAREN;case")":return t.RPAREN;case"{":return t.LBRACE;case"}":return t.RBRACE;case"[":return t.LBRACKET;case"]":return t.RBRACKET;case">":return t.GT;case"<":return t.LT;default:return null}}isNumberStart(e,t){return r.isDigit(e)||"-"===e&&r.isDigit(t)}tokenize(){for(this.tokens=[];this.position<this.input.length;){const e=this.getCurrentPosition(),s=this.peek();if(r.isWhitespace(s)){this.skipWhitespace();continue}if(r.isLineTerminator(s)){this.advance();continue}if("#"===s){this.skipComment();continue}if('"'===s){for(this.inStringTemplate=!0,this.advance(),this.tokens.push(this.createToken(t.DQUOTE,'"',e));this.position<this.input.length&&this.inStringTemplate;){const e=this.getCurrentPosition();if('"'===this.peek()){this.advance(),this.tokens.push(this.createToken(t.DQUOTE,'"',e)),this.inStringTemplate=!1;break}if("{"===this.peek()&&"{"===this.peek(1)){this.advance(),this.advance(),this.tokens.push(this.createToken(t.TEMPLATE_START,"{{",e)),this.templateDepth++,this.tokenizeExpression();continue}const s=this.readStringTemplatePart();s&&this.tokens.push(this.createToken(t.STRING_LITERAL,s,e))}continue}const i=this.matchTwoCharOperator();if(i){this.advance(),this.advance(),this.tokens.push(this.createToken(i.type,i.value,e)),i.type===t.TEMPLATE_START?this.templateDepth++:i.type===t.TEMPLATE_END&&this.templateDepth--;continue}const a=this.getSingleCharTokenType(s);if(!a){if(this.isNumberStart(s,this.peek(1))){const{value:s,isDecimal:r}=this.readNumber(),n=r?t.NUMBER:t.INTEGER;this.tokens.push(this.createToken(n,s,e));continue}if(r.isIdentifierStart(s)){const s=this.readIdentifier(),r=this.getKeywordType(s);r?this.tokens.push(this.createToken(r,s,e)):this.tokens.push(this.createToken(t.IDENTIFIER,s,e));continue}throw new n(`Unexpected character: '${s}' (code: ${s.charCodeAt(0)})`,e)}this.advance(),this.tokens.push(this.createToken(a,s,e))}return this.tokens.push(this.createToken(t.EOF,"",this.getCurrentPosition())),this.tokens}tokenizeExpression(){let e=1;for(;this.position<this.input.length&&e>0;){const s=this.getCurrentPosition(),i=this.peek();if(r.isWhitespace(i)||r.isLineTerminator(i))r.isLineTerminator(i)?this.advance():this.skipWhitespace();else if("#"!==i){if("}"===i&&"}"===this.peek(1)){this.advance(),this.advance(),this.tokens.push(this.createToken(t.TEMPLATE_END,"}}",s)),e--,this.templateDepth--;break}if("{"!==i||"{"!==this.peek(1)){if('"'===i){const e=this.readString();this.tokens.push(this.createToken(t.STRING_LITERAL,e,s));continue}if(!this.tokenizeNextToken())throw new n(`Unable to tokenize character: '${i}' (code: ${i.charCodeAt(0)})`,s)}else this.advance(),this.advance(),this.tokens.push(this.createToken(t.TEMPLATE_START,"{{",s)),e++,this.templateDepth++}else this.skipComment()}}tokenizeNextToken(){const e=this.getCurrentPosition(),s=this.peek(),n=this.matchTwoCharOperator();if(n)return this.advance(),this.advance(),this.tokens.push(this.createToken(n.type,n.value,e)),!0;const i=this.getSingleCharTokenType(s);if(i)return this.advance(),this.tokens.push(this.createToken(i,s,e)),!0;if(this.isNumberStart(s,this.peek(1))){const{value:s,isDecimal:r}=this.readNumber(),n=r?t.NUMBER:t.INTEGER;return this.tokens.push(this.createToken(n,s,e)),!0}if(r.isIdentifierStart(s)){const s=this.readIdentifier(),r=this.getKeywordType(s);return r?this.tokens.push(this.createToken(r,s,e)):this.tokens.push(this.createToken(t.IDENTIFIER,s,e)),!0}return!1}}function a(e){return new i(e).tokenize()}!function(e){e[e.LOWEST=0]="LOWEST",e[e.LOGICAL_OR=10]="LOGICAL_OR",e[e.LOGICAL_AND=20]="LOGICAL_AND",e[e.IN=30]="IN",e[e.EQUALITY=40]="EQUALITY",e[e.RELATIONAL=50]="RELATIONAL",e[e.DEFAULTING=60]="DEFAULTING",e[e.UNARY=70]="UNARY",e[e.ACCESS=80]="ACCESS",e[e.PRIMARY=90]="PRIMARY"}(s||(s={}));class o extends Error{constructor(e,t,s){super(`Parse error at line ${t.line}, column ${t.column}: ${e}`),this.position=t,this.token=s,this.name="ParseError"}}class h{constructor(e){this.position=0,this.prefixParseFns=new Map,this.infixParseFns=new Map,this.precedences=new Map,this.tokens=e,this.current=this.tokens[0]||this.createEOFToken(),this.initializeParseFunctions(),this.initializePrecedences()}createEOFToken(){return{type:t.EOF,value:"",start:{line:1,column:1,offset:0},end:{line:1,column:1,offset:0}}}initializePrecedences(){this.precedences.set(t.OR,s.LOGICAL_OR),this.precedences.set(t.AND,s.LOGICAL_AND),this.precedences.set(t.IN,s.IN),this.precedences.set(t.EQ,s.EQUALITY),this.precedences.set(t.NE,s.EQUALITY),this.precedences.set(t.GT,s.RELATIONAL),this.precedences.set(t.LT,s.RELATIONAL),this.precedences.set(t.GTE,s.RELATIONAL),this.precedences.set(t.LTE,s.RELATIONAL),this.precedences.set(t.DEFAULT,s.DEFAULTING),this.precedences.set(t.DOT,s.ACCESS),this.precedences.set(t.LBRACKET,s.ACCESS),this.precedences.set(t.QUESTION,s.ACCESS)}initializeParseFunctions(){this.prefixParseFns.set(t.IDENTIFIER,this.parseIdentifierOrFunctionCall.bind(this)),this.prefixParseFns.set(t.STRING_LITERAL,this.parseStringLiteral.bind(this)),this.prefixParseFns.set(t.NUMBER,this.parseNumber.bind(this)),this.prefixParseFns.set(t.INTEGER,this.parseInteger.bind(this)),this.prefixParseFns.set(t.TRUE,this.parseBoolean.bind(this)),this.prefixParseFns.set(t.FALSE,this.parseBoolean.bind(this)),this.prefixParseFns.set(t.NULL,this.parseNull.bind(this)),this.prefixParseFns.set(t.LBRACKET,this.parseArray.bind(this)),this.prefixParseFns.set(t.LBRACE,this.parseObject.bind(this)),this.prefixParseFns.set(t.DQUOTE,this.parseStringTemplate.bind(this)),this.prefixParseFns.set(t.TEMPLATE_START,this.parseTemplateExpression.bind(this)),this.prefixParseFns.set(t.NOT,this.parseUnaryExpression.bind(this)),this.prefixParseFns.set(t.LPAREN,this.parseGroupedExpression.bind(this)),this.infixParseFns.set(t.OR,this.parseBinaryExpression.bind(this)),this.infixParseFns.set(t.AND,this.parseBinaryExpression.bind(this)),this.infixParseFns.set(t.IN,this.parseBinaryExpression.bind(this)),this.infixParseFns.set(t.EQ,this.parseBinaryExpression.bind(this)),this.infixParseFns.set(t.NE,this.parseBinaryExpression.bind(this)),this.infixParseFns.set(t.GT,this.parseBinaryExpression.bind(this)),this.infixParseFns.set(t.LT,this.parseBinaryExpression.bind(this)),this.infixParseFns.set(t.GTE,this.parseBinaryExpression.bind(this)),this.infixParseFns.set(t.LTE,this.parseBinaryExpression.bind(this)),this.infixParseFns.set(t.DEFAULT,this.parseBinaryExpression.bind(this)),this.infixParseFns.set(t.DOT,this.parseFieldAccess.bind(this)),this.infixParseFns.set(t.LBRACKET,this.parseComputedAccess.bind(this)),this.infixParseFns.set(t.QUESTION,this.parseOptionalChain.bind(this))}currentPrecedence(){return this.precedences.get(this.current.type)||s.LOWEST}isTerminatingToken(e){return[t.EOF,t.TEMPLATE_END,t.RPAREN,t.RBRACKET,t.RBRACE,t.COMMA,t.COLON,t.DQUOTE,t.SQUOTE,t.ASSIGN,t.IF,t.ELIF,t.ELSE,t.END,t.RANGE].includes(e)}advance(){const e=this.current;return this.position<this.tokens.length-1&&(this.position++,this.current=this.tokens[this.position]),e}peek(e=1){const t=this.position+e;return t<this.tokens.length?this.tokens[t]:this.createEOFToken()}match(...e){return e.includes(this.current.type)}consume(e,t){if(this.current.type===e){const e=this.current;return this.advance(),e}throw new o(t||`Expected ${e}, got ${this.current.type}`,this.current.start,this.current)}createNode(e,t,s,r={}){return{type:e,start:t,end:s,...r}}parse(){const e=this.parseExpression();if(this.current.type!==t.EOF)throw new o("Unexpected token after expression",this.current.start,this.current);return e}parseExpression(e=s.LOWEST){const t=this.prefixParseFns.get(this.current.type);if(!t)throw new o(`No prefix parse function found for token type: ${this.current.type}`,this.current.start,this.current);let r=t();for(;!this.isTerminatingToken(this.current.type)&&e<this.currentPrecedence();){const e=this.infixParseFns.get(this.current.type);if(!e)return r;r=e(r)}return r}parseIdentifierOrFunctionCall(){const e=this.current.start;if(!this.match(t.IDENTIFIER))throw new o("Expected identifier",this.current.start,this.current);if(this.peek().type===t.LPAREN){const e=this.advance();this.consume(t.LPAREN,'Expected "("');const s=this.parseExpression(),r=this.consume(t.RPAREN,'Expected ")"');return this.createNode("FunctionCall",e.start,r.end,{name:e.value,argument:s})}const s=this.advance();return this.createNode("Variable",e,s.end,{name:s.value})}parseStringLiteral(){const e=this.current.start,t=this.advance();return this.createNode("Literal",e,t.end,{valueType:"string",value:t.value})}parseNumber(){const e=this.current.start,t=this.advance();return this.createNode("Literal",e,t.end,{valueType:"number",value:parseFloat(t.value)})}parseInteger(){const e=this.current.start,t=this.advance();return this.createNode("Literal",e,t.end,{valueType:"integer",value:parseInt(t.value,10)})}parseBoolean(){const e=this.current.start,s=this.advance();return this.createNode("Literal",e,s.end,{valueType:"boolean",value:s.type===t.TRUE})}parseNull(){const e=this.current.start,t=this.advance();return this.createNode("Literal",e,t.end,{valueType:"null",value:null})}parseUnaryExpression(){const e=this.advance(),t=this.parseExpression(s.UNARY);return this.createNode("UnaryExpression",e.start,t.end,{operator:"not",operand:t})}parseGroupedExpression(){this.advance();const e=this.parseExpression();return this.consume(t.RPAREN,'Expected ")"'),e}parseTemplateExpression(){const e=this.peek();if(e.type===t.RANGE)return this.parseRange();if(e.type===t.IF)return this.parseConditional();{const e=this.advance().start,s=this.parseExpression(),r=this.consume(t.TEMPLATE_END,'Expected "}}"').end;return this.createNode("TemplateExpression",e,r,{expression:s})}}parseBinaryExpression(e){const s=this.current,r=this.currentPrecedence(),n={[t.OR]:"||",[t.AND]:"&&",[t.IN]:"in",[t.EQ]:"==",[t.NE]:"!=",[t.GT]:">",[t.LT]:"<",[t.GTE]:">=",[t.LTE]:"<=",[t.DEFAULT]:"??"}[s.type];if(!n)throw new o(`Unknown binary operator: ${s.type}`,s.start,s);this.advance();const i=this.parseExpression(r);return this.createNode("BinaryExpression",e.start,i.end,{operator:n,left:e,right:i})}parseFieldAccess(e){this.advance();const s=this.consume(t.IDENTIFIER,'Expected field name after "."');return this.createNode("FieldAccess",e.start,s.end,{object:e,field:s.value,computed:!1,optional:!1})}parseComputedAccess(e){let r;if(this.advance(),this.match(t.SQUOTE)){this.advance();const e=this.match(t.STRING_LITERAL)?this.consume(t.STRING_LITERAL,"Expected string literal"):this.consume(t.IDENTIFIER,"Expected string literal or identifier");this.consume(t.SQUOTE,"Expected closing single quote"),r=this.createNode("Literal",e.start,e.end,{valueType:"string",value:e.value})}else r=this.parseExpression(s.LOWEST);const n=this.consume(t.RBRACKET,'Expected "]"');return this.createNode("FieldAccess",e.start,n.end,{object:e,field:r,computed:!0,optional:!1})}parseOptionalChain(e){this.advance();const s=[];let r=this.current.start||e.end;for(;this.match(t.DOT,t.LBRACKET);)if(this.match(t.DOT)){this.advance();const e=this.consume(t.IDENTIFIER,'Expected field name after "."');s.push({type:"field",value:e.value}),r=e.end}else if(this.match(t.LBRACKET)){let e;if(this.advance(),this.match(t.SQUOTE)){this.advance();const s=this.match(t.STRING_LITERAL)?this.consume(t.STRING_LITERAL,"Expected string literal"):this.consume(t.IDENTIFIER,"Expected string literal or identifier");this.consume(t.SQUOTE,"Expected closing single quote"),e=s.value}else e=this.parseExpression();const n=this.consume(t.RBRACKET,'Expected "]"');s.push({type:"computed",value:e}),r=n.end}return this.createNode("OptionalChain",e.start,r,{object:e,chain:s})}parseArray(){const e=this.advance().start,s=[];if(!this.match(t.RBRACKET))do{s.push(this.parseExpression())}while(this.match(t.COMMA)&&this.advance());const r=this.consume(t.RBRACKET,'Expected "]"').end;return this.createNode("Array",e,r,{elements:s})}parseObject(){const e=this.advance().start,s=[];if(!this.match(t.RBRACE))do{const e=this.parseStringTemplate();this.consume(t.COLON,'Expected ":"');const r=this.parseExpression();s.push({key:e,value:r})}while(this.match(t.COMMA)&&this.advance());const r=this.consume(t.RBRACE,'Expected "}"').end;return this.createNode("Object",e,r,{properties:s})}parseStringTemplate(){const e=this.advance().start,s=[];for(;!this.match(t.DQUOTE)&&!this.match(t.EOF);)if(this.match(t.STRING_LITERAL)){const e=this.advance();s.push(this.createNode("Literal",e.start,e.end,{valueType:"string",value:e.value}))}else{if(!this.match(t.TEMPLATE_START))break;{this.advance();const e=this.parseExpression();this.consume(t.TEMPLATE_END,'Expected "}}"'),s.push(e)}}const r=this.consume(t.DQUOTE,'Expected closing """').end;return this.createNode("StringTemplate",e,r,{parts:s})}parseRange(){const e=this.advance().start;this.consume(t.RANGE,'Expected "range"');let s=null;this.match(t.IDENTIFIER)?(s=this.advance().value,this.consume(t.COMMA,'Expected ","')):this.match(t.UNDERSCORE)&&(this.advance(),this.consume(t.COMMA,'Expected ","'));const r=this.consume(t.IDENTIFIER,"Expected variable name").value;this.consume(t.ASSIGN,'Expected ":="');const n=this.parseExpression();this.consume(t.TEMPLATE_END,'Expected "}}"');const i=this.parseExpression();this.consume(t.TEMPLATE_START,'Expected "{{"'),this.consume(t.END,'Expected "end"');const a=this.consume(t.TEMPLATE_END,'Expected "}}"').end;return this.createNode("Range",e,a,{index:s,variable:r,iterable:n,body:i})}parseConditional(){const e=this.advance().start;this.consume(t.IF,'Expected "if"');const s=this.parseExpression();this.consume(t.TEMPLATE_END,'Expected "}}"');const r=this.parseExpression(),n=[];for(;this.match(t.TEMPLATE_START)&&this.peek().type===t.ELIF;){this.advance(),this.consume(t.ELIF,'Expected "elif"');const e=this.parseExpression();this.consume(t.TEMPLATE_END,'Expected "}}"');const s=this.parseExpression();n.push({condition:e,expression:s})}this.consume(t.TEMPLATE_START,'Expected "{{"'),this.consume(t.ELSE,'Expected "else"'),this.consume(t.TEMPLATE_END,'Expected "}}"');const i=this.parseExpression();this.consume(t.TEMPLATE_START,'Expected "{{"'),this.consume(t.END,'Expected "end"');const a=this.consume(t.TEMPLATE_END,'Expected "}}"').end;return this.createNode("Conditional",e,a,{condition:s,thenExpression:r,elifs:n,elseExpression:i})}}function c(e){return new h(e).parse()}class u extends Error{constructor(e,t,s){super(t?`Runtime error at line ${t.line}, column ${t.column}: ${e}`:`Runtime error: ${e}`),this.position=t,this.node=s,this.name="RuntimeError"}}class p{static createDefaultFunctions(){const e=new Map;return e.set("not",e=>{if("boolean"==typeof e)return!e;throw new u("Cannot apply 'not' to non-boolean value: "+typeof e)}),e.set("empty",e=>null==e||("string"==typeof e?""===e.trim():"number"==typeof e?0===e:Array.isArray(e)?0===e.length:"object"==typeof e&&0===Object.keys(e).length)),e.set("length",e=>{if("string"==typeof e)return e.length;if(Array.isArray(e))return e.length;if(e&&"object"==typeof e)return Object.keys(e).length;if("number"==typeof e)return e;throw new u("Cannot get length of: "+typeof e)}),e.set("size",e.get("length")),e.set("toUpper",e=>{if("string"==typeof e)return e.toUpperCase();throw new u("Cannot convert non-string to uppercase: "+typeof e)}),e.set("toLower",e=>{if("string"==typeof e)return e.toLowerCase();throw new u("Cannot convert non-string to lowercase: "+typeof e)}),e.set("toCaseFold",e=>{if("string"==typeof e)return e.toLowerCase();throw new u("Cannot case fold non-string: "+typeof e)}),e.set("toTitle",e=>{if("string"==typeof e)return e.replace(/\b\w+/g,e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase());throw new u("Cannot convert non-string to title case: "+typeof e)}),e.set("head",e=>{if(Array.isArray(e)){if(0===e.length)throw new u("Cannot get head of empty array");return e[0]}if("string"==typeof e){if(0===e.length)throw new u("Cannot get head of empty string");return e.charAt(0)}throw new u("Cannot get head of: "+typeof e)}),e.set("tail",e=>{if(Array.isArray(e))return e.slice(1);if("string"==typeof e)return e.slice(1);throw new u("Cannot get tail of: "+typeof e)}),e.set("inverse",e=>{if(Array.isArray(e))return[...e].reverse();if("string"==typeof e)return e.split("").reverse().join("");if("number"==typeof e)return 1/e;if("boolean"==typeof e)return!e;if(null===e)return null;if("object"==typeof e)return e;throw new u("Cannot get inverse of: "+typeof e)}),e.set("concat",e=>{if(Array.isArray(e)){if(e.every(e=>e&&"object"==typeof e&&!Array.isArray(e))){const t={};for(const s of e)Object.assign(t,s);return t}if(e.every(e=>"string"==typeof e))return e.join("");const t=[];for(const s of e)Array.isArray(s)?t.push(...s):t.push(s);return t}throw new u("Cannot concat non-array: "+typeof e)}),e.set("toPairs",e=>{if(e&&"object"==typeof e&&!Array.isArray(e))return Object.entries(e).map(([e,t])=>[e,t]);throw new u("Cannot convert non-object to pairs: "+typeof e)}),e.set("fromPairs",e=>{if(Array.isArray(e)){const t={};for(const s of e){if(!Array.isArray(s)||2!==s.length)throw new u("fromPairs requires array of [key, value] pairs");{const[e,r]=s;if("string"!=typeof e)throw new u("Object keys must be strings");t[e]=r}}return t}throw new u("Cannot convert non-array to object: "+typeof e)}),e.set("removeNulls",e=>{if(Array.isArray(e))return e.filter(e=>null!==e);throw new u("Cannot remove nulls from non-array: "+typeof e)}),e.set("escapeUri",e=>{if("string"==typeof e)return encodeURIComponent(e);throw new u("Cannot escape non-string URI: "+typeof e)}),e}}class l{constructor(e={},t=new Map){const s=p.createDefaultFunctions(),r=new Map([...s,...t]);this.context={variables:new Map(Object.entries(e)),functions:r}}evaluate(e){try{return this.evaluateNode(e,this.context)}catch(t){if(t instanceof u)throw t;throw new u(`Unexpected error during evaluation: ${t}`,e.start,e)}}evaluateNode(e,t){switch(e.type){case"Literal":return this.evaluateLiteral(e);case"Variable":return this.evaluateVariable(e,t);case"BinaryExpression":return this.evaluateBinaryExpression(e,t);case"UnaryExpression":return this.evaluateUnaryExpression(e,t);case"FieldAccess":return this.evaluateFieldAccess(e,t);case"OptionalChain":return this.evaluateOptionalChain(e,t);case"FunctionCall":return this.evaluateFunctionCall(e,t);case"Array":return this.evaluateArray(e,t);case"Object":return this.evaluateObject(e,t);case"StringTemplate":return this.evaluateStringTemplate(e,t);case"Conditional":return this.evaluateConditional(e,t);case"Range":return this.evaluateRange(e,t);case"TemplateExpression":return this.evaluateNode(e.expression,t);default:throw new u(`Unknown node type: ${e.type}`,e.start,e)}}evaluateLiteral(e){return e.value}evaluateVariable(e,t){let s=t;for(;s;){if(s.variables.has(e.name))return s.variables.get(e.name);s=s.parent}throw new u(`Variable '${e.name}' not found in scope`,e.start,e)}evaluateBinaryExpression(e,t){const s=this.evaluateNode(e.left,t),r=this.evaluateNode(e.right,t);switch(e.operator){case"==":return this.deepEqual(s,r);case"!=":return!this.deepEqual(s,r);case">":return this.compare(s,r,e)>0;case"<":return this.compare(s,r,e)<0;case">=":return this.compare(s,r,e)>=0;case"<=":return this.compare(s,r,e)<=0;case"&&":return this.isTruthy(s)&&this.isTruthy(r);case"||":return this.isTruthy(s)||this.isTruthy(r);case"in":return this.evaluateInOperator(s,r,e);case"??":return null!=s?s:r;default:throw new u(`Unknown binary operator: ${e.operator}`,e.start,e)}}evaluateUnaryExpression(e,t){const s=this.evaluateNode(e.operand,t);if("not"===e.operator)return!this.isTruthy(s);throw new u(`Unknown unary operator: ${e.operator}`,e.start,e)}evaluateFieldAccess(e,t){const s=this.evaluateNode(e.object,t);if(null==s)throw new u("Cannot access property of null or undefined",e.start,e);let r;if("string"==typeof e.field)r=e.field;else{const s=this.evaluateNode(e.field,t);if("string"!=typeof s&&"number"!=typeof s)throw new u("Field access key must be string or number, got: "+typeof s,e.start,e);r=s}if(Array.isArray(s)){if("number"==typeof r){if(r<0||r>=s.length||!Number.isInteger(r))throw new u(`Array index out of bounds: ${r}`,e.start,e);return s[r]}if("length"===r)return s.length;throw new u(`Invalid array property: ${r}`,e.start,e)}if("object"==typeof s){const t=s[r.toString()];if(void 0===t)throw new u(`Property '${r}' not found on object`,e.start,e);return t}throw new u("Cannot access property of "+typeof s,e.start,e)}evaluateOptionalChain(e,t){let s;try{s=this.evaluateNode(e.object,t)}catch(e){if(e instanceof u)return null;throw e}for(const r of e.chain){if(null==s)return null;try{if("field"===r.type){if("object"!=typeof s||Array.isArray(s))return null;s=s[r.value]}else{const e="string"==typeof r.value?r.value:this.evaluateNode(r.value,t);if(Array.isArray(s)){if(!("number"==typeof e&&Number.isInteger(e)&&e>=0&&e<s.length))return null;s=s[e]}else{if("object"!=typeof s)return null;s=s[e.toString()]}}}catch{return null}}return void 0===s?null:s}evaluateFunctionCall(e,t){const s=t.functions.get(e.name);if(!s)throw new u(`Function '${e.name}' not found`,e.start,e);const r=this.evaluateNode(e.argument,t);try{return s(r,t)}catch(t){if(t instanceof u)throw t;throw new u(`Error in function '${e.name}': ${t}`,e.start,e)}}evaluateArray(e,t){return e.elements.map(e=>this.evaluateNode(e,t))}evaluateObject(e,t){const s={};for(const r of e.properties){const n=this.evaluateNode(r.key,t);if("string"!=typeof n)throw new u("Object key must be string, got: "+typeof n,e.start,e);const i=this.evaluateNode(r.value,t);s[n]=i}return s}evaluateStringTemplate(e,t){let s="";for(const r of e.parts)if("Literal"===r.type)s+=r.value;else{const e=this.evaluateNode(r,t);s+=this.valueToString(e)}return s}evaluateConditional(e,t){const s=this.evaluateNode(e.condition,t);if(this.isTruthy(s))return this.evaluateNode(e.thenExpression,t);for(const s of e.elifs){const e=this.evaluateNode(s.condition,t);if(this.isTruthy(e))return this.evaluateNode(s.expression,t)}return this.evaluateNode(e.elseExpression,t)}evaluateRange(e,t){const s=this.evaluateNode(e.iterable,t);if(!Array.isArray(s))throw new u("Range requires array, got: "+typeof s,e.start,e);const r=[];for(let n=0;n<s.length;n++){const i=s[n],a={variables:new Map(t.variables),functions:t.functions,parent:t};a.variables.set(e.variable,i),e.index&&a.variables.set(e.index,n);const o=this.evaluateNode(e.body,a);r.push(o)}return r}deepEqual(e,t){if(e===t)return!0;if(null===e||null===t)return e===t;if(void 0===e||void 0===t)return e===t;if(typeof e!=typeof t)return!1;if(Array.isArray(e)&&Array.isArray(t))return e.length===t.length&&e.every((e,s)=>this.deepEqual(e,t[s]));if("object"==typeof e&&"object"==typeof t){const s=e,r=t,n=Object.keys(s),i=Object.keys(r);return n.length===i.length&&n.every(e=>this.deepEqual(s[e],r[e]))}return!1}compare(e,t,s){if("number"==typeof e&&"number"==typeof t)return e-t;if("string"==typeof e&&"string"==typeof t)return e.localeCompare(t);throw new u(`Cannot compare ${typeof e} and ${typeof t}`,s.start,s)}evaluateInOperator(e,t,s){if(Array.isArray(t))return t.some(t=>this.deepEqual(e,t));if("object"==typeof t&&null!==t&&"string"==typeof e)return e in t;throw new u("'in' operator requires array or object on right side, got: "+typeof t,s.start,s)}isTruthy(e){return null!=e&&("boolean"==typeof e?e:"number"==typeof e?0!==e:"string"==typeof e||Array.isArray(e)?e.length>0:"object"!=typeof e||Object.keys(e).length>0)}valueToString(e){return null===e?"null":void 0===e?"undefined":"string"==typeof e?e:"number"==typeof e||"boolean"==typeof e?String(e):Array.isArray(e)||"object"==typeof e?JSON.stringify(e):String(e)}}function E(e,t={},s){return new l(t,s).evaluate(e)}e.evaluate=function(e,t={},s=new Map){return E(c(a(e)),t,s)},e.evaluateAST=E,e.parseTokens=c,e.tokenize=a});
//# sourceMappingURL=index.umd.min.js.map