Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
machineuser
commited on
Commit
·
43f8c77
1
Parent(s):
29387b0
Sync widgets demo
Browse files- packages/jinja/package.json +1 -1
- packages/jinja/src/ast.ts +26 -0
- packages/jinja/src/index.ts +8 -7
- packages/jinja/src/lexer.ts +38 -2
- packages/jinja/src/parser.ts +59 -7
- packages/jinja/src/runtime.ts +156 -39
- packages/jinja/src/utils.ts +29 -0
- packages/jinja/test/e2e.test.js +156 -3
- packages/jinja/test/interpreter.test.js +118 -0
- packages/jinja/test/templates.test.js +339 -3
- packages/tasks/package.json +1 -1
packages/jinja/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
{
|
2 |
"name": "@huggingface/jinja",
|
3 |
"packageManager": "[email protected]",
|
4 |
-
"version": "0.
|
5 |
"description": "A minimalistic JavaScript implementation of the Jinja templating engine, specifically designed for parsing and rendering ML chat templates.",
|
6 |
"repository": "https://github.com/huggingface/huggingface.js.git",
|
7 |
"publishConfig": {
|
|
|
1 |
{
|
2 |
"name": "@huggingface/jinja",
|
3 |
"packageManager": "[email protected]",
|
4 |
+
"version": "0.2.0",
|
5 |
"description": "A minimalistic JavaScript implementation of the Jinja templating engine, specifically designed for parsing and rendering ML chat templates.",
|
6 |
"repository": "https://github.com/huggingface/huggingface.js.git",
|
7 |
"publishConfig": {
|
packages/jinja/src/ast.ts
CHANGED
@@ -165,6 +165,21 @@ export class FilterExpression extends Expression {
|
|
165 |
}
|
166 |
}
|
167 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
168 |
/**
|
169 |
* An operation with one side (operator on the left).
|
170 |
*/
|
@@ -201,3 +216,14 @@ export class SliceExpression extends Expression {
|
|
201 |
super();
|
202 |
}
|
203 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
165 |
}
|
166 |
}
|
167 |
|
168 |
+
/**
|
169 |
+
* An operation with two sides, separated by the "is" operator.
|
170 |
+
*/
|
171 |
+
export class TestExpression extends Expression {
|
172 |
+
override type = "TestExpression";
|
173 |
+
|
174 |
+
constructor(
|
175 |
+
public operand: Expression,
|
176 |
+
public negate: boolean,
|
177 |
+
public test: Identifier // TODO: Add support for non-identifier tests
|
178 |
+
) {
|
179 |
+
super();
|
180 |
+
}
|
181 |
+
}
|
182 |
+
|
183 |
/**
|
184 |
* An operation with one side (operator on the left).
|
185 |
*/
|
|
|
216 |
super();
|
217 |
}
|
218 |
}
|
219 |
+
|
220 |
+
export class KeywordArgumentExpression extends Expression {
|
221 |
+
override type = "KeywordArgumentExpression";
|
222 |
+
|
223 |
+
constructor(
|
224 |
+
public key: Identifier,
|
225 |
+
public value: Expression
|
226 |
+
) {
|
227 |
+
super();
|
228 |
+
}
|
229 |
+
}
|
packages/jinja/src/index.ts
CHANGED
@@ -10,11 +10,12 @@
|
|
10 |
*
|
11 |
* @module index
|
12 |
*/
|
13 |
-
import type { Program } from "./ast";
|
14 |
import { tokenize } from "./lexer";
|
15 |
import { parse } from "./parser";
|
16 |
-
import type { StringValue } from "./runtime";
|
17 |
import { Environment, Interpreter } from "./runtime";
|
|
|
|
|
|
|
18 |
|
19 |
export class Template {
|
20 |
parsed: Program;
|
@@ -23,11 +24,10 @@ export class Template {
|
|
23 |
* @param {string} template The template string
|
24 |
*/
|
25 |
constructor(template: string) {
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
const tokens = tokenize(template);
|
31 |
this.parsed = parse(tokens);
|
32 |
}
|
33 |
|
@@ -41,6 +41,7 @@ export class Template {
|
|
41 |
env.set("raise_exception", (args: string) => {
|
42 |
throw new Error(args);
|
43 |
});
|
|
|
44 |
|
45 |
// Add user-defined variables
|
46 |
for (const [key, value] of Object.entries(items)) {
|
|
|
10 |
*
|
11 |
* @module index
|
12 |
*/
|
|
|
13 |
import { tokenize } from "./lexer";
|
14 |
import { parse } from "./parser";
|
|
|
15 |
import { Environment, Interpreter } from "./runtime";
|
16 |
+
import type { Program } from "./ast";
|
17 |
+
import type { StringValue } from "./runtime";
|
18 |
+
import { range } from "./utils";
|
19 |
|
20 |
export class Template {
|
21 |
parsed: Program;
|
|
|
24 |
* @param {string} template The template string
|
25 |
*/
|
26 |
constructor(template: string) {
|
27 |
+
const tokens = tokenize(template, {
|
28 |
+
lstrip_blocks: true,
|
29 |
+
trim_blocks: true,
|
30 |
+
});
|
|
|
31 |
this.parsed = parse(tokens);
|
32 |
}
|
33 |
|
|
|
41 |
env.set("raise_exception", (args: string) => {
|
42 |
throw new Error(args);
|
43 |
});
|
44 |
+
env.set("range", range);
|
45 |
|
46 |
// Add user-defined variables
|
47 |
for (const [key, value] of Object.entries(items)) {
|
packages/jinja/src/lexer.ts
CHANGED
@@ -33,6 +33,7 @@ export const TOKEN_TYPES = Object.freeze({
|
|
33 |
If: "If",
|
34 |
For: "For",
|
35 |
In: "In",
|
|
|
36 |
NotIn: "NotIn",
|
37 |
Else: "Else",
|
38 |
EndIf: "EndIf",
|
@@ -52,6 +53,7 @@ const KEYWORDS = Object.freeze({
|
|
52 |
set: TOKEN_TYPES.Set,
|
53 |
for: TOKEN_TYPES.For,
|
54 |
in: TOKEN_TYPES.In,
|
|
|
55 |
if: TOKEN_TYPES.If,
|
56 |
else: TOKEN_TYPES.Else,
|
57 |
endif: TOKEN_TYPES.EndIf,
|
@@ -137,12 +139,46 @@ const ESCAPE_CHARACTERS = new Map([
|
|
137 |
["\\", "\\"], // Backslash
|
138 |
]);
|
139 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
140 |
/**
|
141 |
* Generate a list of tokens from a source string.
|
142 |
*/
|
143 |
-
export function tokenize(source: string): Token[] {
|
144 |
const tokens: Token[] = [];
|
145 |
-
const src: string = source;
|
146 |
|
147 |
let cursorPosition = 0;
|
148 |
|
|
|
33 |
If: "If",
|
34 |
For: "For",
|
35 |
In: "In",
|
36 |
+
Is: "Is",
|
37 |
NotIn: "NotIn",
|
38 |
Else: "Else",
|
39 |
EndIf: "EndIf",
|
|
|
53 |
set: TOKEN_TYPES.Set,
|
54 |
for: TOKEN_TYPES.For,
|
55 |
in: TOKEN_TYPES.In,
|
56 |
+
is: TOKEN_TYPES.Is,
|
57 |
if: TOKEN_TYPES.If,
|
58 |
else: TOKEN_TYPES.Else,
|
59 |
endif: TOKEN_TYPES.EndIf,
|
|
|
139 |
["\\", "\\"], // Backslash
|
140 |
]);
|
141 |
|
142 |
+
export interface PreprocessOptions {
|
143 |
+
trim_blocks?: boolean;
|
144 |
+
lstrip_blocks?: boolean;
|
145 |
+
}
|
146 |
+
|
147 |
+
function preprocess(template: string, options: PreprocessOptions = {}): string {
|
148 |
+
// According to https://jinja.palletsprojects.com/en/3.0.x/templates/#whitespace-control
|
149 |
+
|
150 |
+
// In the default configuration:
|
151 |
+
// - a single trailing newline is stripped if present
|
152 |
+
// - other whitespace (spaces, tabs, newlines etc.) is returned unchanged
|
153 |
+
if (template.endsWith("\n")) {
|
154 |
+
template = template.slice(0, -1);
|
155 |
+
}
|
156 |
+
|
157 |
+
if (options.trim_blocks) {
|
158 |
+
// If an application configures Jinja to trim_blocks, the first newline after
|
159 |
+
// a template tag is removed automatically (like in PHP).
|
160 |
+
template = template.replace(/%}\n/g, "%}");
|
161 |
+
}
|
162 |
+
if (options.lstrip_blocks) {
|
163 |
+
// The lstrip_blocks option can also be set to strip tabs and spaces from the
|
164 |
+
// beginning of a line to the start of a block. (Nothing will be stripped if
|
165 |
+
// there are other characters before the start of the block.)
|
166 |
+
template = template.replace(/^[ \t]*{%/gm, "{%");
|
167 |
+
}
|
168 |
+
|
169 |
+
return template
|
170 |
+
.replace(/-%}\s*/g, "%}")
|
171 |
+
.replace(/\s*{%-/g, "{%")
|
172 |
+
.replace(/-}}\s*/g, "}}")
|
173 |
+
.replace(/\s*{{-/g, "{{");
|
174 |
+
}
|
175 |
+
|
176 |
/**
|
177 |
* Generate a list of tokens from a source string.
|
178 |
*/
|
179 |
+
export function tokenize(source: string, options: PreprocessOptions = {}): Token[] {
|
180 |
const tokens: Token[] = [];
|
181 |
+
const src: string = preprocess(source, options);
|
182 |
|
183 |
let cursorPosition = 0;
|
184 |
|
packages/jinja/src/parser.ts
CHANGED
@@ -14,8 +14,10 @@ import {
|
|
14 |
BooleanLiteral,
|
15 |
BinaryExpression,
|
16 |
FilterExpression,
|
|
|
17 |
UnaryExpression,
|
18 |
SliceExpression,
|
|
|
19 |
} from "./ast";
|
20 |
|
21 |
/**
|
@@ -26,6 +28,12 @@ export function parse(tokens: Token[]): Program {
|
|
26 |
const program = new Program([]);
|
27 |
let current = 0;
|
28 |
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
function expect(type: string, error: string): Token {
|
30 |
const prev = tokens[current++];
|
31 |
if (!prev || prev.type !== type) {
|
@@ -191,6 +199,7 @@ export function parse(tokens: Token[]): Program {
|
|
191 |
// Choose parse function with lowest precedence
|
192 |
return parseLogicalOrExpression();
|
193 |
}
|
|
|
194 |
function parseLogicalOrExpression(): Statement {
|
195 |
let left = parseLogicalAndExpression();
|
196 |
while (is(TOKEN_TYPES.Or)) {
|
@@ -278,21 +287,36 @@ export function parse(tokens: Token[]): Program {
|
|
278 |
// add (x + 5, foo())
|
279 |
expect(TOKEN_TYPES.OpenParen, "Expected opening parenthesis for arguments list");
|
280 |
|
281 |
-
const args =
|
282 |
|
283 |
expect(TOKEN_TYPES.CloseParen, "Expected closing parenthesis for arguments list");
|
284 |
return args;
|
285 |
}
|
286 |
function parseArgumentsList(): Statement[] {
|
287 |
// comma-separated arguments list
|
288 |
-
const args = [parseExpression()]; // Update when we allow assignment expressions
|
289 |
|
290 |
-
|
291 |
-
|
292 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
293 |
}
|
294 |
return args;
|
295 |
}
|
|
|
296 |
function parseMemberExpressionArgumentsList(): Statement {
|
297 |
// NOTE: This also handles slice expressions colon-separated arguments list
|
298 |
// e.g., ['test'], [0], [:2], [1:], [1:2], [1:2:3]
|
@@ -354,17 +378,45 @@ export function parse(tokens: Token[]): Program {
|
|
354 |
}
|
355 |
|
356 |
function parseMultiplicativeExpression(): Statement {
|
357 |
-
let left =
|
|
|
|
|
|
|
358 |
|
359 |
while (is(TOKEN_TYPES.MultiplicativeBinaryOperator)) {
|
360 |
const operator = tokens[current];
|
361 |
++current;
|
362 |
-
const right =
|
363 |
left = new BinaryExpression(operator, left, right);
|
364 |
}
|
365 |
return left;
|
366 |
}
|
367 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
368 |
function parseFilterExpression(): Statement {
|
369 |
let operand = parseCallMemberExpression();
|
370 |
|
|
|
14 |
BooleanLiteral,
|
15 |
BinaryExpression,
|
16 |
FilterExpression,
|
17 |
+
TestExpression,
|
18 |
UnaryExpression,
|
19 |
SliceExpression,
|
20 |
+
KeywordArgumentExpression,
|
21 |
} from "./ast";
|
22 |
|
23 |
/**
|
|
|
28 |
const program = new Program([]);
|
29 |
let current = 0;
|
30 |
|
31 |
+
/**
|
32 |
+
* Consume the next token if it matches the expected type, otherwise throw an error.
|
33 |
+
* @param type The expected token type
|
34 |
+
* @param error The error message to throw if the token does not match the expected type
|
35 |
+
* @returns The consumed token
|
36 |
+
*/
|
37 |
function expect(type: string, error: string): Token {
|
38 |
const prev = tokens[current++];
|
39 |
if (!prev || prev.type !== type) {
|
|
|
199 |
// Choose parse function with lowest precedence
|
200 |
return parseLogicalOrExpression();
|
201 |
}
|
202 |
+
|
203 |
function parseLogicalOrExpression(): Statement {
|
204 |
let left = parseLogicalAndExpression();
|
205 |
while (is(TOKEN_TYPES.Or)) {
|
|
|
287 |
// add (x + 5, foo())
|
288 |
expect(TOKEN_TYPES.OpenParen, "Expected opening parenthesis for arguments list");
|
289 |
|
290 |
+
const args = parseArgumentsList();
|
291 |
|
292 |
expect(TOKEN_TYPES.CloseParen, "Expected closing parenthesis for arguments list");
|
293 |
return args;
|
294 |
}
|
295 |
function parseArgumentsList(): Statement[] {
|
296 |
// comma-separated arguments list
|
|
|
297 |
|
298 |
+
const args = [];
|
299 |
+
while (!is(TOKEN_TYPES.CloseParen)) {
|
300 |
+
let argument = parseExpression();
|
301 |
+
|
302 |
+
if (is(TOKEN_TYPES.Equals)) {
|
303 |
+
// keyword argument
|
304 |
+
// e.g., func(x = 5, y = a or b)
|
305 |
+
++current; // consume equals
|
306 |
+
if (!(argument instanceof Identifier)) {
|
307 |
+
throw new SyntaxError(`Expected identifier for keyword argument`);
|
308 |
+
}
|
309 |
+
const value = parseExpression();
|
310 |
+
argument = new KeywordArgumentExpression(argument, value);
|
311 |
+
}
|
312 |
+
args.push(argument);
|
313 |
+
if (is(TOKEN_TYPES.Comma)) {
|
314 |
+
++current; // consume comma
|
315 |
+
}
|
316 |
}
|
317 |
return args;
|
318 |
}
|
319 |
+
|
320 |
function parseMemberExpressionArgumentsList(): Statement {
|
321 |
// NOTE: This also handles slice expressions colon-separated arguments list
|
322 |
// e.g., ['test'], [0], [:2], [1:], [1:2], [1:2:3]
|
|
|
378 |
}
|
379 |
|
380 |
function parseMultiplicativeExpression(): Statement {
|
381 |
+
let left = parseTestExpression();
|
382 |
+
|
383 |
+
// Multiplicative operators have higher precedence than test expressions
|
384 |
+
// e.g., (4 * 4 is divisibleby(2)) evaluates as (4 * (4 is divisibleby(2)))
|
385 |
|
386 |
while (is(TOKEN_TYPES.MultiplicativeBinaryOperator)) {
|
387 |
const operator = tokens[current];
|
388 |
++current;
|
389 |
+
const right = parseTestExpression();
|
390 |
left = new BinaryExpression(operator, left, right);
|
391 |
}
|
392 |
return left;
|
393 |
}
|
394 |
|
395 |
+
function parseTestExpression(): Statement {
|
396 |
+
let operand = parseFilterExpression();
|
397 |
+
|
398 |
+
while (is(TOKEN_TYPES.Is)) {
|
399 |
+
// Support chaining tests
|
400 |
+
++current; // consume is
|
401 |
+
const negate = is(TOKEN_TYPES.Not);
|
402 |
+
if (negate) {
|
403 |
+
++current; // consume not
|
404 |
+
}
|
405 |
+
|
406 |
+
let filter = parsePrimaryExpression();
|
407 |
+
if (filter instanceof BooleanLiteral) {
|
408 |
+
// Special case: treat boolean literals as identifiers
|
409 |
+
filter = new Identifier(filter.value.toString());
|
410 |
+
}
|
411 |
+
if (!(filter instanceof Identifier)) {
|
412 |
+
throw new SyntaxError(`Expected identifier for the test`);
|
413 |
+
}
|
414 |
+
// TODO: Add support for non-identifier tests
|
415 |
+
operand = new TestExpression(operand, negate, filter);
|
416 |
+
}
|
417 |
+
return operand;
|
418 |
+
}
|
419 |
+
|
420 |
function parseFilterExpression(): Statement {
|
421 |
let operand = parseCallMemberExpression();
|
422 |
|
packages/jinja/src/runtime.ts
CHANGED
@@ -12,10 +12,12 @@ import type {
|
|
12 |
Identifier,
|
13 |
BinaryExpression,
|
14 |
FilterExpression,
|
|
|
15 |
UnaryExpression,
|
16 |
SliceExpression,
|
|
|
17 |
} from "./ast";
|
18 |
-
import { slice } from "./utils";
|
19 |
|
20 |
export type AnyRuntimeValue =
|
21 |
| NumericValue
|
@@ -89,6 +91,12 @@ export class StringValue extends RuntimeValue<string> {
|
|
89 |
return new StringValue(this.value.trim());
|
90 |
}),
|
91 |
],
|
|
|
|
|
|
|
|
|
|
|
|
|
92 |
["length", new NumericValue(this.value.length)],
|
93 |
]);
|
94 |
}
|
@@ -167,7 +175,20 @@ export class Environment {
|
|
167 |
/**
|
168 |
* The variables declared in this environment.
|
169 |
*/
|
170 |
-
variables: Map<string, AnyRuntimeValue> = new Map(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
171 |
|
172 |
constructor(public parent?: Environment) {}
|
173 |
|
@@ -245,25 +266,27 @@ export class Interpreter {
|
|
245 |
}
|
246 |
|
247 |
/**
|
248 |
-
*
|
249 |
*/
|
250 |
private evaluateBinaryExpression(node: BinaryExpression, environment: Environment): AnyRuntimeValue {
|
251 |
const left = this.evaluate(node.left, environment);
|
252 |
-
const right = this.evaluate(node.right, environment);
|
253 |
|
254 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
255 |
switch (node.operator.value) {
|
256 |
-
// Equality operators
|
257 |
case "==":
|
258 |
return new BooleanValue(left.value == right.value);
|
259 |
case "!=":
|
260 |
return new BooleanValue(left.value != right.value);
|
261 |
-
|
262 |
-
// Logical operators
|
263 |
-
case "and":
|
264 |
-
return left.__bool__().value ? right : left;
|
265 |
-
case "or":
|
266 |
-
return left.__bool__().value ? left : right;
|
267 |
}
|
268 |
|
269 |
if (left instanceof UndefinedValue || right instanceof UndefinedValue) {
|
@@ -326,7 +349,7 @@ export class Interpreter {
|
|
326 |
}
|
327 |
|
328 |
/**
|
329 |
-
*
|
330 |
*/
|
331 |
private evaluateFilterExpression(node: FilterExpression, environment: Environment): AnyRuntimeValue {
|
332 |
const operand = this.evaluate(node.operand, environment);
|
@@ -380,7 +403,7 @@ export class Interpreter {
|
|
380 |
case "lower":
|
381 |
return new StringValue(operand.value.toLowerCase());
|
382 |
case "title":
|
383 |
-
return new StringValue(operand.value
|
384 |
case "capitalize":
|
385 |
return new StringValue(operand.value.charAt(0).toUpperCase() + operand.value.slice(1));
|
386 |
case "trim":
|
@@ -401,7 +424,77 @@ export class Interpreter {
|
|
401 |
}
|
402 |
|
403 |
/**
|
404 |
-
*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
405 |
*/
|
406 |
private evaluateUnaryExpression(node: UnaryExpression, environment: Environment): AnyRuntimeValue {
|
407 |
const argument = this.evaluate(node.argument, environment);
|
@@ -414,7 +507,7 @@ export class Interpreter {
|
|
414 |
}
|
415 |
}
|
416 |
|
417 |
-
private evalProgram(program: Program, environment: Environment):
|
418 |
return this.evaluateBlock(program.body, environment);
|
419 |
}
|
420 |
|
@@ -431,9 +524,6 @@ export class Interpreter {
|
|
431 |
}
|
432 |
}
|
433 |
|
434 |
-
// Since `trim_blocks` is enabled, we remove the first newline after the template tag
|
435 |
-
result = result.replace(/^\n/, "");
|
436 |
-
|
437 |
return new StringValue(result);
|
438 |
}
|
439 |
|
@@ -442,7 +532,22 @@ export class Interpreter {
|
|
442 |
}
|
443 |
|
444 |
private evaluateCallExpression(expr: CallExpression, environment: Environment): AnyRuntimeValue {
|
445 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
446 |
const fn = this.evaluate(expr.callee, environment);
|
447 |
if (fn.type !== "FunctionValue") {
|
448 |
throw new Error(`Cannot call something that is not a function: got ${fn.type}`);
|
@@ -525,12 +630,25 @@ export class Interpreter {
|
|
525 |
}
|
526 |
|
527 |
private evaluateSet(node: SetStatement, environment: Environment): NullValue {
|
528 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
529 |
throw new Error(`Invalid LHS inside assignment expression: ${JSON.stringify(node.assignee)}`);
|
530 |
}
|
531 |
|
532 |
-
const variableName = (node.assignee as Identifier).value;
|
533 |
-
environment.setVariable(variableName, this.evaluate(node.value, environment));
|
534 |
return new NullValue();
|
535 |
}
|
536 |
|
@@ -553,22 +671,19 @@ export class Interpreter {
|
|
553 |
for (let i = 0; i < iterable.value.length; ++i) {
|
554 |
// Update the loop variable
|
555 |
// TODO: Only create object once, then update value?
|
556 |
-
|
557 |
-
"
|
558 |
-
new
|
559 |
-
|
560 |
-
|
561 |
-
|
562 |
-
|
563 |
-
|
564 |
-
|
565 |
-
|
566 |
-
|
567 |
-
|
568 |
-
|
569 |
-
)
|
570 |
-
)
|
571 |
-
);
|
572 |
|
573 |
// For this iteration, set the loop variable to the current element
|
574 |
scope.setVariable(node.loopvar.value, iterable.value[i]);
|
@@ -617,6 +732,8 @@ export class Interpreter {
|
|
617 |
return this.evaluateBinaryExpression(statement as BinaryExpression, environment);
|
618 |
case "FilterExpression":
|
619 |
return this.evaluateFilterExpression(statement as FilterExpression, environment);
|
|
|
|
|
620 |
|
621 |
default:
|
622 |
throw new SyntaxError(`Unknown node type: ${statement.type}`);
|
|
|
12 |
Identifier,
|
13 |
BinaryExpression,
|
14 |
FilterExpression,
|
15 |
+
TestExpression,
|
16 |
UnaryExpression,
|
17 |
SliceExpression,
|
18 |
+
KeywordArgumentExpression,
|
19 |
} from "./ast";
|
20 |
+
import { slice, titleCase } from "./utils";
|
21 |
|
22 |
export type AnyRuntimeValue =
|
23 |
| NumericValue
|
|
|
91 |
return new StringValue(this.value.trim());
|
92 |
}),
|
93 |
],
|
94 |
+
[
|
95 |
+
"title",
|
96 |
+
new FunctionValue(() => {
|
97 |
+
return new StringValue(titleCase(this.value));
|
98 |
+
}),
|
99 |
+
],
|
100 |
["length", new NumericValue(this.value.length)],
|
101 |
]);
|
102 |
}
|
|
|
175 |
/**
|
176 |
* The variables declared in this environment.
|
177 |
*/
|
178 |
+
variables: Map<string, AnyRuntimeValue> = new Map([
|
179 |
+
[
|
180 |
+
"namespace",
|
181 |
+
new FunctionValue((args) => {
|
182 |
+
if (args.length === 0) {
|
183 |
+
return new ObjectValue(new Map());
|
184 |
+
}
|
185 |
+
if (args.length !== 1 || !(args[0] instanceof ObjectValue)) {
|
186 |
+
throw new Error("`namespace` expects either zero arguments or a single object argument");
|
187 |
+
}
|
188 |
+
return args[0];
|
189 |
+
}),
|
190 |
+
],
|
191 |
+
]);
|
192 |
|
193 |
constructor(public parent?: Environment) {}
|
194 |
|
|
|
266 |
}
|
267 |
|
268 |
/**
|
269 |
+
* Evaluates expressions following the binary operation type.
|
270 |
*/
|
271 |
private evaluateBinaryExpression(node: BinaryExpression, environment: Environment): AnyRuntimeValue {
|
272 |
const left = this.evaluate(node.left, environment);
|
|
|
273 |
|
274 |
+
// Logical operators
|
275 |
+
// NOTE: Short-circuiting is handled by the `evaluate` function
|
276 |
+
switch (node.operator.value) {
|
277 |
+
case "and":
|
278 |
+
return left.__bool__().value ? this.evaluate(node.right, environment) : left;
|
279 |
+
case "or":
|
280 |
+
return left.__bool__().value ? left : this.evaluate(node.right, environment);
|
281 |
+
}
|
282 |
+
|
283 |
+
// Equality operators
|
284 |
+
const right = this.evaluate(node.right, environment);
|
285 |
switch (node.operator.value) {
|
|
|
286 |
case "==":
|
287 |
return new BooleanValue(left.value == right.value);
|
288 |
case "!=":
|
289 |
return new BooleanValue(left.value != right.value);
|
|
|
|
|
|
|
|
|
|
|
|
|
290 |
}
|
291 |
|
292 |
if (left instanceof UndefinedValue || right instanceof UndefinedValue) {
|
|
|
349 |
}
|
350 |
|
351 |
/**
|
352 |
+
* Evaluates expressions following the filter operation type.
|
353 |
*/
|
354 |
private evaluateFilterExpression(node: FilterExpression, environment: Environment): AnyRuntimeValue {
|
355 |
const operand = this.evaluate(node.operand, environment);
|
|
|
403 |
case "lower":
|
404 |
return new StringValue(operand.value.toLowerCase());
|
405 |
case "title":
|
406 |
+
return new StringValue(titleCase(operand.value));
|
407 |
case "capitalize":
|
408 |
return new StringValue(operand.value.charAt(0).toUpperCase() + operand.value.slice(1));
|
409 |
case "trim":
|
|
|
424 |
}
|
425 |
|
426 |
/**
|
427 |
+
* Evaluates expressions following the test operation type.
|
428 |
+
*/
|
429 |
+
private evaluateTestExpression(node: TestExpression, environment: Environment): BooleanValue {
|
430 |
+
// For now, we only support the built-in tests
|
431 |
+
// https://jinja.palletsprojects.com/en/3.0.x/templates/#list-of-builtin-tests
|
432 |
+
//
|
433 |
+
// TODO: Add support for non-identifier tests. e.g., divisibleby(number)
|
434 |
+
|
435 |
+
const result: boolean = (() => {
|
436 |
+
try {
|
437 |
+
const operand = this.evaluate(node.operand, environment);
|
438 |
+
|
439 |
+
switch (node.test.value) {
|
440 |
+
case "boolean":
|
441 |
+
return operand.type === "BooleanValue";
|
442 |
+
case "callable":
|
443 |
+
return operand instanceof FunctionValue;
|
444 |
+
case "odd":
|
445 |
+
if (operand.type !== "NumericValue") {
|
446 |
+
throw new Error(`Cannot apply test "odd" to type: ${operand.type}`);
|
447 |
+
}
|
448 |
+
return (operand as NumericValue).value % 2 !== 0;
|
449 |
+
case "even":
|
450 |
+
if (operand.type !== "NumericValue") {
|
451 |
+
throw new Error(`Cannot apply test "even" to type: ${operand.type}`);
|
452 |
+
}
|
453 |
+
return (operand as NumericValue).value % 2 === 0;
|
454 |
+
case "false":
|
455 |
+
return operand.type === "BooleanValue" && !(operand as BooleanValue).value;
|
456 |
+
case "true":
|
457 |
+
return operand.type === "BooleanValue" && (operand as BooleanValue).value;
|
458 |
+
case "number":
|
459 |
+
return operand.type === "NumericValue";
|
460 |
+
case "integer":
|
461 |
+
return operand.type === "NumericValue" && Number.isInteger((operand as NumericValue).value);
|
462 |
+
case "iterable":
|
463 |
+
return operand instanceof ArrayValue || operand instanceof StringValue;
|
464 |
+
case "lower": {
|
465 |
+
const str = (operand as StringValue).value;
|
466 |
+
return operand.type === "StringValue" && str === str.toLowerCase();
|
467 |
+
}
|
468 |
+
case "upper": {
|
469 |
+
const str = (operand as StringValue).value;
|
470 |
+
return operand.type === "StringValue" && str === str.toUpperCase();
|
471 |
+
}
|
472 |
+
case "none":
|
473 |
+
return operand.type === "NullValue";
|
474 |
+
case "defined":
|
475 |
+
return true;
|
476 |
+
case "undefined":
|
477 |
+
return false;
|
478 |
+
}
|
479 |
+
throw new Error(`Unknown test: ${node.test.value}`);
|
480 |
+
} catch (e) {
|
481 |
+
if (node.operand.type === "Identifier") {
|
482 |
+
// Special cases where we want to check if a variable is defined
|
483 |
+
if (node.test.value === "defined") {
|
484 |
+
return false;
|
485 |
+
} else if (node.test.value === "undefined") {
|
486 |
+
return true;
|
487 |
+
}
|
488 |
+
}
|
489 |
+
throw e;
|
490 |
+
}
|
491 |
+
})();
|
492 |
+
|
493 |
+
return new BooleanValue(node.negate ? !result : result);
|
494 |
+
}
|
495 |
+
|
496 |
+
/**
|
497 |
+
* Evaluates expressions following the unary operation type.
|
498 |
*/
|
499 |
private evaluateUnaryExpression(node: UnaryExpression, environment: Environment): AnyRuntimeValue {
|
500 |
const argument = this.evaluate(node.argument, environment);
|
|
|
507 |
}
|
508 |
}
|
509 |
|
510 |
+
private evalProgram(program: Program, environment: Environment): StringValue {
|
511 |
return this.evaluateBlock(program.body, environment);
|
512 |
}
|
513 |
|
|
|
524 |
}
|
525 |
}
|
526 |
|
|
|
|
|
|
|
527 |
return new StringValue(result);
|
528 |
}
|
529 |
|
|
|
532 |
}
|
533 |
|
534 |
private evaluateCallExpression(expr: CallExpression, environment: Environment): AnyRuntimeValue {
|
535 |
+
// Accumulate all keyword arguments into a single object, which will be
|
536 |
+
// used as the final argument in the call function.
|
537 |
+
const args: AnyRuntimeValue[] = [];
|
538 |
+
const kwargs = new Map();
|
539 |
+
for (const argument of expr.args) {
|
540 |
+
if (argument.type === "KeywordArgumentExpression") {
|
541 |
+
const kwarg = argument as KeywordArgumentExpression;
|
542 |
+
kwargs.set(kwarg.key.value, this.evaluate(kwarg.value, environment));
|
543 |
+
} else {
|
544 |
+
args.push(this.evaluate(argument, environment));
|
545 |
+
}
|
546 |
+
}
|
547 |
+
if (kwargs.size > 0) {
|
548 |
+
args.push(new ObjectValue(kwargs));
|
549 |
+
}
|
550 |
+
|
551 |
const fn = this.evaluate(expr.callee, environment);
|
552 |
if (fn.type !== "FunctionValue") {
|
553 |
throw new Error(`Cannot call something that is not a function: got ${fn.type}`);
|
|
|
630 |
}
|
631 |
|
632 |
private evaluateSet(node: SetStatement, environment: Environment): NullValue {
|
633 |
+
const rhs = this.evaluate(node.value, environment);
|
634 |
+
if (node.assignee.type === "Identifier") {
|
635 |
+
const variableName = (node.assignee as Identifier).value;
|
636 |
+
environment.setVariable(variableName, rhs);
|
637 |
+
} else if (node.assignee.type === "MemberExpression") {
|
638 |
+
const member = node.assignee as MemberExpression;
|
639 |
+
|
640 |
+
const object = this.evaluate(member.object, environment);
|
641 |
+
if (!(object instanceof ObjectValue)) {
|
642 |
+
throw new Error("Cannot assign to member of non-object");
|
643 |
+
}
|
644 |
+
if (member.property.type !== "Identifier") {
|
645 |
+
throw new Error("Cannot assign to member with non-identifier property");
|
646 |
+
}
|
647 |
+
object.value.set((member.property as Identifier).value, rhs);
|
648 |
+
} else {
|
649 |
throw new Error(`Invalid LHS inside assignment expression: ${JSON.stringify(node.assignee)}`);
|
650 |
}
|
651 |
|
|
|
|
|
652 |
return new NullValue();
|
653 |
}
|
654 |
|
|
|
671 |
for (let i = 0; i < iterable.value.length; ++i) {
|
672 |
// Update the loop variable
|
673 |
// TODO: Only create object once, then update value?
|
674 |
+
const loop = new Map([
|
675 |
+
["index", new NumericValue(i + 1)],
|
676 |
+
["index0", new NumericValue(i)],
|
677 |
+
["revindex", new NumericValue(iterable.value.length - i)],
|
678 |
+
["revindex0", new NumericValue(iterable.value.length - i - 1)],
|
679 |
+
["first", new BooleanValue(i === 0)],
|
680 |
+
["last", new BooleanValue(i === iterable.value.length - 1)],
|
681 |
+
["length", new NumericValue(iterable.value.length)],
|
682 |
+
["previtem", i > 0 ? iterable.value[i - 1] : new UndefinedValue()],
|
683 |
+
["nextitem", i < iterable.value.length - 1 ? iterable.value[i + 1] : new UndefinedValue()],
|
684 |
+
] as [string, AnyRuntimeValue][]);
|
685 |
+
|
686 |
+
scope.setVariable("loop", new ObjectValue(loop));
|
|
|
|
|
|
|
687 |
|
688 |
// For this iteration, set the loop variable to the current element
|
689 |
scope.setVariable(node.loopvar.value, iterable.value[i]);
|
|
|
732 |
return this.evaluateBinaryExpression(statement as BinaryExpression, environment);
|
733 |
case "FilterExpression":
|
734 |
return this.evaluateFilterExpression(statement as FilterExpression, environment);
|
735 |
+
case "TestExpression":
|
736 |
+
return this.evaluateTestExpression(statement as TestExpression, environment);
|
737 |
|
738 |
default:
|
739 |
throw new SyntaxError(`Unknown node type: ${statement.type}`);
|
packages/jinja/src/utils.ts
CHANGED
@@ -1,3 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
/**
|
2 |
* Function that mimics Python's array slicing.
|
3 |
* @param array The array to slice.
|
@@ -23,3 +43,12 @@ export function slice<T>(array: T[], start?: number, stop?: number, step = 1): T
|
|
23 |
}
|
24 |
return result;
|
25 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/**
|
2 |
+
* Function that mimics Python's range() function.
|
3 |
+
* @param start The start value of the range.
|
4 |
+
* @param stop The stop value of the range. If not provided, start will be 0 and stop will be the provided start value.
|
5 |
+
* @param step The step value of the range. Defaults to 1.
|
6 |
+
* @returns The range of numbers.
|
7 |
+
*/
|
8 |
+
export function range(start: number, stop?: number, step = 1): number[] {
|
9 |
+
if (stop === undefined) {
|
10 |
+
stop = start;
|
11 |
+
start = 0;
|
12 |
+
}
|
13 |
+
|
14 |
+
const result: number[] = [];
|
15 |
+
for (let i = start; i < stop; i += step) {
|
16 |
+
result.push(i);
|
17 |
+
}
|
18 |
+
return result;
|
19 |
+
}
|
20 |
+
|
21 |
/**
|
22 |
* Function that mimics Python's array slicing.
|
23 |
* @param array The array to slice.
|
|
|
43 |
}
|
44 |
return result;
|
45 |
}
|
46 |
+
|
47 |
+
/**
|
48 |
+
* Function that mimics Python's string.title() function.
|
49 |
+
* @param value The string to title case.
|
50 |
+
* @returns The title cased string.
|
51 |
+
*/
|
52 |
+
export function titleCase(value: string): string {
|
53 |
+
return value.replace(/\b\w/g, (c) => c.toUpperCase());
|
54 |
+
}
|
packages/jinja/test/e2e.test.js
CHANGED
@@ -10,7 +10,7 @@ const EXAMPLE_CHAT = [
|
|
10 |
{ role: "user", content: "I'd like to show off how chat templating works!" },
|
11 |
];
|
12 |
|
13 |
-
const
|
14 |
{
|
15 |
role: "system",
|
16 |
content: "You are a friendly chatbot who always responds in the style of a pirate",
|
@@ -80,7 +80,7 @@ const TEST_DEFAULT_TEMPLATES = Object.freeze({
|
|
80 |
// hf-internal-testing/llama-tokenizer
|
81 |
chat_template: `{% if messages[0]['role'] == 'system' %}{% set loop_messages = messages[1:] %}{% set system_message = messages[0]['content'] %}{% elif USE_DEFAULT_PROMPT == true and not '<<SYS>>' in messages[0]['content'] %}{% set loop_messages = messages %}{% set system_message = 'DEFAULT_SYSTEM_MESSAGE' %}{% else %}{% set loop_messages = messages %}{% set system_message = false %}{% endif %}{% for message in loop_messages %}{% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}{{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}{% endif %}{% if loop.index0 == 0 and system_message != false %}{% set content = '<<SYS>>\\n' + system_message + '\\n<</SYS>>\\n\\n' + message['content'] %}{% else %}{% set content = message['content'] %}{% endif %}{% if message['role'] == 'user' %}{{ bos_token + '[INST] ' + content.strip() + ' [/INST]' }}{% elif message['role'] == 'system' %}{{ '<<SYS>>\\n' + content.strip() + '\\n<</SYS>>\\n\\n' }}{% elif message['role'] == 'assistant' %}{{ ' ' + content.strip() + ' ' + eos_token }}{% endif %}{% endfor %}`,
|
82 |
data: {
|
83 |
-
messages:
|
84 |
bos_token: "<s>",
|
85 |
eos_token: "</s>",
|
86 |
USE_DEFAULT_PROMPT: true,
|
@@ -106,7 +106,7 @@ const TEST_CUSTOM_TEMPLATES = Object.freeze({
|
|
106 |
"HuggingFaceH4/zephyr-7b-beta (add_generation_prompt=false)": {
|
107 |
chat_template: `{% for message in messages %}\n{% if message['role'] == 'user' %}\n{{ '<|user|>\n' + message['content'] + eos_token }}\n{% elif message['role'] == 'system' %}\n{{ '<|system|>\n' + message['content'] + eos_token }}\n{% elif message['role'] == 'assistant' %}\n{{ '<|assistant|>\n' + message['content'] + eos_token }}\n{% endif %}\n{% if loop.last and add_generation_prompt %}\n{{ '<|assistant|>' }}\n{% endif %}\n{% endfor %}`,
|
108 |
data: {
|
109 |
-
messages:
|
110 |
eos_token: "</s>",
|
111 |
add_generation_prompt: false,
|
112 |
},
|
@@ -124,6 +124,16 @@ const TEST_CUSTOM_TEMPLATES = Object.freeze({
|
|
124 |
},
|
125 |
target: `<|system|>\nYou are a friendly chatbot who always responds in the style of a pirate</s>\n<|user|>\nHow many helicopters can a human eat in one sitting?</s>\n<|assistant|>\n`,
|
126 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
127 |
"mistralai/Mistral-7B-Instruct-v0.1": {
|
128 |
chat_template: `{{ bos_token }}{% for message in messages %}{% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}{{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}{% endif %}{% if message['role'] == 'user' %}{{ '[INST] ' + message['content'] + ' [/INST]' }}{% elif message['role'] == 'assistant' %}{{ message['content'] + eos_token + ' ' }}{% else %}{{ raise_exception('Only user and assistant roles are supported!') }}{% endif %}{% endfor %}`,
|
129 |
data: {
|
@@ -133,6 +143,149 @@ const TEST_CUSTOM_TEMPLATES = Object.freeze({
|
|
133 |
},
|
134 |
target: `<s>[INST] Hello, how are you? [/INST]I'm doing great. How can I help you today?</s> [INST] I'd like to show off how chat templating works! [/INST]`,
|
135 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
136 |
});
|
137 |
|
138 |
describe("End-to-end tests", () => {
|
|
|
10 |
{ role: "user", content: "I'd like to show off how chat templating works!" },
|
11 |
];
|
12 |
|
13 |
+
const EXAMPLE_CHAT_WITH_SYSTEM = [
|
14 |
{
|
15 |
role: "system",
|
16 |
content: "You are a friendly chatbot who always responds in the style of a pirate",
|
|
|
80 |
// hf-internal-testing/llama-tokenizer
|
81 |
chat_template: `{% if messages[0]['role'] == 'system' %}{% set loop_messages = messages[1:] %}{% set system_message = messages[0]['content'] %}{% elif USE_DEFAULT_PROMPT == true and not '<<SYS>>' in messages[0]['content'] %}{% set loop_messages = messages %}{% set system_message = 'DEFAULT_SYSTEM_MESSAGE' %}{% else %}{% set loop_messages = messages %}{% set system_message = false %}{% endif %}{% for message in loop_messages %}{% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}{{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}{% endif %}{% if loop.index0 == 0 and system_message != false %}{% set content = '<<SYS>>\\n' + system_message + '\\n<</SYS>>\\n\\n' + message['content'] %}{% else %}{% set content = message['content'] %}{% endif %}{% if message['role'] == 'user' %}{{ bos_token + '[INST] ' + content.strip() + ' [/INST]' }}{% elif message['role'] == 'system' %}{{ '<<SYS>>\\n' + content.strip() + '\\n<</SYS>>\\n\\n' }}{% elif message['role'] == 'assistant' %}{{ ' ' + content.strip() + ' ' + eos_token }}{% endif %}{% endfor %}`,
|
82 |
data: {
|
83 |
+
messages: EXAMPLE_CHAT_WITH_SYSTEM,
|
84 |
bos_token: "<s>",
|
85 |
eos_token: "</s>",
|
86 |
USE_DEFAULT_PROMPT: true,
|
|
|
106 |
"HuggingFaceH4/zephyr-7b-beta (add_generation_prompt=false)": {
|
107 |
chat_template: `{% for message in messages %}\n{% if message['role'] == 'user' %}\n{{ '<|user|>\n' + message['content'] + eos_token }}\n{% elif message['role'] == 'system' %}\n{{ '<|system|>\n' + message['content'] + eos_token }}\n{% elif message['role'] == 'assistant' %}\n{{ '<|assistant|>\n' + message['content'] + eos_token }}\n{% endif %}\n{% if loop.last and add_generation_prompt %}\n{{ '<|assistant|>' }}\n{% endif %}\n{% endfor %}`,
|
108 |
data: {
|
109 |
+
messages: EXAMPLE_CHAT_WITH_SYSTEM,
|
110 |
eos_token: "</s>",
|
111 |
add_generation_prompt: false,
|
112 |
},
|
|
|
124 |
},
|
125 |
target: `<|system|>\nYou are a friendly chatbot who always responds in the style of a pirate</s>\n<|user|>\nHow many helicopters can a human eat in one sitting?</s>\n<|assistant|>\n`,
|
126 |
},
|
127 |
+
"HuggingFaceH4/zephyr-7b-gemma-v0.1": {
|
128 |
+
chat_template: `{% if messages[0]['role'] == 'user' or messages[0]['role'] == 'system' %}{{ bos_token }}{% endif %}{% for message in messages %}{{ '<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>' + '\n' }}{% endfor %}{% if add_generation_prompt %}{{ '<|im_start|>assistant\n' }}{% elif messages[-1]['role'] == 'assistant' %}{{ eos_token }}{% endif %}`,
|
129 |
+
data: {
|
130 |
+
messages: EXAMPLE_CHAT,
|
131 |
+
bos_token: "<bos>",
|
132 |
+
eos_token: "<eos>",
|
133 |
+
add_generation_prompt: false,
|
134 |
+
},
|
135 |
+
target: `<bos><|im_start|>user\nHello, how are you?<|im_end|>\n<|im_start|>assistant\nI'm doing great. How can I help you today?<|im_end|>\n<|im_start|>user\nI'd like to show off how chat templating works!<|im_end|>\n`,
|
136 |
+
},
|
137 |
"mistralai/Mistral-7B-Instruct-v0.1": {
|
138 |
chat_template: `{{ bos_token }}{% for message in messages %}{% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}{{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}{% endif %}{% if message['role'] == 'user' %}{{ '[INST] ' + message['content'] + ' [/INST]' }}{% elif message['role'] == 'assistant' %}{{ message['content'] + eos_token + ' ' }}{% else %}{{ raise_exception('Only user and assistant roles are supported!') }}{% endif %}{% endfor %}`,
|
139 |
data: {
|
|
|
143 |
},
|
144 |
target: `<s>[INST] Hello, how are you? [/INST]I'm doing great. How can I help you today?</s> [INST] I'd like to show off how chat templating works! [/INST]`,
|
145 |
},
|
146 |
+
"mistralai/Mixtral-8x7B-Instruct-v0.1": {
|
147 |
+
chat_template: `{{ bos_token }}{% for message in messages %}{% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}{{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}{% endif %}{% if message['role'] == 'user' %}{{ '[INST] ' + message['content'] + ' [/INST]' }}{% elif message['role'] == 'assistant' %}{{ message['content'] + eos_token}}{% else %}{{ raise_exception('Only user and assistant roles are supported!') }}{% endif %}{% endfor %}`,
|
148 |
+
data: {
|
149 |
+
messages: EXAMPLE_CHAT,
|
150 |
+
bos_token: "<s>",
|
151 |
+
eos_token: "</s>",
|
152 |
+
},
|
153 |
+
target: `<s>[INST] Hello, how are you? [/INST]I'm doing great. How can I help you today?</s>[INST] I'd like to show off how chat templating works! [/INST]`,
|
154 |
+
},
|
155 |
+
"cognitivecomputations/dolphin-2.5-mixtral-8x7b": {
|
156 |
+
chat_template: `{% if not add_generation_prompt is defined %}{% set add_generation_prompt = false %}{% endif %}{% for message in messages %}{{'<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>' + '\n'}}{% endfor %}{% if add_generation_prompt %}{{ '<|im_start|>assistant\n' }}{% endif %}`,
|
157 |
+
data: {
|
158 |
+
messages: EXAMPLE_CHAT,
|
159 |
+
bos_token: "<s>",
|
160 |
+
eos_token: "</s>",
|
161 |
+
},
|
162 |
+
target: `<|im_start|>user\nHello, how are you?<|im_end|>\n<|im_start|>assistant\nI'm doing great. How can I help you today?<|im_end|>\n<|im_start|>user\nI'd like to show off how chat templating works!<|im_end|>\n`,
|
163 |
+
},
|
164 |
+
"openchat/openchat-3.5-0106": {
|
165 |
+
chat_template: `{{ bos_token }}{% for message in messages %}{{ 'GPT4 Correct ' + message['role'].title() + ': ' + message['content'] + '<|end_of_turn|>'}}{% endfor %}{% if add_generation_prompt %}{{ 'GPT4 Correct Assistant:' }}{% endif %}`,
|
166 |
+
data: {
|
167 |
+
messages: EXAMPLE_CHAT,
|
168 |
+
bos_token: "<s>",
|
169 |
+
eos_token: "</s>",
|
170 |
+
add_generation_prompt: false,
|
171 |
+
},
|
172 |
+
target: `<s>GPT4 Correct User: Hello, how are you?<|end_of_turn|>GPT4 Correct Assistant: I'm doing great. How can I help you today?<|end_of_turn|>GPT4 Correct User: I'd like to show off how chat templating works!<|end_of_turn|>`,
|
173 |
+
},
|
174 |
+
"upstage/SOLAR-10.7B-Instruct-v1.0": {
|
175 |
+
chat_template: `{% for message in messages %}{% if message['role'] == 'system' %}{% if message['content']%}{{'### System:\n' + message['content']+'\n\n'}}{% endif %}{% elif message['role'] == 'user' %}{{'### User:\n' + message['content']+'\n\n'}}{% elif message['role'] == 'assistant' %}{{'### Assistant:\n' + message['content']}}{% endif %}{% if loop.last and add_generation_prompt %}{{ '### Assistant:\n' }}{% endif %}{% endfor %}`,
|
176 |
+
data: {
|
177 |
+
messages: EXAMPLE_CHAT,
|
178 |
+
bos_token: "<s>",
|
179 |
+
eos_token: "</s>",
|
180 |
+
add_generation_prompt: false,
|
181 |
+
},
|
182 |
+
target: `### User:\nHello, how are you?\n\n### Assistant:\nI'm doing great. How can I help you today?### User:\nI'd like to show off how chat templating works!\n\n`,
|
183 |
+
},
|
184 |
+
"codellama/CodeLlama-70b-Instruct-hf": {
|
185 |
+
chat_template: `{% if messages[0]['role'] == 'system' %}{% set user_index = 1 %}{% else %}{% set user_index = 0 %}{% endif %}{% for message in messages %}{% if (message['role'] == 'user') != ((loop.index0 + user_index) % 2 == 0) %}{{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}{% endif %}{% if loop.index0 == 0 %}{{ '<s>' }}{% endif %}{% set content = 'Source: ' + message['role'] + '\n\n ' + message['content'] | trim %}{{ content + ' <step> ' }}{% endfor %}{{'Source: assistant\nDestination: user\n\n '}}`,
|
186 |
+
data: {
|
187 |
+
messages: EXAMPLE_CHAT,
|
188 |
+
bos_token: "<s>",
|
189 |
+
eos_token: "</s>",
|
190 |
+
},
|
191 |
+
target: `<s>Source: user\n\n Hello, how are you? <step> Source: assistant\n\n I'm doing great. How can I help you today? <step> Source: user\n\n I'd like to show off how chat templating works! <step> Source: assistant\nDestination: user\n\n `,
|
192 |
+
},
|
193 |
+
"Deci/DeciLM-7B-instruct": {
|
194 |
+
chat_template: `{% for message in messages %}\n{% if message['role'] == 'user' %}\n{{ '### User:\n' + message['content'] }}\n{% elif message['role'] == 'system' %}\n{{ '### System:\n' + message['content'] }}\n{% elif message['role'] == 'assistant' %}\n{{ '### Assistant:\n' + message['content'] }}\n{% endif %}\n{% if loop.last and add_generation_prompt %}\n{{ '### Assistant:' }}\n{% endif %}\n{% endfor %}`,
|
195 |
+
data: {
|
196 |
+
messages: EXAMPLE_CHAT,
|
197 |
+
bos_token: "<s>",
|
198 |
+
eos_token: "</s>",
|
199 |
+
add_generation_prompt: false,
|
200 |
+
},
|
201 |
+
target: `### User:\nHello, how are you?\n### Assistant:\nI'm doing great. How can I help you today?\n### User:\nI'd like to show off how chat templating works!\n`,
|
202 |
+
},
|
203 |
+
"Qwen/Qwen1.5-72B-Chat": {
|
204 |
+
chat_template: `{% for message in messages %}{% if loop.first and messages[0]['role'] != 'system' %}{{ '<|im_start|>system\nYou are a helpful assistant<|im_end|>\n' }}{% endif %}{{'<|im_start|>' + message['role'] + '\n' + message['content']}}{% if (loop.last and add_generation_prompt) or not loop.last %}{{ '<|im_end|>' + '\n'}}{% endif %}{% endfor %}{% if add_generation_prompt and messages[-1]['role'] != 'assistant' %}{{ '<|im_start|>assistant\n' }}{% endif %}`,
|
205 |
+
data: {
|
206 |
+
messages: EXAMPLE_CHAT,
|
207 |
+
bos_token: "<s>",
|
208 |
+
eos_token: "</s>",
|
209 |
+
add_generation_prompt: false,
|
210 |
+
},
|
211 |
+
target: `<|im_start|>system\nYou are a helpful assistant<|im_end|>\n<|im_start|>user\nHello, how are you?<|im_end|>\n<|im_start|>assistant\nI'm doing great. How can I help you today?<|im_end|>\n<|im_start|>user\nI'd like to show off how chat templating works!`,
|
212 |
+
},
|
213 |
+
"deepseek-ai/deepseek-llm-7b-chat": {
|
214 |
+
chat_template: `{% if not add_generation_prompt is defined %}{% set add_generation_prompt = false %}{% endif %}{{ bos_token }}{% for message in messages %}{% if message['role'] == 'user' %}{{ 'User: ' + message['content'] + '\n\n' }}{% elif message['role'] == 'assistant' %}{{ 'Assistant: ' + message['content'] + eos_token }}{% elif message['role'] == 'system' %}{{ message['content'] + '\n\n' }}{% endif %}{% endfor %}{% if add_generation_prompt %}{{ 'Assistant:' }}{% endif %}`,
|
215 |
+
data: {
|
216 |
+
messages: EXAMPLE_CHAT,
|
217 |
+
bos_token: "<|begin▁of▁sentence|>",
|
218 |
+
eos_token: "<|end▁of▁sentence|>",
|
219 |
+
},
|
220 |
+
target: `<|begin▁of▁sentence|>User: Hello, how are you?\n\nAssistant: I'm doing great. How can I help you today?<|end▁of▁sentence|>User: I'd like to show off how chat templating works!\n\n`,
|
221 |
+
},
|
222 |
+
"h2oai/h2o-danube-1.8b-chat": {
|
223 |
+
chat_template: `{% for message in messages %}{% if message['role'] == 'user' %}{{ '<|prompt|>' + message['content'] + eos_token }}{% elif message['role'] == 'system' %}{{ '<|system|>' + message['content'] + eos_token }}{% elif message['role'] == 'assistant' %}{{ '<|answer|>' + message['content'] + eos_token }}{% endif %}{% if loop.last and add_generation_prompt %}{{ '<|answer|>' }}{% endif %}{% endfor %}`,
|
224 |
+
data: {
|
225 |
+
messages: EXAMPLE_CHAT,
|
226 |
+
bos_token: "<s>",
|
227 |
+
eos_token: "</s>",
|
228 |
+
add_generation_prompt: false,
|
229 |
+
},
|
230 |
+
target: `<|prompt|>Hello, how are you?</s><|answer|>I'm doing great. How can I help you today?</s><|prompt|>I'd like to show off how chat templating works!</s>`,
|
231 |
+
},
|
232 |
+
"internlm/internlm2-chat-7b": {
|
233 |
+
chat_template: `{% if messages[0]['role'] == 'user' or messages[0]['role'] == 'system' %}{{ bos_token }}{% endif %}{% for message in messages %}{{ '<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>' + '\n' }}{% endfor %}{% if add_generation_prompt %}{{ '<|im_start|>assistant\n' }}{% elif messages[-1]['role'] == 'assistant' %}{{ eos_token }}{% endif %}`,
|
234 |
+
data: {
|
235 |
+
messages: EXAMPLE_CHAT,
|
236 |
+
bos_token: "<s>",
|
237 |
+
eos_token: "</s>",
|
238 |
+
add_generation_prompt: false,
|
239 |
+
},
|
240 |
+
target: `<s><|im_start|>user\nHello, how are you?<|im_end|>\n<|im_start|>assistant\nI'm doing great. How can I help you today?<|im_end|>\n<|im_start|>user\nI'd like to show off how chat templating works!<|im_end|>\n`,
|
241 |
+
},
|
242 |
+
"TheBloke/deepseek-coder-33B-instruct-AWQ": {
|
243 |
+
chat_template: `{%- set found_item = false -%}\n{%- for message in messages -%}\n {%- if message['role'] == 'system' -%}\n {%- set found_item = true -%}\n {%- endif -%}\n{%- endfor -%}\n{%- if not found_item -%}\n{{'You are an AI programming assistant, utilizing the Deepseek Coder model, developed by Deepseek Company, and you only answer questions related to computer science. For politically sensitive questions, security and privacy issues, and other non-computer science questions, you will refuse to answer.\\n'}}\n{%- endif %}\n{%- for message in messages %}\n {%- if message['role'] == 'system' %}\n{{ message['content'] }}\n {%- else %}\n {%- if message['role'] == 'user' %}\n{{'### Instruction:\\n' + message['content'] + '\\n'}}\n {%- else %}\n{{'### Response:\\n' + message['content'] + '\\n<|EOT|>\\n'}}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{{'### Response:\\n'}}\n`,
|
244 |
+
data: {
|
245 |
+
messages: EXAMPLE_CHAT,
|
246 |
+
bos_token: "<|begin▁of▁sentence|>",
|
247 |
+
eos_token: "<|EOT|>",
|
248 |
+
},
|
249 |
+
target: `You are an AI programming assistant, utilizing the Deepseek Coder model, developed by Deepseek Company, and you only answer questions related to computer science. For politically sensitive questions, security and privacy issues, and other non-computer science questions, you will refuse to answer.\n### Instruction:\nHello, how are you?\n### Response:\nI'm doing great. How can I help you today?\n<|EOT|>\n### Instruction:\nI'd like to show off how chat templating works!\n### Response:\n`,
|
250 |
+
},
|
251 |
+
"ericzzz/falcon-rw-1b-chat": {
|
252 |
+
chat_template: `{% for message in messages %}{% if loop.index > 1 and loop.previtem['role'] != 'assistant' %}{{ ' ' }}{% endif %}{% if message['role'] == 'system' %}{{ '[SYS] ' + message['content'].strip() }}{% elif message['role'] == 'user' %}{{ '[INST] ' + message['content'].strip() }}{% elif message['role'] == 'assistant' %}{{ '[RESP] ' + message['content'] + eos_token }}{% endif %}{% endfor %}{% if add_generation_prompt %}{{ ' [RESP] ' }}{% endif %}`,
|
253 |
+
data: {
|
254 |
+
messages: EXAMPLE_CHAT,
|
255 |
+
bos_token: "<|endoftext|>",
|
256 |
+
eos_token: "<|endoftext|>",
|
257 |
+
add_generation_prompt: false,
|
258 |
+
},
|
259 |
+
target: `[INST] Hello, how are you? [RESP] I'm doing great. How can I help you today?<|endoftext|>[INST] I'd like to show off how chat templating works!`,
|
260 |
+
},
|
261 |
+
"abacusai/Smaug-34B-v0.1": {
|
262 |
+
chat_template: `{%- for idx in range(0, messages|length) -%}\n{%- if messages[idx]['role'] == 'user' -%}\n{%- if idx > 1 -%}\n{{- bos_token + '[INST] ' + messages[idx]['content'] + ' [/INST]' -}}\n{%- else -%}\n{{- messages[idx]['content'] + ' [/INST]' -}}\n{%- endif -%}\n{% elif messages[idx]['role'] == 'system' %}\n{{- '[INST] <<SYS>>\\n' + messages[idx]['content'] + '\\n<</SYS>>\\n\\n' -}}\n{%- elif messages[idx]['role'] == 'assistant' -%}\n{{- ' ' + messages[idx]['content'] + ' ' + eos_token -}}\n{% endif %}\n{% endfor %}`,
|
263 |
+
data: {
|
264 |
+
messages: EXAMPLE_CHAT,
|
265 |
+
bos_token: "<s>",
|
266 |
+
eos_token: "</s>",
|
267 |
+
},
|
268 |
+
target: `Hello, how are you? [/INST] I'm doing great. How can I help you today? </s><s>[INST] I'd like to show off how chat templating works! [/INST]`,
|
269 |
+
},
|
270 |
+
"maywell/Synatra-Mixtral-8x7B": {
|
271 |
+
chat_template: `Below is an instruction that describes a task. Write a response that appropriately completes the request.\n\n{% for message in messages %}{% if message['role'] == 'user' %}### Instruction:\n{{ message['content']|trim -}}{% if not loop.last %}{% endif %}\n{% elif message['role'] == 'assistant' %}### Response:\n{{ message['content']|trim -}}{% if not loop.last %}{% endif %}\n{% elif message['role'] == 'system' %}{{ message['content']|trim -}}{% if not loop.last %}{% endif %}\n{% endif %}\n{% endfor %}\n{% if add_generation_prompt and messages[-1]['role'] != 'assistant' %}\n### Response:\n{% endif %}`,
|
272 |
+
data: {
|
273 |
+
messages: EXAMPLE_CHAT,
|
274 |
+
bos_token: "<s>",
|
275 |
+
eos_token: "</s>",
|
276 |
+
add_generation_prompt: false,
|
277 |
+
},
|
278 |
+
target: `Below is an instruction that describes a task. Write a response that appropriately completes the request.\n\n### Instruction:\nHello, how are you?### Response:\nI'm doing great. How can I help you today?### Instruction:\nI'd like to show off how chat templating works!`,
|
279 |
+
},
|
280 |
+
"deepseek-ai/deepseek-coder-33b-instruct": {
|
281 |
+
chat_template: `{% if not add_generation_prompt is defined %}\n{% set add_generation_prompt = false %}\n{% endif %}\n{%- set ns = namespace(found=false) -%}\n{%- for message in messages -%}\n {%- if message['role'] == 'system' -%}\n {%- set ns.found = true -%}\n {%- endif -%}\n{%- endfor -%}\n{{bos_token}}{%- if not ns.found -%}\n{{'You are an AI programming assistant, utilizing the Deepseek Coder model, developed by Deepseek Company, and you only answer questions related to computer science. For politically sensitive questions, security and privacy issues, and other non-computer science questions, you will refuse to answer\\n'}}\n{%- endif %}\n{%- for message in messages %}\n {%- if message['role'] == 'system' %}\n{{ message['content'] }}\n {%- else %}\n {%- if message['role'] == 'user' %}\n{{'### Instruction:\\n' + message['content'] + '\\n'}}\n {%- else %}\n{{'### Response:\\n' + message['content'] + '\\n<|EOT|>\\n'}}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{% if add_generation_prompt %}\n{{'### Response:'}}\n{% endif %}`,
|
282 |
+
data: {
|
283 |
+
messages: EXAMPLE_CHAT,
|
284 |
+
bos_token: "<|begin▁of▁sentence|>",
|
285 |
+
eos_token: "<|EOT|>",
|
286 |
+
},
|
287 |
+
target: `<|begin▁of▁sentence|>You are an AI programming assistant, utilizing the Deepseek Coder model, developed by Deepseek Company, and you only answer questions related to computer science. For politically sensitive questions, security and privacy issues, and other non-computer science questions, you will refuse to answer\n### Instruction:\nHello, how are you?\n### Response:\nI'm doing great. How can I help you today?\n<|EOT|>\n### Instruction:\nI'd like to show off how chat templating works!\n`,
|
288 |
+
},
|
289 |
});
|
290 |
|
291 |
describe("End-to-end tests", () => {
|
packages/jinja/test/interpreter.test.js
ADDED
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { describe, it, expect } from "vitest";
|
2 |
+
|
3 |
+
import { Environment, Interpreter } from "../src/runtime";
|
4 |
+
import { tokenize } from "../src/lexer";
|
5 |
+
import { parse } from "../src/parser";
|
6 |
+
|
7 |
+
describe("Test interpreter options", () => {
|
8 |
+
// https://jinja.palletsprojects.com/en/3.0.x/templates/#whitespace-control
|
9 |
+
it("should handle whitespace control", () => {
|
10 |
+
const EXAMPLE_IF_TEMPLATE = `<div>\n {% if True %}\n yay\n {% endif %}\n</div>`;
|
11 |
+
const EXAMPLE_FOR_TEMPLATE = `{% for item in seq %}\n {{ item }}\n{% endfor %}`;
|
12 |
+
const EXAMPLE_FOR_TEMPLATE_2 = `{% for item in seq -%}\n {{ item }}\n{% endfor %}`;
|
13 |
+
const EXAMPLE_FOR_TEMPLATE_3 = `{% for item in seq %}\n {{ item }}\n{%- endfor %}`;
|
14 |
+
const EXAMPLE_FOR_TEMPLATE_4 = `{% for item in seq -%}\n {{ item }}\n{%- endfor %}`;
|
15 |
+
|
16 |
+
const seq = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
17 |
+
|
18 |
+
const TESTS = [
|
19 |
+
// If tests
|
20 |
+
{
|
21 |
+
template: EXAMPLE_IF_TEMPLATE,
|
22 |
+
data: {},
|
23 |
+
options: {},
|
24 |
+
target: `<div>\n \n yay\n \n</div>`,
|
25 |
+
},
|
26 |
+
{
|
27 |
+
template: EXAMPLE_IF_TEMPLATE,
|
28 |
+
data: {},
|
29 |
+
options: {
|
30 |
+
lstrip_blocks: true,
|
31 |
+
},
|
32 |
+
target: `<div>\n\n yay\n\n</div>`,
|
33 |
+
},
|
34 |
+
{
|
35 |
+
template: EXAMPLE_IF_TEMPLATE,
|
36 |
+
data: {},
|
37 |
+
options: {
|
38 |
+
trim_blocks: true,
|
39 |
+
},
|
40 |
+
target: `<div>\n yay\n </div>`,
|
41 |
+
},
|
42 |
+
{
|
43 |
+
template: EXAMPLE_IF_TEMPLATE,
|
44 |
+
data: {},
|
45 |
+
options: {
|
46 |
+
lstrip_blocks: true,
|
47 |
+
trim_blocks: true,
|
48 |
+
},
|
49 |
+
target: `<div>\n yay\n</div>`,
|
50 |
+
},
|
51 |
+
|
52 |
+
// For tests
|
53 |
+
{
|
54 |
+
template: EXAMPLE_FOR_TEMPLATE,
|
55 |
+
data: { seq },
|
56 |
+
options: {},
|
57 |
+
target: `\n 1\n\n 2\n\n 3\n\n 4\n\n 5\n\n 6\n\n 7\n\n 8\n\n 9\n`,
|
58 |
+
},
|
59 |
+
{
|
60 |
+
template: EXAMPLE_FOR_TEMPLATE,
|
61 |
+
data: { seq },
|
62 |
+
options: { lstrip_blocks: true },
|
63 |
+
target: `\n 1\n\n 2\n\n 3\n\n 4\n\n 5\n\n 6\n\n 7\n\n 8\n\n 9\n`,
|
64 |
+
},
|
65 |
+
{
|
66 |
+
template: EXAMPLE_FOR_TEMPLATE,
|
67 |
+
data: { seq },
|
68 |
+
options: { trim_blocks: true },
|
69 |
+
target: ` 1\n 2\n 3\n 4\n 5\n 6\n 7\n 8\n 9\n`,
|
70 |
+
},
|
71 |
+
{
|
72 |
+
template: EXAMPLE_FOR_TEMPLATE,
|
73 |
+
data: { seq },
|
74 |
+
options: { lstrip_blocks: true, trim_blocks: true },
|
75 |
+
target: ` 1\n 2\n 3\n 4\n 5\n 6\n 7\n 8\n 9\n`,
|
76 |
+
},
|
77 |
+
{
|
78 |
+
template: EXAMPLE_FOR_TEMPLATE_2,
|
79 |
+
data: { seq },
|
80 |
+
options: {},
|
81 |
+
target: `1\n2\n3\n4\n5\n6\n7\n8\n9\n`,
|
82 |
+
},
|
83 |
+
{
|
84 |
+
template: EXAMPLE_FOR_TEMPLATE_3,
|
85 |
+
data: { seq },
|
86 |
+
options: {},
|
87 |
+
target: `\n 1\n 2\n 3\n 4\n 5\n 6\n 7\n 8\n 9`,
|
88 |
+
},
|
89 |
+
{
|
90 |
+
template: EXAMPLE_FOR_TEMPLATE_3,
|
91 |
+
data: { seq },
|
92 |
+
options: { trim_blocks: true },
|
93 |
+
target: ` 1 2 3 4 5 6 7 8 9`,
|
94 |
+
},
|
95 |
+
{
|
96 |
+
template: EXAMPLE_FOR_TEMPLATE_4,
|
97 |
+
data: { seq },
|
98 |
+
options: {},
|
99 |
+
target: `123456789`,
|
100 |
+
},
|
101 |
+
];
|
102 |
+
|
103 |
+
for (const test of TESTS) {
|
104 |
+
const env = new Environment();
|
105 |
+
env.set("True", true);
|
106 |
+
for (const [key, value] of Object.entries(test.data)) {
|
107 |
+
env.set(key, value);
|
108 |
+
}
|
109 |
+
|
110 |
+
const tokens = tokenize(test.template, test.options);
|
111 |
+
const parsed = parse(tokens);
|
112 |
+
|
113 |
+
const interpreter = new Interpreter(env);
|
114 |
+
const result = interpreter.run(parsed);
|
115 |
+
expect(result.value).toEqual(test.target);
|
116 |
+
}
|
117 |
+
});
|
118 |
+
});
|
packages/jinja/test/templates.test.js
CHANGED
@@ -49,6 +49,7 @@ const TEST_STRINGS = {
|
|
49 |
// Object methods
|
50 |
OBJ_METHODS: `{{ obj.x(x, y) }}{{ ' ' + obj.x() + ' ' }}{{ obj.z[x](x, y) }}`,
|
51 |
STRING_METHODS: `{{ ' A '.strip() }}{% set x = ' B ' %}{{ x.strip() }}{% set y = ' aBcD ' %}{{ y.upper() }}{{ y.lower() }}`,
|
|
|
52 |
|
53 |
// String indexing and slicing
|
54 |
STRING_SLICING: `|{{ x[0] }}|{{ x[:] }}|{{ x[:3] }}|{{ x[1:4] }}|{{ x[1:-1] }}|{{ x[1::2] }}|{{ x[5::-1] }}|`,
|
@@ -78,6 +79,22 @@ const TEST_STRINGS = {
|
|
78 |
BOOLEAN_MIXED: `|{{ true and 1 }}|{{ true and 0 }}|{{ false and 1 }}|{{ false and 0 }}|{{ true or 1 }}|{{ true or 0 }}|{{ false or 1 }}|{{ false or 0 }}|`,
|
79 |
BOOLEAN_MIXED_2: `|{{ true and '' }}|{{ true and 'a' }}|{{ false or '' }}|{{ false or 'a' }}|{{ '' and true }}|{{ 'a' and true }}|{{ '' or false }}|{{ 'a' or false }}|`,
|
80 |
BOOLEAN_MIXED_IF: `{% if '' %}{{ 'A' }}{% endif %}{% if 'a' %}{{ 'B' }}{% endif %}{% if true and '' %}{{ 'C' }}{% endif %}{% if true and 'a' %}{{ 'D' }}{% endif %}`,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
81 |
};
|
82 |
|
83 |
const TEST_PARSED = {
|
@@ -728,6 +745,15 @@ const TEST_PARSED = {
|
|
728 |
{ value: ")", type: "CloseParen" },
|
729 |
{ value: "}}", type: "CloseExpression" },
|
730 |
],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
731 |
|
732 |
// String indexing and slicing
|
733 |
STRING_SLICING: [
|
@@ -1477,6 +1503,280 @@ const TEST_PARSED = {
|
|
1477 |
{ value: "endif", type: "EndIf" },
|
1478 |
{ value: "%}", type: "CloseStatement" },
|
1479 |
],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1480 |
};
|
1481 |
|
1482 |
const TEST_CONTEXT = {
|
@@ -1552,6 +1852,7 @@ const TEST_CONTEXT = {
|
|
1552 |
|
1553 |
// String methods
|
1554 |
STRING_METHODS: {},
|
|
|
1555 |
|
1556 |
// String indexing and slicing
|
1557 |
STRING_SLICING: {
|
@@ -1593,6 +1894,26 @@ const TEST_CONTEXT = {
|
|
1593 |
BOOLEAN_MIXED: {},
|
1594 |
BOOLEAN_MIXED_2: {},
|
1595 |
BOOLEAN_MIXED_IF: {},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1596 |
};
|
1597 |
|
1598 |
const EXPECTED_OUTPUTS = {
|
@@ -1640,6 +1961,7 @@ const EXPECTED_OUTPUTS = {
|
|
1640 |
// Object methods
|
1641 |
OBJ_METHODS: "AB A_B",
|
1642 |
STRING_METHODS: "AB ABCD abcd ",
|
|
|
1643 |
|
1644 |
// String indexing and slicing
|
1645 |
STRING_SLICING: "|0|0123456789|012|123|12345678|13579|543210|",
|
@@ -1653,9 +1975,7 @@ const EXPECTED_OUTPUTS = {
|
|
1653 |
MEMBERSHIP_NEGATION_2: "|false|true|false|true|false|true|",
|
1654 |
|
1655 |
// Escaped characters
|
1656 |
-
|
1657 |
-
// meaning the first newline in the output is not present
|
1658 |
-
ESCAPED_CHARS: `\t'"\\|\n|\t|'|"|\\|`,
|
1659 |
|
1660 |
// Substring inclusion
|
1661 |
SUBSTRING_INCLUSION: `|true|true|false|true|false|true|false|`,
|
@@ -1671,6 +1991,22 @@ const EXPECTED_OUTPUTS = {
|
|
1671 |
BOOLEAN_MIXED: `|1|0|false|false|true|true|1|0|`,
|
1672 |
BOOLEAN_MIXED_2: `||a||a||true|false|a|`,
|
1673 |
BOOLEAN_MIXED_IF: `BD`,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1674 |
};
|
1675 |
|
1676 |
describe("Templates", () => {
|
|
|
49 |
// Object methods
|
50 |
OBJ_METHODS: `{{ obj.x(x, y) }}{{ ' ' + obj.x() + ' ' }}{{ obj.z[x](x, y) }}`,
|
51 |
STRING_METHODS: `{{ ' A '.strip() }}{% set x = ' B ' %}{{ x.strip() }}{% set y = ' aBcD ' %}{{ y.upper() }}{{ y.lower() }}`,
|
52 |
+
STRING_METHODS_2: `{{ 'test test'.title() }}`,
|
53 |
|
54 |
// String indexing and slicing
|
55 |
STRING_SLICING: `|{{ x[0] }}|{{ x[:] }}|{{ x[:3] }}|{{ x[1:4] }}|{{ x[1:-1] }}|{{ x[1::2] }}|{{ x[5::-1] }}|`,
|
|
|
79 |
BOOLEAN_MIXED: `|{{ true and 1 }}|{{ true and 0 }}|{{ false and 1 }}|{{ false and 0 }}|{{ true or 1 }}|{{ true or 0 }}|{{ false or 1 }}|{{ false or 0 }}|`,
|
80 |
BOOLEAN_MIXED_2: `|{{ true and '' }}|{{ true and 'a' }}|{{ false or '' }}|{{ false or 'a' }}|{{ '' and true }}|{{ 'a' and true }}|{{ '' or false }}|{{ 'a' or false }}|`,
|
81 |
BOOLEAN_MIXED_IF: `{% if '' %}{{ 'A' }}{% endif %}{% if 'a' %}{{ 'B' }}{% endif %}{% if true and '' %}{{ 'C' }}{% endif %}{% if true and 'a' %}{{ 'D' }}{% endif %}`,
|
82 |
+
|
83 |
+
// Tests (is operator)
|
84 |
+
IS_OPERATOR: `|{{ unknown_var is defined }}|{{ unknown_var is not defined }}|{{ known_var is defined }}|{{ known_var is not defined }}|`,
|
85 |
+
IS_OPERATOR_2: `|{{ true is true }}|{{ true is not true }}|{{ true is false }}|{{ true is not false }}|{{ true is boolean }}|{{ 1 is boolean }}|`,
|
86 |
+
IS_OPERATOR_3: `|{{ 1 is odd }}|{{ 2 is odd }}|{{ 1 is even }}|{{ 2 is even }}|{{ 2 is number }}|{{ '2' is number }}|{{ 2 is integer }}|{{ '2' is integer }}|`,
|
87 |
+
IS_OPERATOR_4: `|{{ func is callable }}|{{ 2 is callable }}|{{ 1 is iterable }}|{{ 'hello' is iterable }}|`,
|
88 |
+
IS_OPERATOR_5: `|{{ 'a' is lower }}|{{ 'A' is lower }}|{{ 'a' is upper }}|{{ 'A' is upper }}|`,
|
89 |
+
|
90 |
+
// Short-circuit evaluation
|
91 |
+
SHORT_CIRCUIT: `{{ false and raise_exception('This should not be printed') }}`,
|
92 |
+
SHORT_CIRCUIT_1: `{{ true or raise_exception('This should not be printed') }}`,
|
93 |
+
|
94 |
+
// Namespaces
|
95 |
+
NAMESPACE: `{% set ns = namespace() %}{% set ns.foo = 'bar' %}{{ ns.foo }}`,
|
96 |
+
NAMESPACE_1: `{% set ns = namespace(default=false) %}{{ ns.default }}`,
|
97 |
+
NAMESPACE_2: `{% set ns = namespace(default=false, number=1+1) %}|{{ ns.default }}|{{ ns.number }}|`,
|
98 |
};
|
99 |
|
100 |
const TEST_PARSED = {
|
|
|
745 |
{ value: ")", type: "CloseParen" },
|
746 |
{ value: "}}", type: "CloseExpression" },
|
747 |
],
|
748 |
+
STRING_METHODS_2: [
|
749 |
+
{ value: "{{", type: "OpenExpression" },
|
750 |
+
{ value: "test test", type: "StringLiteral" },
|
751 |
+
{ value: ".", type: "Dot" },
|
752 |
+
{ value: "title", type: "Identifier" },
|
753 |
+
{ value: "(", type: "OpenParen" },
|
754 |
+
{ value: ")", type: "CloseParen" },
|
755 |
+
{ value: "}}", type: "CloseExpression" },
|
756 |
+
],
|
757 |
|
758 |
// String indexing and slicing
|
759 |
STRING_SLICING: [
|
|
|
1503 |
{ value: "endif", type: "EndIf" },
|
1504 |
{ value: "%}", type: "CloseStatement" },
|
1505 |
],
|
1506 |
+
|
1507 |
+
// Tests (is operator)
|
1508 |
+
IS_OPERATOR: [
|
1509 |
+
{ value: "|", type: "Text" },
|
1510 |
+
{ value: "{{", type: "OpenExpression" },
|
1511 |
+
{ value: "unknown_var", type: "Identifier" },
|
1512 |
+
{ value: "is", type: "Is" },
|
1513 |
+
{ value: "defined", type: "Identifier" },
|
1514 |
+
{ value: "}}", type: "CloseExpression" },
|
1515 |
+
{ value: "|", type: "Text" },
|
1516 |
+
{ value: "{{", type: "OpenExpression" },
|
1517 |
+
{ value: "unknown_var", type: "Identifier" },
|
1518 |
+
{ value: "is", type: "Is" },
|
1519 |
+
{ value: "not", type: "UnaryOperator" },
|
1520 |
+
{ value: "defined", type: "Identifier" },
|
1521 |
+
{ value: "}}", type: "CloseExpression" },
|
1522 |
+
{ value: "|", type: "Text" },
|
1523 |
+
{ value: "{{", type: "OpenExpression" },
|
1524 |
+
{ value: "known_var", type: "Identifier" },
|
1525 |
+
{ value: "is", type: "Is" },
|
1526 |
+
{ value: "defined", type: "Identifier" },
|
1527 |
+
{ value: "}}", type: "CloseExpression" },
|
1528 |
+
{ value: "|", type: "Text" },
|
1529 |
+
{ value: "{{", type: "OpenExpression" },
|
1530 |
+
{ value: "known_var", type: "Identifier" },
|
1531 |
+
{ value: "is", type: "Is" },
|
1532 |
+
{ value: "not", type: "UnaryOperator" },
|
1533 |
+
{ value: "defined", type: "Identifier" },
|
1534 |
+
{ value: "}}", type: "CloseExpression" },
|
1535 |
+
{ value: "|", type: "Text" },
|
1536 |
+
],
|
1537 |
+
IS_OPERATOR_2: [
|
1538 |
+
{ value: "|", type: "Text" },
|
1539 |
+
{ value: "{{", type: "OpenExpression" },
|
1540 |
+
{ value: "true", type: "BooleanLiteral" },
|
1541 |
+
{ value: "is", type: "Is" },
|
1542 |
+
{ value: "true", type: "BooleanLiteral" },
|
1543 |
+
{ value: "}}", type: "CloseExpression" },
|
1544 |
+
{ value: "|", type: "Text" },
|
1545 |
+
{ value: "{{", type: "OpenExpression" },
|
1546 |
+
{ value: "true", type: "BooleanLiteral" },
|
1547 |
+
{ value: "is", type: "Is" },
|
1548 |
+
{ value: "not", type: "UnaryOperator" },
|
1549 |
+
{ value: "true", type: "BooleanLiteral" },
|
1550 |
+
{ value: "}}", type: "CloseExpression" },
|
1551 |
+
{ value: "|", type: "Text" },
|
1552 |
+
{ value: "{{", type: "OpenExpression" },
|
1553 |
+
{ value: "true", type: "BooleanLiteral" },
|
1554 |
+
{ value: "is", type: "Is" },
|
1555 |
+
{ value: "false", type: "BooleanLiteral" },
|
1556 |
+
{ value: "}}", type: "CloseExpression" },
|
1557 |
+
{ value: "|", type: "Text" },
|
1558 |
+
{ value: "{{", type: "OpenExpression" },
|
1559 |
+
{ value: "true", type: "BooleanLiteral" },
|
1560 |
+
{ value: "is", type: "Is" },
|
1561 |
+
{ value: "not", type: "UnaryOperator" },
|
1562 |
+
{ value: "false", type: "BooleanLiteral" },
|
1563 |
+
{ value: "}}", type: "CloseExpression" },
|
1564 |
+
{ value: "|", type: "Text" },
|
1565 |
+
{ value: "{{", type: "OpenExpression" },
|
1566 |
+
{ value: "true", type: "BooleanLiteral" },
|
1567 |
+
{ value: "is", type: "Is" },
|
1568 |
+
{ value: "boolean", type: "Identifier" },
|
1569 |
+
{ value: "}}", type: "CloseExpression" },
|
1570 |
+
{ value: "|", type: "Text" },
|
1571 |
+
{ value: "{{", type: "OpenExpression" },
|
1572 |
+
{ value: "1", type: "NumericLiteral" },
|
1573 |
+
{ value: "is", type: "Is" },
|
1574 |
+
{ value: "boolean", type: "Identifier" },
|
1575 |
+
{ value: "}}", type: "CloseExpression" },
|
1576 |
+
{ value: "|", type: "Text" },
|
1577 |
+
],
|
1578 |
+
IS_OPERATOR_3: [
|
1579 |
+
{ value: "|", type: "Text" },
|
1580 |
+
{ value: "{{", type: "OpenExpression" },
|
1581 |
+
{ value: "1", type: "NumericLiteral" },
|
1582 |
+
{ value: "is", type: "Is" },
|
1583 |
+
{ value: "odd", type: "Identifier" },
|
1584 |
+
{ value: "}}", type: "CloseExpression" },
|
1585 |
+
{ value: "|", type: "Text" },
|
1586 |
+
{ value: "{{", type: "OpenExpression" },
|
1587 |
+
{ value: "2", type: "NumericLiteral" },
|
1588 |
+
{ value: "is", type: "Is" },
|
1589 |
+
{ value: "odd", type: "Identifier" },
|
1590 |
+
{ value: "}}", type: "CloseExpression" },
|
1591 |
+
{ value: "|", type: "Text" },
|
1592 |
+
{ value: "{{", type: "OpenExpression" },
|
1593 |
+
{ value: "1", type: "NumericLiteral" },
|
1594 |
+
{ value: "is", type: "Is" },
|
1595 |
+
{ value: "even", type: "Identifier" },
|
1596 |
+
{ value: "}}", type: "CloseExpression" },
|
1597 |
+
{ value: "|", type: "Text" },
|
1598 |
+
{ value: "{{", type: "OpenExpression" },
|
1599 |
+
{ value: "2", type: "NumericLiteral" },
|
1600 |
+
{ value: "is", type: "Is" },
|
1601 |
+
{ value: "even", type: "Identifier" },
|
1602 |
+
{ value: "}}", type: "CloseExpression" },
|
1603 |
+
{ value: "|", type: "Text" },
|
1604 |
+
{ value: "{{", type: "OpenExpression" },
|
1605 |
+
{ value: "2", type: "NumericLiteral" },
|
1606 |
+
{ value: "is", type: "Is" },
|
1607 |
+
{ value: "number", type: "Identifier" },
|
1608 |
+
{ value: "}}", type: "CloseExpression" },
|
1609 |
+
{ value: "|", type: "Text" },
|
1610 |
+
{ value: "{{", type: "OpenExpression" },
|
1611 |
+
{ value: "2", type: "StringLiteral" },
|
1612 |
+
{ value: "is", type: "Is" },
|
1613 |
+
{ value: "number", type: "Identifier" },
|
1614 |
+
{ value: "}}", type: "CloseExpression" },
|
1615 |
+
{ value: "|", type: "Text" },
|
1616 |
+
{ value: "{{", type: "OpenExpression" },
|
1617 |
+
{ value: "2", type: "NumericLiteral" },
|
1618 |
+
{ value: "is", type: "Is" },
|
1619 |
+
{ value: "integer", type: "Identifier" },
|
1620 |
+
{ value: "}}", type: "CloseExpression" },
|
1621 |
+
{ value: "|", type: "Text" },
|
1622 |
+
{ value: "{{", type: "OpenExpression" },
|
1623 |
+
{ value: "2", type: "StringLiteral" },
|
1624 |
+
{ value: "is", type: "Is" },
|
1625 |
+
{ value: "integer", type: "Identifier" },
|
1626 |
+
{ value: "}}", type: "CloseExpression" },
|
1627 |
+
{ value: "|", type: "Text" },
|
1628 |
+
],
|
1629 |
+
IS_OPERATOR_4: [
|
1630 |
+
{ value: "|", type: "Text" },
|
1631 |
+
{ value: "{{", type: "OpenExpression" },
|
1632 |
+
{ value: "func", type: "Identifier" },
|
1633 |
+
{ value: "is", type: "Is" },
|
1634 |
+
{ value: "callable", type: "Identifier" },
|
1635 |
+
{ value: "}}", type: "CloseExpression" },
|
1636 |
+
{ value: "|", type: "Text" },
|
1637 |
+
{ value: "{{", type: "OpenExpression" },
|
1638 |
+
{ value: "2", type: "NumericLiteral" },
|
1639 |
+
{ value: "is", type: "Is" },
|
1640 |
+
{ value: "callable", type: "Identifier" },
|
1641 |
+
{ value: "}}", type: "CloseExpression" },
|
1642 |
+
{ value: "|", type: "Text" },
|
1643 |
+
{ value: "{{", type: "OpenExpression" },
|
1644 |
+
{ value: "1", type: "NumericLiteral" },
|
1645 |
+
{ value: "is", type: "Is" },
|
1646 |
+
{ value: "iterable", type: "Identifier" },
|
1647 |
+
{ value: "}}", type: "CloseExpression" },
|
1648 |
+
{ value: "|", type: "Text" },
|
1649 |
+
{ value: "{{", type: "OpenExpression" },
|
1650 |
+
{ value: "hello", type: "StringLiteral" },
|
1651 |
+
{ value: "is", type: "Is" },
|
1652 |
+
{ value: "iterable", type: "Identifier" },
|
1653 |
+
{ value: "}}", type: "CloseExpression" },
|
1654 |
+
{ value: "|", type: "Text" },
|
1655 |
+
],
|
1656 |
+
IS_OPERATOR_5: [
|
1657 |
+
{ value: "|", type: "Text" },
|
1658 |
+
{ value: "{{", type: "OpenExpression" },
|
1659 |
+
{ value: "a", type: "StringLiteral" },
|
1660 |
+
{ value: "is", type: "Is" },
|
1661 |
+
{ value: "lower", type: "Identifier" },
|
1662 |
+
{ value: "}}", type: "CloseExpression" },
|
1663 |
+
{ value: "|", type: "Text" },
|
1664 |
+
{ value: "{{", type: "OpenExpression" },
|
1665 |
+
{ value: "A", type: "StringLiteral" },
|
1666 |
+
{ value: "is", type: "Is" },
|
1667 |
+
{ value: "lower", type: "Identifier" },
|
1668 |
+
{ value: "}}", type: "CloseExpression" },
|
1669 |
+
{ value: "|", type: "Text" },
|
1670 |
+
{ value: "{{", type: "OpenExpression" },
|
1671 |
+
{ value: "a", type: "StringLiteral" },
|
1672 |
+
{ value: "is", type: "Is" },
|
1673 |
+
{ value: "upper", type: "Identifier" },
|
1674 |
+
{ value: "}}", type: "CloseExpression" },
|
1675 |
+
{ value: "|", type: "Text" },
|
1676 |
+
{ value: "{{", type: "OpenExpression" },
|
1677 |
+
{ value: "A", type: "StringLiteral" },
|
1678 |
+
{ value: "is", type: "Is" },
|
1679 |
+
{ value: "upper", type: "Identifier" },
|
1680 |
+
{ value: "}}", type: "CloseExpression" },
|
1681 |
+
{ value: "|", type: "Text" },
|
1682 |
+
],
|
1683 |
+
|
1684 |
+
// Short-circuit evaluation
|
1685 |
+
SHORT_CIRCUIT: [
|
1686 |
+
{ value: "{{", type: "OpenExpression" },
|
1687 |
+
{ value: "false", type: "BooleanLiteral" },
|
1688 |
+
{ value: "and", type: "And" },
|
1689 |
+
{ value: "raise_exception", type: "Identifier" },
|
1690 |
+
{ value: "(", type: "OpenParen" },
|
1691 |
+
{ value: "This should not be printed", type: "StringLiteral" },
|
1692 |
+
{ value: ")", type: "CloseParen" },
|
1693 |
+
{ value: "}}", type: "CloseExpression" },
|
1694 |
+
],
|
1695 |
+
SHORT_CIRCUIT_1: [
|
1696 |
+
{ value: "{{", type: "OpenExpression" },
|
1697 |
+
{ value: "true", type: "BooleanLiteral" },
|
1698 |
+
{ value: "or", type: "Or" },
|
1699 |
+
{ value: "raise_exception", type: "Identifier" },
|
1700 |
+
{ value: "(", type: "OpenParen" },
|
1701 |
+
{ value: "This should not be printed", type: "StringLiteral" },
|
1702 |
+
{ value: ")", type: "CloseParen" },
|
1703 |
+
{ value: "}}", type: "CloseExpression" },
|
1704 |
+
],
|
1705 |
+
|
1706 |
+
// Namespaces
|
1707 |
+
NAMESPACE: [
|
1708 |
+
{ value: "{%", type: "OpenStatement" },
|
1709 |
+
{ value: "set", type: "Set" },
|
1710 |
+
{ value: "ns", type: "Identifier" },
|
1711 |
+
{ value: "=", type: "Equals" },
|
1712 |
+
{ value: "namespace", type: "Identifier" },
|
1713 |
+
{ value: "(", type: "OpenParen" },
|
1714 |
+
{ value: ")", type: "CloseParen" },
|
1715 |
+
{ value: "%}", type: "CloseStatement" },
|
1716 |
+
{ value: "{%", type: "OpenStatement" },
|
1717 |
+
{ value: "set", type: "Set" },
|
1718 |
+
{ value: "ns", type: "Identifier" },
|
1719 |
+
{ value: ".", type: "Dot" },
|
1720 |
+
{ value: "foo", type: "Identifier" },
|
1721 |
+
{ value: "=", type: "Equals" },
|
1722 |
+
{ value: "bar", type: "StringLiteral" },
|
1723 |
+
{ value: "%}", type: "CloseStatement" },
|
1724 |
+
{ value: "{{", type: "OpenExpression" },
|
1725 |
+
{ value: "ns", type: "Identifier" },
|
1726 |
+
{ value: ".", type: "Dot" },
|
1727 |
+
{ value: "foo", type: "Identifier" },
|
1728 |
+
{ value: "}}", type: "CloseExpression" },
|
1729 |
+
],
|
1730 |
+
NAMESPACE_1: [
|
1731 |
+
{ value: "{%", type: "OpenStatement" },
|
1732 |
+
{ value: "set", type: "Set" },
|
1733 |
+
{ value: "ns", type: "Identifier" },
|
1734 |
+
{ value: "=", type: "Equals" },
|
1735 |
+
{ value: "namespace", type: "Identifier" },
|
1736 |
+
{ value: "(", type: "OpenParen" },
|
1737 |
+
{ value: "default", type: "Identifier" },
|
1738 |
+
{ value: "=", type: "Equals" },
|
1739 |
+
{ value: "false", type: "BooleanLiteral" },
|
1740 |
+
{ value: ")", type: "CloseParen" },
|
1741 |
+
{ value: "%}", type: "CloseStatement" },
|
1742 |
+
{ value: "{{", type: "OpenExpression" },
|
1743 |
+
{ value: "ns", type: "Identifier" },
|
1744 |
+
{ value: ".", type: "Dot" },
|
1745 |
+
{ value: "default", type: "Identifier" },
|
1746 |
+
{ value: "}}", type: "CloseExpression" },
|
1747 |
+
],
|
1748 |
+
NAMESPACE_2: [
|
1749 |
+
{ value: "{%", type: "OpenStatement" },
|
1750 |
+
{ value: "set", type: "Set" },
|
1751 |
+
{ value: "ns", type: "Identifier" },
|
1752 |
+
{ value: "=", type: "Equals" },
|
1753 |
+
{ value: "namespace", type: "Identifier" },
|
1754 |
+
{ value: "(", type: "OpenParen" },
|
1755 |
+
{ value: "default", type: "Identifier" },
|
1756 |
+
{ value: "=", type: "Equals" },
|
1757 |
+
{ value: "false", type: "BooleanLiteral" },
|
1758 |
+
{ value: ",", type: "Comma" },
|
1759 |
+
{ value: "number", type: "Identifier" },
|
1760 |
+
{ value: "=", type: "Equals" },
|
1761 |
+
{ value: "1", type: "NumericLiteral" },
|
1762 |
+
{ value: "+", type: "AdditiveBinaryOperator" },
|
1763 |
+
{ value: "1", type: "NumericLiteral" },
|
1764 |
+
{ value: ")", type: "CloseParen" },
|
1765 |
+
{ value: "%}", type: "CloseStatement" },
|
1766 |
+
{ value: "|", type: "Text" },
|
1767 |
+
{ value: "{{", type: "OpenExpression" },
|
1768 |
+
{ value: "ns", type: "Identifier" },
|
1769 |
+
{ value: ".", type: "Dot" },
|
1770 |
+
{ value: "default", type: "Identifier" },
|
1771 |
+
{ value: "}}", type: "CloseExpression" },
|
1772 |
+
{ value: "|", type: "Text" },
|
1773 |
+
{ value: "{{", type: "OpenExpression" },
|
1774 |
+
{ value: "ns", type: "Identifier" },
|
1775 |
+
{ value: ".", type: "Dot" },
|
1776 |
+
{ value: "number", type: "Identifier" },
|
1777 |
+
{ value: "}}", type: "CloseExpression" },
|
1778 |
+
{ value: "|", type: "Text" },
|
1779 |
+
],
|
1780 |
};
|
1781 |
|
1782 |
const TEST_CONTEXT = {
|
|
|
1852 |
|
1853 |
// String methods
|
1854 |
STRING_METHODS: {},
|
1855 |
+
STRING_METHODS_2: {},
|
1856 |
|
1857 |
// String indexing and slicing
|
1858 |
STRING_SLICING: {
|
|
|
1894 |
BOOLEAN_MIXED: {},
|
1895 |
BOOLEAN_MIXED_2: {},
|
1896 |
BOOLEAN_MIXED_IF: {},
|
1897 |
+
|
1898 |
+
// Tests (is operator)
|
1899 |
+
IS_OPERATOR: {
|
1900 |
+
known_var: "Hello World",
|
1901 |
+
},
|
1902 |
+
IS_OPERATOR_2: {},
|
1903 |
+
IS_OPERATOR_3: {},
|
1904 |
+
IS_OPERATOR_4: {
|
1905 |
+
func: () => {},
|
1906 |
+
},
|
1907 |
+
IS_OPERATOR_5: {},
|
1908 |
+
|
1909 |
+
// Short-circuit evaluation
|
1910 |
+
SHORT_CIRCUIT: {},
|
1911 |
+
SHORT_CIRCUIT_1: {},
|
1912 |
+
|
1913 |
+
// Namespaces
|
1914 |
+
NAMESPACE: {},
|
1915 |
+
NAMESPACE_1: {},
|
1916 |
+
NAMESPACE_2: {},
|
1917 |
};
|
1918 |
|
1919 |
const EXPECTED_OUTPUTS = {
|
|
|
1961 |
// Object methods
|
1962 |
OBJ_METHODS: "AB A_B",
|
1963 |
STRING_METHODS: "AB ABCD abcd ",
|
1964 |
+
STRING_METHODS_2: "Test Test",
|
1965 |
|
1966 |
// String indexing and slicing
|
1967 |
STRING_SLICING: "|0|0123456789|012|123|12345678|13579|543210|",
|
|
|
1975 |
MEMBERSHIP_NEGATION_2: "|false|true|false|true|false|true|",
|
1976 |
|
1977 |
// Escaped characters
|
1978 |
+
ESCAPED_CHARS: `\n\t'"\\|\n|\t|'|"|\\|`,
|
|
|
|
|
1979 |
|
1980 |
// Substring inclusion
|
1981 |
SUBSTRING_INCLUSION: `|true|true|false|true|false|true|false|`,
|
|
|
1991 |
BOOLEAN_MIXED: `|1|0|false|false|true|true|1|0|`,
|
1992 |
BOOLEAN_MIXED_2: `||a||a||true|false|a|`,
|
1993 |
BOOLEAN_MIXED_IF: `BD`,
|
1994 |
+
|
1995 |
+
// Tests (is operator)
|
1996 |
+
IS_OPERATOR: `|false|true|true|false|`,
|
1997 |
+
IS_OPERATOR_2: `|true|false|false|true|true|false|`,
|
1998 |
+
IS_OPERATOR_3: `|true|false|false|true|true|false|true|false|`,
|
1999 |
+
IS_OPERATOR_4: `|true|false|false|true|`,
|
2000 |
+
IS_OPERATOR_5: `|true|false|false|true|`,
|
2001 |
+
|
2002 |
+
// Short-circuit evaluation
|
2003 |
+
SHORT_CIRCUIT: `false`,
|
2004 |
+
SHORT_CIRCUIT_1: `true`,
|
2005 |
+
|
2006 |
+
// Namespaces
|
2007 |
+
NAMESPACE: `bar`,
|
2008 |
+
NAMESPACE_1: `false`,
|
2009 |
+
NAMESPACE_2: `|false|2|`,
|
2010 |
};
|
2011 |
|
2012 |
describe("Templates", () => {
|
packages/tasks/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
{
|
2 |
"name": "@huggingface/tasks",
|
3 |
"packageManager": "[email protected]",
|
4 |
-
"version": "0.5.
|
5 |
"description": "List of ML tasks for huggingface.co/tasks",
|
6 |
"repository": "https://github.com/huggingface/huggingface.js.git",
|
7 |
"publishConfig": {
|
|
|
1 |
{
|
2 |
"name": "@huggingface/tasks",
|
3 |
"packageManager": "[email protected]",
|
4 |
+
"version": "0.5.2",
|
5 |
"description": "List of ML tasks for huggingface.co/tasks",
|
6 |
"repository": "https://github.com/huggingface/huggingface.js.git",
|
7 |
"publishConfig": {
|