@huggingface/jinja
Advanced tools
+38
-26
@@ -55,2 +55,7 @@ import type { Token } from "./lexer"; | ||
| } | ||
| export declare class Comment extends Statement { | ||
| value: string; | ||
| type: string; | ||
| constructor(value: string); | ||
| } | ||
| /** | ||
@@ -95,8 +100,8 @@ * Expressions will result in a value at runtime (unlike statements). | ||
| } | ||
| /** | ||
| * Represents a numeric constant in the template. | ||
| */ | ||
| export declare class NumericLiteral extends Literal<number> { | ||
| export declare class IntegerLiteral extends Literal<number> { | ||
| type: string; | ||
| } | ||
| export declare class FloatLiteral extends Literal<number> { | ||
| type: string; | ||
| } | ||
| /** | ||
@@ -109,14 +114,2 @@ * Represents a text constant in the template. | ||
| /** | ||
| * Represents a boolean constant in the template. | ||
| */ | ||
| export declare class BooleanLiteral extends Literal<boolean> { | ||
| type: string; | ||
| } | ||
| /** | ||
| * Represents null (none) in the template. | ||
| */ | ||
| export declare class NullLiteral extends Literal<null> { | ||
| type: string; | ||
| } | ||
| /** | ||
| * Represents an array literal in the template. | ||
@@ -161,11 +154,19 @@ */ | ||
| } | ||
| export declare class FilterStatement extends Statement { | ||
| filter: Identifier | CallExpression; | ||
| body: Statement[]; | ||
| type: string; | ||
| constructor(filter: Identifier | CallExpression, body: Statement[]); | ||
| } | ||
| /** | ||
| * An operation which filters a sequence of objects by applying a test to each object, | ||
| * and only selecting the objects with the test succeeding. | ||
| * | ||
| * It may also be used as a shortcut for a ternary operator. | ||
| */ | ||
| export declare class SelectExpression extends Expression { | ||
| iterable: Expression; | ||
| lhs: Expression; | ||
| test: Expression; | ||
| type: string; | ||
| constructor(iterable: Expression, test: Expression); | ||
| constructor(lhs: Expression, test: Expression); | ||
| } | ||
@@ -191,10 +192,2 @@ /** | ||
| } | ||
| /** | ||
| * Logical negation of an expression. | ||
| */ | ||
| export declare class LogicalNegationExpression extends Expression { | ||
| argument: Expression; | ||
| type: string; | ||
| constructor(argument: Expression); | ||
| } | ||
| export declare class SliceExpression extends Expression { | ||
@@ -213,3 +206,22 @@ start: Expression | undefined; | ||
| } | ||
| export declare class SpreadExpression extends Expression { | ||
| argument: Expression; | ||
| type: string; | ||
| constructor(argument: Expression); | ||
| } | ||
| export declare class CallStatement extends Statement { | ||
| call: CallExpression; | ||
| callerArgs: Expression[] | null; | ||
| body: Statement[]; | ||
| type: string; | ||
| constructor(call: CallExpression, callerArgs: Expression[] | null, body: Statement[]); | ||
| } | ||
| export declare class Ternary extends Expression { | ||
| condition: Expression; | ||
| trueExpr: Expression; | ||
| falseExpr: Expression; | ||
| type: string; | ||
| constructor(condition: Expression, trueExpr: Expression, falseExpr: Expression); | ||
| } | ||
| export {}; | ||
| //# sourceMappingURL=ast.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../src/ast.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC;;GAEG;AACH,qBAAa,SAAS;IACrB,IAAI,SAAe;CACnB;AAED;;GAEG;AACH,qBAAa,OAAQ,SAAQ,SAAS;IAGlB,IAAI,EAAE,SAAS,EAAE;IAF3B,IAAI,SAAa;gBAEP,IAAI,EAAE,SAAS,EAAE;CAGpC;AAED,qBAAa,EAAG,SAAQ,SAAS;IAIxB,IAAI,EAAE,UAAU;IAChB,IAAI,EAAE,SAAS,EAAE;IACjB,SAAS,EAAE,SAAS,EAAE;IALrB,IAAI,SAAQ;gBAGb,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,SAAS,EAAE,EACjB,SAAS,EAAE,SAAS,EAAE;CAI9B;AAED;;;GAGG;AACH,qBAAa,GAAI,SAAQ,SAAS;IAIzB,OAAO,EAAE,UAAU,GAAG,YAAY;IAClC,QAAQ,EAAE,UAAU;IACpB,IAAI,EAAE,SAAS,EAAE;IACjB,YAAY,EAAE,SAAS,EAAE;IANxB,IAAI,SAAS;gBAGd,OAAO,EAAE,UAAU,GAAG,YAAY,EAClC,QAAQ,EAAE,UAAU,EACpB,IAAI,EAAE,SAAS,EAAE,EACjB,YAAY,EAAE,SAAS,EAAE;CAIjC;AAED,qBAAa,KAAM,SAAQ,SAAS;IAC1B,IAAI,SAAW;CACxB;AACD,qBAAa,QAAS,SAAQ,SAAS;IAC7B,IAAI,SAAc;CAC3B;AAED,qBAAa,YAAa,SAAQ,SAAS;IAGlC,QAAQ,EAAE,UAAU;IACpB,KAAK,EAAE,UAAU,GAAG,IAAI;IACxB,IAAI,EAAE,SAAS,EAAE;IAJhB,IAAI,SAAS;gBAEd,QAAQ,EAAE,UAAU,EACpB,KAAK,EAAE,UAAU,GAAG,IAAI,EACxB,IAAI,EAAE,SAAS,EAAE;CAIzB;AAED,qBAAa,KAAM,SAAQ,SAAS;IAI3B,IAAI,EAAE,UAAU;IAChB,IAAI,EAAE,UAAU,EAAE;IAClB,IAAI,EAAE,SAAS,EAAE;IALhB,IAAI,SAAW;gBAGhB,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,UAAU,EAAE,EAClB,IAAI,EAAE,SAAS,EAAE;CAIzB;AAED;;GAEG;AACH,qBAAa,UAAW,SAAQ,SAAS;IAC/B,IAAI,SAAgB;CAC7B;AAED,qBAAa,gBAAiB,SAAQ,UAAU;IAIvC,MAAM,EAAE,UAAU;IAClB,QAAQ,EAAE,UAAU;IACpB,QAAQ,EAAE,OAAO;IALhB,IAAI,SAAsB;gBAG3B,MAAM,EAAE,UAAU,EAClB,QAAQ,EAAE,UAAU,EACpB,QAAQ,EAAE,OAAO;CAIzB;AAED,qBAAa,cAAe,SAAQ,UAAU;IAIrC,MAAM,EAAE,UAAU;IAClB,IAAI,EAAE,UAAU,EAAE;IAJjB,IAAI,SAAoB;gBAGzB,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,UAAU,EAAE;CAI1B;AAED;;GAEG;AACH,qBAAa,UAAW,SAAQ,UAAU;IAMtB,KAAK,EAAE,MAAM;IALvB,IAAI,SAAgB;IAE7B;;OAEG;gBACgB,KAAK,EAAE,MAAM;CAGhC;AAED;;;GAGG;AACH,uBAAe,OAAO,CAAC,CAAC,CAAE,SAAQ,UAAU;IAGxB,KAAK,EAAE,CAAC;IAFlB,IAAI,SAAa;gBAEP,KAAK,EAAE,CAAC;CAG3B;AAED;;GAEG;AACH,qBAAa,cAAe,SAAQ,OAAO,CAAC,MAAM,CAAC;IACzC,IAAI,SAAoB;CACjC;AAED;;GAEG;AACH,qBAAa,aAAc,SAAQ,OAAO,CAAC,MAAM,CAAC;IACxC,IAAI,SAAmB;CAChC;AAED;;GAEG;AACH,qBAAa,cAAe,SAAQ,OAAO,CAAC,OAAO,CAAC;IAC1C,IAAI,SAAoB;CACjC;AAED;;GAEG;AACH,qBAAa,WAAY,SAAQ,OAAO,CAAC,IAAI,CAAC;IACpC,IAAI,SAAiB;CAC9B;AAED;;GAEG;AACH,qBAAa,YAAa,SAAQ,OAAO,CAAC,UAAU,EAAE,CAAC;IAC7C,IAAI,SAAkB;CAC/B;AAED;;GAEG;AACH,qBAAa,YAAa,SAAQ,OAAO,CAAC,UAAU,EAAE,CAAC;IAC7C,IAAI,SAAkB;CAC/B;AAED;;GAEG;AACH,qBAAa,aAAc,SAAQ,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;IAC7D,IAAI,SAAmB;CAChC;AAED;;;;GAIG;AACH,qBAAa,gBAAiB,SAAQ,UAAU;IAIvC,QAAQ,EAAE,KAAK;IACf,IAAI,EAAE,UAAU;IAChB,KAAK,EAAE,UAAU;IALhB,IAAI,SAAsB;gBAG3B,QAAQ,EAAE,KAAK,EACf,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,UAAU;CAIzB;AAED;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,UAAU;IAIvC,OAAO,EAAE,UAAU;IACnB,MAAM,EAAE,UAAU,GAAG,cAAc;IAJlC,IAAI,SAAsB;gBAG3B,OAAO,EAAE,UAAU,EACnB,MAAM,EAAE,UAAU,GAAG,cAAc;CAI3C;AAED;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,UAAU;IAIvC,QAAQ,EAAE,UAAU;IACpB,IAAI,EAAE,UAAU;IAJf,IAAI,SAAsB;gBAG3B,QAAQ,EAAE,UAAU,EACpB,IAAI,EAAE,UAAU;CAIxB;AAED;;GAEG;AACH,qBAAa,cAAe,SAAQ,UAAU;IAIrC,OAAO,EAAE,UAAU;IACnB,MAAM,EAAE,OAAO;IACf,IAAI,EAAE,UAAU;IALf,IAAI,SAAoB;gBAGzB,OAAO,EAAE,UAAU,EACnB,MAAM,EAAE,OAAO,EACf,IAAI,EAAE,UAAU;CAIxB;AAED;;GAEG;AACH,qBAAa,eAAgB,SAAQ,UAAU;IAItC,QAAQ,EAAE,KAAK;IACf,QAAQ,EAAE,UAAU;IAJnB,IAAI,SAAqB;gBAG1B,QAAQ,EAAE,KAAK,EACf,QAAQ,EAAE,UAAU;CAI5B;AAED;;GAEG;AACH,qBAAa,yBAA0B,SAAQ,UAAU;IAGrC,QAAQ,EAAE,UAAU;IAF9B,IAAI,SAA+B;gBAEzB,QAAQ,EAAE,UAAU;CAGvC;AAED,qBAAa,eAAgB,SAAQ,UAAU;IAItC,KAAK,EAAE,UAAU,GAAG,SAAS;IAC7B,IAAI,EAAE,UAAU,GAAG,SAAS;IAC5B,IAAI,EAAE,UAAU,GAAG,SAAS;IAL3B,IAAI,SAAqB;gBAG1B,KAAK,GAAE,UAAU,GAAG,SAAqB,EACzC,IAAI,GAAE,UAAU,GAAG,SAAqB,EACxC,IAAI,GAAE,UAAU,GAAG,SAAqB;CAIhD;AAED,qBAAa,yBAA0B,SAAQ,UAAU;IAIhD,GAAG,EAAE,UAAU;IACf,KAAK,EAAE,UAAU;IAJhB,IAAI,SAA+B;gBAGpC,GAAG,EAAE,UAAU,EACf,KAAK,EAAE,UAAU;CAIzB"} | ||
| {"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../src/ast.ts"],"names":[],"mappings":"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"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,OAAO,EAwBP,MAAM,OAAO,CAAC;AAYf,wBAAgB,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,GAAE,MAAM,GAAG,MAAa,GAAG,MAAM,CAI/E"} | ||
| {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,OAAO,EA4BP,MAAM,OAAO,CAAC;AAsBf,wBAAgB,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,GAAE,MAAM,GAAG,MAAa,GAAG,MAAM,CAI/E"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAKrC,qBAAa,QAAQ;IACpB,MAAM,EAAE,OAAO,CAAC;IAEhB;;OAEG;gBACS,QAAQ,EAAE,MAAM;IAQ5B,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM;IAyB/C,MAAM,CAAC,OAAO,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,GAAG,MAAM;CAGrD;AAED,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC"} | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,WAAW,EAAgB,MAAM,WAAW,CAAC;AACnE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAIrC,qBAAa,QAAQ;IACpB,MAAM,EAAE,OAAO,CAAC;IAEhB;;OAEG;gBACS,QAAQ,EAAE,MAAM;IAQ5B,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM;IAkB/C,MAAM,CAAC,OAAO,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,GAAG,MAAM;CAGrD;AAED,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC"} |
+1
-20
@@ -7,4 +7,2 @@ /** | ||
| NumericLiteral: "NumericLiteral"; | ||
| BooleanLiteral: "BooleanLiteral"; | ||
| NullLiteral: "NullLiteral"; | ||
| StringLiteral: "StringLiteral"; | ||
@@ -32,20 +30,3 @@ Identifier: "Identifier"; | ||
| UnaryOperator: "UnaryOperator"; | ||
| Set: "Set"; | ||
| If: "If"; | ||
| For: "For"; | ||
| In: "In"; | ||
| Is: "Is"; | ||
| NotIn: "NotIn"; | ||
| Else: "Else"; | ||
| EndSet: "EndSet"; | ||
| EndIf: "EndIf"; | ||
| ElseIf: "ElseIf"; | ||
| EndFor: "EndFor"; | ||
| And: "And"; | ||
| Or: "Or"; | ||
| Not: "UnaryOperator"; | ||
| Macro: "Macro"; | ||
| EndMacro: "EndMacro"; | ||
| Break: "Break"; | ||
| Continue: "Continue"; | ||
| Comment: "Comment"; | ||
| }>; | ||
@@ -52,0 +33,0 @@ export type TokenType = keyof typeof TOKEN_TYPES; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"lexer.d.ts","sourceRoot":"","sources":["../src/lexer.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiDtB,CAAC;AAEH,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,WAAW,CAAC;AAuCjD;;GAEG;AACH,qBAAa,KAAK;IAOT,KAAK,EAAE,MAAM;IACb,IAAI,EAAE,SAAS;IAPvB;;;;OAIG;gBAEK,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,SAAS;CAEvB;AA2DD,MAAM,WAAW,iBAAiB;IACjC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;CACxB;AAqCD;;GAEG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB,GAAG,KAAK,EAAE,CAgJjF"} | ||
| {"version":3,"file":"lexer.d.ts","sourceRoot":"","sources":["../src/lexer.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;EA4BtB,CAAC;AAEH,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,WAAW,CAAC;AAEjD;;GAEG;AACH,qBAAa,KAAK;IAOT,KAAK,EAAE,MAAM;IACb,IAAI,EAAE,SAAS;IAPvB;;;;OAIG;gBAEK,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,SAAS;CAEvB;AA4DD,MAAM,WAAW,iBAAiB;IACjC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;CACxB;AAwCD;;GAEG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,iBAAsB,GAAG,KAAK,EAAE,CA2KjF"} |
+1
-1
@@ -1,2 +0,2 @@ | ||
| import type { Token } from "./lexer"; | ||
| import { Token } from "./lexer"; | ||
| import { Program } from "./ast"; | ||
@@ -3,0 +3,0 @@ /** |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAa,MAAM,SAAS,CAAC;AAGhD,OAAO,EACN,OAAO,EAwBP,MAAM,OAAO,CAAC;AAEf;;;GAGG;AACH,wBAAgB,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAqkB9C"} | ||
| {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAe,MAAM,SAAS,CAAC;AAG7C,OAAO,EACN,OAAO,EA4BP,MAAM,OAAO,CAAC;AAEf;;;GAGG;AACH,wBAAgB,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAunB9C"} |
+23
-3
| import type { Statement, Program } from "./ast"; | ||
| export type AnyRuntimeValue = NumericValue | StringValue | BooleanValue | ObjectValue | ArrayValue | FunctionValue | NullValue | UndefinedValue; | ||
| export type AnyRuntimeValue = IntegerValue | FloatValue | StringValue | BooleanValue | ObjectValue | ArrayValue | FunctionValue | NullValue | UndefinedValue; | ||
| /** | ||
@@ -24,10 +24,18 @@ * Abstract base class for all Runtime values. | ||
| __bool__(): BooleanValue; | ||
| toString(): string; | ||
| } | ||
| /** | ||
| * Represents a numeric value at runtime. | ||
| * Represents an integer value at runtime. | ||
| */ | ||
| export declare class NumericValue extends RuntimeValue<number> { | ||
| export declare class IntegerValue extends RuntimeValue<number> { | ||
| type: string; | ||
| } | ||
| /** | ||
| * Represents a float value at runtime. | ||
| */ | ||
| export declare class FloatValue extends RuntimeValue<number> { | ||
| type: string; | ||
| toString(): string; | ||
| } | ||
| /** | ||
| * Represents a string value at runtime. | ||
@@ -60,2 +68,5 @@ */ | ||
| builtins: Map<string, AnyRuntimeValue>; | ||
| items(): ArrayValue; | ||
| keys(): ArrayValue; | ||
| values(): ArrayValue; | ||
| } | ||
@@ -141,2 +152,3 @@ /** | ||
| } | ||
| export declare function setupGlobals(env: Environment): void; | ||
| export declare class Interpreter { | ||
@@ -154,2 +166,3 @@ global: Environment; | ||
| private evaluateArguments; | ||
| private applyFilter; | ||
| /** | ||
@@ -164,5 +177,10 @@ * Evaluates expressions following the filter operation type. | ||
| /** | ||
| * Evaluates expressions following the select operation type. | ||
| */ | ||
| private evaluateSelectExpression; | ||
| /** | ||
| * Evaluates expressions following the unary operation type. | ||
| */ | ||
| private evaluateUnaryExpression; | ||
| private evaluateTernaryExpression; | ||
| private evalProgram; | ||
@@ -181,2 +199,4 @@ private evaluateBlock; | ||
| private evaluateMacro; | ||
| private evaluateCallStatement; | ||
| private evaluateFilterStatement; | ||
| evaluate(statement: Statement | undefined, environment: Environment): AnyRuntimeValue; | ||
@@ -183,0 +203,0 @@ } |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAMX,SAAS,EACT,OAAO,EAkBP,MAAM,OAAO,CAAC;AAGf,MAAM,MAAM,eAAe,GACxB,YAAY,GACZ,WAAW,GACX,YAAY,GACZ,WAAW,GACX,UAAU,GACV,aAAa,GACb,SAAS,GACT,cAAc,CAAC;AAMlB;;;GAGG;AACH,uBAAe,YAAY,CAAC,CAAC;IAC5B,IAAI,SAAkB;IACtB,KAAK,EAAE,CAAC,CAAC;IAET;;OAEG;IACH,QAAQ,+BAAsC;IAE9C;;OAEG;gBACS,KAAK,GAAE,CAA6B;IAIhD;;;;OAIG;IACH,QAAQ,IAAI,YAAY;CAGxB;AAED;;GAEG;AACH,qBAAa,YAAa,SAAQ,YAAY,CAAC,MAAM,CAAC;IAC5C,IAAI,SAAkB;CAC/B;AAED;;GAEG;AACH,qBAAa,WAAY,SAAQ,YAAY,CAAC,MAAM,CAAC;IAC3C,IAAI,SAAiB;IAErB,QAAQ,+BA0Gd;CACH;AAED;;GAEG;AACH,qBAAa,YAAa,SAAQ,YAAY,CAAC,OAAO,CAAC;IAC7C,IAAI,SAAkB;CAC/B;AAED;;GAEG;AACH,qBAAa,WAAY,SAAQ,YAAY,CAAC,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACjE,IAAI,SAAiB;IAE9B;;;;;;;OAOG;IACM,QAAQ,IAAI,YAAY;IAIxB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAkB5C;CACH;AAED;;GAEG;AACH,qBAAa,qBAAsB,SAAQ,WAAW;IAC5C,IAAI,SAA2B;CACxC;AAED;;GAEG;AACH,qBAAa,UAAW,SAAQ,YAAY,CAAC,eAAe,EAAE,CAAC;IACrD,IAAI,SAAgB;IACpB,QAAQ,+BAAuF;IAExG;;;;;;;OAOG;IACM,QAAQ,IAAI,YAAY;CAGjC;AAED;;;GAGG;AACH,qBAAa,UAAW,SAAQ,UAAU;IAChC,IAAI,SAAgB;CAC7B;AAED;;GAEG;AACH,qBAAa,aAAc,SAAQ,YAAY,CAAC,CAAC,IAAI,EAAE,eAAe,EAAE,EAAE,KAAK,EAAE,WAAW,KAAK,eAAe,CAAC;IACvG,IAAI,SAAmB;CAChC;AAED;;GAEG;AACH,qBAAa,SAAU,SAAQ,YAAY,CAAC,IAAI,CAAC;IACvC,IAAI,SAAe;CAC5B;AAED;;GAEG;AACH,qBAAa,cAAe,SAAQ,YAAY,CAAC,SAAS,CAAC;IACjD,IAAI,SAAoB;CACjC;AAED;;GAEG;AACH,qBAAa,WAAW;IAwEJ,MAAM,CAAC;IAvE1B;;OAEG;IACH,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAapC;IAEH;;OAEG;IACH,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,KAAK,EAAE,eAAe,EAAE,KAAK,OAAO,CAAC,CAgDzD;gBAEgB,MAAM,CAAC,yBAAa;IAEvC;;OAEG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,eAAe;IAIlD,OAAO,CAAC,eAAe;IAcvB;;;OAGG;IACH,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,eAAe;IAKlE;;;;OAIG;IACH,OAAO,CAAC,OAAO;IAaf,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe;CAO7C;AAED,qBAAa,WAAW;IACvB,MAAM,EAAE,WAAW,CAAC;gBAER,GAAG,CAAC,EAAE,WAAW;IAI7B;;OAEG;IACH,GAAG,CAAC,OAAO,EAAE,OAAO,GAAG,eAAe;IAItC;;OAEG;IACH,OAAO,CAAC,wBAAwB;IA+FhC,OAAO,CAAC,iBAAiB;IAsBzB;;OAEG;IACH,OAAO,CAAC,wBAAwB;IA8OhC;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAe9B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAW/B,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,aAAa;IAgBrB,OAAO,CAAC,kBAAkB;IAI1B,OAAO,CAAC,sBAAsB;IAgB9B,OAAO,CAAC,uBAAuB;IA+B/B,OAAO,CAAC,wBAAwB;IAyChC,OAAO,CAAC,WAAW;IAuBnB,OAAO,CAAC,UAAU;IAKlB,OAAO,CAAC,WAAW;IAkHnB;;OAEG;IACH,OAAO,CAAC,aAAa;IA2CrB,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,SAAS,EAAE,WAAW,EAAE,WAAW,GAAG,eAAe;CAmErF"} | ||
| {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"} |
+9
-0
@@ -24,2 +24,11 @@ /** | ||
| export declare function titleCase(value: string): string; | ||
| export declare function strftime_now(format: string): string; | ||
| /** | ||
| * A minimalistic implementation of Python's strftime function. | ||
| */ | ||
| export declare function strftime(date: Date, format: string): string; | ||
| /** | ||
| * Function that mimics Python's string.replace() function. | ||
| */ | ||
| export declare function replace(str: string, oldvalue: string, newvalue: string, count?: number | null): string; | ||
| //# sourceMappingURL=utils.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,SAAI,GAAG,MAAM,EAAE,CAWtE;AAED;;;;;;;GAOG;AACH,wBAAgB,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,SAAI,GAAG,CAAC,EAAE,CAgBjF;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/C"} | ||
| {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,SAAI,GAAG,MAAM,EAAE,CAWtE;AAED;;;;;;;GAOG;AACH,wBAAgB,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,SAAI,GAAG,CAAC,EAAE,CAgBjF;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CA6B3D;AAMD;;GAEG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAYtG"} |
+1
-1
| { | ||
| "name": "@huggingface/jinja", | ||
| "packageManager": "pnpm@8.10.5", | ||
| "version": "0.4.1", | ||
| "version": "0.5.0", | ||
| "description": "A minimalistic JavaScript implementation of the Jinja templating engine, specifically designed for parsing and rendering ML chat templates.", | ||
@@ -6,0 +6,0 @@ "repository": "https://github.com/huggingface/huggingface.js.git", |
+58
-31
@@ -80,2 +80,9 @@ import type { Token } from "./lexer"; | ||
| export class Comment extends Statement { | ||
| override type = "Comment"; | ||
| constructor(public value: string) { | ||
| super(); | ||
| } | ||
| } | ||
| /** | ||
@@ -137,9 +144,10 @@ * Expressions will result in a value at runtime (unlike statements). | ||
| /** | ||
| * Represents a numeric constant in the template. | ||
| */ | ||
| export class NumericLiteral extends Literal<number> { | ||
| override type = "NumericLiteral"; | ||
| export class IntegerLiteral extends Literal<number> { | ||
| override type = "IntegerLiteral"; | ||
| } | ||
| export class FloatLiteral extends Literal<number> { | ||
| override type = "FloatLiteral"; | ||
| } | ||
| /** | ||
@@ -153,16 +161,2 @@ * Represents a text constant in the template. | ||
| /** | ||
| * Represents a boolean constant in the template. | ||
| */ | ||
| export class BooleanLiteral extends Literal<boolean> { | ||
| override type = "BooleanLiteral"; | ||
| } | ||
| /** | ||
| * Represents null (none) in the template. | ||
| */ | ||
| export class NullLiteral extends Literal<null> { | ||
| override type = "NullLiteral"; | ||
| } | ||
| /** | ||
| * Represents an array literal in the template. | ||
@@ -220,5 +214,18 @@ */ | ||
| export class FilterStatement extends Statement { | ||
| override type = "FilterStatement"; | ||
| constructor( | ||
| public filter: Identifier | CallExpression, | ||
| public body: Statement[] | ||
| ) { | ||
| super(); | ||
| } | ||
| } | ||
| /** | ||
| * An operation which filters a sequence of objects by applying a test to each object, | ||
| * and only selecting the objects with the test succeeding. | ||
| * | ||
| * It may also be used as a shortcut for a ternary operator. | ||
| */ | ||
@@ -229,3 +236,3 @@ export class SelectExpression extends Expression { | ||
| constructor( | ||
| public iterable: Expression, | ||
| public lhs: Expression, | ||
| public test: Expression | ||
@@ -266,13 +273,2 @@ ) { | ||
| /** | ||
| * Logical negation of an expression. | ||
| */ | ||
| export class LogicalNegationExpression extends Expression { | ||
| override type = "LogicalNegationExpression"; | ||
| constructor(public argument: Expression) { | ||
| super(); | ||
| } | ||
| } | ||
| export class SliceExpression extends Expression { | ||
@@ -300,1 +296,32 @@ override type = "SliceExpression"; | ||
| } | ||
| export class SpreadExpression extends Expression { | ||
| override type = "SpreadExpression"; | ||
| constructor(public argument: Expression) { | ||
| super(); | ||
| } | ||
| } | ||
| export class CallStatement extends Statement { | ||
| override type = "CallStatement"; | ||
| constructor( | ||
| public call: CallExpression, | ||
| public callerArgs: Expression[] | null, | ||
| public body: Statement[] | ||
| ) { | ||
| super(); | ||
| } | ||
| } | ||
| export class Ternary extends Expression { | ||
| override type = "Ternary"; | ||
| constructor( | ||
| public condition: Expression, | ||
| public trueExpr: Expression, | ||
| public falseExpr: Expression | ||
| ) { | ||
| super(); | ||
| } | ||
| } |
+96
-40
| import type { | ||
| Program, | ||
| Statement, | ||
| Comment, | ||
| If, | ||
@@ -12,5 +13,5 @@ For, | ||
| Identifier, | ||
| NumericLiteral, | ||
| FloatLiteral, | ||
| IntegerLiteral, | ||
| StringLiteral, | ||
| BooleanLiteral, | ||
| ArrayLiteral, | ||
@@ -24,5 +25,8 @@ TupleLiteral, | ||
| UnaryExpression, | ||
| LogicalNegationExpression, | ||
| SliceExpression, | ||
| KeywordArgumentExpression, | ||
| CallStatement, | ||
| FilterStatement, | ||
| SpreadExpression, | ||
| Ternary, | ||
| } from "./ast"; | ||
@@ -34,7 +38,17 @@ | ||
| const OPERATOR_PRECEDENCE: Record<string, number> = { | ||
| MultiplicativeBinaryOperator: 2, | ||
| AdditiveBinaryOperator: 1, | ||
| ComparisonBinaryOperator: 0, | ||
| }; | ||
| function getBinaryOperatorPrecedence(expr: BinaryExpression): number { | ||
| switch (expr.operator.type) { | ||
| case "MultiplicativeBinaryOperator": | ||
| return 4; | ||
| case "AdditiveBinaryOperator": | ||
| return 3; | ||
| case "ComparisonBinaryOperator": | ||
| return 2; | ||
| case "Identifier": | ||
| if (expr.operator.value === "and") return 1; | ||
| if (expr.operator.value === "in" || expr.operator.value === "not in") return 2; | ||
| return 0; | ||
| } | ||
| return 0; | ||
| } | ||
@@ -72,2 +86,8 @@ export function format(program: Program, indent: string | number = "\t"): string { | ||
| return pad + createStatement("continue"); | ||
| case "CallStatement": | ||
| return formatCallStatement(node as CallStatement, depth, indentStr); | ||
| case "FilterStatement": | ||
| return formatFilterStatement(node as FilterStatement, depth, indentStr); | ||
| case "Comment": | ||
| return pad + "{# " + (node as Comment).value + " #}"; | ||
| default: | ||
@@ -100,3 +120,3 @@ return pad + "{{- " + formatExpression(node as Expression) + " -}}"; | ||
| // ELIF(s) | ||
| for (let i = 1; i < clauses.length; i++) { | ||
| for (let i = 1; i < clauses.length; ++i) { | ||
| out += | ||
@@ -127,3 +147,3 @@ NEWLINE + | ||
| const n = node.iterable as SelectExpression; | ||
| formattedIterable = `${formatExpression(n.iterable)} if ${formatExpression(n.test)}`; | ||
| formattedIterable = `${formatExpression(n.lhs)} if ${formatExpression(n.test)}`; | ||
| } else { | ||
@@ -175,11 +195,37 @@ formattedIterable = formatExpression(node.iterable); | ||
| function formatCallStatement(node: CallStatement, depth: number, indentStr: string): string { | ||
| const pad = indentStr.repeat(depth); | ||
| const params = | ||
| node.callerArgs && node.callerArgs.length > 0 ? `(${node.callerArgs.map(formatExpression).join(", ")})` : ""; | ||
| const callExpr = formatExpression(node.call); | ||
| let out = pad + createStatement(`call${params}`, callExpr) + NEWLINE; | ||
| out += formatStatements(node.body, depth + 1, indentStr) + NEWLINE; | ||
| out += pad + createStatement("endcall"); | ||
| return out; | ||
| } | ||
| function formatFilterStatement(node: FilterStatement, depth: number, indentStr: string): string { | ||
| const pad = indentStr.repeat(depth); | ||
| const spec = | ||
| node.filter.type === "Identifier" | ||
| ? (node.filter as Identifier).value | ||
| : formatExpression(node.filter as CallExpression); | ||
| let out = pad + createStatement("filter", spec) + NEWLINE; | ||
| out += formatStatements(node.body, depth + 1, indentStr) + NEWLINE; | ||
| out += pad + createStatement("endfilter"); | ||
| return out; | ||
| } | ||
| function formatExpression(node: Expression, parentPrec: number = -1): string { | ||
| switch (node.type) { | ||
| case "SpreadExpression": { | ||
| const n = node as SpreadExpression; | ||
| return `*${formatExpression(n.argument)}`; | ||
| } | ||
| case "Identifier": | ||
| return (node as Identifier).value; | ||
| case "NullLiteral": | ||
| return "none"; | ||
| case "NumericLiteral": | ||
| case "BooleanLiteral": | ||
| return `${(node as NumericLiteral | BooleanLiteral).value}`; | ||
| case "IntegerLiteral": | ||
| return `${(node as IntegerLiteral).value}`; | ||
| case "FloatLiteral": | ||
| return `${(node as FloatLiteral).value}`; | ||
| case "StringLiteral": | ||
@@ -189,3 +235,3 @@ return JSON.stringify((node as StringLiteral).value); | ||
| const n = node as BinaryExpression; | ||
| const thisPrecedence = OPERATOR_PRECEDENCE[n.operator.type] ?? 0; | ||
| const thisPrecedence = getBinaryOperatorPrecedence(n); | ||
| const left = formatExpression(n.left, thisPrecedence); | ||
@@ -201,16 +247,27 @@ const right = formatExpression(n.right, thisPrecedence + 1); | ||
| } | ||
| case "LogicalNegationExpression": | ||
| return `not ${formatExpression((node as LogicalNegationExpression).argument, Infinity)}`; | ||
| case "CallExpression": { | ||
| const n = node as CallExpression; | ||
| const args = n.args.map((a) => formatExpression(a, -1)).join(", "); | ||
| return `${formatExpression(n.callee, -1)}(${args})`; | ||
| const args = n.args.map(formatExpression).join(", "); | ||
| return `${formatExpression(n.callee)}(${args})`; | ||
| } | ||
| case "MemberExpression": { | ||
| const n = node as MemberExpression; | ||
| let obj = formatExpression(n.object, -1); | ||
| if (n.object.type !== "Identifier") { | ||
| let obj = formatExpression(n.object); | ||
| // only wrap if it's not a simple or chained access/call | ||
| if ( | ||
| ![ | ||
| "Identifier", | ||
| "MemberExpression", | ||
| "CallExpression", | ||
| "StringLiteral", | ||
| "IntegerLiteral", | ||
| "FloatLiteral", | ||
| "ArrayLiteral", | ||
| "TupleLiteral", | ||
| "ObjectLiteral", | ||
| ].includes(n.object.type) | ||
| ) { | ||
| obj = `(${obj})`; | ||
| } | ||
| let prop = formatExpression(n.property, -1); | ||
| let prop = formatExpression(n.property); | ||
| if (!n.computed && n.property.type !== "Identifier") { | ||
@@ -225,3 +282,3 @@ prop = `(${prop})`; | ||
| if (n.filter.type === "CallExpression") { | ||
| return `${operand} | ${formatExpression(n.filter, -1)}`; | ||
| return `${operand} | ${formatExpression(n.filter)}`; | ||
| } | ||
@@ -232,11 +289,11 @@ return `${operand} | ${(n.filter as Identifier).value}`; | ||
| const n = node as SelectExpression; | ||
| return `${formatExpression(n.iterable, -1)} | select(${formatExpression(n.test, -1)})`; | ||
| return `${formatExpression(n.lhs)} if ${formatExpression(n.test)}`; | ||
| } | ||
| case "TestExpression": { | ||
| const n = node as TestExpression; | ||
| return `${formatExpression(n.operand, -1)} is${n.negate ? " not" : ""} ${n.test.value}`; | ||
| return `${formatExpression(n.operand)} is${n.negate ? " not" : ""} ${n.test.value}`; | ||
| } | ||
| case "ArrayLiteral": | ||
| case "TupleLiteral": { | ||
| const elems = ((node as ArrayLiteral | TupleLiteral).value as Expression[]).map((e) => formatExpression(e, -1)); | ||
| const elems = ((node as ArrayLiteral | TupleLiteral).value as Expression[]).map(formatExpression); | ||
| const brackets = node.type === "ArrayLiteral" ? "[]" : "()"; | ||
@@ -247,11 +304,11 @@ return `${brackets[0]}${elems.join(", ")}${brackets[1]}`; | ||
| const entries = Array.from((node as ObjectLiteral).value.entries()).map( | ||
| ([k, v]) => `${formatExpression(k, -1)}: ${formatExpression(v, -1)}` | ||
| ([k, v]) => `${formatExpression(k)}: ${formatExpression(v)}` | ||
| ); | ||
| return `{ ${entries.join(", ")} }`; | ||
| return `{${entries.join(", ")}}`; | ||
| } | ||
| case "SliceExpression": { | ||
| const n = node as SliceExpression; | ||
| const s = n.start ? formatExpression(n.start, -1) : ""; | ||
| const t = n.stop ? formatExpression(n.stop, -1) : ""; | ||
| const st = n.step ? `:${formatExpression(n.step, -1)}` : ""; | ||
| const s = n.start ? formatExpression(n.start) : ""; | ||
| const t = n.stop ? formatExpression(n.stop) : ""; | ||
| const st = n.step ? `:${formatExpression(n.step)}` : ""; | ||
| return `${s}:${t}${st}`; | ||
@@ -261,11 +318,10 @@ } | ||
| const n = node as KeywordArgumentExpression; | ||
| return `${n.key.value}=${formatExpression(n.value, -1)}`; | ||
| return `${n.key.value}=${formatExpression(n.value)}`; | ||
| } | ||
| case "If": { | ||
| // Special case for ternary operator (If as an expression, not a statement) | ||
| const n = node as If; | ||
| const test = formatExpression(n.test, -1); | ||
| const body = formatExpression(n.body[0], 0); // Ternary operators have a single body and alternate | ||
| const alternate = formatExpression(n.alternate[0], -1); | ||
| return `${body} if ${test} else ${alternate}`; | ||
| case "Ternary": { | ||
| const n = node as Ternary; | ||
| const expr = `${formatExpression(n.trueExpr)} if ${formatExpression(n.condition, 0)} else ${formatExpression( | ||
| n.falseExpr | ||
| )}`; | ||
| return parentPrec > -1 ? `(${expr})` : expr; | ||
| } | ||
@@ -272,0 +328,0 @@ default: |
+2
-10
@@ -15,6 +15,5 @@ /** | ||
| import { parse } from "./parser"; | ||
| import { Environment, Interpreter } from "./runtime"; | ||
| import { Environment, Interpreter, setupGlobals } from "./runtime"; | ||
| import type { Program } from "./ast"; | ||
| import type { StringValue } from "./runtime"; | ||
| import { range } from "./utils"; | ||
| import { format } from "./format"; | ||
@@ -39,11 +38,4 @@ | ||
| const env = new Environment(); | ||
| setupGlobals(env); | ||
| // Declare global variables | ||
| env.set("false", false); | ||
| env.set("true", true); | ||
| env.set("raise_exception", (args: string) => { | ||
| throw new Error(args); | ||
| }); | ||
| env.set("range", range); | ||
| // Add user-defined variables | ||
@@ -50,0 +42,0 @@ if (items) { |
+72
-99
@@ -7,7 +7,5 @@ /** | ||
| NumericLiteral: "NumericLiteral", // e.g., 123 | ||
| BooleanLiteral: "BooleanLiteral", // true or false | ||
| NullLiteral: "NullLiteral", // none | ||
| NumericLiteral: "NumericLiteral", // e.g., 123, 1.0 | ||
| StringLiteral: "StringLiteral", // 'string' | ||
| Identifier: "Identifier", // Variables, functions, etc. | ||
| Identifier: "Identifier", // Variables, functions, statements, booleans, etc. | ||
| Equals: "Equals", // = | ||
@@ -30,26 +28,7 @@ OpenParen: "OpenParen", // ( | ||
| CallOperator: "CallOperator", // () | ||
| AdditiveBinaryOperator: "AdditiveBinaryOperator", // + - | ||
| AdditiveBinaryOperator: "AdditiveBinaryOperator", // + - ~ | ||
| MultiplicativeBinaryOperator: "MultiplicativeBinaryOperator", // * / % | ||
| ComparisonBinaryOperator: "ComparisonBinaryOperator", // < > <= >= == != | ||
| UnaryOperator: "UnaryOperator", // ! - + | ||
| // Keywords | ||
| Set: "Set", | ||
| If: "If", | ||
| For: "For", | ||
| In: "In", | ||
| Is: "Is", | ||
| NotIn: "NotIn", | ||
| Else: "Else", | ||
| EndSet: "EndSet", | ||
| EndIf: "EndIf", | ||
| ElseIf: "ElseIf", | ||
| EndFor: "EndFor", | ||
| And: "And", | ||
| Or: "Or", | ||
| Not: "UnaryOperator", | ||
| Macro: "Macro", | ||
| EndMacro: "EndMacro", | ||
| Break: "Break", | ||
| Continue: "Continue", | ||
| Comment: "Comment", // {# ... #} | ||
| }); | ||
@@ -60,39 +39,2 @@ | ||
| /** | ||
| * Constant lookup for keywords and known identifiers + symbols. | ||
| */ | ||
| const KEYWORDS = Object.freeze({ | ||
| set: TOKEN_TYPES.Set, | ||
| for: TOKEN_TYPES.For, | ||
| in: TOKEN_TYPES.In, | ||
| is: TOKEN_TYPES.Is, | ||
| if: TOKEN_TYPES.If, | ||
| else: TOKEN_TYPES.Else, | ||
| endset: TOKEN_TYPES.EndSet, | ||
| endif: TOKEN_TYPES.EndIf, | ||
| elif: TOKEN_TYPES.ElseIf, | ||
| endfor: TOKEN_TYPES.EndFor, | ||
| and: TOKEN_TYPES.And, | ||
| or: TOKEN_TYPES.Or, | ||
| not: TOKEN_TYPES.Not, | ||
| "not in": TOKEN_TYPES.NotIn, | ||
| macro: TOKEN_TYPES.Macro, | ||
| endmacro: TOKEN_TYPES.EndMacro, | ||
| break: TOKEN_TYPES.Break, | ||
| continue: TOKEN_TYPES.Continue, | ||
| // Literals | ||
| true: TOKEN_TYPES.BooleanLiteral, | ||
| false: TOKEN_TYPES.BooleanLiteral, | ||
| none: TOKEN_TYPES.NullLiteral, | ||
| // NOTE: According to the Jinja docs: The special constants true, false, and none are indeed lowercase. | ||
| // Because that caused confusion in the past, (True used to expand to an undefined variable that was considered false), | ||
| // all three can now also be written in title case (True, False, and None). However, for consistency, (all Jinja identifiers are lowercase) | ||
| // you should use the lowercase versions. | ||
| True: TOKEN_TYPES.BooleanLiteral, | ||
| False: TOKEN_TYPES.BooleanLiteral, | ||
| None: TOKEN_TYPES.NullLiteral, | ||
| }); | ||
| /** | ||
| * Represents a single token in the template. | ||
@@ -150,2 +92,3 @@ */ | ||
| ["-", TOKEN_TYPES.AdditiveBinaryOperator], | ||
| ["~", TOKEN_TYPES.AdditiveBinaryOperator], | ||
| ["*", TOKEN_TYPES.MultiplicativeBinaryOperator], | ||
@@ -185,6 +128,2 @@ ["/", TOKEN_TYPES.MultiplicativeBinaryOperator], | ||
| // Replace all comments with a placeholder | ||
| // This ensures that comments don't interfere with the following options | ||
| template = template.replace(/{#.*?#}/gs, "{##}"); | ||
| if (options.lstrip_blocks) { | ||
@@ -194,3 +133,3 @@ // The lstrip_blocks option can also be set to strip tabs and spaces from the | ||
| // there are other characters before the start of the block.) | ||
| template = template.replace(/^[ \t]*({[#%])/gm, "$1"); | ||
| template = template.replace(/^[ \t]*({[#%-])/gm, "$1"); | ||
| } | ||
@@ -201,11 +140,18 @@ | ||
| // a template tag is removed automatically (like in PHP). | ||
| template = template.replace(/([#%]})\n/g, "$1"); | ||
| template = template.replace(/([#%-]})\n/g, "$1"); | ||
| } | ||
| return template | ||
| .replace(/{##}/g, "") // Remove comments | ||
| .replace(/-%}\s*/g, "%}") | ||
| .replace(/\s*{%-/g, "{%") | ||
| .replace(/-}}\s*/g, "}}") | ||
| .replace(/\s*{{-/g, "{{"); | ||
| return ( | ||
| template | ||
| .replace(/-%}\s*/g, "%}") | ||
| .replace(/\s*{%-/g, "{%") | ||
| .replace(/-}}\s*/g, "}}") | ||
| .replace(/\s*{{-/g, "{{") | ||
| .replace(/-#}\s*/g, "#}") | ||
| .replace(/\s*{#-/g, "{#") | ||
| // Handle the custom transformers-specific `generation` tag. | ||
| // See https://github.com/huggingface/transformers/pull/30650 for more information. | ||
| .replace(/{%\s*generation\s*%}.+?{%\s*endgeneration\s*%}/gs, "") | ||
| ); | ||
| } | ||
@@ -221,2 +167,3 @@ | ||
| let cursorPosition = 0; | ||
| let curlyBracketDepth = 0; | ||
@@ -256,3 +203,4 @@ const consumeWhile = (predicate: (char: string) => boolean): string => { | ||
| lastTokenType === TOKEN_TYPES.CloseStatement || | ||
| lastTokenType === TOKEN_TYPES.CloseExpression | ||
| lastTokenType === TOKEN_TYPES.CloseExpression || | ||
| lastTokenType === TOKEN_TYPES.Comment | ||
| ) { | ||
@@ -263,3 +211,6 @@ let text = ""; | ||
| // Keep going until we hit the next Jinja statement or expression | ||
| !(src[cursorPosition] === "{" && (src[cursorPosition + 1] === "%" || src[cursorPosition + 1] === "{")) | ||
| !( | ||
| src[cursorPosition] === "{" && | ||
| (src[cursorPosition + 1] === "%" || src[cursorPosition + 1] === "{" || src[cursorPosition + 1] === "#") | ||
| ) | ||
| ) { | ||
@@ -277,2 +228,19 @@ // Consume text | ||
| // Possibly consume a comment | ||
| if (src[cursorPosition] === "{" && src[cursorPosition + 1] === "#") { | ||
| cursorPosition += 2; // Skip the opening {# | ||
| let comment = ""; | ||
| while (src[cursorPosition] !== "#" || src[cursorPosition + 1] !== "}") { | ||
| // Check for end of input | ||
| if (cursorPosition + 2 >= src.length) { | ||
| throw new SyntaxError("Missing end of comment tag"); | ||
| } | ||
| comment += src[cursorPosition++]; | ||
| } | ||
| tokens.push(new Token(comment, TOKEN_TYPES.Comment)); | ||
| cursorPosition += 2; // Skip the closing #} | ||
| continue; | ||
| } | ||
| // Consume (and ignore) all whitespace inside Jinja statements or expressions | ||
@@ -293,4 +261,2 @@ consumeWhile((char) => /\s/.test(char)); | ||
| case TOKEN_TYPES.NumericLiteral: | ||
| case TOKEN_TYPES.BooleanLiteral: | ||
| case TOKEN_TYPES.NullLiteral: | ||
| case TOKEN_TYPES.StringLiteral: | ||
@@ -320,7 +286,20 @@ case TOKEN_TYPES.CloseParen: | ||
| // Try to match one of the tokens in the mapping table | ||
| for (const [char, token] of ORDERED_MAPPING_TABLE) { | ||
| const slice = src.slice(cursorPosition, cursorPosition + char.length); | ||
| if (slice === char) { | ||
| tokens.push(new Token(char, token)); | ||
| cursorPosition += char.length; | ||
| for (const [seq, type] of ORDERED_MAPPING_TABLE) { | ||
| // inside an object literal, don't treat "}}" as expression-end | ||
| if (seq === "}}" && curlyBracketDepth > 0) { | ||
| continue; | ||
| } | ||
| const slice = src.slice(cursorPosition, cursorPosition + seq.length); | ||
| if (slice === seq) { | ||
| tokens.push(new Token(seq, type)); | ||
| // possibly adjust the curly bracket depth | ||
| if (type === TOKEN_TYPES.OpenExpression) { | ||
| curlyBracketDepth = 0; | ||
| } else if (type === TOKEN_TYPES.OpenCurlyBracket) { | ||
| ++curlyBracketDepth; | ||
| } else if (type === TOKEN_TYPES.CloseCurlyBracket) { | ||
| --curlyBracketDepth; | ||
| } | ||
| cursorPosition += seq.length; | ||
| continue main; | ||
@@ -339,3 +318,10 @@ } | ||
| if (isInteger(char)) { | ||
| const num = consumeWhile(isInteger); | ||
| // Consume integer part | ||
| let num = consumeWhile(isInteger); | ||
| // Possibly, consume fractional part | ||
| if (src[cursorPosition] === "." && isInteger(src[cursorPosition + 1])) { | ||
| ++cursorPosition; // consume '.' | ||
| const frac = consumeWhile(isInteger); | ||
| num = `${num}.${frac}`; | ||
| } | ||
| tokens.push(new Token(num, TOKEN_TYPES.NumericLiteral)); | ||
@@ -345,18 +331,5 @@ continue; | ||
| if (isWord(char)) { | ||
| // consume any word characters and always classify as Identifier | ||
| const word = consumeWhile(isWord); | ||
| // Check for special/reserved keywords | ||
| // NOTE: We use Object.hasOwn() to avoid matching `.toString()` and other Object methods | ||
| const type = Object.hasOwn(KEYWORDS, word) ? KEYWORDS[word as keyof typeof KEYWORDS] : TOKEN_TYPES.Identifier; | ||
| // Special case of not in: | ||
| // If the previous token was a "not", and this token is "in" | ||
| // then we want to combine them into a single token | ||
| if (type === TOKEN_TYPES.In && tokens.at(-1)?.type === TOKEN_TYPES.Not) { | ||
| tokens.pop(); | ||
| tokens.push(new Token("not in", TOKEN_TYPES.NotIn)); | ||
| } else { | ||
| tokens.push(new Token(word, type)); | ||
| } | ||
| tokens.push(new Token(word, TOKEN_TYPES.Identifier)); | ||
| continue; | ||
@@ -363,0 +336,0 @@ } |
+188
-134
@@ -1,3 +0,3 @@ | ||
| import type { Token, TokenType } from "./lexer"; | ||
| import { TOKEN_TYPES } from "./lexer"; | ||
| import { Token, TOKEN_TYPES } from "./lexer"; | ||
| import type { TokenType } from "./lexer"; | ||
| import type { Statement } from "./ast"; | ||
@@ -14,6 +14,3 @@ import { | ||
| Identifier, | ||
| NumericLiteral, | ||
| StringLiteral, | ||
| BooleanLiteral, | ||
| NullLiteral, | ||
| ArrayLiteral, | ||
@@ -30,2 +27,9 @@ ObjectLiteral, | ||
| SelectExpression, | ||
| CallStatement, | ||
| FilterStatement, | ||
| SpreadExpression, | ||
| IntegerLiteral, | ||
| FloatLiteral, | ||
| Ternary, | ||
| Comment, | ||
| } from "./ast"; | ||
@@ -55,4 +59,13 @@ | ||
| function expectIdentifier(name: string): void { | ||
| if (!isIdentifier(name)) { | ||
| throw new SyntaxError(`Expected ${name}`); | ||
| } | ||
| ++current; | ||
| } | ||
| function parseAny(): Statement { | ||
| switch (tokens[current].type) { | ||
| case TOKEN_TYPES.Comment: | ||
| return new Comment(tokens[current++].value); | ||
| case TOKEN_TYPES.Text: | ||
@@ -69,6 +82,2 @@ return parseText(); | ||
| function not(...types: TokenType[]): boolean { | ||
| return current + types.length <= tokens.length && types.some((type, i) => type !== tokens[current + i].type); | ||
| } | ||
| function is(...types: TokenType[]): boolean { | ||
@@ -78,2 +87,17 @@ return current + types.length <= tokens.length && types.every((type, i) => type === tokens[current + i].type); | ||
| function isStatement(...names: string[]): boolean { | ||
| return ( | ||
| tokens[current]?.type === TOKEN_TYPES.OpenStatement && | ||
| tokens[current + 1]?.type === TOKEN_TYPES.Identifier && | ||
| names.includes(tokens[current + 1]?.value) | ||
| ); | ||
| } | ||
| function isIdentifier(...names: string[]): boolean { | ||
| return ( | ||
| current + names.length <= tokens.length && | ||
| names.every((name, i) => tokens[current + i].type === "Identifier" && name === tokens[current + i].value) | ||
| ); | ||
| } | ||
| function parseText(): StringLiteral { | ||
@@ -84,37 +108,65 @@ return new StringLiteral(expect(TOKEN_TYPES.Text, "Expected text token").value); | ||
| function parseJinjaStatement(): Statement { | ||
| // Consume {% %} tokens | ||
| // Consume {% token | ||
| expect(TOKEN_TYPES.OpenStatement, "Expected opening statement token"); | ||
| let result; | ||
| switch (tokens[current].type) { | ||
| case TOKEN_TYPES.Set: | ||
| // next token must be Identifier whose .value tells us which statement | ||
| if (tokens[current].type !== TOKEN_TYPES.Identifier) { | ||
| throw new SyntaxError(`Unknown statement, got ${tokens[current].type}`); | ||
| } | ||
| const name = tokens[current].value; | ||
| let result: Statement; | ||
| switch (name) { | ||
| case "set": | ||
| ++current; | ||
| result = parseSetStatement(); | ||
| expect(TOKEN_TYPES.CloseStatement, "Expected closing statement token"); | ||
| break; | ||
| case TOKEN_TYPES.If: | ||
| case "if": | ||
| ++current; | ||
| result = parseIfStatement(); | ||
| // expect {% endif %} | ||
| expect(TOKEN_TYPES.OpenStatement, "Expected {% token"); | ||
| expect(TOKEN_TYPES.EndIf, "Expected endif token"); | ||
| expectIdentifier("endif"); | ||
| expect(TOKEN_TYPES.CloseStatement, "Expected %} token"); | ||
| break; | ||
| case TOKEN_TYPES.Macro: | ||
| case "macro": | ||
| ++current; | ||
| result = parseMacroStatement(); | ||
| // expect {% endmacro %} | ||
| expect(TOKEN_TYPES.OpenStatement, "Expected {% token"); | ||
| expect(TOKEN_TYPES.EndMacro, "Expected endmacro token"); | ||
| expectIdentifier("endmacro"); | ||
| expect(TOKEN_TYPES.CloseStatement, "Expected %} token"); | ||
| break; | ||
| case TOKEN_TYPES.For: | ||
| case "for": | ||
| ++current; | ||
| result = parseForStatement(); | ||
| // expect {% endfor %} | ||
| expect(TOKEN_TYPES.OpenStatement, "Expected {% token"); | ||
| expect(TOKEN_TYPES.EndFor, "Expected endfor token"); | ||
| expectIdentifier("endfor"); | ||
| expect(TOKEN_TYPES.CloseStatement, "Expected %} token"); | ||
| break; | ||
| case TOKEN_TYPES.Break: | ||
| case "call": { | ||
| ++current; // consume 'call' | ||
| let callerArgs: Statement[] | null = null; | ||
| if (is(TOKEN_TYPES.OpenParen)) { | ||
| // Optional caller arguments, e.g. {% call(user) dump_users(...) %} | ||
| callerArgs = parseArgs(); | ||
| } | ||
| const callee = parsePrimaryExpression(); | ||
| if (callee.type !== "Identifier") { | ||
| throw new SyntaxError(`Expected identifier following call statement`); | ||
| } | ||
| const callArgs = parseArgs(); | ||
| expect(TOKEN_TYPES.CloseStatement, "Expected closing statement token"); | ||
| const body: Statement[] = []; | ||
| while (!isStatement("endcall")) { | ||
| body.push(parseAny()); | ||
| } | ||
| expect(TOKEN_TYPES.OpenStatement, "Expected '{%'"); | ||
| expectIdentifier("endcall"); | ||
| expect(TOKEN_TYPES.CloseStatement, "Expected closing statement token"); | ||
| const callExpr = new CallExpression(callee, callArgs); | ||
| result = new CallStatement(callExpr, callerArgs, body); | ||
| break; | ||
| } | ||
| case "break": | ||
| ++current; | ||
@@ -124,3 +176,3 @@ expect(TOKEN_TYPES.CloseStatement, "Expected closing statement token"); | ||
| break; | ||
| case TOKEN_TYPES.Continue: | ||
| case "continue": | ||
| ++current; | ||
@@ -130,4 +182,21 @@ expect(TOKEN_TYPES.CloseStatement, "Expected closing statement token"); | ||
| break; | ||
| case "filter": { | ||
| ++current; // consume 'filter' | ||
| let filterNode = parsePrimaryExpression(); | ||
| if (filterNode instanceof Identifier && is(TOKEN_TYPES.OpenParen)) { | ||
| filterNode = parseCallExpression(filterNode); | ||
| } | ||
| expect(TOKEN_TYPES.CloseStatement, "Expected closing statement token"); | ||
| const filterBody: Statement[] = []; | ||
| while (!isStatement("endfilter")) { | ||
| filterBody.push(parseAny()); | ||
| } | ||
| expect(TOKEN_TYPES.OpenStatement, "Expected '{%'"); | ||
| expectIdentifier("endfilter"); | ||
| expect(TOKEN_TYPES.CloseStatement, "Expected '%}'"); | ||
| result = new FilterStatement(filterNode as Identifier | CallExpression, filterBody); | ||
| break; | ||
| } | ||
| default: | ||
| throw new SyntaxError(`Unknown statement type: ${tokens[current].type}`); | ||
| throw new SyntaxError(`Unknown statement type: ${name}`); | ||
| } | ||
@@ -150,24 +219,19 @@ | ||
| function parseSetStatement(): Statement { | ||
| const left = parseExpression(); | ||
| const left = parseExpressionSequence(); | ||
| let value: Statement | null = null; | ||
| const body: Statement[] = []; | ||
| if (is(TOKEN_TYPES.Equals)) { | ||
| ++current; | ||
| const value = parseExpression(); | ||
| return new SetStatement(left, value, []); | ||
| value = parseExpressionSequence(); | ||
| } else { | ||
| // parsing multiline set here | ||
| const body: Statement[] = []; | ||
| expect(TOKEN_TYPES.CloseStatement, "Expected %} token"); | ||
| while ( | ||
| !(tokens[current]?.type === TOKEN_TYPES.OpenStatement && tokens[current + 1]?.type === TOKEN_TYPES.EndSet) | ||
| ) { | ||
| const another = parseAny(); | ||
| body.push(another); | ||
| while (!isStatement("endset")) { | ||
| body.push(parseAny()); | ||
| } | ||
| expect(TOKEN_TYPES.OpenStatement, "Expected {% token"); | ||
| expect(TOKEN_TYPES.EndSet, "Expected endset token"); | ||
| return new SetStatement(left, null, body); | ||
| expectIdentifier("endset"); | ||
| } | ||
| expect(TOKEN_TYPES.CloseStatement, "Expected closing statement token"); | ||
| return new SetStatement(left, value, body); | ||
| } | ||
@@ -183,34 +247,23 @@ | ||
| // Keep parsing if body until we reach the first {% elif %} or {% else %} or {% endif %} | ||
| while ( | ||
| !( | ||
| tokens[current]?.type === TOKEN_TYPES.OpenStatement && | ||
| (tokens[current + 1]?.type === TOKEN_TYPES.ElseIf || | ||
| tokens[current + 1]?.type === TOKEN_TYPES.Else || | ||
| tokens[current + 1]?.type === TOKEN_TYPES.EndIf) | ||
| ) | ||
| ) { | ||
| // Keep parsing 'if' body until we reach the first {% elif %} or {% else %} or {% endif %} | ||
| while (!isStatement("elif", "else", "endif")) { | ||
| body.push(parseAny()); | ||
| } | ||
| // Alternate branch: Check for {% elif %} or {% else %} | ||
| if ( | ||
| tokens[current]?.type === TOKEN_TYPES.OpenStatement && | ||
| tokens[current + 1]?.type !== TOKEN_TYPES.EndIf // There is some body | ||
| ) { | ||
| ++current; // eat {% token | ||
| if (is(TOKEN_TYPES.ElseIf)) { | ||
| expect(TOKEN_TYPES.ElseIf, "Expected elseif token"); | ||
| alternate.push(parseIfStatement()); | ||
| } else { | ||
| // tokens[current]?.type === TokenType.Else | ||
| expect(TOKEN_TYPES.Else, "Expected else token"); | ||
| expect(TOKEN_TYPES.CloseStatement, "Expected closing statement token"); | ||
| // handle {% elif %} | ||
| if (isStatement("elif")) { | ||
| ++current; // consume {% | ||
| ++current; // consume 'elif' | ||
| const result = parseIfStatement(); // nested If | ||
| alternate.push(result); | ||
| } | ||
| // handle {% else %} | ||
| else if (isStatement("else")) { | ||
| ++current; // consume {% | ||
| ++current; // consume 'else' | ||
| expect(TOKEN_TYPES.CloseStatement, "Expected closing statement token"); | ||
| // keep going until we hit {% endif %} | ||
| while ( | ||
| !(tokens[current]?.type === TOKEN_TYPES.OpenStatement && tokens[current + 1]?.type === TOKEN_TYPES.EndIf) | ||
| ) { | ||
| alternate.push(parseAny()); | ||
| } | ||
| // keep going until we hit {% endif %} | ||
| while (!isStatement("endif")) { | ||
| alternate.push(parseAny()); | ||
| } | ||
@@ -234,3 +287,3 @@ } | ||
| // Keep going until we hit {% endmacro | ||
| while (not(TOKEN_TYPES.OpenStatement, TOKEN_TYPES.EndMacro)) { | ||
| while (!isStatement("endmacro")) { | ||
| body.push(parseAny()); | ||
@@ -263,3 +316,6 @@ } | ||
| expect(TOKEN_TYPES.In, "Expected `in` keyword following loop variable"); | ||
| if (!isIdentifier("in")) { | ||
| throw new SyntaxError("Expected `in` keyword following loop variable"); | ||
| } | ||
| ++current; | ||
@@ -275,3 +331,3 @@ // `messages` in `for message in messages` | ||
| // Keep going until we hit {% endfor or {% else | ||
| while (not(TOKEN_TYPES.OpenStatement, TOKEN_TYPES.EndFor) && not(TOKEN_TYPES.OpenStatement, TOKEN_TYPES.Else)) { | ||
| while (!isStatement("endfor", "else")) { | ||
| body.push(parseAny()); | ||
@@ -282,9 +338,7 @@ } | ||
| const alternative: Statement[] = []; | ||
| if (is(TOKEN_TYPES.OpenStatement, TOKEN_TYPES.Else)) { | ||
| if (isStatement("else")) { | ||
| ++current; // consume {% | ||
| ++current; // consume else | ||
| ++current; // consume 'else' | ||
| expect(TOKEN_TYPES.CloseStatement, "Expected closing statement token"); | ||
| // keep going until we hit {% endfor | ||
| while (not(TOKEN_TYPES.OpenStatement, TOKEN_TYPES.EndFor)) { | ||
| while (!isStatement("endfor")) { | ||
| alternative.push(parseAny()); | ||
@@ -304,15 +358,15 @@ } | ||
| const a = parseLogicalOrExpression(); | ||
| if (is(TOKEN_TYPES.If)) { | ||
| if (isIdentifier("if")) { | ||
| // Ternary expression | ||
| ++current; // consume if | ||
| const predicate = parseLogicalOrExpression(); | ||
| ++current; // consume 'if' | ||
| const test = parseLogicalOrExpression(); | ||
| if (is(TOKEN_TYPES.Else)) { | ||
| if (isIdentifier("else")) { | ||
| // Ternary expression with else | ||
| ++current; // consume else | ||
| const b = parseLogicalOrExpression(); | ||
| return new If(predicate, [a], [b]); | ||
| ++current; // consume 'else' | ||
| const falseExpr = parseIfExpression(); // recurse to support chained ternaries | ||
| return new Ternary(test, a, falseExpr); | ||
| } else { | ||
| // Select expression on iterable | ||
| return new SelectExpression(a, predicate); | ||
| return new SelectExpression(a, test); | ||
| } | ||
@@ -325,3 +379,3 @@ } | ||
| let left = parseLogicalAndExpression(); | ||
| while (is(TOKEN_TYPES.Or)) { | ||
| while (isIdentifier("or")) { | ||
| const operator = tokens[current]; | ||
@@ -337,3 +391,3 @@ ++current; | ||
| let left = parseLogicalNegationExpression(); | ||
| while (is(TOKEN_TYPES.And)) { | ||
| while (isIdentifier("and")) { | ||
| const operator = tokens[current]; | ||
@@ -351,3 +405,3 @@ ++current; | ||
| // Try parse unary operators | ||
| while (is(TOKEN_TYPES.Not)) { | ||
| while (isIdentifier("not")) { | ||
| // not not ... | ||
@@ -367,5 +421,14 @@ const operator = tokens[current]; | ||
| let left = parseAdditiveExpression(); | ||
| while (is(TOKEN_TYPES.ComparisonBinaryOperator) || is(TOKEN_TYPES.In) || is(TOKEN_TYPES.NotIn)) { | ||
| const operator = tokens[current]; | ||
| ++current; | ||
| while (true) { | ||
| let operator: Token; | ||
| if (isIdentifier("not", "in")) { | ||
| operator = new Token("not in", TOKEN_TYPES.Identifier); | ||
| current += 2; | ||
| } else if (isIdentifier("in")) { | ||
| operator = tokens[current++]; | ||
| } else if (is(TOKEN_TYPES.ComparisonBinaryOperator)) { | ||
| operator = tokens[current++]; | ||
| } else { | ||
| break; | ||
| } | ||
| const right = parseAdditiveExpression(); | ||
@@ -426,13 +489,21 @@ left = new BinaryExpression(operator, left, right); | ||
| while (!is(TOKEN_TYPES.CloseParen)) { | ||
| let argument = parseExpression(); | ||
| let argument: Statement; | ||
| if (is(TOKEN_TYPES.Equals)) { | ||
| // keyword argument | ||
| // e.g., func(x = 5, y = a or b) | ||
| ++current; // consume equals | ||
| if (!(argument instanceof Identifier)) { | ||
| throw new SyntaxError(`Expected identifier for keyword argument`); | ||
| // unpacking: *expr | ||
| if (tokens[current].type === TOKEN_TYPES.MultiplicativeBinaryOperator && tokens[current].value === "*") { | ||
| ++current; | ||
| const expr = parseExpression(); | ||
| argument = new SpreadExpression(expr); | ||
| } else { | ||
| argument = parseExpression(); | ||
| if (is(TOKEN_TYPES.Equals)) { | ||
| // keyword argument | ||
| // e.g., func(x = 5, y = a or b) | ||
| ++current; // consume equals | ||
| if (!(argument instanceof Identifier)) { | ||
| throw new SyntaxError(`Expected identifier for keyword argument`); | ||
| } | ||
| const value = parseExpression(); | ||
| argument = new KeywordArgumentExpression(argument as Identifier, value); | ||
| } | ||
| const value = parseExpression(); | ||
| argument = new KeywordArgumentExpression(argument, value); | ||
| } | ||
@@ -488,3 +559,3 @@ args.push(argument); | ||
| let property: Statement; | ||
| const computed = operator.type !== TOKEN_TYPES.Dot; | ||
| const computed = operator.type === TOKEN_TYPES.OpenSquareBracket; | ||
| if (computed) { | ||
@@ -513,4 +584,3 @@ // computed (i.e., bracket notation: obj[expr]) | ||
| while (is(TOKEN_TYPES.MultiplicativeBinaryOperator)) { | ||
| const operator = tokens[current]; | ||
| ++current; | ||
| const operator = tokens[current++]; | ||
| const right = parseTestExpression(); | ||
@@ -525,6 +595,6 @@ left = new BinaryExpression(operator, left, right); | ||
| while (is(TOKEN_TYPES.Is)) { | ||
| while (isIdentifier("is")) { | ||
| // Support chaining tests | ||
| ++current; // consume is | ||
| const negate = is(TOKEN_TYPES.Not); | ||
| const negate = isIdentifier("not"); | ||
| if (negate) { | ||
@@ -534,9 +604,3 @@ ++current; // consume not | ||
| let filter = parsePrimaryExpression(); | ||
| if (filter instanceof BooleanLiteral) { | ||
| // Special case: treat boolean literals as identifiers | ||
| filter = new Identifier(filter.value.toString()); | ||
| } else if (filter instanceof NullLiteral) { | ||
| filter = new Identifier("none"); | ||
| } | ||
| const filter = parsePrimaryExpression(); | ||
| if (!(filter instanceof Identifier)) { | ||
@@ -571,31 +635,23 @@ throw new SyntaxError(`Expected identifier for the test`); | ||
| // Primary expression: number, string, identifier, function call, parenthesized expression | ||
| const token = tokens[current]; | ||
| const token = tokens[current++]; | ||
| switch (token.type) { | ||
| case TOKEN_TYPES.NumericLiteral: | ||
| ++current; | ||
| return new NumericLiteral(Number(token.value)); | ||
| case TOKEN_TYPES.StringLiteral: | ||
| ++current; | ||
| return new StringLiteral(token.value); | ||
| case TOKEN_TYPES.BooleanLiteral: | ||
| ++current; | ||
| return new BooleanLiteral(token.value.toLowerCase() === "true"); | ||
| case TOKEN_TYPES.NullLiteral: | ||
| ++current; | ||
| return new NullLiteral(null); | ||
| case TOKEN_TYPES.NumericLiteral: { | ||
| const num = token.value; | ||
| return num.includes(".") ? new FloatLiteral(Number(num)) : new IntegerLiteral(Number(num)); | ||
| } | ||
| case TOKEN_TYPES.StringLiteral: { | ||
| let value = token.value; | ||
| while (is(TOKEN_TYPES.StringLiteral)) { | ||
| value += tokens[current++].value; | ||
| } | ||
| return new StringLiteral(value); | ||
| } | ||
| case TOKEN_TYPES.Identifier: | ||
| ++current; | ||
| return new Identifier(token.value); | ||
| case TOKEN_TYPES.OpenParen: { | ||
| ++current; // consume opening parenthesis | ||
| const expression = parseExpressionSequence(); | ||
| if (tokens[current].type !== TOKEN_TYPES.CloseParen) { | ||
| throw new SyntaxError(`Expected closing parenthesis, got ${tokens[current].type} instead`); | ||
| } | ||
| ++current; // consume closing parenthesis | ||
| expect(TOKEN_TYPES.CloseParen, "Expected closing parenthesis, got ${tokens[current].type} instead."); | ||
| return expression; | ||
| } | ||
| case TOKEN_TYPES.OpenSquareBracket: { | ||
| ++current; // consume opening square bracket | ||
| const values = []; | ||
@@ -614,4 +670,2 @@ while (!is(TOKEN_TYPES.CloseSquareBracket)) { | ||
| case TOKEN_TYPES.OpenCurlyBracket: { | ||
| ++current; // consume opening curly bracket | ||
| const values = new Map(); | ||
@@ -618,0 +672,0 @@ while (!is(TOKEN_TYPES.CloseCurlyBracket)) { |
+388
-103
| import type { | ||
| NumericLiteral, | ||
| StringLiteral, | ||
| BooleanLiteral, | ||
| NullLiteral, | ||
| FloatLiteral, | ||
| IntegerLiteral, | ||
| ArrayLiteral, | ||
@@ -26,7 +25,12 @@ Statement, | ||
| SelectExpression, | ||
| CallStatement, | ||
| FilterStatement, | ||
| Ternary, | ||
| SpreadExpression, | ||
| } from "./ast"; | ||
| import { slice, titleCase } from "./utils"; | ||
| import { range, replace, slice, strftime_now, titleCase } from "./utils"; | ||
| export type AnyRuntimeValue = | ||
| | NumericValue | ||
| | IntegerValue | ||
| | FloatValue | ||
| | StringValue | ||
@@ -72,12 +76,27 @@ | BooleanValue | ||
| } | ||
| toString(): string { | ||
| return String(this.value); | ||
| } | ||
| } | ||
| /** | ||
| * Represents a numeric value at runtime. | ||
| * Represents an integer value at runtime. | ||
| */ | ||
| export class NumericValue extends RuntimeValue<number> { | ||
| override type = "NumericValue"; | ||
| export class IntegerValue extends RuntimeValue<number> { | ||
| override type = "IntegerValue"; | ||
| } | ||
| /** | ||
| * Represents a float value at runtime. | ||
| */ | ||
| export class FloatValue extends RuntimeValue<number> { | ||
| override type = "FloatValue"; | ||
| override toString(): string { | ||
| return this.value % 1 === 0 ? this.value.toFixed(1) : this.value.toString(); | ||
| } | ||
| } | ||
| /** | ||
| * Represents a string value at runtime. | ||
@@ -113,4 +132,10 @@ */ | ||
| ], | ||
| ["length", new NumericValue(this.value.length)], | ||
| [ | ||
| "capitalize", | ||
| new FunctionValue(() => { | ||
| return new StringValue(this.value.charAt(0).toUpperCase() + this.value.slice(1)); | ||
| }), | ||
| ], | ||
| ["length", new IntegerValue(this.value.length)], | ||
| [ | ||
| "rstrip", | ||
@@ -133,7 +158,17 @@ new FunctionValue(() => { | ||
| } | ||
| const prefix = args[0]; | ||
| if (!(prefix instanceof StringValue)) { | ||
| throw new Error("startswith() argument must be a string"); | ||
| const pattern = args[0]; | ||
| if (pattern instanceof StringValue) { | ||
| return new BooleanValue(this.value.startsWith(pattern.value)); | ||
| } else if (pattern instanceof ArrayValue) { | ||
| for (const item of pattern.value) { | ||
| if (!(item instanceof StringValue)) { | ||
| throw new Error("startswith() tuple elements must be strings"); | ||
| } | ||
| if (this.value.startsWith(item.value)) { | ||
| return new BooleanValue(true); | ||
| } | ||
| } | ||
| return new BooleanValue(false); | ||
| } | ||
| return new BooleanValue(this.value.startsWith(prefix.value)); | ||
| throw new Error("startswith() argument must be a string or tuple of strings"); | ||
| }), | ||
@@ -147,7 +182,17 @@ ], | ||
| } | ||
| const suffix = args[0]; | ||
| if (!(suffix instanceof StringValue)) { | ||
| throw new Error("endswith() argument must be a string"); | ||
| const pattern = args[0]; | ||
| if (pattern instanceof StringValue) { | ||
| return new BooleanValue(this.value.endsWith(pattern.value)); | ||
| } else if (pattern instanceof ArrayValue) { | ||
| for (const item of pattern.value) { | ||
| if (!(item instanceof StringValue)) { | ||
| throw new Error("endswith() tuple elements must be strings"); | ||
| } | ||
| if (this.value.endsWith(item.value)) { | ||
| return new BooleanValue(true); | ||
| } | ||
| } | ||
| return new BooleanValue(false); | ||
| } | ||
| return new BooleanValue(this.value.endsWith(suffix.value)); | ||
| throw new Error("endswith() argument must be a string or tuple of strings"); | ||
| }), | ||
@@ -164,4 +209,4 @@ ], | ||
| } | ||
| const maxsplit = args[1] ?? new NumericValue(-1); | ||
| if (!(maxsplit instanceof NumericValue)) { | ||
| const maxsplit = args[1] ?? new IntegerValue(-1); | ||
| if (!(maxsplit instanceof IntegerValue)) { | ||
| throw new Error("maxsplit argument must be a number"); | ||
@@ -198,2 +243,30 @@ } | ||
| ], | ||
| [ | ||
| "replace", | ||
| new FunctionValue((args): StringValue => { | ||
| if (args.length < 2) { | ||
| throw new Error("replace() requires at least two arguments"); | ||
| } | ||
| const oldValue = args[0]; | ||
| const newValue = args[1]; | ||
| if (!(oldValue instanceof StringValue && newValue instanceof StringValue)) { | ||
| throw new Error("replace() arguments must be strings"); | ||
| } | ||
| let count: AnyRuntimeValue | undefined; | ||
| if (args.length > 2) { | ||
| if (args[2].type === "KeywordArgumentsValue") { | ||
| count = (args[2] as KeywordArgumentsValue).value.get("count") ?? new NullValue(); | ||
| } else { | ||
| count = args[2]; | ||
| } | ||
| } else { | ||
| count = new NullValue(); | ||
| } | ||
| if (!(count instanceof IntegerValue || count instanceof NullValue)) { | ||
| throw new Error("replace() count argument must be a number or null"); | ||
| } | ||
| return new StringValue(replace(this.value, oldValue.value, newValue.value, count.value)); | ||
| }), | ||
| ], | ||
| ]); | ||
@@ -237,11 +310,18 @@ } | ||
| ], | ||
| [ | ||
| "items", | ||
| new FunctionValue(() => { | ||
| return new ArrayValue( | ||
| Array.from(this.value.entries()).map(([key, value]) => new ArrayValue([new StringValue(key), value])) | ||
| ); | ||
| }), | ||
| ], | ||
| ["items", new FunctionValue(() => this.items())], | ||
| ["keys", new FunctionValue(() => this.keys())], | ||
| ["values", new FunctionValue(() => this.values())], | ||
| ]); | ||
| items(): ArrayValue { | ||
| return new ArrayValue( | ||
| Array.from(this.value.entries()).map(([key, value]) => new ArrayValue([new StringValue(key), value])) | ||
| ); | ||
| } | ||
| keys(): ArrayValue { | ||
| return new ArrayValue(Array.from(this.value.keys()).map((key) => new StringValue(key))); | ||
| } | ||
| values(): ArrayValue { | ||
| return new ArrayValue(Array.from(this.value.values())); | ||
| } | ||
| } | ||
@@ -261,3 +341,3 @@ | ||
| override type = "ArrayValue"; | ||
| override builtins = new Map<string, AnyRuntimeValue>([["length", new NumericValue(this.value.length)]]); | ||
| override builtins = new Map<string, AnyRuntimeValue>([["length", new IntegerValue(this.value.length)]]); | ||
@@ -337,6 +417,6 @@ /** | ||
| (operand) => { | ||
| if (operand.type !== "NumericValue") { | ||
| throw new Error(`Cannot apply test "odd" to type: ${operand.type}`); | ||
| if (!(operand instanceof IntegerValue)) { | ||
| throw new Error(`cannot odd on ${operand.type}`); | ||
| } | ||
| return (operand as NumericValue).value % 2 !== 0; | ||
| return operand.value % 2 !== 0; | ||
| }, | ||
@@ -347,6 +427,6 @@ ], | ||
| (operand) => { | ||
| if (operand.type !== "NumericValue") { | ||
| throw new Error(`Cannot apply test "even" to type: ${operand.type}`); | ||
| if (!(operand instanceof IntegerValue)) { | ||
| throw new Error(`cannot even on ${operand.type}`); | ||
| } | ||
| return (operand as NumericValue).value % 2 === 0; | ||
| return operand.value % 2 === 0; | ||
| }, | ||
@@ -358,4 +438,4 @@ ], | ||
| ["string", (operand) => operand.type === "StringValue"], | ||
| ["number", (operand) => operand.type === "NumericValue"], | ||
| ["integer", (operand) => operand.type === "NumericValue" && Number.isInteger((operand as NumericValue).value)], | ||
| ["number", (operand) => operand instanceof IntegerValue || operand instanceof FloatValue], | ||
| ["integer", (operand) => operand instanceof IntegerValue], | ||
| ["iterable", (operand) => operand.type === "ArrayValue" || operand.type === "StringValue"], | ||
@@ -443,2 +523,22 @@ ["mapping", (operand) => operand.type === "ObjectValue"], | ||
| export function setupGlobals(env: Environment): void { | ||
| // Declare global variables | ||
| env.set("false", false); | ||
| env.set("true", true); | ||
| env.set("none", null); | ||
| env.set("raise_exception", (args: string) => { | ||
| throw new Error(args); | ||
| }); | ||
| env.set("range", range); | ||
| env.set("strftime_now", strftime_now); | ||
| // NOTE: According to the Jinja docs: The special constants true, false, and none are indeed lowercase. | ||
| // Because that caused confusion in the past, (True used to expand to an undefined variable that was considered false), | ||
| // all three can now also be written in title case (True, False, and None). However, for consistency, (all Jinja identifiers are lowercase) | ||
| // you should use the lowercase versions. | ||
| env.set("True", true); | ||
| env.set("False", false); | ||
| env.set("None", null); | ||
| } | ||
| export class Interpreter { | ||
@@ -483,29 +583,44 @@ global: Environment; | ||
| if (left instanceof UndefinedValue || right instanceof UndefinedValue) { | ||
| throw new Error("Cannot perform operation on undefined values"); | ||
| if (right instanceof UndefinedValue && ["in", "not in"].includes(node.operator.value)) { | ||
| // Special case: `anything in undefined` is `false` and `anything not in undefined` is `true` | ||
| return new BooleanValue(node.operator.value === "not in"); | ||
| } | ||
| throw new Error(`Cannot perform operation ${node.operator.value} on undefined values`); | ||
| } else if (left instanceof NullValue || right instanceof NullValue) { | ||
| throw new Error("Cannot perform operation on null values"); | ||
| } else if (left instanceof NumericValue && right instanceof NumericValue) { | ||
| } else if (node.operator.value === "~") { | ||
| // toString and concatenation | ||
| return new StringValue(left.value.toString() + right.value.toString()); | ||
| } else if ( | ||
| (left instanceof IntegerValue || left instanceof FloatValue) && | ||
| (right instanceof IntegerValue || right instanceof FloatValue) | ||
| ) { | ||
| // Evaulate pure numeric operations with binary operators. | ||
| const a = left.value, | ||
| b = right.value; | ||
| switch (node.operator.value) { | ||
| // Arithmetic operators | ||
| case "+": | ||
| return new NumericValue(left.value + right.value); | ||
| case "-": | ||
| return new NumericValue(left.value - right.value); | ||
| case "*": | ||
| return new NumericValue(left.value * right.value); | ||
| case "*": { | ||
| const res = node.operator.value === "+" ? a + b : node.operator.value === "-" ? a - b : a * b; | ||
| const isFloat = left instanceof FloatValue || right instanceof FloatValue; | ||
| return isFloat ? new FloatValue(res) : new IntegerValue(res); | ||
| } | ||
| case "/": | ||
| return new NumericValue(left.value / right.value); | ||
| case "%": | ||
| return new NumericValue(left.value % right.value); | ||
| return new FloatValue(a / b); | ||
| case "%": { | ||
| const rem = a % b; | ||
| const isFloat = left instanceof FloatValue || right instanceof FloatValue; | ||
| return isFloat ? new FloatValue(rem) : new IntegerValue(rem); | ||
| } | ||
| // Comparison operators | ||
| case "<": | ||
| return new BooleanValue(left.value < right.value); | ||
| return new BooleanValue(a < b); | ||
| case ">": | ||
| return new BooleanValue(left.value > right.value); | ||
| return new BooleanValue(a > b); | ||
| case ">=": | ||
| return new BooleanValue(left.value >= right.value); | ||
| return new BooleanValue(a >= b); | ||
| case "<=": | ||
| return new BooleanValue(left.value <= right.value); | ||
| return new BooleanValue(a <= b); | ||
| } | ||
@@ -562,7 +677,17 @@ } else if (left instanceof ArrayValue && right instanceof ArrayValue) { | ||
| // Accumulate args and kwargs | ||
| const positionalArguments = []; | ||
| const keywordArguments = new Map(); | ||
| const positionalArguments: AnyRuntimeValue[] = []; | ||
| const keywordArguments = new Map<string, AnyRuntimeValue>(); | ||
| for (const argument of args) { | ||
| // TODO: Lazy evaluation of arguments | ||
| if (argument.type === "KeywordArgumentExpression") { | ||
| if (argument.type === "SpreadExpression") { | ||
| const spreadNode = argument as SpreadExpression; | ||
| const val = this.evaluate(spreadNode.argument, environment); | ||
| if (!(val instanceof ArrayValue)) { | ||
| throw new Error(`Cannot unpack non-iterable type: ${val.type}`); | ||
| } | ||
| for (const item of val.value) { | ||
| positionalArguments.push(item); | ||
| } | ||
| } else if (argument.type === "KeywordArgumentExpression") { | ||
| const kwarg = argument as KeywordArgumentExpression; | ||
@@ -580,8 +705,3 @@ keywordArguments.set(kwarg.key.value, this.evaluate(kwarg.value, environment)); | ||
| /** | ||
| * Evaluates expressions following the filter operation type. | ||
| */ | ||
| private evaluateFilterExpression(node: FilterExpression, environment: Environment): AnyRuntimeValue { | ||
| const operand = this.evaluate(node.operand, environment); | ||
| private applyFilter(operand: AnyRuntimeValue, filterNode: Identifier | CallExpression, environment: Environment) { | ||
| // For now, we only support the built-in filters | ||
@@ -599,4 +719,4 @@ // TODO: Add support for non-identifier filters | ||
| if (node.filter.type === "Identifier") { | ||
| const filter = node.filter as Identifier; | ||
| if (filterNode.type === "Identifier") { | ||
| const filter = filterNode as Identifier; | ||
@@ -616,3 +736,3 @@ if (filter.value === "tojson") { | ||
| case "length": | ||
| return new NumericValue(operand.value.length); | ||
| return new IntegerValue(operand.value.length); | ||
| case "reverse": | ||
@@ -627,4 +747,5 @@ return new ArrayValue(operand.value.reverse()); | ||
| switch (a.type) { | ||
| case "NumericValue": | ||
| return (a as NumericValue).value - (b as NumericValue).value; | ||
| case "IntegerValue": | ||
| case "FloatValue": | ||
| return (a as IntegerValue | FloatValue).value - (b as IntegerValue | FloatValue).value; | ||
| case "StringValue": | ||
@@ -641,2 +762,13 @@ return (a as StringValue).value.localeCompare((b as StringValue).value); | ||
| return new StringValue(toJSON(operand)); | ||
| case "unique": { | ||
| const seen = new Set(); | ||
| const output: AnyRuntimeValue[] = []; | ||
| for (const item of operand.value) { | ||
| if (!seen.has(item.value)) { | ||
| seen.add(item.value); | ||
| output.push(item); | ||
| } | ||
| } | ||
| return new ArrayValue(output); | ||
| } | ||
| default: | ||
@@ -647,12 +779,17 @@ throw new Error(`Unknown ArrayValue filter: ${filter.value}`); | ||
| switch (filter.value) { | ||
| // Filters that are also built-in functions | ||
| case "length": | ||
| return new NumericValue(operand.value.length); | ||
| case "upper": | ||
| return new StringValue(operand.value.toUpperCase()); | ||
| case "lower": | ||
| return new StringValue(operand.value.toLowerCase()); | ||
| case "title": | ||
| return new StringValue(titleCase(operand.value)); | ||
| case "capitalize": | ||
| return new StringValue(operand.value.charAt(0).toUpperCase() + operand.value.slice(1)); | ||
| case "capitalize": { | ||
| const builtin = operand.builtins.get(filter.value); | ||
| if (builtin instanceof FunctionValue) { | ||
| return builtin.value(/* no arguments */ [], environment); | ||
| } else if (builtin instanceof IntegerValue) { | ||
| return builtin; | ||
| } else { | ||
| throw new Error(`Unknown StringValue filter: ${filter.value}`); | ||
| } | ||
| } | ||
| case "trim": | ||
@@ -673,9 +810,23 @@ return new StringValue(operand.value.trim()); | ||
| return operand; // no-op | ||
| case "int": { | ||
| const val = parseInt(operand.value, 10); | ||
| return new IntegerValue(isNaN(val) ? 0 : val); | ||
| } | ||
| case "float": { | ||
| const val = parseFloat(operand.value); | ||
| return new FloatValue(isNaN(val) ? 0.0 : val); | ||
| } | ||
| default: | ||
| throw new Error(`Unknown StringValue filter: ${filter.value}`); | ||
| } | ||
| } else if (operand instanceof NumericValue) { | ||
| } else if (operand instanceof IntegerValue || operand instanceof FloatValue) { | ||
| switch (filter.value) { | ||
| case "abs": | ||
| return new NumericValue(Math.abs(operand.value)); | ||
| return operand instanceof IntegerValue | ||
| ? new IntegerValue(Math.abs(operand.value)) | ||
| : new FloatValue(Math.abs(operand.value)); | ||
| case "int": | ||
| return new IntegerValue(Math.floor(operand.value)); | ||
| case "float": | ||
| return new FloatValue(operand.value); | ||
| default: | ||
@@ -691,10 +842,23 @@ throw new Error(`Unknown NumericValue filter: ${filter.value}`); | ||
| case "length": | ||
| return new NumericValue(operand.value.size); | ||
| return new IntegerValue(operand.value.size); | ||
| default: | ||
| throw new Error(`Unknown ObjectValue filter: ${filter.value}`); | ||
| } | ||
| } else if (operand instanceof BooleanValue) { | ||
| switch (filter.value) { | ||
| case "bool": | ||
| return new BooleanValue(operand.value); | ||
| case "int": | ||
| return new IntegerValue(operand.value ? 1 : 0); | ||
| case "float": | ||
| return new FloatValue(operand.value ? 1.0 : 0.0); | ||
| case "string": | ||
| return new StringValue(operand.value ? "true" : "false"); | ||
| default: | ||
| throw new Error(`Unknown BooleanValue filter: ${filter.value}`); | ||
| } | ||
| } | ||
| throw new Error(`Cannot apply filter "${filter.value}" to type: ${operand.type}`); | ||
| } else if (node.filter.type === "CallExpression") { | ||
| const filter = node.filter as CallExpression; | ||
| } else if (filterNode.type === "CallExpression") { | ||
| const filter = filterNode as CallExpression; | ||
@@ -709,3 +873,3 @@ if (filter.callee.type !== "Identifier") { | ||
| const indent = kwargs.get("indent") ?? new NullValue(); | ||
| if (!(indent instanceof NumericValue || indent instanceof NullValue)) { | ||
| if (!(indent instanceof IntegerValue || indent instanceof NullValue)) { | ||
| throw new Error("If set, indent must be a number"); | ||
@@ -732,2 +896,30 @@ } | ||
| return new StringValue(value.join(separator.value)); | ||
| } else if (filterName === "int" || filterName === "float") { | ||
| const [args, kwargs] = this.evaluateArguments(filter.args, environment); | ||
| const defaultValue = | ||
| args.at(0) ?? kwargs.get("default") ?? (filterName === "int" ? new IntegerValue(0) : new FloatValue(0.0)); | ||
| if (operand instanceof StringValue) { | ||
| const val = filterName === "int" ? parseInt(operand.value, 10) : parseFloat(operand.value); | ||
| return isNaN(val) ? defaultValue : filterName === "int" ? new IntegerValue(val) : new FloatValue(val); | ||
| } else if (operand instanceof IntegerValue || operand instanceof FloatValue) { | ||
| return operand; | ||
| } else if (operand instanceof BooleanValue) { | ||
| return filterName === "int" | ||
| ? new IntegerValue(operand.value ? 1 : 0) | ||
| : new FloatValue(operand.value ? 1.0 : 0.0); | ||
| } else { | ||
| throw new Error(`Cannot apply filter "${filterName}" to type: ${operand.type}`); | ||
| } | ||
| } else if (filterName === "default") { | ||
| const [args, kwargs] = this.evaluateArguments(filter.args, environment); | ||
| const defaultValue = args[0] ?? new StringValue(""); | ||
| const booleanValue = args[1] ?? kwargs.get("boolean") ?? new BooleanValue(false); | ||
| if (!(booleanValue instanceof BooleanValue)) { | ||
| throw new Error("`default` filter flag must be a boolean"); | ||
| } | ||
| if (operand instanceof UndefinedValue || (booleanValue.value && !operand.__bool__().value)) { | ||
| return defaultValue; | ||
| } | ||
| return operand; | ||
| } | ||
@@ -808,4 +1000,4 @@ | ||
| const width = args.at(0) ?? kwargs.get("width") ?? new NumericValue(4); | ||
| if (!(width instanceof NumericValue)) { | ||
| const width = args.at(0) ?? kwargs.get("width") ?? new IntegerValue(4); | ||
| if (!(width instanceof IntegerValue)) { | ||
| throw new Error("width must be a number"); | ||
@@ -823,2 +1015,10 @@ } | ||
| } | ||
| case "replace": { | ||
| const replaceFn = operand.builtins.get("replace"); | ||
| if (!(replaceFn instanceof FunctionValue)) { | ||
| throw new Error("replace filter not available"); | ||
| } | ||
| const [args, kwargs] = this.evaluateArguments(filter.args, environment); | ||
| return replaceFn.value([...args, new KeywordArgumentsValue(kwargs)], environment); | ||
| } | ||
| } | ||
@@ -830,6 +1030,14 @@ throw new Error(`Unknown StringValue filter: ${filterName}`); | ||
| } | ||
| throw new Error(`Unknown filter: ${node.filter.type}`); | ||
| throw new Error(`Unknown filter: ${filterNode.type}`); | ||
| } | ||
| /** | ||
| * Evaluates expressions following the filter operation type. | ||
| */ | ||
| private evaluateFilterExpression(node: FilterExpression, environment: Environment): AnyRuntimeValue { | ||
| const operand = this.evaluate(node.operand, environment); | ||
| return this.applyFilter(operand, node.filter, environment); | ||
| } | ||
| /** | ||
| * Evaluates expressions following the test operation type. | ||
@@ -853,2 +1061,13 @@ */ | ||
| /** | ||
| * Evaluates expressions following the select operation type. | ||
| */ | ||
| private evaluateSelectExpression(node: SelectExpression, environment: Environment): AnyRuntimeValue { | ||
| const predicate = this.evaluate(node.test, environment); | ||
| if (!predicate.__bool__().value) { | ||
| return new UndefinedValue(); | ||
| } | ||
| return this.evaluate(node.lhs, environment); | ||
| } | ||
| /** | ||
| * Evaluates expressions following the unary operation type. | ||
@@ -867,2 +1086,9 @@ */ | ||
| private evaluateTernaryExpression(node: Ternary, environment: Environment): AnyRuntimeValue { | ||
| const cond = this.evaluate(node.condition, environment); | ||
| return cond.__bool__().value | ||
| ? this.evaluate(node.trueExpr, environment) | ||
| : this.evaluate(node.falseExpr, environment); | ||
| } | ||
| private evalProgram(program: Program, environment: Environment): StringValue { | ||
@@ -878,6 +1104,6 @@ return this.evaluateBlock(program.body, environment); | ||
| for (const statement of statements) { | ||
| const lastEvaluated = this.evaluate(statement, environment); | ||
| const lastEvaluated: AnyRuntimeValue = this.evaluate(statement, environment); | ||
| if (lastEvaluated.type !== "NullValue" && lastEvaluated.type !== "UndefinedValue") { | ||
| result += lastEvaluated.value; | ||
| result += lastEvaluated.toString(); | ||
| } | ||
@@ -923,9 +1149,9 @@ } | ||
| // Validate arguments | ||
| if (!(start instanceof NumericValue || start instanceof UndefinedValue)) { | ||
| if (!(start instanceof IntegerValue || start instanceof UndefinedValue)) { | ||
| throw new Error("Slice start must be numeric or undefined"); | ||
| } | ||
| if (!(stop instanceof NumericValue || stop instanceof UndefinedValue)) { | ||
| if (!(stop instanceof IntegerValue || stop instanceof UndefinedValue)) { | ||
| throw new Error("Slice stop must be numeric or undefined"); | ||
| } | ||
| if (!(step instanceof NumericValue || step instanceof UndefinedValue)) { | ||
| if (!(step instanceof IntegerValue || step instanceof UndefinedValue)) { | ||
| throw new Error("Slice step must be numeric or undefined"); | ||
@@ -962,3 +1188,3 @@ } | ||
| } else if (object instanceof ArrayValue || object instanceof StringValue) { | ||
| if (property instanceof NumericValue) { | ||
| if (property instanceof IntegerValue) { | ||
| value = object.value.at(property.value); | ||
@@ -988,2 +1214,18 @@ if (object instanceof StringValue) { | ||
| environment.setVariable(variableName, rhs); | ||
| } else if (node.assignee.type === "TupleLiteral") { | ||
| const tuple = node.assignee as TupleLiteral; | ||
| if (!(rhs instanceof ArrayValue)) { | ||
| throw new Error(`Cannot unpack non-iterable type in set: ${rhs.type}`); | ||
| } | ||
| const arr = rhs.value; | ||
| if (arr.length !== tuple.value.length) { | ||
| throw new Error(`Too ${tuple.value.length > arr.length ? "few" : "many"} items to unpack in set`); | ||
| } | ||
| for (let i = 0; i < tuple.value.length; ++i) { | ||
| const elem = tuple.value[i]; | ||
| if (elem.type !== "Identifier") { | ||
| throw new Error(`Cannot unpack to non-identifier in set: ${elem.type}`); | ||
| } | ||
| environment.setVariable((elem as Identifier).value, arr[i]); | ||
| } | ||
| } else if (node.assignee.type === "MemberExpression") { | ||
@@ -1019,3 +1261,3 @@ const member = node.assignee as MemberExpression; | ||
| const select = node.iterable as SelectExpression; | ||
| iterable = this.evaluate(select.iterable, scope); | ||
| iterable = this.evaluate(select.lhs, scope); | ||
| test = select.test; | ||
@@ -1026,6 +1268,10 @@ } else { | ||
| if (!(iterable instanceof ArrayValue)) { | ||
| throw new Error(`Expected iterable type in for loop: got ${iterable.type}`); | ||
| if (!(iterable instanceof ArrayValue || iterable instanceof ObjectValue)) { | ||
| throw new Error(`Expected iterable or object type in for loop: got ${iterable.type}`); | ||
| } | ||
| if (iterable instanceof ObjectValue) { | ||
| iterable = iterable.keys(); | ||
| } | ||
| const items: Expression[] = []; | ||
@@ -1085,9 +1331,9 @@ const scopeUpdateFunctions: ((scope: Environment) => void)[] = []; | ||
| const loop = new Map([ | ||
| ["index", new NumericValue(i + 1)], | ||
| ["index0", new NumericValue(i)], | ||
| ["revindex", new NumericValue(items.length - i)], | ||
| ["revindex0", new NumericValue(items.length - i - 1)], | ||
| ["index", new IntegerValue(i + 1)], | ||
| ["index0", new IntegerValue(i)], | ||
| ["revindex", new IntegerValue(items.length - i)], | ||
| ["revindex0", new IntegerValue(items.length - i - 1)], | ||
| ["first", new BooleanValue(i === 0)], | ||
| ["last", new BooleanValue(i === items.length - 1)], | ||
| ["length", new NumericValue(items.length)], | ||
| ["length", new IntegerValue(items.length)], | ||
| ["previtem", i > 0 ? items[i - 1] : new UndefinedValue()], | ||
@@ -1175,4 +1421,35 @@ ["nextitem", i < items.length - 1 ? items[i + 1] : new UndefinedValue()], | ||
| private evaluateCallStatement(node: CallStatement, environment: Environment): AnyRuntimeValue { | ||
| const callerFn = new FunctionValue((callerArgs: AnyRuntimeValue[], callerEnv: Environment) => { | ||
| const callBlockEnv = new Environment(callerEnv); | ||
| if (node.callerArgs) { | ||
| for (let i = 0; i < node.callerArgs.length; ++i) { | ||
| const param = node.callerArgs[i]; | ||
| if (param.type !== "Identifier") { | ||
| throw new Error(`Caller parameter must be an identifier, got ${param.type}`); | ||
| } | ||
| callBlockEnv.setVariable((param as Identifier).value, callerArgs[i] ?? new UndefinedValue()); | ||
| } | ||
| } | ||
| return this.evaluateBlock(node.body, callBlockEnv); | ||
| }); | ||
| const [macroArgs, macroKwargs] = this.evaluateArguments(node.call.args, environment); | ||
| macroArgs.push(new KeywordArgumentsValue(macroKwargs)); | ||
| const fn = this.evaluate(node.call.callee, environment); | ||
| if (fn.type !== "FunctionValue") { | ||
| throw new Error(`Cannot call something that is not a function: got ${fn.type}`); | ||
| } | ||
| const newEnv = new Environment(environment); | ||
| newEnv.setVariable("caller", callerFn); | ||
| return (fn as FunctionValue).value(macroArgs, newEnv); | ||
| } | ||
| private evaluateFilterStatement(node: FilterStatement, environment: Environment): AnyRuntimeValue { | ||
| const rendered = this.evaluateBlock(node.body, environment); | ||
| return this.applyFilter(rendered, node.filter, environment); | ||
| } | ||
| evaluate(statement: Statement | undefined, environment: Environment): AnyRuntimeValue { | ||
| if (statement === undefined) return new UndefinedValue(); | ||
| if (!statement) return new UndefinedValue(); | ||
@@ -1193,2 +1470,4 @@ switch (statement.type) { | ||
| return this.evaluateMacro(statement as Macro, environment); | ||
| case "CallStatement": | ||
| return this.evaluateCallStatement(statement as CallStatement, environment); | ||
@@ -1201,10 +1480,8 @@ case "Break": | ||
| // Expressions | ||
| case "NumericLiteral": | ||
| return new NumericValue(Number((statement as NumericLiteral).value)); | ||
| case "IntegerLiteral": | ||
| return new IntegerValue((statement as IntegerLiteral).value); | ||
| case "FloatLiteral": | ||
| return new FloatValue((statement as FloatLiteral).value); | ||
| case "StringLiteral": | ||
| return new StringValue((statement as StringLiteral).value); | ||
| case "BooleanLiteral": | ||
| return new BooleanValue((statement as BooleanLiteral).value); | ||
| case "NullLiteral": | ||
| return new NullValue((statement as NullLiteral).value); | ||
| case "ArrayLiteral": | ||
@@ -1238,5 +1515,12 @@ return new ArrayValue((statement as ArrayLiteral).value.map((x) => this.evaluate(x, environment))); | ||
| return this.evaluateFilterExpression(statement as FilterExpression, environment); | ||
| case "FilterStatement": | ||
| return this.evaluateFilterStatement(statement as FilterStatement, environment); | ||
| case "TestExpression": | ||
| return this.evaluateTestExpression(statement as TestExpression, environment); | ||
| case "SelectExpression": | ||
| return this.evaluateSelectExpression(statement as SelectExpression, environment); | ||
| case "Ternary": | ||
| return this.evaluateTernaryExpression(statement as Ternary, environment); | ||
| case "Comment": | ||
| return new NullValue(); | ||
| default: | ||
@@ -1254,3 +1538,3 @@ throw new SyntaxError(`Unknown node type: ${statement.type}`); | ||
| case "number": | ||
| return new NumericValue(input); | ||
| return Number.isInteger(input) ? new IntegerValue(input) : new FloatValue(input); | ||
| case "string": | ||
@@ -1298,3 +1582,4 @@ return new StringValue(input); | ||
| return "null"; | ||
| case "NumericValue": | ||
| case "IntegerValue": | ||
| case "FloatValue": | ||
| case "StringValue": | ||
@@ -1301,0 +1586,0 @@ case "BooleanValue": |
+59
-0
@@ -55,1 +55,60 @@ /** | ||
| } | ||
| export function strftime_now(format: string): string { | ||
| return strftime(new Date(), format); | ||
| } | ||
| /** | ||
| * A minimalistic implementation of Python's strftime function. | ||
| */ | ||
| export function strftime(date: Date, format: string): string { | ||
| // Set locale to undefined to use the default locale | ||
| const monthFormatterLong = new Intl.DateTimeFormat(undefined, { month: "long" }); | ||
| const monthFormatterShort = new Intl.DateTimeFormat(undefined, { month: "short" }); | ||
| const pad2 = (n: number): string => (n < 10 ? "0" + n : n.toString()); | ||
| return format.replace(/%[YmdbBHM%]/g, (token) => { | ||
| switch (token) { | ||
| case "%Y": | ||
| return date.getFullYear().toString(); | ||
| case "%m": | ||
| return pad2(date.getMonth() + 1); | ||
| case "%d": | ||
| return pad2(date.getDate()); | ||
| case "%b": | ||
| return monthFormatterShort.format(date); | ||
| case "%B": | ||
| return monthFormatterLong.format(date); | ||
| case "%H": | ||
| return pad2(date.getHours()); | ||
| case "%M": | ||
| return pad2(date.getMinutes()); | ||
| case "%%": | ||
| return "%"; | ||
| default: | ||
| return token; | ||
| } | ||
| }); | ||
| } | ||
| function escapeRegExp(s: string): string { | ||
| return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); | ||
| } | ||
| /** | ||
| * Function that mimics Python's string.replace() function. | ||
| */ | ||
| export function replace(str: string, oldvalue: string, newvalue: string, count?: number | null): string { | ||
| if (count === 0) return str; | ||
| let remaining = count == null || count < 0 ? Infinity : count; | ||
| // NB: Use a Unicode-aware global regex so unpaired surrogates won't match | ||
| const pattern = oldvalue.length === 0 ? new RegExp("(?=)", "gu") : new RegExp(escapeRegExp(oldvalue), "gu"); | ||
| return str.replaceAll(pattern, (match) => { | ||
| if (remaining > 0) { | ||
| --remaining; | ||
| return newvalue; | ||
| } | ||
| return match; | ||
| }); | ||
| } |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
317209
17.71%8742
16.81%23
4.55%