chore: update node_modules with new binary files and dependencies

- Add new binary files for nodemon, onnxruntime-web, and xenova/transformers
- Update various JavaScript and TypeScript files in node_modules
- Remove unused files and dependencies
- Add new test fixtures and documentation files
This commit is contained in:
你的名字
2025-07-18 08:41:48 +08:00
parent db8c0adc79
commit 4c2f5c69a1
4655 changed files with 811329 additions and 35112 deletions

21
node_modules/@huggingface/jinja/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Hugging Face
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

79
node_modules/@huggingface/jinja/README.md generated vendored Normal file
View File

@@ -0,0 +1,79 @@
# Jinja
A minimalistic JavaScript implementation of the Jinja templating engine, specifically designed for parsing and rendering ML chat templates.
## Usage
### Load template from a model on the Hugging Face Hub
First, install the jinja and hub packages:
```sh
npm i @huggingface/jinja
npm i @huggingface/hub
```
You can then load a tokenizer from the Hugging Face Hub and render a list of chat messages, as follows:
```js
import { Template } from "@huggingface/jinja";
import { downloadFile } from "@huggingface/hub";
const config = await (
await downloadFile({
repo: "mistralai/Mistral-7B-Instruct-v0.1",
path: "tokenizer_config.json",
})
).json();
const chat = [
{ role: "user", content: "Hello, how are you?" },
{ role: "assistant", content: "I'm doing great. How can I help you today?" },
{ role: "user", content: "I'd like to show off how chat templating works!" },
];
const template = new Template(config.chat_template);
const result = template.render({
messages: chat,
bos_token: config.bos_token,
eos_token: config.eos_token,
});
// "<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]"
```
### Transformers.js
First, install the `@huggingface/jinja` and `@xenova/transformers` packages:
```sh
npm i @huggingface/jinja
npm i @xenova/transformers
```
You can then render a list of chat messages using a tokenizer's `apply_chat_template` method.
```js
import { AutoTokenizer } from "@xenova/transformers";
// Load tokenizer from the Hugging Face Hub
const tokenizer = await AutoTokenizer.from_pretrained("mistralai/Mistral-7B-Instruct-v0.1");
// Define chat messages
const chat = [
{ role: "user", content: "Hello, how are you?" },
{ role: "assistant", content: "I'm doing great. How can I help you today?" },
{ role: "user", content: "I'd like to show off how chat templating works!" },
];
const text = tokenizer.apply_chat_template(chat, { tokenize: false });
// "<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]"
```
Notice how the entire chat is condensed into a single string. If you would instead like to return the tokenized version (i.e., a list of token IDs), you can use the following:
```js
const input_ids = tokenizer.apply_chat_template(chat, { tokenize: true, return_tensor: false });
// [1, 733, 16289, 28793, 22557, 28725, 910, 460, 368, 28804, 733, 28748, 16289, 28793, 28737, 28742, 28719, 2548, 1598, 28723, 1602, 541, 315, 1316, 368, 3154, 28804, 2, 28705, 733, 16289, 28793, 315, 28742, 28715, 737, 298, 1347, 805, 910, 10706, 5752, 1077, 3791, 28808, 733, 28748, 16289, 28793]
```
For more information about chat templates, check out the transformers [documentation](https://huggingface.co/docs/transformers/main/en/chat_templating).

1603
node_modules/@huggingface/jinja/dist/index.cjs generated vendored Normal file

File diff suppressed because it is too large Load Diff

269
node_modules/@huggingface/jinja/dist/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,269 @@
/**
* Represents tokens that our language understands in parsing.
*/
declare const TOKEN_TYPES: Readonly<{
Text: "Text";
NumericLiteral: "NumericLiteral";
BooleanLiteral: "BooleanLiteral";
StringLiteral: "StringLiteral";
Identifier: "Identifier";
Equals: "Equals";
OpenParen: "OpenParen";
CloseParen: "CloseParen";
OpenStatement: "OpenStatement";
CloseStatement: "CloseStatement";
OpenExpression: "OpenExpression";
CloseExpression: "CloseExpression";
OpenSquareBracket: "OpenSquareBracket";
CloseSquareBracket: "CloseSquareBracket";
OpenCurlyBracket: "OpenCurlyBracket";
CloseCurlyBracket: "CloseCurlyBracket";
Comma: "Comma";
Dot: "Dot";
Colon: "Colon";
Pipe: "Pipe";
CallOperator: "CallOperator";
AdditiveBinaryOperator: "AdditiveBinaryOperator";
MultiplicativeBinaryOperator: "MultiplicativeBinaryOperator";
ComparisonBinaryOperator: "ComparisonBinaryOperator";
UnaryOperator: "UnaryOperator";
Set: "Set";
If: "If";
For: "For";
In: "In";
Is: "Is";
NotIn: "NotIn";
Else: "Else";
EndIf: "EndIf";
ElseIf: "ElseIf";
EndFor: "EndFor";
And: "And";
Or: "Or";
Not: "UnaryOperator";
}>;
type TokenType = keyof typeof TOKEN_TYPES;
/**
* Represents a single token in the template.
*/
declare class Token {
value: string;
type: TokenType;
/**
* Constructs a new Token.
* @param {string} value The raw value as seen inside the source code.
* @param {TokenType} type The type of token.
*/
constructor(value: string, type: TokenType);
}
interface PreprocessOptions {
trim_blocks?: boolean;
lstrip_blocks?: boolean;
}
/**
* Generate a list of tokens from a source string.
*/
declare function tokenize(source: string, options?: PreprocessOptions): Token[];
/**
* Statements do not result in a value at runtime. They contain one or more expressions internally.
*/
declare class Statement {
type: string;
}
/**
* Defines a block which contains many statements. Each chat template corresponds to one Program.
*/
declare class Program extends Statement {
body: Statement[];
type: string;
constructor(body: Statement[]);
}
/**
* Generate the Abstract Syntax Tree (AST) from a list of tokens.
* Operator precedence can be found here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_precedence#table
*/
declare function parse(tokens: Token[]): Program;
type AnyRuntimeValue = NumericValue | StringValue | BooleanValue | ObjectValue | ArrayValue | FunctionValue | NullValue | UndefinedValue;
/**
* Abstract base class for all Runtime values.
* Should not be instantiated directly.
*/
declare abstract class RuntimeValue<T> {
type: string;
value: T;
/**
* A collection of built-in functions for this type.
*/
builtins: Map<string, AnyRuntimeValue>;
/**
* Creates a new RuntimeValue.
*/
constructor(value?: T);
/**
* Determines truthiness or falsiness of the runtime value.
* This function should be overridden by subclasses if it has custom truthiness criteria.
* @returns {BooleanValue} BooleanValue(true) if the value is truthy, BooleanValue(false) otherwise.
*/
__bool__(): BooleanValue;
}
/**
* Represents a numeric value at runtime.
*/
declare class NumericValue extends RuntimeValue<number> {
type: string;
}
/**
* Represents a string value at runtime.
*/
declare class StringValue extends RuntimeValue<string> {
type: string;
builtins: Map<string, AnyRuntimeValue>;
}
/**
* Represents a boolean value at runtime.
*/
declare class BooleanValue extends RuntimeValue<boolean> {
type: string;
}
/**
* Represents an Object value at runtime.
*/
declare class ObjectValue extends RuntimeValue<Map<string, AnyRuntimeValue>> {
type: string;
/**
* NOTE: necessary to override since all JavaScript arrays are considered truthy,
* while only non-empty Python arrays are consider truthy.
*
* e.g.,
* - JavaScript: {} && 5 -> 5
* - Python: {} and 5 -> {}
*/
__bool__(): BooleanValue;
builtins: Map<string, AnyRuntimeValue>;
}
/**
* Represents an Array value at runtime.
*/
declare class ArrayValue extends RuntimeValue<AnyRuntimeValue[]> {
type: string;
builtins: Map<string, AnyRuntimeValue>;
/**
* NOTE: necessary to override since all JavaScript arrays are considered truthy,
* while only non-empty Python arrays are consider truthy.
*
* e.g.,
* - JavaScript: [] && 5 -> 5
* - Python: [] and 5 -> []
*/
__bool__(): BooleanValue;
}
/**
* Represents a Function value at runtime.
*/
declare class FunctionValue extends RuntimeValue<(args: AnyRuntimeValue[], scope: Environment) => AnyRuntimeValue> {
type: string;
}
/**
* Represents a Null value at runtime.
*/
declare class NullValue extends RuntimeValue<null> {
type: string;
}
/**
* Represents an Undefined value at runtime.
*/
declare class UndefinedValue extends RuntimeValue<undefined> {
type: string;
}
/**
* Represents the current environment (scope) at runtime.
*/
declare class Environment {
parent?: Environment | undefined;
/**
* The variables declared in this environment.
*/
variables: Map<string, AnyRuntimeValue>;
/**
* The tests available in this environment.
*/
tests: Map<string, (...value: AnyRuntimeValue[]) => boolean>;
constructor(parent?: Environment | undefined);
/**
* Set the value of a variable in the current environment.
*/
set(name: string, value: unknown): AnyRuntimeValue;
private declareVariable;
/**
* Set variable in the current scope.
* See https://jinja.palletsprojects.com/en/3.0.x/templates/#assignments for more information.
*/
setVariable(name: string, value: AnyRuntimeValue): AnyRuntimeValue;
/**
* Resolve the environment in which the variable is declared.
* @param {string} name The name of the variable.
* @returns {Environment} The environment in which the variable is declared.
*/
private resolve;
lookupVariable(name: string): AnyRuntimeValue;
}
declare class Interpreter {
global: Environment;
constructor(env?: Environment);
/**
* Run the program.
*/
run(program: Program): AnyRuntimeValue;
/**
* Evaluates expressions following the binary operation type.
*/
private evaluateBinaryExpression;
/**
* Evaluates expressions following the filter operation type.
*/
private evaluateFilterExpression;
/**
* Evaluates expressions following the test operation type.
*/
private evaluateTestExpression;
/**
* Evaluates expressions following the unary operation type.
*/
private evaluateUnaryExpression;
private evalProgram;
private evaluateBlock;
private evaluateIdentifier;
private evaluateCallExpression;
private evaluateSliceExpression;
private evaluateMemberExpression;
private evaluateSet;
private evaluateIf;
private evaluateFor;
evaluate(statement: Statement | undefined, environment: Environment): AnyRuntimeValue;
}
/**
* @file Jinja templating engine
*
* A minimalistic JavaScript reimplementation of the [Jinja](https://github.com/pallets/jinja) templating engine,
* to support the chat templates. Special thanks to [Tyler Laceby](https://github.com/tlaceby) for his amazing
* ["Guide to Interpreters"](https://github.com/tlaceby/guide-to-interpreters-series) tutorial series,
* which provided the basis for this implementation.
*
* See the [Transformers documentation](https://huggingface.co/docs/transformers/main/en/chat_templating) for more information.
*
* @module index
*/
declare class Template {
parsed: Program;
/**
* @param {string} template The template string
*/
constructor(template: string);
render(items: Record<string, unknown>): string;
}
export { Environment, Interpreter, Template, parse, tokenize };

1572
node_modules/@huggingface/jinja/dist/index.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

55
node_modules/@huggingface/jinja/package.json generated vendored Normal file
View File

@@ -0,0 +1,55 @@
{
"name": "@huggingface/jinja",
"packageManager": "pnpm@8.10.5",
"version": "0.2.2",
"description": "A minimalistic JavaScript implementation of the Jinja templating engine, specifically designed for parsing and rendering ML chat templates.",
"repository": "https://github.com/huggingface/huggingface.js.git",
"publishConfig": {
"access": "public"
},
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"require": "./dist/index.cjs",
"import": "./dist/index.js"
}
},
"engines": {
"node": ">=18"
},
"source": "src/index.ts",
"files": [
"src",
"dist",
"README.md",
"tsconfig.json"
],
"keywords": [
"huggingface",
"jinja",
"templates",
"hugging",
"face"
],
"author": "Hugging Face",
"license": "MIT",
"devDependencies": {
"typescript": "^5.3.2",
"@xenova/transformers": "^2.9.0",
"@huggingface/hub": "^0.14.2"
},
"scripts": {
"lint": "eslint --quiet --fix --ext .cjs,.ts .",
"lint:check": "eslint --ext .cjs,.ts .",
"format": "prettier --write .",
"format:check": "prettier --check .",
"build": "tsup src/index.ts --format cjs,esm --clean --dts",
"test": "vitest run",
"test:browser": "vitest run --browser.name=chrome --browser.headless",
"check": "tsc"
}
}

246
node_modules/@huggingface/jinja/src/ast.ts generated vendored Normal file
View File

@@ -0,0 +1,246 @@
import type { Token } from "./lexer";
/**
* Statements do not result in a value at runtime. They contain one or more expressions internally.
*/
export class Statement {
type = "Statement";
}
/**
* Defines a block which contains many statements. Each chat template corresponds to one Program.
*/
export class Program extends Statement {
override type = "Program";
constructor(public body: Statement[]) {
super();
}
}
export class If extends Statement {
override type = "If";
constructor(
public test: Expression,
public body: Statement[],
public alternate: Statement[]
) {
super();
}
}
export class For extends Statement {
override type = "For";
constructor(
public loopvar: Identifier | TupleLiteral,
public iterable: Expression,
public body: Statement[]
) {
super();
}
}
export class SetStatement extends Statement {
override type = "Set";
constructor(
public assignee: Expression,
public value: Expression
) {
super();
}
}
/**
* Expressions will result in a value at runtime (unlike statements).
*/
export class Expression extends Statement {
override type = "Expression";
}
export class MemberExpression extends Expression {
override type = "MemberExpression";
constructor(
public object: Expression,
public property: Expression,
public computed: boolean
) {
super();
}
}
export class CallExpression extends Expression {
override type = "CallExpression";
constructor(
public callee: Expression,
public args: Expression[]
) {
super();
}
}
/**
* Represents a user-defined variable or symbol in the template.
*/
export class Identifier extends Expression {
override type = "Identifier";
/**
* @param {string} value The name of the identifier
*/
constructor(public value: string) {
super();
}
}
/**
* Abstract base class for all Literal expressions.
* Should not be instantiated directly.
*/
abstract class Literal<T> extends Expression {
override type = "Literal";
constructor(public value: T) {
super();
}
}
/**
* Represents a numeric constant in the template.
*/
export class NumericLiteral extends Literal<number> {
override type = "NumericLiteral";
}
/**
* Represents a text constant in the template.
*/
export class StringLiteral extends Literal<string> {
override type = "StringLiteral";
}
/**
* Represents a boolean constant in the template.
*/
export class BooleanLiteral extends Literal<boolean> {
override type = "BooleanLiteral";
}
/**
* Represents an array literal in the template.
*/
export class ArrayLiteral extends Literal<Expression[]> {
override type = "ArrayLiteral";
}
/**
* Represents a tuple literal in the template.
*/
export class TupleLiteral extends Literal<Expression[]> {
override type = "TupleLiteral";
}
/**
* Represents an object literal in the template.
*/
export class ObjectLiteral extends Literal<Map<Expression, Expression>> {
override type = "ObjectLiteral";
}
/**
* An operation with two sides, separated by an operator.
* Note: Either side can be a Complex Expression, with order
* of operations being determined by the operator.
*/
export class BinaryExpression extends Expression {
override type = "BinaryExpression";
constructor(
public operator: Token,
public left: Expression,
public right: Expression
) {
super();
}
}
/**
* An operation with two sides, separated by the | operator.
* Operator precedence: https://github.com/pallets/jinja/issues/379#issuecomment-168076202
*/
export class FilterExpression extends Expression {
override type = "FilterExpression";
constructor(
public operand: Expression,
public filter: Identifier | CallExpression
) {
super();
}
}
/**
* An operation with two sides, separated by the "is" operator.
*/
export class TestExpression extends Expression {
override type = "TestExpression";
constructor(
public operand: Expression,
public negate: boolean,
public test: Identifier // TODO: Add support for non-identifier tests
) {
super();
}
}
/**
* An operation with one side (operator on the left).
*/
export class UnaryExpression extends Expression {
override type = "UnaryExpression";
constructor(
public operator: Token,
public argument: Expression
) {
super();
}
}
/**
* Logical negation of an expression.
*/
export class LogicalNegationExpression extends Expression {
override type = "LogicalNegationExpression";
constructor(public argument: Expression) {
super();
}
}
export class SliceExpression extends Expression {
override type = "SliceExpression";
constructor(
public start: Expression | undefined = undefined,
public stop: Expression | undefined = undefined,
public step: Expression | undefined = undefined
) {
super();
}
}
export class KeywordArgumentExpression extends Expression {
override type = "KeywordArgumentExpression";
constructor(
public key: Identifier,
public value: Expression
) {
super();
}
}

58
node_modules/@huggingface/jinja/src/index.ts generated vendored Normal file
View File

@@ -0,0 +1,58 @@
/**
* @file Jinja templating engine
*
* A minimalistic JavaScript reimplementation of the [Jinja](https://github.com/pallets/jinja) templating engine,
* to support the chat templates. Special thanks to [Tyler Laceby](https://github.com/tlaceby) for his amazing
* ["Guide to Interpreters"](https://github.com/tlaceby/guide-to-interpreters-series) tutorial series,
* which provided the basis for this implementation.
*
* See the [Transformers documentation](https://huggingface.co/docs/transformers/main/en/chat_templating) for more information.
*
* @module index
*/
import { tokenize } from "./lexer";
import { parse } from "./parser";
import { Environment, Interpreter } from "./runtime";
import type { Program } from "./ast";
import type { StringValue } from "./runtime";
import { range } from "./utils";
export class Template {
parsed: Program;
/**
* @param {string} template The template string
*/
constructor(template: string) {
const tokens = tokenize(template, {
lstrip_blocks: true,
trim_blocks: true,
});
this.parsed = parse(tokens);
}
render(items: Record<string, unknown>): string {
// Create a new environment for this template
const env = new Environment();
// 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
for (const [key, value] of Object.entries(items)) {
env.set(key, value);
}
const interpreter = new Interpreter(env);
const result = interpreter.run(this.parsed) as StringValue;
return result.value;
}
}
export { Environment, Interpreter, tokenize, parse };

332
node_modules/@huggingface/jinja/src/lexer.ts generated vendored Normal file
View File

@@ -0,0 +1,332 @@
/**
* Represents tokens that our language understands in parsing.
*/
export const TOKEN_TYPES = Object.freeze({
Text: "Text", // The text between Jinja statements or expressions
NumericLiteral: "NumericLiteral", // e.g., 123
BooleanLiteral: "BooleanLiteral", // true or false
StringLiteral: "StringLiteral", // 'string'
Identifier: "Identifier", // Variables, functions, etc.
Equals: "Equals", // =
OpenParen: "OpenParen", // (
CloseParen: "CloseParen", // )
OpenStatement: "OpenStatement", // {%
CloseStatement: "CloseStatement", // %}
OpenExpression: "OpenExpression", // {{
CloseExpression: "CloseExpression", // }}
OpenSquareBracket: "OpenSquareBracket", // [
CloseSquareBracket: "CloseSquareBracket", // ]
OpenCurlyBracket: "OpenCurlyBracket", // {
CloseCurlyBracket: "CloseCurlyBracket", // }
Comma: "Comma", // ,
Dot: "Dot", // .
Colon: "Colon", // :
Pipe: "Pipe", // |
CallOperator: "CallOperator", // ()
AdditiveBinaryOperator: "AdditiveBinaryOperator", // + -
MultiplicativeBinaryOperator: "MultiplicativeBinaryOperator", // * / %
ComparisonBinaryOperator: "ComparisonBinaryOperator", // < > <= >= == !=
UnaryOperator: "UnaryOperator", // ! - +
// Keywords
Set: "Set",
If: "If",
For: "For",
In: "In",
Is: "Is",
NotIn: "NotIn",
Else: "Else",
EndIf: "EndIf",
ElseIf: "ElseIf",
EndFor: "EndFor",
And: "And",
Or: "Or",
Not: "UnaryOperator",
});
export type TokenType = keyof typeof TOKEN_TYPES;
/**
* 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,
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,
// Literals
true: TOKEN_TYPES.BooleanLiteral,
false: TOKEN_TYPES.BooleanLiteral,
});
/**
* Represents a single token in the template.
*/
export class Token {
/**
* Constructs a new Token.
* @param {string} value The raw value as seen inside the source code.
* @param {TokenType} type The type of token.
*/
constructor(
public value: string,
public type: TokenType
) {}
}
function isWord(char: string): boolean {
return /\w/.test(char);
}
function isInteger(char: string): boolean {
return /[0-9]/.test(char);
}
/**
* A data structure which contains a list of rules to test
*/
const ORDERED_MAPPING_TABLE: [string, TokenType][] = [
// Control sequences
["{%", TOKEN_TYPES.OpenStatement],
["%}", TOKEN_TYPES.CloseStatement],
["{{", TOKEN_TYPES.OpenExpression],
["}}", TOKEN_TYPES.CloseExpression],
// Single character tokens
["(", TOKEN_TYPES.OpenParen],
[")", TOKEN_TYPES.CloseParen],
["{", TOKEN_TYPES.OpenCurlyBracket],
["}", TOKEN_TYPES.CloseCurlyBracket],
["[", TOKEN_TYPES.OpenSquareBracket],
["]", TOKEN_TYPES.CloseSquareBracket],
[",", TOKEN_TYPES.Comma],
[".", TOKEN_TYPES.Dot],
[":", TOKEN_TYPES.Colon],
["|", TOKEN_TYPES.Pipe],
// Comparison operators
["<=", TOKEN_TYPES.ComparisonBinaryOperator],
[">=", TOKEN_TYPES.ComparisonBinaryOperator],
["==", TOKEN_TYPES.ComparisonBinaryOperator],
["!=", TOKEN_TYPES.ComparisonBinaryOperator],
["<", TOKEN_TYPES.ComparisonBinaryOperator],
[">", TOKEN_TYPES.ComparisonBinaryOperator],
// Arithmetic operators
["+", TOKEN_TYPES.AdditiveBinaryOperator],
["-", TOKEN_TYPES.AdditiveBinaryOperator],
["*", TOKEN_TYPES.MultiplicativeBinaryOperator],
["/", TOKEN_TYPES.MultiplicativeBinaryOperator],
["%", TOKEN_TYPES.MultiplicativeBinaryOperator],
// Assignment operator
["=", TOKEN_TYPES.Equals],
];
const ESCAPE_CHARACTERS = new Map([
["n", "\n"], // New line
["t", "\t"], // Horizontal tab
["r", "\r"], // Carriage return
["b", "\b"], // Backspace
["f", "\f"], // Form feed
["v", "\v"], // Vertical tab
["'", "'"], // Single quote
['"', '"'], // Double quote
["\\", "\\"], // Backslash
]);
export interface PreprocessOptions {
trim_blocks?: boolean;
lstrip_blocks?: boolean;
}
function preprocess(template: string, options: PreprocessOptions = {}): string {
// According to https://jinja.palletsprojects.com/en/3.0.x/templates/#whitespace-control
// In the default configuration:
// - a single trailing newline is stripped if present
// - other whitespace (spaces, tabs, newlines etc.) is returned unchanged
if (template.endsWith("\n")) {
template = template.slice(0, -1);
}
// 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) {
// The lstrip_blocks option can also be set to strip tabs and spaces from the
// beginning of a line to the start of a block. (Nothing will be stripped if
// there are other characters before the start of the block.)
template = template.replace(/^[ \t]*({[#%])/gm, "$1");
}
if (options.trim_blocks) {
// If an application configures Jinja to trim_blocks, the first newline after
// a template tag is removed automatically (like in PHP).
template = template.replace(/([#%]})\n/g, "$1");
}
return template
.replace(/{##}/g, "") // Remove comments
.replace(/-%}\s*/g, "%}")
.replace(/\s*{%-/g, "{%")
.replace(/-}}\s*/g, "}}")
.replace(/\s*{{-/g, "{{");
}
/**
* Generate a list of tokens from a source string.
*/
export function tokenize(source: string, options: PreprocessOptions = {}): Token[] {
const tokens: Token[] = [];
const src: string = preprocess(source, options);
let cursorPosition = 0;
const consumeWhile = (predicate: (char: string) => boolean): string => {
let str = "";
while (predicate(src[cursorPosition])) {
// Check for escaped characters
if (src[cursorPosition] === "\\") {
// Consume the backslash
++cursorPosition;
// Check for end of input
if (cursorPosition >= src.length) throw new SyntaxError("Unexpected end of input");
// Add the escaped character
const escaped = src[cursorPosition++];
const unescaped = ESCAPE_CHARACTERS.get(escaped);
if (unescaped === undefined) {
throw new SyntaxError(`Unexpected escaped character: ${escaped}`);
}
str += unescaped;
continue;
}
str += src[cursorPosition++];
if (cursorPosition >= src.length) throw new SyntaxError("Unexpected end of input");
}
return str;
};
// Build each token until end of input
main: while (cursorPosition < src.length) {
// First, consume all text that is outside of a Jinja statement or expression
const lastTokenType = tokens.at(-1)?.type;
if (
lastTokenType === undefined ||
lastTokenType === TOKEN_TYPES.CloseStatement ||
lastTokenType === TOKEN_TYPES.CloseExpression
) {
let text = "";
while (
cursorPosition < src.length &&
// Keep going until we hit the next Jinja statement or expression
!(src[cursorPosition] === "{" && (src[cursorPosition + 1] === "%" || src[cursorPosition + 1] === "{"))
) {
// Consume text
text += src[cursorPosition++];
}
// There is some text to add
if (text.length > 0) {
tokens.push(new Token(text, TOKEN_TYPES.Text));
continue;
}
}
// Consume (and ignore) all whitespace inside Jinja statements or expressions
consumeWhile((char) => /\s/.test(char));
// Handle multi-character tokens
const char = src[cursorPosition];
// Check for unary operators
if (char === "-" || char === "+") {
const lastTokenType = tokens.at(-1)?.type;
if (lastTokenType === TOKEN_TYPES.Text || lastTokenType === undefined) {
throw new SyntaxError(`Unexpected character: ${char}`);
}
switch (lastTokenType) {
case TOKEN_TYPES.Identifier:
case TOKEN_TYPES.NumericLiteral:
case TOKEN_TYPES.BooleanLiteral:
case TOKEN_TYPES.StringLiteral:
case TOKEN_TYPES.CloseParen:
case TOKEN_TYPES.CloseSquareBracket:
// Part of a binary operator
// a - 1, 1 - 1, true - 1, "apple" - 1, (1) - 1, a[1] - 1
// Continue parsing normally
break;
default: {
// Is part of a unary operator
// (-1), [-1], (1 + -1), not -1, -apple
++cursorPosition; // consume the unary operator
// Check for numbers following the unary operator
const num = consumeWhile(isInteger);
tokens.push(
new Token(`${char}${num}`, num.length > 0 ? TOKEN_TYPES.NumericLiteral : TOKEN_TYPES.UnaryOperator)
);
continue;
}
}
}
// 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;
continue main;
}
}
if (char === "'" || char === '"') {
++cursorPosition; // Skip the opening quote
const str = consumeWhile((c) => c !== char);
tokens.push(new Token(str, TOKEN_TYPES.StringLiteral));
++cursorPosition; // Skip the closing quote
continue;
}
if (isInteger(char)) {
const num = consumeWhile(isInteger);
tokens.push(new Token(num, TOKEN_TYPES.NumericLiteral));
continue;
}
if (isWord(char)) {
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));
}
continue;
}
throw new SyntaxError(`Unexpected character: ${char}`);
}
return tokens;
}

536
node_modules/@huggingface/jinja/src/parser.ts generated vendored Normal file
View File

@@ -0,0 +1,536 @@
import type { Token, TokenType } from "./lexer";
import { TOKEN_TYPES } from "./lexer";
import type { Statement } from "./ast";
import {
Program,
If,
For,
SetStatement,
MemberExpression,
CallExpression,
Identifier,
NumericLiteral,
StringLiteral,
BooleanLiteral,
ArrayLiteral,
ObjectLiteral,
BinaryExpression,
FilterExpression,
TestExpression,
UnaryExpression,
SliceExpression,
KeywordArgumentExpression,
TupleLiteral,
} from "./ast";
/**
* Generate the Abstract Syntax Tree (AST) from a list of tokens.
* Operator precedence can be found here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_precedence#table
*/
export function parse(tokens: Token[]): Program {
const program = new Program([]);
let current = 0;
/**
* Consume the next token if it matches the expected type, otherwise throw an error.
* @param type The expected token type
* @param error The error message to throw if the token does not match the expected type
* @returns The consumed token
*/
function expect(type: string, error: string): Token {
const prev = tokens[current++];
if (!prev || prev.type !== type) {
throw new Error(`Parser Error: ${error}. ${prev.type} !== ${type}.`);
}
return prev;
}
function parseAny(): Statement {
switch (tokens[current].type) {
case TOKEN_TYPES.Text:
return parseText();
case TOKEN_TYPES.OpenStatement:
return parseJinjaStatement();
case TOKEN_TYPES.OpenExpression:
return parseJinjaExpression();
default:
throw new SyntaxError(`Unexpected token type: ${tokens[current].type}`);
}
}
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 {
return current + types.length <= tokens.length && types.every((type, i) => type === tokens[current + i].type);
}
function parseText(): StringLiteral {
return new StringLiteral(expect(TOKEN_TYPES.Text, "Expected text token").value);
}
function parseJinjaStatement(): Statement {
// Consume {% %} tokens
expect(TOKEN_TYPES.OpenStatement, "Expected opening statement token");
let result;
switch (tokens[current].type) {
case TOKEN_TYPES.Set:
++current;
result = parseSetStatement();
expect(TOKEN_TYPES.CloseStatement, "Expected closing statement token");
break;
case TOKEN_TYPES.If:
++current;
result = parseIfStatement();
expect(TOKEN_TYPES.OpenStatement, "Expected {% token");
expect(TOKEN_TYPES.EndIf, "Expected endif token");
expect(TOKEN_TYPES.CloseStatement, "Expected %} token");
break;
case TOKEN_TYPES.For:
++current;
result = parseForStatement();
expect(TOKEN_TYPES.OpenStatement, "Expected {% token");
expect(TOKEN_TYPES.EndFor, "Expected endfor token");
expect(TOKEN_TYPES.CloseStatement, "Expected %} token");
break;
default:
throw new SyntaxError(`Unknown statement type: ${tokens[current].type}`);
}
return result;
}
function parseJinjaExpression(): Statement {
// Consume {{ }} tokens
expect(TOKEN_TYPES.OpenExpression, "Expected opening expression token");
const result = parseExpression();
expect(TOKEN_TYPES.CloseExpression, "Expected closing expression token");
return result;
}
// NOTE: `set` acts as both declaration statement and assignment expression
function parseSetStatement(): Statement {
const left = parseExpression();
if (is(TOKEN_TYPES.Equals)) {
++current;
const value = parseSetStatement();
return new SetStatement(left, value);
}
return left;
}
function parseIfStatement(): If {
const test = parseExpression();
expect(TOKEN_TYPES.CloseStatement, "Expected closing statement token");
const body: Statement[] = [];
const alternate: Statement[] = [];
// 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)
)
) {
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");
// keep going until we hit {% endif %}
while (
!(tokens[current]?.type === TOKEN_TYPES.OpenStatement && tokens[current + 1]?.type === TOKEN_TYPES.EndIf)
) {
alternate.push(parseAny());
}
}
}
return new If(test, body, alternate);
}
function parseExpressionSequence(primary = false): Statement {
const fn = primary ? parsePrimaryExpression : parseExpression;
const expressions = [fn()];
const isTuple = is(TOKEN_TYPES.Comma);
while (isTuple) {
++current; // consume comma
expressions.push(fn());
if (!is(TOKEN_TYPES.Comma)) {
break;
}
}
return isTuple ? new TupleLiteral(expressions) : expressions[0];
}
function parseForStatement(): For {
// e.g., `message` in `for message in messages`
const loopVariable = parseExpressionSequence(true); // should be an identifier
if (!(loopVariable instanceof Identifier || loopVariable instanceof TupleLiteral)) {
throw new SyntaxError(`Expected identifier/tuple for the loop variable, got ${loopVariable.type} instead`);
}
expect(TOKEN_TYPES.In, "Expected `in` keyword following loop variable");
// `messages` in `for message in messages`
const iterable = parseExpression();
expect(TOKEN_TYPES.CloseStatement, "Expected closing statement token");
// Body of for loop
const body: Statement[] = [];
// Keep going until we hit {% endfor
while (not(TOKEN_TYPES.OpenStatement, TOKEN_TYPES.EndFor)) {
body.push(parseAny());
}
return new For(loopVariable, iterable, body);
}
function parseExpression(): Statement {
// Choose parse function with lowest precedence
return parseTernaryExpression();
}
function parseTernaryExpression(): Statement {
const a = parseLogicalOrExpression();
if (is(TOKEN_TYPES.If)) {
// Ternary expression
++current; // consume if
const predicate = parseLogicalOrExpression();
expect(TOKEN_TYPES.Else, "Expected else token");
const b = parseLogicalOrExpression();
return new If(predicate, [a], [b]);
}
return a;
}
function parseLogicalOrExpression(): Statement {
let left = parseLogicalAndExpression();
while (is(TOKEN_TYPES.Or)) {
const operator = tokens[current];
++current;
const right = parseLogicalAndExpression();
left = new BinaryExpression(operator, left, right);
}
return left;
}
function parseLogicalAndExpression(): Statement {
let left = parseLogicalNegationExpression();
while (is(TOKEN_TYPES.And)) {
const operator = tokens[current];
++current;
const right = parseLogicalNegationExpression();
left = new BinaryExpression(operator, left, right);
}
return left;
}
function parseLogicalNegationExpression(): Statement {
let right: UnaryExpression | undefined;
// Try parse unary operators
while (is(TOKEN_TYPES.Not)) {
// not not ...
const operator = tokens[current];
++current;
const arg = parseLogicalNegationExpression(); // not test.x === not (test.x)
right = new UnaryExpression(operator, arg);
}
return right ?? parseComparisonExpression();
}
function parseComparisonExpression(): Statement {
// NOTE: membership has same precedence as comparison
// e.g., ('a' in 'apple' == 'b' in 'banana') evaluates as ('a' in ('apple' == ('b' in 'banana')))
let left = parseAdditiveExpression();
while (is(TOKEN_TYPES.ComparisonBinaryOperator) || is(TOKEN_TYPES.In) || is(TOKEN_TYPES.NotIn)) {
const operator = tokens[current];
++current;
const right = parseAdditiveExpression();
left = new BinaryExpression(operator, left, right);
}
return left;
}
function parseAdditiveExpression(): Statement {
let left = parseMultiplicativeExpression();
while (is(TOKEN_TYPES.AdditiveBinaryOperator)) {
const operator = tokens[current];
++current;
const right = parseMultiplicativeExpression();
left = new BinaryExpression(operator, left, right);
}
return left;
}
function parseCallMemberExpression(): Statement {
// Handle member expressions recursively
const member = parseMemberExpression(); // foo.x
if (is(TOKEN_TYPES.OpenParen)) {
// foo.x()
return parseCallExpression(member);
}
return member;
}
function parseCallExpression(callee: Statement): CallExpression {
let callExpression = new CallExpression(callee, parseArgs());
if (is(TOKEN_TYPES.OpenParen)) {
// foo.x()()
callExpression = parseCallExpression(callExpression);
}
return callExpression;
}
function parseArgs(): Statement[] {
// add (x + 5, foo())
expect(TOKEN_TYPES.OpenParen, "Expected opening parenthesis for arguments list");
const args = parseArgumentsList();
expect(TOKEN_TYPES.CloseParen, "Expected closing parenthesis for arguments list");
return args;
}
function parseArgumentsList(): Statement[] {
// comma-separated arguments list
const args = [];
while (!is(TOKEN_TYPES.CloseParen)) {
let 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, value);
}
args.push(argument);
if (is(TOKEN_TYPES.Comma)) {
++current; // consume comma
}
}
return args;
}
function parseMemberExpressionArgumentsList(): Statement {
// NOTE: This also handles slice expressions colon-separated arguments list
// e.g., ['test'], [0], [:2], [1:], [1:2], [1:2:3]
const slices: (Statement | undefined)[] = [];
let isSlice = false;
while (!is(TOKEN_TYPES.CloseSquareBracket)) {
if (is(TOKEN_TYPES.Colon)) {
// A case where a default is used
// e.g., [:2] will be parsed as [undefined, 2]
slices.push(undefined);
++current; // consume colon
isSlice = true;
} else {
slices.push(parseExpression());
if (is(TOKEN_TYPES.Colon)) {
++current; // consume colon after expression, if it exists
isSlice = true;
}
}
}
if (slices.length === 0) {
// []
throw new SyntaxError(`Expected at least one argument for member/slice expression`);
}
if (isSlice) {
if (slices.length > 3) {
throw new SyntaxError(`Expected 0-3 arguments for slice expression`);
}
return new SliceExpression(...slices);
}
return slices[0] as Statement; // normal member expression
}
function parseMemberExpression(): Statement {
let object = parsePrimaryExpression();
while (is(TOKEN_TYPES.Dot) || is(TOKEN_TYPES.OpenSquareBracket)) {
const operator = tokens[current]; // . or [
++current;
let property: Statement;
const computed = operator.type !== TOKEN_TYPES.Dot;
if (computed) {
// computed (i.e., bracket notation: obj[expr])
property = parseMemberExpressionArgumentsList();
expect(TOKEN_TYPES.CloseSquareBracket, "Expected closing square bracket");
} else {
// non-computed (i.e., dot notation: obj.expr)
property = parsePrimaryExpression(); // should be an identifier
if (property.type !== "Identifier") {
throw new SyntaxError(`Expected identifier following dot operator`);
}
}
object = new MemberExpression(object, property, computed);
}
return object;
}
function parseMultiplicativeExpression(): Statement {
let left = parseTestExpression();
// Multiplicative operators have higher precedence than test expressions
// e.g., (4 * 4 is divisibleby(2)) evaluates as (4 * (4 is divisibleby(2)))
while (is(TOKEN_TYPES.MultiplicativeBinaryOperator)) {
const operator = tokens[current];
++current;
const right = parseTestExpression();
left = new BinaryExpression(operator, left, right);
}
return left;
}
function parseTestExpression(): Statement {
let operand = parseFilterExpression();
while (is(TOKEN_TYPES.Is)) {
// Support chaining tests
++current; // consume is
const negate = is(TOKEN_TYPES.Not);
if (negate) {
++current; // consume not
}
let filter = parsePrimaryExpression();
if (filter instanceof BooleanLiteral) {
// Special case: treat boolean literals as identifiers
filter = new Identifier(filter.value.toString());
}
if (!(filter instanceof Identifier)) {
throw new SyntaxError(`Expected identifier for the test`);
}
// TODO: Add support for non-identifier tests
operand = new TestExpression(operand, negate, filter);
}
return operand;
}
function parseFilterExpression(): Statement {
let operand = parseCallMemberExpression();
while (is(TOKEN_TYPES.Pipe)) {
// Support chaining filters
++current; // consume pipe
let filter = parsePrimaryExpression(); // should be an identifier
if (!(filter instanceof Identifier)) {
throw new SyntaxError(`Expected identifier for the filter`);
}
if (is(TOKEN_TYPES.OpenParen)) {
filter = parseCallExpression(filter);
}
operand = new FilterExpression(operand, filter as Identifier | CallExpression);
}
return operand;
}
function parsePrimaryExpression(): Statement {
// Primary expression: number, string, identifier, function call, parenthesized expression
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 === "true");
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
return expression;
}
case TOKEN_TYPES.OpenSquareBracket: {
++current; // consume opening square bracket
const values = [];
while (!is(TOKEN_TYPES.CloseSquareBracket)) {
values.push(parseExpression());
if (is(TOKEN_TYPES.Comma)) {
++current; // consume comma
}
}
++current; // consume closing square bracket
return new ArrayLiteral(values);
}
case TOKEN_TYPES.OpenCurlyBracket: {
++current; // consume opening curly bracket
const values = new Map();
while (!is(TOKEN_TYPES.CloseCurlyBracket)) {
const key = parseExpression();
expect(TOKEN_TYPES.Colon, "Expected colon between key and value in object literal");
const value = parseExpression();
values.set(key, value);
if (is(TOKEN_TYPES.Comma)) {
++current; // consume comma
}
}
++current; // consume closing curly bracket
return new ObjectLiteral(values);
}
default:
throw new SyntaxError(`Unexpected token: ${token.type}`);
}
}
while (current < tokens.length) {
program.body.push(parseAny());
}
return program;
}

918
node_modules/@huggingface/jinja/src/runtime.ts generated vendored Normal file
View File

@@ -0,0 +1,918 @@
import type {
NumericLiteral,
StringLiteral,
BooleanLiteral,
ArrayLiteral,
Statement,
Program,
If,
For,
SetStatement,
MemberExpression,
CallExpression,
Identifier,
BinaryExpression,
FilterExpression,
TestExpression,
UnaryExpression,
SliceExpression,
KeywordArgumentExpression,
ObjectLiteral,
TupleLiteral,
} from "./ast";
import { slice, titleCase } from "./utils";
export type AnyRuntimeValue =
| NumericValue
| StringValue
| BooleanValue
| ObjectValue
| ArrayValue
| FunctionValue
| NullValue
| UndefinedValue;
/**
* Abstract base class for all Runtime values.
* Should not be instantiated directly.
*/
abstract class RuntimeValue<T> {
type = "RuntimeValue";
value: T;
/**
* A collection of built-in functions for this type.
*/
builtins = new Map<string, AnyRuntimeValue>();
/**
* Creates a new RuntimeValue.
*/
constructor(value: T = undefined as unknown as T) {
this.value = value;
}
/**
* Determines truthiness or falsiness of the runtime value.
* This function should be overridden by subclasses if it has custom truthiness criteria.
* @returns {BooleanValue} BooleanValue(true) if the value is truthy, BooleanValue(false) otherwise.
*/
__bool__(): BooleanValue {
return new BooleanValue(!!this.value);
}
}
/**
* Represents a numeric value at runtime.
*/
export class NumericValue extends RuntimeValue<number> {
override type = "NumericValue";
}
/**
* Represents a string value at runtime.
*/
export class StringValue extends RuntimeValue<string> {
override type = "StringValue";
override builtins = new Map<string, AnyRuntimeValue>([
[
"upper",
new FunctionValue(() => {
return new StringValue(this.value.toUpperCase());
}),
],
[
"lower",
new FunctionValue(() => {
return new StringValue(this.value.toLowerCase());
}),
],
[
"strip",
new FunctionValue(() => {
return new StringValue(this.value.trim());
}),
],
[
"title",
new FunctionValue(() => {
return new StringValue(titleCase(this.value));
}),
],
["length", new NumericValue(this.value.length)],
]);
}
/**
* Represents a boolean value at runtime.
*/
export class BooleanValue extends RuntimeValue<boolean> {
override type = "BooleanValue";
}
/**
* Represents an Object value at runtime.
*/
export class ObjectValue extends RuntimeValue<Map<string, AnyRuntimeValue>> {
override type = "ObjectValue";
/**
* NOTE: necessary to override since all JavaScript arrays are considered truthy,
* while only non-empty Python arrays are consider truthy.
*
* e.g.,
* - JavaScript: {} && 5 -> 5
* - Python: {} and 5 -> {}
*/
override __bool__(): BooleanValue {
return new BooleanValue(this.value.size > 0);
}
override builtins: Map<string, AnyRuntimeValue> = new Map<string, AnyRuntimeValue>([
[
"get",
new FunctionValue(([key, defaultValue]) => {
if (!(key instanceof StringValue)) {
throw new Error(`Object key must be a string: got ${key.type}`);
}
return this.value.get(key.value) ?? defaultValue ?? new NullValue();
}),
],
[
"items",
new FunctionValue(() => {
return new ArrayValue(
Array.from(this.value.entries()).map(([key, value]) => new ArrayValue([new StringValue(key), value]))
);
}),
],
]);
}
/**
* Represents an Array value at runtime.
*/
export class ArrayValue extends RuntimeValue<AnyRuntimeValue[]> {
override type = "ArrayValue";
override builtins = new Map<string, AnyRuntimeValue>([["length", new NumericValue(this.value.length)]]);
/**
* NOTE: necessary to override since all JavaScript arrays are considered truthy,
* while only non-empty Python arrays are consider truthy.
*
* e.g.,
* - JavaScript: [] && 5 -> 5
* - Python: [] and 5 -> []
*/
override __bool__(): BooleanValue {
return new BooleanValue(this.value.length > 0);
}
}
/**
* Represents a Tuple value at runtime.
* NOTE: We extend ArrayValue since JavaScript does not have a built-in Tuple type.
*/
export class TupleValue extends ArrayValue {
override type = "TupleValue";
}
/**
* Represents a Function value at runtime.
*/
export class FunctionValue extends RuntimeValue<(args: AnyRuntimeValue[], scope: Environment) => AnyRuntimeValue> {
override type = "FunctionValue";
}
/**
* Represents a Null value at runtime.
*/
export class NullValue extends RuntimeValue<null> {
override type = "NullValue";
}
/**
* Represents an Undefined value at runtime.
*/
export class UndefinedValue extends RuntimeValue<undefined> {
override type = "UndefinedValue";
}
/**
* Represents the current environment (scope) at runtime.
*/
export class Environment {
/**
* The variables declared in this environment.
*/
variables: Map<string, AnyRuntimeValue> = new Map([
[
"namespace",
new FunctionValue((args) => {
if (args.length === 0) {
return new ObjectValue(new Map());
}
if (args.length !== 1 || !(args[0] instanceof ObjectValue)) {
throw new Error("`namespace` expects either zero arguments or a single object argument");
}
return args[0];
}),
],
]);
/**
* The tests available in this environment.
*/
tests: Map<string, (...value: AnyRuntimeValue[]) => boolean> = new Map([
["boolean", (operand) => operand.type === "BooleanValue"],
["callable", (operand) => operand instanceof FunctionValue],
[
"odd",
(operand) => {
if (operand.type !== "NumericValue") {
throw new Error(`Cannot apply test "odd" to type: ${operand.type}`);
}
return (operand as NumericValue).value % 2 !== 0;
},
],
[
"even",
(operand) => {
if (operand.type !== "NumericValue") {
throw new Error(`Cannot apply test "even" to type: ${operand.type}`);
}
return (operand as NumericValue).value % 2 === 0;
},
],
["false", (operand) => operand.type === "BooleanValue" && !(operand as BooleanValue).value],
["true", (operand) => operand.type === "BooleanValue" && (operand as BooleanValue).value],
["number", (operand) => operand.type === "NumericValue"],
["integer", (operand) => operand.type === "NumericValue" && Number.isInteger((operand as NumericValue).value)],
["iterable", (operand) => operand instanceof ArrayValue || operand instanceof StringValue],
[
"lower",
(operand) => {
const str = (operand as StringValue).value;
return operand.type === "StringValue" && str === str.toLowerCase();
},
],
[
"upper",
(operand) => {
const str = (operand as StringValue).value;
return operand.type === "StringValue" && str === str.toUpperCase();
},
],
["none", (operand) => operand.type === "NullValue"],
["defined", (operand) => operand.type !== "UndefinedValue"],
["undefined", (operand) => operand.type === "UndefinedValue"],
["equalto", (a, b) => a.value === b.value],
]);
constructor(public parent?: Environment) {}
/**
* Set the value of a variable in the current environment.
*/
set(name: string, value: unknown): AnyRuntimeValue {
return this.declareVariable(name, convertToRuntimeValues(value));
}
private declareVariable(name: string, value: AnyRuntimeValue): AnyRuntimeValue {
if (this.variables.has(name)) {
throw new SyntaxError(`Variable already declared: ${name}`);
}
this.variables.set(name, value);
return value;
}
// private assignVariable(name: string, value: AnyRuntimeValue): AnyRuntimeValue {
// const env = this.resolve(name);
// env.variables.set(name, value);
// return value;
// }
/**
* Set variable in the current scope.
* See https://jinja.palletsprojects.com/en/3.0.x/templates/#assignments for more information.
*/
setVariable(name: string, value: AnyRuntimeValue): AnyRuntimeValue {
this.variables.set(name, value);
return value;
}
/**
* Resolve the environment in which the variable is declared.
* @param {string} name The name of the variable.
* @returns {Environment} The environment in which the variable is declared.
*/
private resolve(name: string): Environment {
if (this.variables.has(name)) {
return this;
}
// Traverse scope chain
if (this.parent) {
return this.parent.resolve(name);
}
throw new Error(`Unknown variable: ${name}`);
}
lookupVariable(name: string): AnyRuntimeValue {
try {
return this.resolve(name).variables.get(name) ?? new UndefinedValue();
} catch {
return new UndefinedValue();
}
}
}
export class Interpreter {
global: Environment;
constructor(env?: Environment) {
this.global = env ?? new Environment();
}
/**
* Run the program.
*/
run(program: Program): AnyRuntimeValue {
return this.evaluate(program, this.global);
}
/**
* Evaluates expressions following the binary operation type.
*/
private evaluateBinaryExpression(node: BinaryExpression, environment: Environment): AnyRuntimeValue {
const left = this.evaluate(node.left, environment);
// Logical operators
// NOTE: Short-circuiting is handled by the `evaluate` function
switch (node.operator.value) {
case "and":
return left.__bool__().value ? this.evaluate(node.right, environment) : left;
case "or":
return left.__bool__().value ? left : this.evaluate(node.right, environment);
}
// Equality operators
const right = this.evaluate(node.right, environment);
switch (node.operator.value) {
case "==":
return new BooleanValue(left.value == right.value);
case "!=":
return new BooleanValue(left.value != right.value);
}
if (left instanceof UndefinedValue || right instanceof UndefinedValue) {
throw new Error("Cannot perform operation 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) {
// Evaulate pure numeric operations with binary operators.
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 "/":
return new NumericValue(left.value / right.value);
case "%":
return new NumericValue(left.value % right.value);
// Comparison operators
case "<":
return new BooleanValue(left.value < right.value);
case ">":
return new BooleanValue(left.value > right.value);
case ">=":
return new BooleanValue(left.value >= right.value);
case "<=":
return new BooleanValue(left.value <= right.value);
}
} else if (left instanceof ArrayValue && right instanceof ArrayValue) {
// Evaluate array operands with binary operator.
switch (node.operator.value) {
case "+":
return new ArrayValue(left.value.concat(right.value));
}
} else if (right instanceof ArrayValue) {
const member = right.value.find((x) => x.value === left.value) !== undefined;
switch (node.operator.value) {
case "in":
return new BooleanValue(member);
case "not in":
return new BooleanValue(!member);
}
}
if (left instanceof StringValue || right instanceof StringValue) {
// Support string concatenation as long as at least one operand is a string
switch (node.operator.value) {
case "+":
return new StringValue(left.value.toString() + right.value.toString());
}
}
if (left instanceof StringValue && right instanceof StringValue) {
switch (node.operator.value) {
case "in":
return new BooleanValue(right.value.includes(left.value));
case "not in":
return new BooleanValue(!right.value.includes(left.value));
}
}
if (left instanceof StringValue && right instanceof ObjectValue) {
switch (node.operator.value) {
case "in":
return new BooleanValue(right.value.has(left.value));
case "not in":
return new BooleanValue(!right.value.has(left.value));
}
}
throw new SyntaxError(`Unknown operator "${node.operator.value}" between ${left.type} and ${right.type}`);
}
/**
* Evaluates expressions following the filter operation type.
*/
private evaluateFilterExpression(node: FilterExpression, environment: Environment): AnyRuntimeValue {
const operand = this.evaluate(node.operand, environment);
// For now, we only support the built-in filters
// TODO: Add support for non-identifier filters
// e.g., functions which return filters: {{ numbers | select("odd") }}
// TODO: Add support for user-defined filters
// const filter = environment.lookupVariable(node.filter.value);
// if (!(filter instanceof FunctionValue)) {
// throw new Error(`Filter must be a function: got ${filter.type}`);
// }
// return filter.value([operand], environment);
// https://jinja.palletsprojects.com/en/3.0.x/templates/#list-of-builtin-filters
if (node.filter.type === "Identifier") {
const filter = node.filter as Identifier;
if (operand instanceof ArrayValue) {
switch (filter.value) {
case "list":
return operand;
case "first":
return operand.value[0];
case "last":
return operand.value[operand.value.length - 1];
case "length":
return new NumericValue(operand.value.length);
case "reverse":
return new ArrayValue(operand.value.reverse());
case "sort":
return new ArrayValue(
operand.value.sort((a, b) => {
if (a.type !== b.type) {
throw new Error(`Cannot compare different types: ${a.type} and ${b.type}`);
}
switch (a.type) {
case "NumericValue":
return (a as NumericValue).value - (b as NumericValue).value;
case "StringValue":
return (a as StringValue).value.localeCompare((b as StringValue).value);
default:
throw new Error(`Cannot compare type: ${a.type}`);
}
})
);
default:
throw new Error(`Unknown ArrayValue filter: ${filter.value}`);
}
} else if (operand instanceof StringValue) {
switch (filter.value) {
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 "trim":
return new StringValue(operand.value.trim());
default:
throw new Error(`Unknown StringValue filter: ${filter.value}`);
}
} else if (operand instanceof NumericValue) {
switch (filter.value) {
case "abs":
return new NumericValue(Math.abs(operand.value));
default:
throw new Error(`Unknown NumericValue filter: ${filter.value}`);
}
} else if (operand instanceof ObjectValue) {
switch (filter.value) {
case "items":
return new ArrayValue(
Array.from(operand.value.entries()).map(([key, value]) => new ArrayValue([new StringValue(key), value]))
);
case "length":
return new NumericValue(operand.value.size);
default:
throw new Error(`Unknown ObjectValue 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;
if (filter.callee.type !== "Identifier") {
throw new Error(`Unknown filter: ${filter.callee.type}`);
}
const filterName = (filter.callee as Identifier).value;
if (operand instanceof ArrayValue) {
switch (filterName) {
case "selectattr": {
if (operand.value.some((x) => !(x instanceof ObjectValue))) {
throw new Error("`selectattr` can only be applied to array of objects");
}
if (filter.args.some((x) => x.type !== "StringLiteral")) {
throw new Error("arguments of `selectattr` must be strings");
}
const [attr, testName, value] = filter.args.map((x) => this.evaluate(x, environment)) as StringValue[];
let testFunction: (...x: AnyRuntimeValue[]) => boolean;
if (testName) {
// Get the test function from the environment
const test = environment.tests.get(testName.value);
if (!test) {
throw new Error(`Unknown test: ${testName.value}`);
}
testFunction = test;
} else {
// Default to truthiness of first argument
testFunction = (...x: AnyRuntimeValue[]) => x[0].__bool__().value;
}
// Filter the array using the test function
const filtered = (operand.value as ObjectValue[]).filter((item) => {
const a = item.value.get(attr.value);
if (a) {
return testFunction(a, value);
}
return false;
});
return new ArrayValue(filtered);
}
}
throw new Error(`Unknown ArrayValue filter: ${filterName}`);
} else {
throw new Error(`Cannot apply filter "${filterName}" to type: ${operand.type}`);
}
}
throw new Error(`Unknown filter: ${node.filter.type}`);
}
/**
* Evaluates expressions following the test operation type.
*/
private evaluateTestExpression(node: TestExpression, environment: Environment): BooleanValue {
// For now, we only support the built-in tests
// https://jinja.palletsprojects.com/en/3.0.x/templates/#list-of-builtin-tests
//
// TODO: Add support for non-identifier tests. e.g., divisibleby(number)
const operand = this.evaluate(node.operand, environment);
const test = environment.tests.get(node.test.value);
if (!test) {
throw new Error(`Unknown test: ${node.test.value}`);
}
const result = test(operand);
return new BooleanValue(node.negate ? !result : result);
}
/**
* Evaluates expressions following the unary operation type.
*/
private evaluateUnaryExpression(node: UnaryExpression, environment: Environment): AnyRuntimeValue {
const argument = this.evaluate(node.argument, environment);
switch (node.operator.value) {
case "not":
return new BooleanValue(!argument.value);
default:
throw new SyntaxError(`Unknown operator: ${node.operator.value}`);
}
}
private evalProgram(program: Program, environment: Environment): StringValue {
return this.evaluateBlock(program.body, environment);
}
private evaluateBlock(statements: Statement[], environment: Environment): StringValue {
// Jinja templates always evaluate to a String,
// so we accumulate the result of each statement into a final string
let result = "";
for (const statement of statements) {
const lastEvaluated = this.evaluate(statement, environment);
if (lastEvaluated.type !== "NullValue" && lastEvaluated.type !== "UndefinedValue") {
result += lastEvaluated.value;
}
}
return new StringValue(result);
}
private evaluateIdentifier(node: Identifier, environment: Environment): AnyRuntimeValue {
return environment.lookupVariable(node.value);
}
private evaluateCallExpression(expr: CallExpression, environment: Environment): AnyRuntimeValue {
// Accumulate all keyword arguments into a single object, which will be
// used as the final argument in the call function.
const args: AnyRuntimeValue[] = [];
const kwargs = new Map();
for (const argument of expr.args) {
if (argument.type === "KeywordArgumentExpression") {
const kwarg = argument as KeywordArgumentExpression;
kwargs.set(kwarg.key.value, this.evaluate(kwarg.value, environment));
} else {
args.push(this.evaluate(argument, environment));
}
}
if (kwargs.size > 0) {
args.push(new ObjectValue(kwargs));
}
const fn = this.evaluate(expr.callee, environment);
if (fn.type !== "FunctionValue") {
throw new Error(`Cannot call something that is not a function: got ${fn.type}`);
}
return (fn as FunctionValue).value(args, environment);
}
private evaluateSliceExpression(
object: AnyRuntimeValue,
expr: SliceExpression,
environment: Environment
): ArrayValue | StringValue {
if (!(object instanceof ArrayValue || object instanceof StringValue)) {
throw new Error("Slice object must be an array or string");
}
const start = this.evaluate(expr.start, environment);
const stop = this.evaluate(expr.stop, environment);
const step = this.evaluate(expr.step, environment);
// Validate arguments
if (!(start instanceof NumericValue || start instanceof UndefinedValue)) {
throw new Error("Slice start must be numeric or undefined");
}
if (!(stop instanceof NumericValue || stop instanceof UndefinedValue)) {
throw new Error("Slice stop must be numeric or undefined");
}
if (!(step instanceof NumericValue || step instanceof UndefinedValue)) {
throw new Error("Slice step must be numeric or undefined");
}
if (object instanceof ArrayValue) {
return new ArrayValue(slice(object.value, start.value, stop.value, step.value));
} else {
return new StringValue(slice(Array.from(object.value), start.value, stop.value, step.value).join(""));
}
}
private evaluateMemberExpression(expr: MemberExpression, environment: Environment): AnyRuntimeValue {
const object = this.evaluate(expr.object, environment);
let property;
if (expr.computed) {
if (expr.property.type === "SliceExpression") {
return this.evaluateSliceExpression(object, expr.property as SliceExpression, environment);
} else {
property = this.evaluate(expr.property, environment);
}
} else {
property = new StringValue((expr.property as Identifier).value);
}
let value;
if (object instanceof ObjectValue) {
if (!(property instanceof StringValue)) {
throw new Error(`Cannot access property with non-string: got ${property.type}`);
}
value = object.value.get(property.value) ?? object.builtins.get(property.value);
} else if (object instanceof ArrayValue || object instanceof StringValue) {
if (property instanceof NumericValue) {
value = object.value.at(property.value);
if (object instanceof StringValue) {
value = new StringValue(object.value.at(property.value));
}
} else if (property instanceof StringValue) {
value = object.builtins.get(property.value);
} else {
throw new Error(`Cannot access property with non-string/non-number: got ${property.type}`);
}
} else {
if (!(property instanceof StringValue)) {
throw new Error(`Cannot access property with non-string: got ${property.type}`);
}
value = object.builtins.get(property.value);
}
return value instanceof RuntimeValue ? value : new UndefinedValue();
}
private evaluateSet(node: SetStatement, environment: Environment): NullValue {
const rhs = this.evaluate(node.value, environment);
if (node.assignee.type === "Identifier") {
const variableName = (node.assignee as Identifier).value;
environment.setVariable(variableName, rhs);
} else if (node.assignee.type === "MemberExpression") {
const member = node.assignee as MemberExpression;
const object = this.evaluate(member.object, environment);
if (!(object instanceof ObjectValue)) {
throw new Error("Cannot assign to member of non-object");
}
if (member.property.type !== "Identifier") {
throw new Error("Cannot assign to member with non-identifier property");
}
object.value.set((member.property as Identifier).value, rhs);
} else {
throw new Error(`Invalid LHS inside assignment expression: ${JSON.stringify(node.assignee)}`);
}
return new NullValue();
}
private evaluateIf(node: If, environment: Environment): StringValue {
const test = this.evaluate(node.test, environment);
return this.evaluateBlock(test.__bool__().value ? node.body : node.alternate, environment);
}
private evaluateFor(node: For, environment: Environment): StringValue {
// Scope for the for loop
const scope = new Environment(environment);
const iterable = this.evaluate(node.iterable, scope);
if (!(iterable instanceof ArrayValue)) {
throw new Error(`Expected iterable type in for loop: got ${iterable.type}`);
}
let result = "";
for (let i = 0; i < iterable.value.length; ++i) {
// Update the loop variable
// TODO: Only create object once, then update value?
const loop = new Map([
["index", new NumericValue(i + 1)],
["index0", new NumericValue(i)],
["revindex", new NumericValue(iterable.value.length - i)],
["revindex0", new NumericValue(iterable.value.length - i - 1)],
["first", new BooleanValue(i === 0)],
["last", new BooleanValue(i === iterable.value.length - 1)],
["length", new NumericValue(iterable.value.length)],
["previtem", i > 0 ? iterable.value[i - 1] : new UndefinedValue()],
["nextitem", i < iterable.value.length - 1 ? iterable.value[i + 1] : new UndefinedValue()],
] as [string, AnyRuntimeValue][]);
scope.setVariable("loop", new ObjectValue(loop));
const current = iterable.value[i];
// For this iteration, set the loop variable to the current element
if (node.loopvar.type === "Identifier") {
scope.setVariable((node.loopvar as Identifier).value, current);
} else if (node.loopvar.type === "TupleLiteral") {
const loopvar = node.loopvar as TupleLiteral;
if (current.type !== "ArrayValue") {
throw new Error(`Cannot unpack non-iterable type: ${current.type}`);
}
const c = current as ArrayValue;
// check if too few or many items to unpack
if (loopvar.value.length !== c.value.length) {
throw new Error(`Too ${loopvar.value.length > c.value.length ? "few" : "many"} items to unpack`);
}
for (let j = 0; j < loopvar.value.length; ++j) {
if (loopvar.value[j].type !== "Identifier") {
throw new Error(`Cannot unpack non-identifier type: ${loopvar.value[j].type}`);
}
scope.setVariable((loopvar.value[j] as Identifier).value, c.value[j]);
}
}
// Evaluate the body of the for loop
const evaluated = this.evaluateBlock(node.body, scope);
result += evaluated.value;
}
return new StringValue(result);
}
evaluate(statement: Statement | undefined, environment: Environment): AnyRuntimeValue {
if (statement === undefined) return new UndefinedValue();
switch (statement.type) {
// Program
case "Program":
return this.evalProgram(statement as Program, environment);
// Statements
case "Set":
return this.evaluateSet(statement as SetStatement, environment);
case "If":
return this.evaluateIf(statement as If, environment);
case "For":
return this.evaluateFor(statement as For, environment);
// Expressions
case "NumericLiteral":
return new NumericValue(Number((statement as NumericLiteral).value));
case "StringLiteral":
return new StringValue((statement as StringLiteral).value);
case "BooleanLiteral":
return new BooleanValue((statement as BooleanLiteral).value);
case "ArrayLiteral":
return new ArrayValue((statement as ArrayLiteral).value.map((x) => this.evaluate(x, environment)));
case "TupleLiteral":
return new TupleValue((statement as TupleLiteral).value.map((x) => this.evaluate(x, environment)));
case "ObjectLiteral": {
const mapping = new Map();
for (const [key, value] of (statement as ObjectLiteral).value) {
const evaluatedKey = this.evaluate(key, environment);
if (!(evaluatedKey instanceof StringValue)) {
throw new Error(`Object keys must be strings: got ${evaluatedKey.type}`);
}
mapping.set(evaluatedKey.value, this.evaluate(value, environment));
}
return new ObjectValue(mapping);
}
case "Identifier":
return this.evaluateIdentifier(statement as Identifier, environment);
case "CallExpression":
return this.evaluateCallExpression(statement as CallExpression, environment);
case "MemberExpression":
return this.evaluateMemberExpression(statement as MemberExpression, environment);
case "UnaryExpression":
return this.evaluateUnaryExpression(statement as UnaryExpression, environment);
case "BinaryExpression":
return this.evaluateBinaryExpression(statement as BinaryExpression, environment);
case "FilterExpression":
return this.evaluateFilterExpression(statement as FilterExpression, environment);
case "TestExpression":
return this.evaluateTestExpression(statement as TestExpression, environment);
default:
throw new SyntaxError(`Unknown node type: ${statement.type}`);
}
}
}
/**
* Helper function to convert JavaScript values to runtime values.
*/
function convertToRuntimeValues(input: unknown): AnyRuntimeValue {
switch (typeof input) {
case "number":
return new NumericValue(input);
case "string":
return new StringValue(input);
case "boolean":
return new BooleanValue(input);
case "object":
if (input === null) {
return new NullValue();
} else if (Array.isArray(input)) {
return new ArrayValue(input.map(convertToRuntimeValues));
} else {
return new ObjectValue(
new Map(Object.entries(input).map(([key, value]) => [key, convertToRuntimeValues(value)]))
);
}
case "function":
// Wrap the user's function in a runtime function
// eslint-disable-next-line @typescript-eslint/no-unused-vars
return new FunctionValue((args, _scope) => {
// NOTE: `_scope` is not used since it's in the global scope
const result = input(...args.map((x) => x.value)) ?? null; // map undefined -> null
return convertToRuntimeValues(result);
});
default:
throw new Error(`Cannot convert to runtime value: ${input}`);
}
}

54
node_modules/@huggingface/jinja/src/utils.ts generated vendored Normal file
View File

@@ -0,0 +1,54 @@
/**
* Function that mimics Python's range() function.
* @param start The start value of the range.
* @param stop The stop value of the range. If not provided, start will be 0 and stop will be the provided start value.
* @param step The step value of the range. Defaults to 1.
* @returns The range of numbers.
*/
export function range(start: number, stop?: number, step = 1): number[] {
if (stop === undefined) {
stop = start;
start = 0;
}
const result: number[] = [];
for (let i = start; i < stop; i += step) {
result.push(i);
}
return result;
}
/**
* Function that mimics Python's array slicing.
* @param array The array to slice.
* @param start The start index of the slice. Defaults to 0.
* @param stop The last index of the slice. Defaults to `array.length`.
* @param step The step value of the slice. Defaults to 1.
* @returns The sliced array.
*/
export function slice<T>(array: T[], start?: number, stop?: number, step = 1): T[] {
const direction = Math.sign(step);
if (direction >= 0) {
start = (start ??= 0) < 0 ? Math.max(array.length + start, 0) : Math.min(start, array.length);
stop = (stop ??= array.length) < 0 ? Math.max(array.length + stop, 0) : Math.min(stop, array.length);
} else {
start = (start ??= array.length - 1) < 0 ? Math.max(array.length + start, -1) : Math.min(start, array.length - 1);
stop = (stop ??= -1) < -1 ? Math.max(array.length + stop, -1) : Math.min(stop, array.length - 1);
}
const result: T[] = [];
for (let i = start; direction * i < direction * stop; i += step) {
result.push(array[i]);
}
return result;
}
/**
* Function that mimics Python's string.title() function.
* @param value The string to title case.
* @returns The title cased string.
*/
export function titleCase(value: string): string {
return value.replace(/\b\w/g, (c) => c.toUpperCase());
}

19
node_modules/@huggingface/jinja/tsconfig.json generated vendored Normal file
View File

@@ -0,0 +1,19 @@
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"lib": ["ES2022", "DOM"],
"module": "ESNext",
"moduleResolution": "node",
"target": "ESNext",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"skipLibCheck": true,
"noImplicitOverride": true,
"outDir": "./dist",
"declaration": true
},
"include": ["src", "index.ts"],
"exclude": ["dist"]
}