You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

@huggingface/jinja

Package Overview
Dependencies
Maintainers
4
Versions
26
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@huggingface/jinja - npm Package Compare versions

Comparing version
0.4.1
to
0.5.0
+38
-26
dist/ast.d.ts

@@ -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":"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"}
{"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"}

@@ -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,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"}
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":"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"}
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}

@@ -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"}
{
"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",

@@ -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();
}
}
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:

@@ -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) {

@@ -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 @@ }

@@ -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)) {

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":

@@ -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