spec-cat 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.output/nitro.json +1 -1
- package/.output/public/_nuxt/{Bqlz6CoK.js → BE_75kPa.js} +1 -1
- package/.output/public/_nuxt/{B2wdmh_w.js → BJ7m4fRW.js} +53 -53
- package/.output/public/_nuxt/{KNuzSjk0.js → CCNYUZ9m.js} +1 -1
- package/.output/public/_nuxt/{BvosqTnx.js → DGtcdWVl.js} +1 -1
- package/.output/public/_nuxt/DxEx-kFx.js +1 -0
- package/.output/public/_nuxt/{BwcbSlWF.js → DyMq_cQC.js} +2 -2
- package/.output/public/_nuxt/{COTT6rNZ.js → _cj5lOdZ.js} +1 -1
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/3a0aacc1-0bd1-4d15-8b8a-3cee48cbfc69.json +1 -0
- package/.output/public/_nuxt/{BUOk7wkI.js → gDut6QrP.js} +1 -1
- package/.output/public/_nuxt/{C5wk2twv.js → nJpWpjzg.js} +1 -1
- package/.output/public/_nuxt/{DBab5Zcv.js → waQ9fPC1.js} +1 -1
- package/.output/server/chunks/_/codexProvider.mjs +64 -18
- package/.output/server/chunks/_/codexProvider.mjs.map +1 -1
- package/.output/server/chunks/build/client.precomputed.mjs +1 -1
- package/.output/server/chunks/build/client.precomputed.mjs.map +1 -1
- package/.output/server/chunks/nitro/nitro.mjs +702 -703
- package/.output/server/chunks/routes/_ws.mjs +37 -7
- package/.output/server/chunks/routes/_ws.mjs.map +1 -1
- package/.output/server/node_modules/@huggingface/jinja/dist/index.js +1572 -0
- package/.output/server/node_modules/@huggingface/jinja/package.json +55 -0
- package/.output/server/node_modules/@xenova/transformers/package.json +84 -0
- package/.output/server/node_modules/@xenova/transformers/src/backends/onnx.js +50 -0
- package/.output/server/node_modules/@xenova/transformers/src/configs.js +107 -0
- package/.output/server/node_modules/@xenova/transformers/src/env.js +128 -0
- package/.output/server/node_modules/@xenova/transformers/src/models.js +6267 -0
- package/.output/server/node_modules/@xenova/transformers/src/pipelines.js +3287 -0
- package/.output/server/node_modules/@xenova/transformers/src/processors.js +2248 -0
- package/.output/server/node_modules/@xenova/transformers/src/tokenizers.js +4479 -0
- package/.output/server/node_modules/@xenova/transformers/src/transformers.js +24 -0
- package/.output/server/node_modules/@xenova/transformers/src/utils/audio.js +672 -0
- package/.output/server/node_modules/@xenova/transformers/src/utils/core.js +175 -0
- package/.output/server/node_modules/@xenova/transformers/src/utils/data-structures.js +415 -0
- package/.output/server/node_modules/@xenova/transformers/src/utils/generation.js +873 -0
- package/.output/server/node_modules/@xenova/transformers/src/utils/hub.js +658 -0
- package/.output/server/node_modules/@xenova/transformers/src/utils/image.js +731 -0
- package/.output/server/node_modules/@xenova/transformers/src/utils/maths.js +985 -0
- package/.output/server/node_modules/@xenova/transformers/src/utils/tensor.js +1239 -0
- package/.output/server/node_modules/color/index.js +496 -0
- package/.output/server/node_modules/color/package.json +47 -0
- package/.output/server/node_modules/color-convert/conversions.js +839 -0
- package/.output/server/node_modules/color-convert/index.js +81 -0
- package/.output/server/node_modules/color-convert/package.json +48 -0
- package/.output/server/node_modules/color-convert/route.js +97 -0
- package/.output/server/node_modules/color-name/index.js +152 -0
- package/.output/server/node_modules/color-name/package.json +28 -0
- package/.output/server/node_modules/color-string/index.js +242 -0
- package/.output/server/node_modules/color-string/package.json +39 -0
- package/.output/server/node_modules/detect-libc/lib/detect-libc.js +313 -0
- package/.output/server/node_modules/detect-libc/lib/elf.js +39 -0
- package/.output/server/node_modules/detect-libc/lib/filesystem.js +51 -0
- package/.output/server/node_modules/detect-libc/lib/process.js +24 -0
- package/.output/server/node_modules/detect-libc/package.json +44 -0
- package/.output/server/node_modules/is-arrayish/index.js +9 -0
- package/.output/server/node_modules/is-arrayish/package.json +45 -0
- package/.output/server/node_modules/onnxruntime-common/dist/ort-common.node.js +7 -0
- package/.output/server/node_modules/onnxruntime-common/package.json +31 -0
- package/.output/server/node_modules/onnxruntime-node/bin/napi-v3/darwin/arm64/onnxruntime_binding.node +0 -0
- package/.output/server/node_modules/onnxruntime-node/bin/napi-v3/darwin/x64/onnxruntime_binding.node +0 -0
- package/.output/server/node_modules/onnxruntime-node/bin/napi-v3/linux/arm64/libonnxruntime.so.1.14.0 +0 -0
- package/.output/server/node_modules/onnxruntime-node/bin/napi-v3/linux/arm64/onnxruntime_binding.node +0 -0
- package/.output/server/node_modules/onnxruntime-node/bin/napi-v3/linux/x64/libonnxruntime.so.1.14.0 +0 -0
- package/.output/server/node_modules/onnxruntime-node/bin/napi-v3/linux/x64/onnxruntime_binding.node +0 -0
- package/.output/server/node_modules/onnxruntime-node/bin/napi-v3/win32/arm64/onnxruntime_binding.node +0 -0
- package/.output/server/node_modules/onnxruntime-node/bin/napi-v3/win32/x64/onnxruntime_binding.node +0 -0
- package/.output/server/node_modules/onnxruntime-node/dist/backend.js +75 -0
- package/.output/server/node_modules/onnxruntime-node/dist/binding.js +10 -0
- package/.output/server/node_modules/onnxruntime-node/dist/index.js +23 -0
- package/.output/server/node_modules/onnxruntime-node/package.json +58 -0
- package/.output/server/node_modules/onnxruntime-web/dist/ort-web.node.js +7 -0
- package/.output/server/node_modules/onnxruntime-web/package.json +84 -0
- package/.output/server/node_modules/semver/classes/semver.js +333 -0
- package/.output/server/node_modules/semver/functions/coerce.js +62 -0
- package/.output/server/node_modules/semver/functions/compare.js +7 -0
- package/.output/server/node_modules/semver/functions/gte.js +5 -0
- package/.output/server/node_modules/semver/functions/parse.js +18 -0
- package/.output/server/node_modules/semver/internal/constants.js +37 -0
- package/.output/server/node_modules/semver/internal/debug.js +11 -0
- package/.output/server/node_modules/semver/internal/identifiers.js +29 -0
- package/.output/server/node_modules/semver/internal/parse-options.js +17 -0
- package/.output/server/node_modules/semver/internal/re.js +223 -0
- package/.output/server/node_modules/semver/package.json +78 -0
- package/.output/server/node_modules/sharp/build/Release/sharp-linux-x64.node +0 -0
- package/.output/server/node_modules/sharp/lib/channel.js +174 -0
- package/.output/server/node_modules/sharp/lib/colour.js +184 -0
- package/.output/server/node_modules/sharp/lib/composite.js +210 -0
- package/.output/server/node_modules/sharp/lib/constructor.js +439 -0
- package/.output/server/node_modules/sharp/lib/index.js +16 -0
- package/.output/server/node_modules/sharp/lib/input.js +631 -0
- package/.output/server/node_modules/sharp/lib/is.js +155 -0
- package/.output/server/node_modules/sharp/lib/libvips.js +140 -0
- package/.output/server/node_modules/sharp/lib/operation.js +919 -0
- package/.output/server/node_modules/sharp/lib/output.js +1413 -0
- package/.output/server/node_modules/sharp/lib/platform.js +30 -0
- package/.output/server/node_modules/sharp/lib/resize.js +582 -0
- package/.output/server/node_modules/sharp/lib/sharp.js +38 -0
- package/.output/server/node_modules/sharp/lib/utility.js +287 -0
- package/.output/server/node_modules/sharp/package.json +204 -0
- package/.output/server/node_modules/sharp/vendor/8.14.5/linux-x64/THIRD-PARTY-NOTICES.md +43 -0
- package/.output/server/node_modules/sharp/vendor/8.14.5/linux-x64/lib/libvips-cpp.so.42 +0 -0
- package/.output/server/node_modules/sharp/vendor/8.14.5/linux-x64/platform.json +1 -0
- package/.output/server/node_modules/sharp/vendor/8.14.5/linux-x64/versions.json +31 -0
- package/.output/server/node_modules/simple-swizzle/index.js +29 -0
- package/.output/server/node_modules/simple-swizzle/package.json +36 -0
- package/.output/server/package.json +15 -1
- package/README.md +2 -0
- package/package.json +12 -19
- package/.output/public/_nuxt/5FxpIoe_.js +0 -1
- package/.output/public/_nuxt/builds/meta/21578a05-1b7e-4847-a8ff-7480800ea4a6.json +0 -1
|
@@ -0,0 +1,1572 @@
|
|
|
1
|
+
// src/lexer.ts
|
|
2
|
+
var TOKEN_TYPES = Object.freeze({
|
|
3
|
+
Text: "Text",
|
|
4
|
+
// The text between Jinja statements or expressions
|
|
5
|
+
NumericLiteral: "NumericLiteral",
|
|
6
|
+
// e.g., 123
|
|
7
|
+
BooleanLiteral: "BooleanLiteral",
|
|
8
|
+
// true or false
|
|
9
|
+
StringLiteral: "StringLiteral",
|
|
10
|
+
// 'string'
|
|
11
|
+
Identifier: "Identifier",
|
|
12
|
+
// Variables, functions, etc.
|
|
13
|
+
Equals: "Equals",
|
|
14
|
+
// =
|
|
15
|
+
OpenParen: "OpenParen",
|
|
16
|
+
// (
|
|
17
|
+
CloseParen: "CloseParen",
|
|
18
|
+
// )
|
|
19
|
+
OpenStatement: "OpenStatement",
|
|
20
|
+
// {%
|
|
21
|
+
CloseStatement: "CloseStatement",
|
|
22
|
+
// %}
|
|
23
|
+
OpenExpression: "OpenExpression",
|
|
24
|
+
// {{
|
|
25
|
+
CloseExpression: "CloseExpression",
|
|
26
|
+
// }}
|
|
27
|
+
OpenSquareBracket: "OpenSquareBracket",
|
|
28
|
+
// [
|
|
29
|
+
CloseSquareBracket: "CloseSquareBracket",
|
|
30
|
+
// ]
|
|
31
|
+
OpenCurlyBracket: "OpenCurlyBracket",
|
|
32
|
+
// {
|
|
33
|
+
CloseCurlyBracket: "CloseCurlyBracket",
|
|
34
|
+
// }
|
|
35
|
+
Comma: "Comma",
|
|
36
|
+
// ,
|
|
37
|
+
Dot: "Dot",
|
|
38
|
+
// .
|
|
39
|
+
Colon: "Colon",
|
|
40
|
+
// :
|
|
41
|
+
Pipe: "Pipe",
|
|
42
|
+
// |
|
|
43
|
+
CallOperator: "CallOperator",
|
|
44
|
+
// ()
|
|
45
|
+
AdditiveBinaryOperator: "AdditiveBinaryOperator",
|
|
46
|
+
// + -
|
|
47
|
+
MultiplicativeBinaryOperator: "MultiplicativeBinaryOperator",
|
|
48
|
+
// * / %
|
|
49
|
+
ComparisonBinaryOperator: "ComparisonBinaryOperator",
|
|
50
|
+
// < > <= >= == !=
|
|
51
|
+
UnaryOperator: "UnaryOperator",
|
|
52
|
+
// ! - +
|
|
53
|
+
// Keywords
|
|
54
|
+
Set: "Set",
|
|
55
|
+
If: "If",
|
|
56
|
+
For: "For",
|
|
57
|
+
In: "In",
|
|
58
|
+
Is: "Is",
|
|
59
|
+
NotIn: "NotIn",
|
|
60
|
+
Else: "Else",
|
|
61
|
+
EndIf: "EndIf",
|
|
62
|
+
ElseIf: "ElseIf",
|
|
63
|
+
EndFor: "EndFor",
|
|
64
|
+
And: "And",
|
|
65
|
+
Or: "Or",
|
|
66
|
+
Not: "UnaryOperator"
|
|
67
|
+
});
|
|
68
|
+
var KEYWORDS = Object.freeze({
|
|
69
|
+
set: TOKEN_TYPES.Set,
|
|
70
|
+
for: TOKEN_TYPES.For,
|
|
71
|
+
in: TOKEN_TYPES.In,
|
|
72
|
+
is: TOKEN_TYPES.Is,
|
|
73
|
+
if: TOKEN_TYPES.If,
|
|
74
|
+
else: TOKEN_TYPES.Else,
|
|
75
|
+
endif: TOKEN_TYPES.EndIf,
|
|
76
|
+
elif: TOKEN_TYPES.ElseIf,
|
|
77
|
+
endfor: TOKEN_TYPES.EndFor,
|
|
78
|
+
and: TOKEN_TYPES.And,
|
|
79
|
+
or: TOKEN_TYPES.Or,
|
|
80
|
+
not: TOKEN_TYPES.Not,
|
|
81
|
+
"not in": TOKEN_TYPES.NotIn,
|
|
82
|
+
// Literals
|
|
83
|
+
true: TOKEN_TYPES.BooleanLiteral,
|
|
84
|
+
false: TOKEN_TYPES.BooleanLiteral
|
|
85
|
+
});
|
|
86
|
+
var Token = class {
|
|
87
|
+
/**
|
|
88
|
+
* Constructs a new Token.
|
|
89
|
+
* @param {string} value The raw value as seen inside the source code.
|
|
90
|
+
* @param {TokenType} type The type of token.
|
|
91
|
+
*/
|
|
92
|
+
constructor(value, type) {
|
|
93
|
+
this.value = value;
|
|
94
|
+
this.type = type;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
function isWord(char) {
|
|
98
|
+
return /\w/.test(char);
|
|
99
|
+
}
|
|
100
|
+
function isInteger(char) {
|
|
101
|
+
return /[0-9]/.test(char);
|
|
102
|
+
}
|
|
103
|
+
var ORDERED_MAPPING_TABLE = [
|
|
104
|
+
// Control sequences
|
|
105
|
+
["{%", TOKEN_TYPES.OpenStatement],
|
|
106
|
+
["%}", TOKEN_TYPES.CloseStatement],
|
|
107
|
+
["{{", TOKEN_TYPES.OpenExpression],
|
|
108
|
+
["}}", TOKEN_TYPES.CloseExpression],
|
|
109
|
+
// Single character tokens
|
|
110
|
+
["(", TOKEN_TYPES.OpenParen],
|
|
111
|
+
[")", TOKEN_TYPES.CloseParen],
|
|
112
|
+
["{", TOKEN_TYPES.OpenCurlyBracket],
|
|
113
|
+
["}", TOKEN_TYPES.CloseCurlyBracket],
|
|
114
|
+
["[", TOKEN_TYPES.OpenSquareBracket],
|
|
115
|
+
["]", TOKEN_TYPES.CloseSquareBracket],
|
|
116
|
+
[",", TOKEN_TYPES.Comma],
|
|
117
|
+
[".", TOKEN_TYPES.Dot],
|
|
118
|
+
[":", TOKEN_TYPES.Colon],
|
|
119
|
+
["|", TOKEN_TYPES.Pipe],
|
|
120
|
+
// Comparison operators
|
|
121
|
+
["<=", TOKEN_TYPES.ComparisonBinaryOperator],
|
|
122
|
+
[">=", TOKEN_TYPES.ComparisonBinaryOperator],
|
|
123
|
+
["==", TOKEN_TYPES.ComparisonBinaryOperator],
|
|
124
|
+
["!=", TOKEN_TYPES.ComparisonBinaryOperator],
|
|
125
|
+
["<", TOKEN_TYPES.ComparisonBinaryOperator],
|
|
126
|
+
[">", TOKEN_TYPES.ComparisonBinaryOperator],
|
|
127
|
+
// Arithmetic operators
|
|
128
|
+
["+", TOKEN_TYPES.AdditiveBinaryOperator],
|
|
129
|
+
["-", TOKEN_TYPES.AdditiveBinaryOperator],
|
|
130
|
+
["*", TOKEN_TYPES.MultiplicativeBinaryOperator],
|
|
131
|
+
["/", TOKEN_TYPES.MultiplicativeBinaryOperator],
|
|
132
|
+
["%", TOKEN_TYPES.MultiplicativeBinaryOperator],
|
|
133
|
+
// Assignment operator
|
|
134
|
+
["=", TOKEN_TYPES.Equals]
|
|
135
|
+
];
|
|
136
|
+
var ESCAPE_CHARACTERS = /* @__PURE__ */ new Map([
|
|
137
|
+
["n", "\n"],
|
|
138
|
+
// New line
|
|
139
|
+
["t", " "],
|
|
140
|
+
// Horizontal tab
|
|
141
|
+
["r", "\r"],
|
|
142
|
+
// Carriage return
|
|
143
|
+
["b", "\b"],
|
|
144
|
+
// Backspace
|
|
145
|
+
["f", "\f"],
|
|
146
|
+
// Form feed
|
|
147
|
+
["v", "\v"],
|
|
148
|
+
// Vertical tab
|
|
149
|
+
["'", "'"],
|
|
150
|
+
// Single quote
|
|
151
|
+
['"', '"'],
|
|
152
|
+
// Double quote
|
|
153
|
+
["\\", "\\"]
|
|
154
|
+
// Backslash
|
|
155
|
+
]);
|
|
156
|
+
function preprocess(template, options = {}) {
|
|
157
|
+
if (template.endsWith("\n")) {
|
|
158
|
+
template = template.slice(0, -1);
|
|
159
|
+
}
|
|
160
|
+
template = template.replace(/{#.*?#}/gs, "{##}");
|
|
161
|
+
if (options.lstrip_blocks) {
|
|
162
|
+
template = template.replace(/^[ \t]*({[#%])/gm, "$1");
|
|
163
|
+
}
|
|
164
|
+
if (options.trim_blocks) {
|
|
165
|
+
template = template.replace(/([#%]})\n/g, "$1");
|
|
166
|
+
}
|
|
167
|
+
return template.replace(/{##}/g, "").replace(/-%}\s*/g, "%}").replace(/\s*{%-/g, "{%").replace(/-}}\s*/g, "}}").replace(/\s*{{-/g, "{{");
|
|
168
|
+
}
|
|
169
|
+
function tokenize(source, options = {}) {
|
|
170
|
+
const tokens = [];
|
|
171
|
+
const src = preprocess(source, options);
|
|
172
|
+
let cursorPosition = 0;
|
|
173
|
+
const consumeWhile = (predicate) => {
|
|
174
|
+
let str = "";
|
|
175
|
+
while (predicate(src[cursorPosition])) {
|
|
176
|
+
if (src[cursorPosition] === "\\") {
|
|
177
|
+
++cursorPosition;
|
|
178
|
+
if (cursorPosition >= src.length)
|
|
179
|
+
throw new SyntaxError("Unexpected end of input");
|
|
180
|
+
const escaped = src[cursorPosition++];
|
|
181
|
+
const unescaped = ESCAPE_CHARACTERS.get(escaped);
|
|
182
|
+
if (unescaped === void 0) {
|
|
183
|
+
throw new SyntaxError(`Unexpected escaped character: ${escaped}`);
|
|
184
|
+
}
|
|
185
|
+
str += unescaped;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
str += src[cursorPosition++];
|
|
189
|
+
if (cursorPosition >= src.length)
|
|
190
|
+
throw new SyntaxError("Unexpected end of input");
|
|
191
|
+
}
|
|
192
|
+
return str;
|
|
193
|
+
};
|
|
194
|
+
main:
|
|
195
|
+
while (cursorPosition < src.length) {
|
|
196
|
+
const lastTokenType = tokens.at(-1)?.type;
|
|
197
|
+
if (lastTokenType === void 0 || lastTokenType === TOKEN_TYPES.CloseStatement || lastTokenType === TOKEN_TYPES.CloseExpression) {
|
|
198
|
+
let text = "";
|
|
199
|
+
while (cursorPosition < src.length && // Keep going until we hit the next Jinja statement or expression
|
|
200
|
+
!(src[cursorPosition] === "{" && (src[cursorPosition + 1] === "%" || src[cursorPosition + 1] === "{"))) {
|
|
201
|
+
text += src[cursorPosition++];
|
|
202
|
+
}
|
|
203
|
+
if (text.length > 0) {
|
|
204
|
+
tokens.push(new Token(text, TOKEN_TYPES.Text));
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
consumeWhile((char2) => /\s/.test(char2));
|
|
209
|
+
const char = src[cursorPosition];
|
|
210
|
+
if (char === "-" || char === "+") {
|
|
211
|
+
const lastTokenType2 = tokens.at(-1)?.type;
|
|
212
|
+
if (lastTokenType2 === TOKEN_TYPES.Text || lastTokenType2 === void 0) {
|
|
213
|
+
throw new SyntaxError(`Unexpected character: ${char}`);
|
|
214
|
+
}
|
|
215
|
+
switch (lastTokenType2) {
|
|
216
|
+
case TOKEN_TYPES.Identifier:
|
|
217
|
+
case TOKEN_TYPES.NumericLiteral:
|
|
218
|
+
case TOKEN_TYPES.BooleanLiteral:
|
|
219
|
+
case TOKEN_TYPES.StringLiteral:
|
|
220
|
+
case TOKEN_TYPES.CloseParen:
|
|
221
|
+
case TOKEN_TYPES.CloseSquareBracket:
|
|
222
|
+
break;
|
|
223
|
+
default: {
|
|
224
|
+
++cursorPosition;
|
|
225
|
+
const num = consumeWhile(isInteger);
|
|
226
|
+
tokens.push(
|
|
227
|
+
new Token(`${char}${num}`, num.length > 0 ? TOKEN_TYPES.NumericLiteral : TOKEN_TYPES.UnaryOperator)
|
|
228
|
+
);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
for (const [char2, token] of ORDERED_MAPPING_TABLE) {
|
|
234
|
+
const slice2 = src.slice(cursorPosition, cursorPosition + char2.length);
|
|
235
|
+
if (slice2 === char2) {
|
|
236
|
+
tokens.push(new Token(char2, token));
|
|
237
|
+
cursorPosition += char2.length;
|
|
238
|
+
continue main;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (char === "'" || char === '"') {
|
|
242
|
+
++cursorPosition;
|
|
243
|
+
const str = consumeWhile((c) => c !== char);
|
|
244
|
+
tokens.push(new Token(str, TOKEN_TYPES.StringLiteral));
|
|
245
|
+
++cursorPosition;
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (isInteger(char)) {
|
|
249
|
+
const num = consumeWhile(isInteger);
|
|
250
|
+
tokens.push(new Token(num, TOKEN_TYPES.NumericLiteral));
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
if (isWord(char)) {
|
|
254
|
+
const word = consumeWhile(isWord);
|
|
255
|
+
const type = Object.hasOwn(KEYWORDS, word) ? KEYWORDS[word] : TOKEN_TYPES.Identifier;
|
|
256
|
+
if (type === TOKEN_TYPES.In && tokens.at(-1)?.type === TOKEN_TYPES.Not) {
|
|
257
|
+
tokens.pop();
|
|
258
|
+
tokens.push(new Token("not in", TOKEN_TYPES.NotIn));
|
|
259
|
+
} else {
|
|
260
|
+
tokens.push(new Token(word, type));
|
|
261
|
+
}
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
throw new SyntaxError(`Unexpected character: ${char}`);
|
|
265
|
+
}
|
|
266
|
+
return tokens;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/ast.ts
|
|
270
|
+
var Statement = class {
|
|
271
|
+
type = "Statement";
|
|
272
|
+
};
|
|
273
|
+
var Program = class extends Statement {
|
|
274
|
+
constructor(body) {
|
|
275
|
+
super();
|
|
276
|
+
this.body = body;
|
|
277
|
+
}
|
|
278
|
+
type = "Program";
|
|
279
|
+
};
|
|
280
|
+
var If = class extends Statement {
|
|
281
|
+
constructor(test, body, alternate) {
|
|
282
|
+
super();
|
|
283
|
+
this.test = test;
|
|
284
|
+
this.body = body;
|
|
285
|
+
this.alternate = alternate;
|
|
286
|
+
}
|
|
287
|
+
type = "If";
|
|
288
|
+
};
|
|
289
|
+
var For = class extends Statement {
|
|
290
|
+
constructor(loopvar, iterable, body) {
|
|
291
|
+
super();
|
|
292
|
+
this.loopvar = loopvar;
|
|
293
|
+
this.iterable = iterable;
|
|
294
|
+
this.body = body;
|
|
295
|
+
}
|
|
296
|
+
type = "For";
|
|
297
|
+
};
|
|
298
|
+
var SetStatement = class extends Statement {
|
|
299
|
+
constructor(assignee, value) {
|
|
300
|
+
super();
|
|
301
|
+
this.assignee = assignee;
|
|
302
|
+
this.value = value;
|
|
303
|
+
}
|
|
304
|
+
type = "Set";
|
|
305
|
+
};
|
|
306
|
+
var Expression = class extends Statement {
|
|
307
|
+
type = "Expression";
|
|
308
|
+
};
|
|
309
|
+
var MemberExpression = class extends Expression {
|
|
310
|
+
constructor(object, property, computed) {
|
|
311
|
+
super();
|
|
312
|
+
this.object = object;
|
|
313
|
+
this.property = property;
|
|
314
|
+
this.computed = computed;
|
|
315
|
+
}
|
|
316
|
+
type = "MemberExpression";
|
|
317
|
+
};
|
|
318
|
+
var CallExpression = class extends Expression {
|
|
319
|
+
constructor(callee, args) {
|
|
320
|
+
super();
|
|
321
|
+
this.callee = callee;
|
|
322
|
+
this.args = args;
|
|
323
|
+
}
|
|
324
|
+
type = "CallExpression";
|
|
325
|
+
};
|
|
326
|
+
var Identifier = class extends Expression {
|
|
327
|
+
/**
|
|
328
|
+
* @param {string} value The name of the identifier
|
|
329
|
+
*/
|
|
330
|
+
constructor(value) {
|
|
331
|
+
super();
|
|
332
|
+
this.value = value;
|
|
333
|
+
}
|
|
334
|
+
type = "Identifier";
|
|
335
|
+
};
|
|
336
|
+
var Literal = class extends Expression {
|
|
337
|
+
constructor(value) {
|
|
338
|
+
super();
|
|
339
|
+
this.value = value;
|
|
340
|
+
}
|
|
341
|
+
type = "Literal";
|
|
342
|
+
};
|
|
343
|
+
var NumericLiteral = class extends Literal {
|
|
344
|
+
type = "NumericLiteral";
|
|
345
|
+
};
|
|
346
|
+
var StringLiteral = class extends Literal {
|
|
347
|
+
type = "StringLiteral";
|
|
348
|
+
};
|
|
349
|
+
var BooleanLiteral = class extends Literal {
|
|
350
|
+
type = "BooleanLiteral";
|
|
351
|
+
};
|
|
352
|
+
var ArrayLiteral = class extends Literal {
|
|
353
|
+
type = "ArrayLiteral";
|
|
354
|
+
};
|
|
355
|
+
var TupleLiteral = class extends Literal {
|
|
356
|
+
type = "TupleLiteral";
|
|
357
|
+
};
|
|
358
|
+
var ObjectLiteral = class extends Literal {
|
|
359
|
+
type = "ObjectLiteral";
|
|
360
|
+
};
|
|
361
|
+
var BinaryExpression = class extends Expression {
|
|
362
|
+
constructor(operator, left, right) {
|
|
363
|
+
super();
|
|
364
|
+
this.operator = operator;
|
|
365
|
+
this.left = left;
|
|
366
|
+
this.right = right;
|
|
367
|
+
}
|
|
368
|
+
type = "BinaryExpression";
|
|
369
|
+
};
|
|
370
|
+
var FilterExpression = class extends Expression {
|
|
371
|
+
constructor(operand, filter) {
|
|
372
|
+
super();
|
|
373
|
+
this.operand = operand;
|
|
374
|
+
this.filter = filter;
|
|
375
|
+
}
|
|
376
|
+
type = "FilterExpression";
|
|
377
|
+
};
|
|
378
|
+
var TestExpression = class extends Expression {
|
|
379
|
+
constructor(operand, negate, test) {
|
|
380
|
+
super();
|
|
381
|
+
this.operand = operand;
|
|
382
|
+
this.negate = negate;
|
|
383
|
+
this.test = test;
|
|
384
|
+
}
|
|
385
|
+
type = "TestExpression";
|
|
386
|
+
};
|
|
387
|
+
var UnaryExpression = class extends Expression {
|
|
388
|
+
constructor(operator, argument) {
|
|
389
|
+
super();
|
|
390
|
+
this.operator = operator;
|
|
391
|
+
this.argument = argument;
|
|
392
|
+
}
|
|
393
|
+
type = "UnaryExpression";
|
|
394
|
+
};
|
|
395
|
+
var SliceExpression = class extends Expression {
|
|
396
|
+
constructor(start = void 0, stop = void 0, step = void 0) {
|
|
397
|
+
super();
|
|
398
|
+
this.start = start;
|
|
399
|
+
this.stop = stop;
|
|
400
|
+
this.step = step;
|
|
401
|
+
}
|
|
402
|
+
type = "SliceExpression";
|
|
403
|
+
};
|
|
404
|
+
var KeywordArgumentExpression = class extends Expression {
|
|
405
|
+
constructor(key, value) {
|
|
406
|
+
super();
|
|
407
|
+
this.key = key;
|
|
408
|
+
this.value = value;
|
|
409
|
+
}
|
|
410
|
+
type = "KeywordArgumentExpression";
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
// src/parser.ts
|
|
414
|
+
function parse(tokens) {
|
|
415
|
+
const program = new Program([]);
|
|
416
|
+
let current = 0;
|
|
417
|
+
function expect(type, error) {
|
|
418
|
+
const prev = tokens[current++];
|
|
419
|
+
if (!prev || prev.type !== type) {
|
|
420
|
+
throw new Error(`Parser Error: ${error}. ${prev.type} !== ${type}.`);
|
|
421
|
+
}
|
|
422
|
+
return prev;
|
|
423
|
+
}
|
|
424
|
+
function parseAny() {
|
|
425
|
+
switch (tokens[current].type) {
|
|
426
|
+
case TOKEN_TYPES.Text:
|
|
427
|
+
return parseText();
|
|
428
|
+
case TOKEN_TYPES.OpenStatement:
|
|
429
|
+
return parseJinjaStatement();
|
|
430
|
+
case TOKEN_TYPES.OpenExpression:
|
|
431
|
+
return parseJinjaExpression();
|
|
432
|
+
default:
|
|
433
|
+
throw new SyntaxError(`Unexpected token type: ${tokens[current].type}`);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
function not(...types) {
|
|
437
|
+
return current + types.length <= tokens.length && types.some((type, i) => type !== tokens[current + i].type);
|
|
438
|
+
}
|
|
439
|
+
function is(...types) {
|
|
440
|
+
return current + types.length <= tokens.length && types.every((type, i) => type === tokens[current + i].type);
|
|
441
|
+
}
|
|
442
|
+
function parseText() {
|
|
443
|
+
return new StringLiteral(expect(TOKEN_TYPES.Text, "Expected text token").value);
|
|
444
|
+
}
|
|
445
|
+
function parseJinjaStatement() {
|
|
446
|
+
expect(TOKEN_TYPES.OpenStatement, "Expected opening statement token");
|
|
447
|
+
let result;
|
|
448
|
+
switch (tokens[current].type) {
|
|
449
|
+
case TOKEN_TYPES.Set:
|
|
450
|
+
++current;
|
|
451
|
+
result = parseSetStatement();
|
|
452
|
+
expect(TOKEN_TYPES.CloseStatement, "Expected closing statement token");
|
|
453
|
+
break;
|
|
454
|
+
case TOKEN_TYPES.If:
|
|
455
|
+
++current;
|
|
456
|
+
result = parseIfStatement();
|
|
457
|
+
expect(TOKEN_TYPES.OpenStatement, "Expected {% token");
|
|
458
|
+
expect(TOKEN_TYPES.EndIf, "Expected endif token");
|
|
459
|
+
expect(TOKEN_TYPES.CloseStatement, "Expected %} token");
|
|
460
|
+
break;
|
|
461
|
+
case TOKEN_TYPES.For:
|
|
462
|
+
++current;
|
|
463
|
+
result = parseForStatement();
|
|
464
|
+
expect(TOKEN_TYPES.OpenStatement, "Expected {% token");
|
|
465
|
+
expect(TOKEN_TYPES.EndFor, "Expected endfor token");
|
|
466
|
+
expect(TOKEN_TYPES.CloseStatement, "Expected %} token");
|
|
467
|
+
break;
|
|
468
|
+
default:
|
|
469
|
+
throw new SyntaxError(`Unknown statement type: ${tokens[current].type}`);
|
|
470
|
+
}
|
|
471
|
+
return result;
|
|
472
|
+
}
|
|
473
|
+
function parseJinjaExpression() {
|
|
474
|
+
expect(TOKEN_TYPES.OpenExpression, "Expected opening expression token");
|
|
475
|
+
const result = parseExpression();
|
|
476
|
+
expect(TOKEN_TYPES.CloseExpression, "Expected closing expression token");
|
|
477
|
+
return result;
|
|
478
|
+
}
|
|
479
|
+
function parseSetStatement() {
|
|
480
|
+
const left = parseExpression();
|
|
481
|
+
if (is(TOKEN_TYPES.Equals)) {
|
|
482
|
+
++current;
|
|
483
|
+
const value = parseSetStatement();
|
|
484
|
+
return new SetStatement(left, value);
|
|
485
|
+
}
|
|
486
|
+
return left;
|
|
487
|
+
}
|
|
488
|
+
function parseIfStatement() {
|
|
489
|
+
const test = parseExpression();
|
|
490
|
+
expect(TOKEN_TYPES.CloseStatement, "Expected closing statement token");
|
|
491
|
+
const body = [];
|
|
492
|
+
const alternate = [];
|
|
493
|
+
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))) {
|
|
494
|
+
body.push(parseAny());
|
|
495
|
+
}
|
|
496
|
+
if (tokens[current]?.type === TOKEN_TYPES.OpenStatement && tokens[current + 1]?.type !== TOKEN_TYPES.EndIf) {
|
|
497
|
+
++current;
|
|
498
|
+
if (is(TOKEN_TYPES.ElseIf)) {
|
|
499
|
+
expect(TOKEN_TYPES.ElseIf, "Expected elseif token");
|
|
500
|
+
alternate.push(parseIfStatement());
|
|
501
|
+
} else {
|
|
502
|
+
expect(TOKEN_TYPES.Else, "Expected else token");
|
|
503
|
+
expect(TOKEN_TYPES.CloseStatement, "Expected closing statement token");
|
|
504
|
+
while (!(tokens[current]?.type === TOKEN_TYPES.OpenStatement && tokens[current + 1]?.type === TOKEN_TYPES.EndIf)) {
|
|
505
|
+
alternate.push(parseAny());
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return new If(test, body, alternate);
|
|
510
|
+
}
|
|
511
|
+
function parseExpressionSequence(primary = false) {
|
|
512
|
+
const fn = primary ? parsePrimaryExpression : parseExpression;
|
|
513
|
+
const expressions = [fn()];
|
|
514
|
+
const isTuple = is(TOKEN_TYPES.Comma);
|
|
515
|
+
while (isTuple) {
|
|
516
|
+
++current;
|
|
517
|
+
expressions.push(fn());
|
|
518
|
+
if (!is(TOKEN_TYPES.Comma)) {
|
|
519
|
+
break;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return isTuple ? new TupleLiteral(expressions) : expressions[0];
|
|
523
|
+
}
|
|
524
|
+
function parseForStatement() {
|
|
525
|
+
const loopVariable = parseExpressionSequence(true);
|
|
526
|
+
if (!(loopVariable instanceof Identifier || loopVariable instanceof TupleLiteral)) {
|
|
527
|
+
throw new SyntaxError(`Expected identifier/tuple for the loop variable, got ${loopVariable.type} instead`);
|
|
528
|
+
}
|
|
529
|
+
expect(TOKEN_TYPES.In, "Expected `in` keyword following loop variable");
|
|
530
|
+
const iterable = parseExpression();
|
|
531
|
+
expect(TOKEN_TYPES.CloseStatement, "Expected closing statement token");
|
|
532
|
+
const body = [];
|
|
533
|
+
while (not(TOKEN_TYPES.OpenStatement, TOKEN_TYPES.EndFor)) {
|
|
534
|
+
body.push(parseAny());
|
|
535
|
+
}
|
|
536
|
+
return new For(loopVariable, iterable, body);
|
|
537
|
+
}
|
|
538
|
+
function parseExpression() {
|
|
539
|
+
return parseTernaryExpression();
|
|
540
|
+
}
|
|
541
|
+
function parseTernaryExpression() {
|
|
542
|
+
const a = parseLogicalOrExpression();
|
|
543
|
+
if (is(TOKEN_TYPES.If)) {
|
|
544
|
+
++current;
|
|
545
|
+
const predicate = parseLogicalOrExpression();
|
|
546
|
+
expect(TOKEN_TYPES.Else, "Expected else token");
|
|
547
|
+
const b = parseLogicalOrExpression();
|
|
548
|
+
return new If(predicate, [a], [b]);
|
|
549
|
+
}
|
|
550
|
+
return a;
|
|
551
|
+
}
|
|
552
|
+
function parseLogicalOrExpression() {
|
|
553
|
+
let left = parseLogicalAndExpression();
|
|
554
|
+
while (is(TOKEN_TYPES.Or)) {
|
|
555
|
+
const operator = tokens[current];
|
|
556
|
+
++current;
|
|
557
|
+
const right = parseLogicalAndExpression();
|
|
558
|
+
left = new BinaryExpression(operator, left, right);
|
|
559
|
+
}
|
|
560
|
+
return left;
|
|
561
|
+
}
|
|
562
|
+
function parseLogicalAndExpression() {
|
|
563
|
+
let left = parseLogicalNegationExpression();
|
|
564
|
+
while (is(TOKEN_TYPES.And)) {
|
|
565
|
+
const operator = tokens[current];
|
|
566
|
+
++current;
|
|
567
|
+
const right = parseLogicalNegationExpression();
|
|
568
|
+
left = new BinaryExpression(operator, left, right);
|
|
569
|
+
}
|
|
570
|
+
return left;
|
|
571
|
+
}
|
|
572
|
+
function parseLogicalNegationExpression() {
|
|
573
|
+
let right;
|
|
574
|
+
while (is(TOKEN_TYPES.Not)) {
|
|
575
|
+
const operator = tokens[current];
|
|
576
|
+
++current;
|
|
577
|
+
const arg = parseLogicalNegationExpression();
|
|
578
|
+
right = new UnaryExpression(operator, arg);
|
|
579
|
+
}
|
|
580
|
+
return right ?? parseComparisonExpression();
|
|
581
|
+
}
|
|
582
|
+
function parseComparisonExpression() {
|
|
583
|
+
let left = parseAdditiveExpression();
|
|
584
|
+
while (is(TOKEN_TYPES.ComparisonBinaryOperator) || is(TOKEN_TYPES.In) || is(TOKEN_TYPES.NotIn)) {
|
|
585
|
+
const operator = tokens[current];
|
|
586
|
+
++current;
|
|
587
|
+
const right = parseAdditiveExpression();
|
|
588
|
+
left = new BinaryExpression(operator, left, right);
|
|
589
|
+
}
|
|
590
|
+
return left;
|
|
591
|
+
}
|
|
592
|
+
function parseAdditiveExpression() {
|
|
593
|
+
let left = parseMultiplicativeExpression();
|
|
594
|
+
while (is(TOKEN_TYPES.AdditiveBinaryOperator)) {
|
|
595
|
+
const operator = tokens[current];
|
|
596
|
+
++current;
|
|
597
|
+
const right = parseMultiplicativeExpression();
|
|
598
|
+
left = new BinaryExpression(operator, left, right);
|
|
599
|
+
}
|
|
600
|
+
return left;
|
|
601
|
+
}
|
|
602
|
+
function parseCallMemberExpression() {
|
|
603
|
+
const member = parseMemberExpression();
|
|
604
|
+
if (is(TOKEN_TYPES.OpenParen)) {
|
|
605
|
+
return parseCallExpression(member);
|
|
606
|
+
}
|
|
607
|
+
return member;
|
|
608
|
+
}
|
|
609
|
+
function parseCallExpression(callee) {
|
|
610
|
+
let callExpression = new CallExpression(callee, parseArgs());
|
|
611
|
+
if (is(TOKEN_TYPES.OpenParen)) {
|
|
612
|
+
callExpression = parseCallExpression(callExpression);
|
|
613
|
+
}
|
|
614
|
+
return callExpression;
|
|
615
|
+
}
|
|
616
|
+
function parseArgs() {
|
|
617
|
+
expect(TOKEN_TYPES.OpenParen, "Expected opening parenthesis for arguments list");
|
|
618
|
+
const args = parseArgumentsList();
|
|
619
|
+
expect(TOKEN_TYPES.CloseParen, "Expected closing parenthesis for arguments list");
|
|
620
|
+
return args;
|
|
621
|
+
}
|
|
622
|
+
function parseArgumentsList() {
|
|
623
|
+
const args = [];
|
|
624
|
+
while (!is(TOKEN_TYPES.CloseParen)) {
|
|
625
|
+
let argument = parseExpression();
|
|
626
|
+
if (is(TOKEN_TYPES.Equals)) {
|
|
627
|
+
++current;
|
|
628
|
+
if (!(argument instanceof Identifier)) {
|
|
629
|
+
throw new SyntaxError(`Expected identifier for keyword argument`);
|
|
630
|
+
}
|
|
631
|
+
const value = parseExpression();
|
|
632
|
+
argument = new KeywordArgumentExpression(argument, value);
|
|
633
|
+
}
|
|
634
|
+
args.push(argument);
|
|
635
|
+
if (is(TOKEN_TYPES.Comma)) {
|
|
636
|
+
++current;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return args;
|
|
640
|
+
}
|
|
641
|
+
function parseMemberExpressionArgumentsList() {
|
|
642
|
+
const slices = [];
|
|
643
|
+
let isSlice = false;
|
|
644
|
+
while (!is(TOKEN_TYPES.CloseSquareBracket)) {
|
|
645
|
+
if (is(TOKEN_TYPES.Colon)) {
|
|
646
|
+
slices.push(void 0);
|
|
647
|
+
++current;
|
|
648
|
+
isSlice = true;
|
|
649
|
+
} else {
|
|
650
|
+
slices.push(parseExpression());
|
|
651
|
+
if (is(TOKEN_TYPES.Colon)) {
|
|
652
|
+
++current;
|
|
653
|
+
isSlice = true;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
if (slices.length === 0) {
|
|
658
|
+
throw new SyntaxError(`Expected at least one argument for member/slice expression`);
|
|
659
|
+
}
|
|
660
|
+
if (isSlice) {
|
|
661
|
+
if (slices.length > 3) {
|
|
662
|
+
throw new SyntaxError(`Expected 0-3 arguments for slice expression`);
|
|
663
|
+
}
|
|
664
|
+
return new SliceExpression(...slices);
|
|
665
|
+
}
|
|
666
|
+
return slices[0];
|
|
667
|
+
}
|
|
668
|
+
function parseMemberExpression() {
|
|
669
|
+
let object = parsePrimaryExpression();
|
|
670
|
+
while (is(TOKEN_TYPES.Dot) || is(TOKEN_TYPES.OpenSquareBracket)) {
|
|
671
|
+
const operator = tokens[current];
|
|
672
|
+
++current;
|
|
673
|
+
let property;
|
|
674
|
+
const computed = operator.type !== TOKEN_TYPES.Dot;
|
|
675
|
+
if (computed) {
|
|
676
|
+
property = parseMemberExpressionArgumentsList();
|
|
677
|
+
expect(TOKEN_TYPES.CloseSquareBracket, "Expected closing square bracket");
|
|
678
|
+
} else {
|
|
679
|
+
property = parsePrimaryExpression();
|
|
680
|
+
if (property.type !== "Identifier") {
|
|
681
|
+
throw new SyntaxError(`Expected identifier following dot operator`);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
object = new MemberExpression(object, property, computed);
|
|
685
|
+
}
|
|
686
|
+
return object;
|
|
687
|
+
}
|
|
688
|
+
function parseMultiplicativeExpression() {
|
|
689
|
+
let left = parseTestExpression();
|
|
690
|
+
while (is(TOKEN_TYPES.MultiplicativeBinaryOperator)) {
|
|
691
|
+
const operator = tokens[current];
|
|
692
|
+
++current;
|
|
693
|
+
const right = parseTestExpression();
|
|
694
|
+
left = new BinaryExpression(operator, left, right);
|
|
695
|
+
}
|
|
696
|
+
return left;
|
|
697
|
+
}
|
|
698
|
+
function parseTestExpression() {
|
|
699
|
+
let operand = parseFilterExpression();
|
|
700
|
+
while (is(TOKEN_TYPES.Is)) {
|
|
701
|
+
++current;
|
|
702
|
+
const negate = is(TOKEN_TYPES.Not);
|
|
703
|
+
if (negate) {
|
|
704
|
+
++current;
|
|
705
|
+
}
|
|
706
|
+
let filter = parsePrimaryExpression();
|
|
707
|
+
if (filter instanceof BooleanLiteral) {
|
|
708
|
+
filter = new Identifier(filter.value.toString());
|
|
709
|
+
}
|
|
710
|
+
if (!(filter instanceof Identifier)) {
|
|
711
|
+
throw new SyntaxError(`Expected identifier for the test`);
|
|
712
|
+
}
|
|
713
|
+
operand = new TestExpression(operand, negate, filter);
|
|
714
|
+
}
|
|
715
|
+
return operand;
|
|
716
|
+
}
|
|
717
|
+
function parseFilterExpression() {
|
|
718
|
+
let operand = parseCallMemberExpression();
|
|
719
|
+
while (is(TOKEN_TYPES.Pipe)) {
|
|
720
|
+
++current;
|
|
721
|
+
let filter = parsePrimaryExpression();
|
|
722
|
+
if (!(filter instanceof Identifier)) {
|
|
723
|
+
throw new SyntaxError(`Expected identifier for the filter`);
|
|
724
|
+
}
|
|
725
|
+
if (is(TOKEN_TYPES.OpenParen)) {
|
|
726
|
+
filter = parseCallExpression(filter);
|
|
727
|
+
}
|
|
728
|
+
operand = new FilterExpression(operand, filter);
|
|
729
|
+
}
|
|
730
|
+
return operand;
|
|
731
|
+
}
|
|
732
|
+
function parsePrimaryExpression() {
|
|
733
|
+
const token = tokens[current];
|
|
734
|
+
switch (token.type) {
|
|
735
|
+
case TOKEN_TYPES.NumericLiteral:
|
|
736
|
+
++current;
|
|
737
|
+
return new NumericLiteral(Number(token.value));
|
|
738
|
+
case TOKEN_TYPES.StringLiteral:
|
|
739
|
+
++current;
|
|
740
|
+
return new StringLiteral(token.value);
|
|
741
|
+
case TOKEN_TYPES.BooleanLiteral:
|
|
742
|
+
++current;
|
|
743
|
+
return new BooleanLiteral(token.value === "true");
|
|
744
|
+
case TOKEN_TYPES.Identifier:
|
|
745
|
+
++current;
|
|
746
|
+
return new Identifier(token.value);
|
|
747
|
+
case TOKEN_TYPES.OpenParen: {
|
|
748
|
+
++current;
|
|
749
|
+
const expression = parseExpressionSequence();
|
|
750
|
+
if (tokens[current].type !== TOKEN_TYPES.CloseParen) {
|
|
751
|
+
throw new SyntaxError(`Expected closing parenthesis, got ${tokens[current].type} instead`);
|
|
752
|
+
}
|
|
753
|
+
++current;
|
|
754
|
+
return expression;
|
|
755
|
+
}
|
|
756
|
+
case TOKEN_TYPES.OpenSquareBracket: {
|
|
757
|
+
++current;
|
|
758
|
+
const values = [];
|
|
759
|
+
while (!is(TOKEN_TYPES.CloseSquareBracket)) {
|
|
760
|
+
values.push(parseExpression());
|
|
761
|
+
if (is(TOKEN_TYPES.Comma)) {
|
|
762
|
+
++current;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
++current;
|
|
766
|
+
return new ArrayLiteral(values);
|
|
767
|
+
}
|
|
768
|
+
case TOKEN_TYPES.OpenCurlyBracket: {
|
|
769
|
+
++current;
|
|
770
|
+
const values = /* @__PURE__ */ new Map();
|
|
771
|
+
while (!is(TOKEN_TYPES.CloseCurlyBracket)) {
|
|
772
|
+
const key = parseExpression();
|
|
773
|
+
expect(TOKEN_TYPES.Colon, "Expected colon between key and value in object literal");
|
|
774
|
+
const value = parseExpression();
|
|
775
|
+
values.set(key, value);
|
|
776
|
+
if (is(TOKEN_TYPES.Comma)) {
|
|
777
|
+
++current;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
++current;
|
|
781
|
+
return new ObjectLiteral(values);
|
|
782
|
+
}
|
|
783
|
+
default:
|
|
784
|
+
throw new SyntaxError(`Unexpected token: ${token.type}`);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
while (current < tokens.length) {
|
|
788
|
+
program.body.push(parseAny());
|
|
789
|
+
}
|
|
790
|
+
return program;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// src/utils.ts
|
|
794
|
+
function range(start, stop, step = 1) {
|
|
795
|
+
if (stop === void 0) {
|
|
796
|
+
stop = start;
|
|
797
|
+
start = 0;
|
|
798
|
+
}
|
|
799
|
+
const result = [];
|
|
800
|
+
for (let i = start; i < stop; i += step) {
|
|
801
|
+
result.push(i);
|
|
802
|
+
}
|
|
803
|
+
return result;
|
|
804
|
+
}
|
|
805
|
+
function slice(array, start, stop, step = 1) {
|
|
806
|
+
const direction = Math.sign(step);
|
|
807
|
+
if (direction >= 0) {
|
|
808
|
+
start = (start ??= 0) < 0 ? Math.max(array.length + start, 0) : Math.min(start, array.length);
|
|
809
|
+
stop = (stop ??= array.length) < 0 ? Math.max(array.length + stop, 0) : Math.min(stop, array.length);
|
|
810
|
+
} else {
|
|
811
|
+
start = (start ??= array.length - 1) < 0 ? Math.max(array.length + start, -1) : Math.min(start, array.length - 1);
|
|
812
|
+
stop = (stop ??= -1) < -1 ? Math.max(array.length + stop, -1) : Math.min(stop, array.length - 1);
|
|
813
|
+
}
|
|
814
|
+
const result = [];
|
|
815
|
+
for (let i = start; direction * i < direction * stop; i += step) {
|
|
816
|
+
result.push(array[i]);
|
|
817
|
+
}
|
|
818
|
+
return result;
|
|
819
|
+
}
|
|
820
|
+
function titleCase(value) {
|
|
821
|
+
return value.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// src/runtime.ts
|
|
825
|
+
var RuntimeValue = class {
|
|
826
|
+
type = "RuntimeValue";
|
|
827
|
+
value;
|
|
828
|
+
/**
|
|
829
|
+
* A collection of built-in functions for this type.
|
|
830
|
+
*/
|
|
831
|
+
builtins = /* @__PURE__ */ new Map();
|
|
832
|
+
/**
|
|
833
|
+
* Creates a new RuntimeValue.
|
|
834
|
+
*/
|
|
835
|
+
constructor(value = void 0) {
|
|
836
|
+
this.value = value;
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Determines truthiness or falsiness of the runtime value.
|
|
840
|
+
* This function should be overridden by subclasses if it has custom truthiness criteria.
|
|
841
|
+
* @returns {BooleanValue} BooleanValue(true) if the value is truthy, BooleanValue(false) otherwise.
|
|
842
|
+
*/
|
|
843
|
+
__bool__() {
|
|
844
|
+
return new BooleanValue(!!this.value);
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
var NumericValue = class extends RuntimeValue {
|
|
848
|
+
type = "NumericValue";
|
|
849
|
+
};
|
|
850
|
+
var StringValue = class extends RuntimeValue {
|
|
851
|
+
type = "StringValue";
|
|
852
|
+
builtins = /* @__PURE__ */ new Map([
|
|
853
|
+
[
|
|
854
|
+
"upper",
|
|
855
|
+
new FunctionValue(() => {
|
|
856
|
+
return new StringValue(this.value.toUpperCase());
|
|
857
|
+
})
|
|
858
|
+
],
|
|
859
|
+
[
|
|
860
|
+
"lower",
|
|
861
|
+
new FunctionValue(() => {
|
|
862
|
+
return new StringValue(this.value.toLowerCase());
|
|
863
|
+
})
|
|
864
|
+
],
|
|
865
|
+
[
|
|
866
|
+
"strip",
|
|
867
|
+
new FunctionValue(() => {
|
|
868
|
+
return new StringValue(this.value.trim());
|
|
869
|
+
})
|
|
870
|
+
],
|
|
871
|
+
[
|
|
872
|
+
"title",
|
|
873
|
+
new FunctionValue(() => {
|
|
874
|
+
return new StringValue(titleCase(this.value));
|
|
875
|
+
})
|
|
876
|
+
],
|
|
877
|
+
["length", new NumericValue(this.value.length)]
|
|
878
|
+
]);
|
|
879
|
+
};
|
|
880
|
+
var BooleanValue = class extends RuntimeValue {
|
|
881
|
+
type = "BooleanValue";
|
|
882
|
+
};
|
|
883
|
+
var ObjectValue = class extends RuntimeValue {
|
|
884
|
+
type = "ObjectValue";
|
|
885
|
+
/**
|
|
886
|
+
* NOTE: necessary to override since all JavaScript arrays are considered truthy,
|
|
887
|
+
* while only non-empty Python arrays are consider truthy.
|
|
888
|
+
*
|
|
889
|
+
* e.g.,
|
|
890
|
+
* - JavaScript: {} && 5 -> 5
|
|
891
|
+
* - Python: {} and 5 -> {}
|
|
892
|
+
*/
|
|
893
|
+
__bool__() {
|
|
894
|
+
return new BooleanValue(this.value.size > 0);
|
|
895
|
+
}
|
|
896
|
+
builtins = /* @__PURE__ */ new Map([
|
|
897
|
+
[
|
|
898
|
+
"get",
|
|
899
|
+
new FunctionValue(([key, defaultValue]) => {
|
|
900
|
+
if (!(key instanceof StringValue)) {
|
|
901
|
+
throw new Error(`Object key must be a string: got ${key.type}`);
|
|
902
|
+
}
|
|
903
|
+
return this.value.get(key.value) ?? defaultValue ?? new NullValue();
|
|
904
|
+
})
|
|
905
|
+
],
|
|
906
|
+
[
|
|
907
|
+
"items",
|
|
908
|
+
new FunctionValue(() => {
|
|
909
|
+
return new ArrayValue(
|
|
910
|
+
Array.from(this.value.entries()).map(([key, value]) => new ArrayValue([new StringValue(key), value]))
|
|
911
|
+
);
|
|
912
|
+
})
|
|
913
|
+
]
|
|
914
|
+
]);
|
|
915
|
+
};
|
|
916
|
+
var ArrayValue = class extends RuntimeValue {
|
|
917
|
+
type = "ArrayValue";
|
|
918
|
+
builtins = /* @__PURE__ */ new Map([["length", new NumericValue(this.value.length)]]);
|
|
919
|
+
/**
|
|
920
|
+
* NOTE: necessary to override since all JavaScript arrays are considered truthy,
|
|
921
|
+
* while only non-empty Python arrays are consider truthy.
|
|
922
|
+
*
|
|
923
|
+
* e.g.,
|
|
924
|
+
* - JavaScript: [] && 5 -> 5
|
|
925
|
+
* - Python: [] and 5 -> []
|
|
926
|
+
*/
|
|
927
|
+
__bool__() {
|
|
928
|
+
return new BooleanValue(this.value.length > 0);
|
|
929
|
+
}
|
|
930
|
+
};
|
|
931
|
+
var TupleValue = class extends ArrayValue {
|
|
932
|
+
type = "TupleValue";
|
|
933
|
+
};
|
|
934
|
+
var FunctionValue = class extends RuntimeValue {
|
|
935
|
+
type = "FunctionValue";
|
|
936
|
+
};
|
|
937
|
+
var NullValue = class extends RuntimeValue {
|
|
938
|
+
type = "NullValue";
|
|
939
|
+
};
|
|
940
|
+
var UndefinedValue = class extends RuntimeValue {
|
|
941
|
+
type = "UndefinedValue";
|
|
942
|
+
};
|
|
943
|
+
var Environment = class {
|
|
944
|
+
constructor(parent) {
|
|
945
|
+
this.parent = parent;
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* The variables declared in this environment.
|
|
949
|
+
*/
|
|
950
|
+
variables = /* @__PURE__ */ new Map([
|
|
951
|
+
[
|
|
952
|
+
"namespace",
|
|
953
|
+
new FunctionValue((args) => {
|
|
954
|
+
if (args.length === 0) {
|
|
955
|
+
return new ObjectValue(/* @__PURE__ */ new Map());
|
|
956
|
+
}
|
|
957
|
+
if (args.length !== 1 || !(args[0] instanceof ObjectValue)) {
|
|
958
|
+
throw new Error("`namespace` expects either zero arguments or a single object argument");
|
|
959
|
+
}
|
|
960
|
+
return args[0];
|
|
961
|
+
})
|
|
962
|
+
]
|
|
963
|
+
]);
|
|
964
|
+
/**
|
|
965
|
+
* The tests available in this environment.
|
|
966
|
+
*/
|
|
967
|
+
tests = /* @__PURE__ */ new Map([
|
|
968
|
+
["boolean", (operand) => operand.type === "BooleanValue"],
|
|
969
|
+
["callable", (operand) => operand instanceof FunctionValue],
|
|
970
|
+
[
|
|
971
|
+
"odd",
|
|
972
|
+
(operand) => {
|
|
973
|
+
if (operand.type !== "NumericValue") {
|
|
974
|
+
throw new Error(`Cannot apply test "odd" to type: ${operand.type}`);
|
|
975
|
+
}
|
|
976
|
+
return operand.value % 2 !== 0;
|
|
977
|
+
}
|
|
978
|
+
],
|
|
979
|
+
[
|
|
980
|
+
"even",
|
|
981
|
+
(operand) => {
|
|
982
|
+
if (operand.type !== "NumericValue") {
|
|
983
|
+
throw new Error(`Cannot apply test "even" to type: ${operand.type}`);
|
|
984
|
+
}
|
|
985
|
+
return operand.value % 2 === 0;
|
|
986
|
+
}
|
|
987
|
+
],
|
|
988
|
+
["false", (operand) => operand.type === "BooleanValue" && !operand.value],
|
|
989
|
+
["true", (operand) => operand.type === "BooleanValue" && operand.value],
|
|
990
|
+
["number", (operand) => operand.type === "NumericValue"],
|
|
991
|
+
["integer", (operand) => operand.type === "NumericValue" && Number.isInteger(operand.value)],
|
|
992
|
+
["iterable", (operand) => operand instanceof ArrayValue || operand instanceof StringValue],
|
|
993
|
+
[
|
|
994
|
+
"lower",
|
|
995
|
+
(operand) => {
|
|
996
|
+
const str = operand.value;
|
|
997
|
+
return operand.type === "StringValue" && str === str.toLowerCase();
|
|
998
|
+
}
|
|
999
|
+
],
|
|
1000
|
+
[
|
|
1001
|
+
"upper",
|
|
1002
|
+
(operand) => {
|
|
1003
|
+
const str = operand.value;
|
|
1004
|
+
return operand.type === "StringValue" && str === str.toUpperCase();
|
|
1005
|
+
}
|
|
1006
|
+
],
|
|
1007
|
+
["none", (operand) => operand.type === "NullValue"],
|
|
1008
|
+
["defined", (operand) => operand.type !== "UndefinedValue"],
|
|
1009
|
+
["undefined", (operand) => operand.type === "UndefinedValue"],
|
|
1010
|
+
["equalto", (a, b) => a.value === b.value]
|
|
1011
|
+
]);
|
|
1012
|
+
/**
|
|
1013
|
+
* Set the value of a variable in the current environment.
|
|
1014
|
+
*/
|
|
1015
|
+
set(name, value) {
|
|
1016
|
+
return this.declareVariable(name, convertToRuntimeValues(value));
|
|
1017
|
+
}
|
|
1018
|
+
declareVariable(name, value) {
|
|
1019
|
+
if (this.variables.has(name)) {
|
|
1020
|
+
throw new SyntaxError(`Variable already declared: ${name}`);
|
|
1021
|
+
}
|
|
1022
|
+
this.variables.set(name, value);
|
|
1023
|
+
return value;
|
|
1024
|
+
}
|
|
1025
|
+
// private assignVariable(name: string, value: AnyRuntimeValue): AnyRuntimeValue {
|
|
1026
|
+
// const env = this.resolve(name);
|
|
1027
|
+
// env.variables.set(name, value);
|
|
1028
|
+
// return value;
|
|
1029
|
+
// }
|
|
1030
|
+
/**
|
|
1031
|
+
* Set variable in the current scope.
|
|
1032
|
+
* See https://jinja.palletsprojects.com/en/3.0.x/templates/#assignments for more information.
|
|
1033
|
+
*/
|
|
1034
|
+
setVariable(name, value) {
|
|
1035
|
+
this.variables.set(name, value);
|
|
1036
|
+
return value;
|
|
1037
|
+
}
|
|
1038
|
+
/**
|
|
1039
|
+
* Resolve the environment in which the variable is declared.
|
|
1040
|
+
* @param {string} name The name of the variable.
|
|
1041
|
+
* @returns {Environment} The environment in which the variable is declared.
|
|
1042
|
+
*/
|
|
1043
|
+
resolve(name) {
|
|
1044
|
+
if (this.variables.has(name)) {
|
|
1045
|
+
return this;
|
|
1046
|
+
}
|
|
1047
|
+
if (this.parent) {
|
|
1048
|
+
return this.parent.resolve(name);
|
|
1049
|
+
}
|
|
1050
|
+
throw new Error(`Unknown variable: ${name}`);
|
|
1051
|
+
}
|
|
1052
|
+
lookupVariable(name) {
|
|
1053
|
+
try {
|
|
1054
|
+
return this.resolve(name).variables.get(name) ?? new UndefinedValue();
|
|
1055
|
+
} catch {
|
|
1056
|
+
return new UndefinedValue();
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
};
|
|
1060
|
+
var Interpreter = class {
|
|
1061
|
+
global;
|
|
1062
|
+
constructor(env) {
|
|
1063
|
+
this.global = env ?? new Environment();
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Run the program.
|
|
1067
|
+
*/
|
|
1068
|
+
run(program) {
|
|
1069
|
+
return this.evaluate(program, this.global);
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Evaluates expressions following the binary operation type.
|
|
1073
|
+
*/
|
|
1074
|
+
evaluateBinaryExpression(node, environment) {
|
|
1075
|
+
const left = this.evaluate(node.left, environment);
|
|
1076
|
+
switch (node.operator.value) {
|
|
1077
|
+
case "and":
|
|
1078
|
+
return left.__bool__().value ? this.evaluate(node.right, environment) : left;
|
|
1079
|
+
case "or":
|
|
1080
|
+
return left.__bool__().value ? left : this.evaluate(node.right, environment);
|
|
1081
|
+
}
|
|
1082
|
+
const right = this.evaluate(node.right, environment);
|
|
1083
|
+
switch (node.operator.value) {
|
|
1084
|
+
case "==":
|
|
1085
|
+
return new BooleanValue(left.value == right.value);
|
|
1086
|
+
case "!=":
|
|
1087
|
+
return new BooleanValue(left.value != right.value);
|
|
1088
|
+
}
|
|
1089
|
+
if (left instanceof UndefinedValue || right instanceof UndefinedValue) {
|
|
1090
|
+
throw new Error("Cannot perform operation on undefined values");
|
|
1091
|
+
} else if (left instanceof NullValue || right instanceof NullValue) {
|
|
1092
|
+
throw new Error("Cannot perform operation on null values");
|
|
1093
|
+
} else if (left instanceof NumericValue && right instanceof NumericValue) {
|
|
1094
|
+
switch (node.operator.value) {
|
|
1095
|
+
case "+":
|
|
1096
|
+
return new NumericValue(left.value + right.value);
|
|
1097
|
+
case "-":
|
|
1098
|
+
return new NumericValue(left.value - right.value);
|
|
1099
|
+
case "*":
|
|
1100
|
+
return new NumericValue(left.value * right.value);
|
|
1101
|
+
case "/":
|
|
1102
|
+
return new NumericValue(left.value / right.value);
|
|
1103
|
+
case "%":
|
|
1104
|
+
return new NumericValue(left.value % right.value);
|
|
1105
|
+
case "<":
|
|
1106
|
+
return new BooleanValue(left.value < right.value);
|
|
1107
|
+
case ">":
|
|
1108
|
+
return new BooleanValue(left.value > right.value);
|
|
1109
|
+
case ">=":
|
|
1110
|
+
return new BooleanValue(left.value >= right.value);
|
|
1111
|
+
case "<=":
|
|
1112
|
+
return new BooleanValue(left.value <= right.value);
|
|
1113
|
+
}
|
|
1114
|
+
} else if (left instanceof ArrayValue && right instanceof ArrayValue) {
|
|
1115
|
+
switch (node.operator.value) {
|
|
1116
|
+
case "+":
|
|
1117
|
+
return new ArrayValue(left.value.concat(right.value));
|
|
1118
|
+
}
|
|
1119
|
+
} else if (right instanceof ArrayValue) {
|
|
1120
|
+
const member = right.value.find((x) => x.value === left.value) !== void 0;
|
|
1121
|
+
switch (node.operator.value) {
|
|
1122
|
+
case "in":
|
|
1123
|
+
return new BooleanValue(member);
|
|
1124
|
+
case "not in":
|
|
1125
|
+
return new BooleanValue(!member);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
if (left instanceof StringValue || right instanceof StringValue) {
|
|
1129
|
+
switch (node.operator.value) {
|
|
1130
|
+
case "+":
|
|
1131
|
+
return new StringValue(left.value.toString() + right.value.toString());
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
if (left instanceof StringValue && right instanceof StringValue) {
|
|
1135
|
+
switch (node.operator.value) {
|
|
1136
|
+
case "in":
|
|
1137
|
+
return new BooleanValue(right.value.includes(left.value));
|
|
1138
|
+
case "not in":
|
|
1139
|
+
return new BooleanValue(!right.value.includes(left.value));
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
if (left instanceof StringValue && right instanceof ObjectValue) {
|
|
1143
|
+
switch (node.operator.value) {
|
|
1144
|
+
case "in":
|
|
1145
|
+
return new BooleanValue(right.value.has(left.value));
|
|
1146
|
+
case "not in":
|
|
1147
|
+
return new BooleanValue(!right.value.has(left.value));
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
throw new SyntaxError(`Unknown operator "${node.operator.value}" between ${left.type} and ${right.type}`);
|
|
1151
|
+
}
|
|
1152
|
+
/**
|
|
1153
|
+
* Evaluates expressions following the filter operation type.
|
|
1154
|
+
*/
|
|
1155
|
+
evaluateFilterExpression(node, environment) {
|
|
1156
|
+
const operand = this.evaluate(node.operand, environment);
|
|
1157
|
+
if (node.filter.type === "Identifier") {
|
|
1158
|
+
const filter = node.filter;
|
|
1159
|
+
if (operand instanceof ArrayValue) {
|
|
1160
|
+
switch (filter.value) {
|
|
1161
|
+
case "list":
|
|
1162
|
+
return operand;
|
|
1163
|
+
case "first":
|
|
1164
|
+
return operand.value[0];
|
|
1165
|
+
case "last":
|
|
1166
|
+
return operand.value[operand.value.length - 1];
|
|
1167
|
+
case "length":
|
|
1168
|
+
return new NumericValue(operand.value.length);
|
|
1169
|
+
case "reverse":
|
|
1170
|
+
return new ArrayValue(operand.value.reverse());
|
|
1171
|
+
case "sort":
|
|
1172
|
+
return new ArrayValue(
|
|
1173
|
+
operand.value.sort((a, b) => {
|
|
1174
|
+
if (a.type !== b.type) {
|
|
1175
|
+
throw new Error(`Cannot compare different types: ${a.type} and ${b.type}`);
|
|
1176
|
+
}
|
|
1177
|
+
switch (a.type) {
|
|
1178
|
+
case "NumericValue":
|
|
1179
|
+
return a.value - b.value;
|
|
1180
|
+
case "StringValue":
|
|
1181
|
+
return a.value.localeCompare(b.value);
|
|
1182
|
+
default:
|
|
1183
|
+
throw new Error(`Cannot compare type: ${a.type}`);
|
|
1184
|
+
}
|
|
1185
|
+
})
|
|
1186
|
+
);
|
|
1187
|
+
default:
|
|
1188
|
+
throw new Error(`Unknown ArrayValue filter: ${filter.value}`);
|
|
1189
|
+
}
|
|
1190
|
+
} else if (operand instanceof StringValue) {
|
|
1191
|
+
switch (filter.value) {
|
|
1192
|
+
case "length":
|
|
1193
|
+
return new NumericValue(operand.value.length);
|
|
1194
|
+
case "upper":
|
|
1195
|
+
return new StringValue(operand.value.toUpperCase());
|
|
1196
|
+
case "lower":
|
|
1197
|
+
return new StringValue(operand.value.toLowerCase());
|
|
1198
|
+
case "title":
|
|
1199
|
+
return new StringValue(titleCase(operand.value));
|
|
1200
|
+
case "capitalize":
|
|
1201
|
+
return new StringValue(operand.value.charAt(0).toUpperCase() + operand.value.slice(1));
|
|
1202
|
+
case "trim":
|
|
1203
|
+
return new StringValue(operand.value.trim());
|
|
1204
|
+
default:
|
|
1205
|
+
throw new Error(`Unknown StringValue filter: ${filter.value}`);
|
|
1206
|
+
}
|
|
1207
|
+
} else if (operand instanceof NumericValue) {
|
|
1208
|
+
switch (filter.value) {
|
|
1209
|
+
case "abs":
|
|
1210
|
+
return new NumericValue(Math.abs(operand.value));
|
|
1211
|
+
default:
|
|
1212
|
+
throw new Error(`Unknown NumericValue filter: ${filter.value}`);
|
|
1213
|
+
}
|
|
1214
|
+
} else if (operand instanceof ObjectValue) {
|
|
1215
|
+
switch (filter.value) {
|
|
1216
|
+
case "items":
|
|
1217
|
+
return new ArrayValue(
|
|
1218
|
+
Array.from(operand.value.entries()).map(([key, value]) => new ArrayValue([new StringValue(key), value]))
|
|
1219
|
+
);
|
|
1220
|
+
case "length":
|
|
1221
|
+
return new NumericValue(operand.value.size);
|
|
1222
|
+
default:
|
|
1223
|
+
throw new Error(`Unknown ObjectValue filter: ${filter.value}`);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
throw new Error(`Cannot apply filter "${filter.value}" to type: ${operand.type}`);
|
|
1227
|
+
} else if (node.filter.type === "CallExpression") {
|
|
1228
|
+
const filter = node.filter;
|
|
1229
|
+
if (filter.callee.type !== "Identifier") {
|
|
1230
|
+
throw new Error(`Unknown filter: ${filter.callee.type}`);
|
|
1231
|
+
}
|
|
1232
|
+
const filterName = filter.callee.value;
|
|
1233
|
+
if (operand instanceof ArrayValue) {
|
|
1234
|
+
switch (filterName) {
|
|
1235
|
+
case "selectattr": {
|
|
1236
|
+
if (operand.value.some((x) => !(x instanceof ObjectValue))) {
|
|
1237
|
+
throw new Error("`selectattr` can only be applied to array of objects");
|
|
1238
|
+
}
|
|
1239
|
+
if (filter.args.some((x) => x.type !== "StringLiteral")) {
|
|
1240
|
+
throw new Error("arguments of `selectattr` must be strings");
|
|
1241
|
+
}
|
|
1242
|
+
const [attr, testName, value] = filter.args.map((x) => this.evaluate(x, environment));
|
|
1243
|
+
let testFunction;
|
|
1244
|
+
if (testName) {
|
|
1245
|
+
const test = environment.tests.get(testName.value);
|
|
1246
|
+
if (!test) {
|
|
1247
|
+
throw new Error(`Unknown test: ${testName.value}`);
|
|
1248
|
+
}
|
|
1249
|
+
testFunction = test;
|
|
1250
|
+
} else {
|
|
1251
|
+
testFunction = (...x) => x[0].__bool__().value;
|
|
1252
|
+
}
|
|
1253
|
+
const filtered = operand.value.filter((item) => {
|
|
1254
|
+
const a = item.value.get(attr.value);
|
|
1255
|
+
if (a) {
|
|
1256
|
+
return testFunction(a, value);
|
|
1257
|
+
}
|
|
1258
|
+
return false;
|
|
1259
|
+
});
|
|
1260
|
+
return new ArrayValue(filtered);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
throw new Error(`Unknown ArrayValue filter: ${filterName}`);
|
|
1264
|
+
} else {
|
|
1265
|
+
throw new Error(`Cannot apply filter "${filterName}" to type: ${operand.type}`);
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
throw new Error(`Unknown filter: ${node.filter.type}`);
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* Evaluates expressions following the test operation type.
|
|
1272
|
+
*/
|
|
1273
|
+
evaluateTestExpression(node, environment) {
|
|
1274
|
+
const operand = this.evaluate(node.operand, environment);
|
|
1275
|
+
const test = environment.tests.get(node.test.value);
|
|
1276
|
+
if (!test) {
|
|
1277
|
+
throw new Error(`Unknown test: ${node.test.value}`);
|
|
1278
|
+
}
|
|
1279
|
+
const result = test(operand);
|
|
1280
|
+
return new BooleanValue(node.negate ? !result : result);
|
|
1281
|
+
}
|
|
1282
|
+
/**
|
|
1283
|
+
* Evaluates expressions following the unary operation type.
|
|
1284
|
+
*/
|
|
1285
|
+
evaluateUnaryExpression(node, environment) {
|
|
1286
|
+
const argument = this.evaluate(node.argument, environment);
|
|
1287
|
+
switch (node.operator.value) {
|
|
1288
|
+
case "not":
|
|
1289
|
+
return new BooleanValue(!argument.value);
|
|
1290
|
+
default:
|
|
1291
|
+
throw new SyntaxError(`Unknown operator: ${node.operator.value}`);
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
evalProgram(program, environment) {
|
|
1295
|
+
return this.evaluateBlock(program.body, environment);
|
|
1296
|
+
}
|
|
1297
|
+
evaluateBlock(statements, environment) {
|
|
1298
|
+
let result = "";
|
|
1299
|
+
for (const statement of statements) {
|
|
1300
|
+
const lastEvaluated = this.evaluate(statement, environment);
|
|
1301
|
+
if (lastEvaluated.type !== "NullValue" && lastEvaluated.type !== "UndefinedValue") {
|
|
1302
|
+
result += lastEvaluated.value;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
return new StringValue(result);
|
|
1306
|
+
}
|
|
1307
|
+
evaluateIdentifier(node, environment) {
|
|
1308
|
+
return environment.lookupVariable(node.value);
|
|
1309
|
+
}
|
|
1310
|
+
evaluateCallExpression(expr, environment) {
|
|
1311
|
+
const args = [];
|
|
1312
|
+
const kwargs = /* @__PURE__ */ new Map();
|
|
1313
|
+
for (const argument of expr.args) {
|
|
1314
|
+
if (argument.type === "KeywordArgumentExpression") {
|
|
1315
|
+
const kwarg = argument;
|
|
1316
|
+
kwargs.set(kwarg.key.value, this.evaluate(kwarg.value, environment));
|
|
1317
|
+
} else {
|
|
1318
|
+
args.push(this.evaluate(argument, environment));
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
if (kwargs.size > 0) {
|
|
1322
|
+
args.push(new ObjectValue(kwargs));
|
|
1323
|
+
}
|
|
1324
|
+
const fn = this.evaluate(expr.callee, environment);
|
|
1325
|
+
if (fn.type !== "FunctionValue") {
|
|
1326
|
+
throw new Error(`Cannot call something that is not a function: got ${fn.type}`);
|
|
1327
|
+
}
|
|
1328
|
+
return fn.value(args, environment);
|
|
1329
|
+
}
|
|
1330
|
+
evaluateSliceExpression(object, expr, environment) {
|
|
1331
|
+
if (!(object instanceof ArrayValue || object instanceof StringValue)) {
|
|
1332
|
+
throw new Error("Slice object must be an array or string");
|
|
1333
|
+
}
|
|
1334
|
+
const start = this.evaluate(expr.start, environment);
|
|
1335
|
+
const stop = this.evaluate(expr.stop, environment);
|
|
1336
|
+
const step = this.evaluate(expr.step, environment);
|
|
1337
|
+
if (!(start instanceof NumericValue || start instanceof UndefinedValue)) {
|
|
1338
|
+
throw new Error("Slice start must be numeric or undefined");
|
|
1339
|
+
}
|
|
1340
|
+
if (!(stop instanceof NumericValue || stop instanceof UndefinedValue)) {
|
|
1341
|
+
throw new Error("Slice stop must be numeric or undefined");
|
|
1342
|
+
}
|
|
1343
|
+
if (!(step instanceof NumericValue || step instanceof UndefinedValue)) {
|
|
1344
|
+
throw new Error("Slice step must be numeric or undefined");
|
|
1345
|
+
}
|
|
1346
|
+
if (object instanceof ArrayValue) {
|
|
1347
|
+
return new ArrayValue(slice(object.value, start.value, stop.value, step.value));
|
|
1348
|
+
} else {
|
|
1349
|
+
return new StringValue(slice(Array.from(object.value), start.value, stop.value, step.value).join(""));
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
evaluateMemberExpression(expr, environment) {
|
|
1353
|
+
const object = this.evaluate(expr.object, environment);
|
|
1354
|
+
let property;
|
|
1355
|
+
if (expr.computed) {
|
|
1356
|
+
if (expr.property.type === "SliceExpression") {
|
|
1357
|
+
return this.evaluateSliceExpression(object, expr.property, environment);
|
|
1358
|
+
} else {
|
|
1359
|
+
property = this.evaluate(expr.property, environment);
|
|
1360
|
+
}
|
|
1361
|
+
} else {
|
|
1362
|
+
property = new StringValue(expr.property.value);
|
|
1363
|
+
}
|
|
1364
|
+
let value;
|
|
1365
|
+
if (object instanceof ObjectValue) {
|
|
1366
|
+
if (!(property instanceof StringValue)) {
|
|
1367
|
+
throw new Error(`Cannot access property with non-string: got ${property.type}`);
|
|
1368
|
+
}
|
|
1369
|
+
value = object.value.get(property.value) ?? object.builtins.get(property.value);
|
|
1370
|
+
} else if (object instanceof ArrayValue || object instanceof StringValue) {
|
|
1371
|
+
if (property instanceof NumericValue) {
|
|
1372
|
+
value = object.value.at(property.value);
|
|
1373
|
+
if (object instanceof StringValue) {
|
|
1374
|
+
value = new StringValue(object.value.at(property.value));
|
|
1375
|
+
}
|
|
1376
|
+
} else if (property instanceof StringValue) {
|
|
1377
|
+
value = object.builtins.get(property.value);
|
|
1378
|
+
} else {
|
|
1379
|
+
throw new Error(`Cannot access property with non-string/non-number: got ${property.type}`);
|
|
1380
|
+
}
|
|
1381
|
+
} else {
|
|
1382
|
+
if (!(property instanceof StringValue)) {
|
|
1383
|
+
throw new Error(`Cannot access property with non-string: got ${property.type}`);
|
|
1384
|
+
}
|
|
1385
|
+
value = object.builtins.get(property.value);
|
|
1386
|
+
}
|
|
1387
|
+
return value instanceof RuntimeValue ? value : new UndefinedValue();
|
|
1388
|
+
}
|
|
1389
|
+
evaluateSet(node, environment) {
|
|
1390
|
+
const rhs = this.evaluate(node.value, environment);
|
|
1391
|
+
if (node.assignee.type === "Identifier") {
|
|
1392
|
+
const variableName = node.assignee.value;
|
|
1393
|
+
environment.setVariable(variableName, rhs);
|
|
1394
|
+
} else if (node.assignee.type === "MemberExpression") {
|
|
1395
|
+
const member = node.assignee;
|
|
1396
|
+
const object = this.evaluate(member.object, environment);
|
|
1397
|
+
if (!(object instanceof ObjectValue)) {
|
|
1398
|
+
throw new Error("Cannot assign to member of non-object");
|
|
1399
|
+
}
|
|
1400
|
+
if (member.property.type !== "Identifier") {
|
|
1401
|
+
throw new Error("Cannot assign to member with non-identifier property");
|
|
1402
|
+
}
|
|
1403
|
+
object.value.set(member.property.value, rhs);
|
|
1404
|
+
} else {
|
|
1405
|
+
throw new Error(`Invalid LHS inside assignment expression: ${JSON.stringify(node.assignee)}`);
|
|
1406
|
+
}
|
|
1407
|
+
return new NullValue();
|
|
1408
|
+
}
|
|
1409
|
+
evaluateIf(node, environment) {
|
|
1410
|
+
const test = this.evaluate(node.test, environment);
|
|
1411
|
+
return this.evaluateBlock(test.__bool__().value ? node.body : node.alternate, environment);
|
|
1412
|
+
}
|
|
1413
|
+
evaluateFor(node, environment) {
|
|
1414
|
+
const scope = new Environment(environment);
|
|
1415
|
+
const iterable = this.evaluate(node.iterable, scope);
|
|
1416
|
+
if (!(iterable instanceof ArrayValue)) {
|
|
1417
|
+
throw new Error(`Expected iterable type in for loop: got ${iterable.type}`);
|
|
1418
|
+
}
|
|
1419
|
+
let result = "";
|
|
1420
|
+
for (let i = 0; i < iterable.value.length; ++i) {
|
|
1421
|
+
const loop = /* @__PURE__ */ new Map([
|
|
1422
|
+
["index", new NumericValue(i + 1)],
|
|
1423
|
+
["index0", new NumericValue(i)],
|
|
1424
|
+
["revindex", new NumericValue(iterable.value.length - i)],
|
|
1425
|
+
["revindex0", new NumericValue(iterable.value.length - i - 1)],
|
|
1426
|
+
["first", new BooleanValue(i === 0)],
|
|
1427
|
+
["last", new BooleanValue(i === iterable.value.length - 1)],
|
|
1428
|
+
["length", new NumericValue(iterable.value.length)],
|
|
1429
|
+
["previtem", i > 0 ? iterable.value[i - 1] : new UndefinedValue()],
|
|
1430
|
+
["nextitem", i < iterable.value.length - 1 ? iterable.value[i + 1] : new UndefinedValue()]
|
|
1431
|
+
]);
|
|
1432
|
+
scope.setVariable("loop", new ObjectValue(loop));
|
|
1433
|
+
const current = iterable.value[i];
|
|
1434
|
+
if (node.loopvar.type === "Identifier") {
|
|
1435
|
+
scope.setVariable(node.loopvar.value, current);
|
|
1436
|
+
} else if (node.loopvar.type === "TupleLiteral") {
|
|
1437
|
+
const loopvar = node.loopvar;
|
|
1438
|
+
if (current.type !== "ArrayValue") {
|
|
1439
|
+
throw new Error(`Cannot unpack non-iterable type: ${current.type}`);
|
|
1440
|
+
}
|
|
1441
|
+
const c = current;
|
|
1442
|
+
if (loopvar.value.length !== c.value.length) {
|
|
1443
|
+
throw new Error(`Too ${loopvar.value.length > c.value.length ? "few" : "many"} items to unpack`);
|
|
1444
|
+
}
|
|
1445
|
+
for (let j = 0; j < loopvar.value.length; ++j) {
|
|
1446
|
+
if (loopvar.value[j].type !== "Identifier") {
|
|
1447
|
+
throw new Error(`Cannot unpack non-identifier type: ${loopvar.value[j].type}`);
|
|
1448
|
+
}
|
|
1449
|
+
scope.setVariable(loopvar.value[j].value, c.value[j]);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
const evaluated = this.evaluateBlock(node.body, scope);
|
|
1453
|
+
result += evaluated.value;
|
|
1454
|
+
}
|
|
1455
|
+
return new StringValue(result);
|
|
1456
|
+
}
|
|
1457
|
+
evaluate(statement, environment) {
|
|
1458
|
+
if (statement === void 0)
|
|
1459
|
+
return new UndefinedValue();
|
|
1460
|
+
switch (statement.type) {
|
|
1461
|
+
case "Program":
|
|
1462
|
+
return this.evalProgram(statement, environment);
|
|
1463
|
+
case "Set":
|
|
1464
|
+
return this.evaluateSet(statement, environment);
|
|
1465
|
+
case "If":
|
|
1466
|
+
return this.evaluateIf(statement, environment);
|
|
1467
|
+
case "For":
|
|
1468
|
+
return this.evaluateFor(statement, environment);
|
|
1469
|
+
case "NumericLiteral":
|
|
1470
|
+
return new NumericValue(Number(statement.value));
|
|
1471
|
+
case "StringLiteral":
|
|
1472
|
+
return new StringValue(statement.value);
|
|
1473
|
+
case "BooleanLiteral":
|
|
1474
|
+
return new BooleanValue(statement.value);
|
|
1475
|
+
case "ArrayLiteral":
|
|
1476
|
+
return new ArrayValue(statement.value.map((x) => this.evaluate(x, environment)));
|
|
1477
|
+
case "TupleLiteral":
|
|
1478
|
+
return new TupleValue(statement.value.map((x) => this.evaluate(x, environment)));
|
|
1479
|
+
case "ObjectLiteral": {
|
|
1480
|
+
const mapping = /* @__PURE__ */ new Map();
|
|
1481
|
+
for (const [key, value] of statement.value) {
|
|
1482
|
+
const evaluatedKey = this.evaluate(key, environment);
|
|
1483
|
+
if (!(evaluatedKey instanceof StringValue)) {
|
|
1484
|
+
throw new Error(`Object keys must be strings: got ${evaluatedKey.type}`);
|
|
1485
|
+
}
|
|
1486
|
+
mapping.set(evaluatedKey.value, this.evaluate(value, environment));
|
|
1487
|
+
}
|
|
1488
|
+
return new ObjectValue(mapping);
|
|
1489
|
+
}
|
|
1490
|
+
case "Identifier":
|
|
1491
|
+
return this.evaluateIdentifier(statement, environment);
|
|
1492
|
+
case "CallExpression":
|
|
1493
|
+
return this.evaluateCallExpression(statement, environment);
|
|
1494
|
+
case "MemberExpression":
|
|
1495
|
+
return this.evaluateMemberExpression(statement, environment);
|
|
1496
|
+
case "UnaryExpression":
|
|
1497
|
+
return this.evaluateUnaryExpression(statement, environment);
|
|
1498
|
+
case "BinaryExpression":
|
|
1499
|
+
return this.evaluateBinaryExpression(statement, environment);
|
|
1500
|
+
case "FilterExpression":
|
|
1501
|
+
return this.evaluateFilterExpression(statement, environment);
|
|
1502
|
+
case "TestExpression":
|
|
1503
|
+
return this.evaluateTestExpression(statement, environment);
|
|
1504
|
+
default:
|
|
1505
|
+
throw new SyntaxError(`Unknown node type: ${statement.type}`);
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
};
|
|
1509
|
+
function convertToRuntimeValues(input) {
|
|
1510
|
+
switch (typeof input) {
|
|
1511
|
+
case "number":
|
|
1512
|
+
return new NumericValue(input);
|
|
1513
|
+
case "string":
|
|
1514
|
+
return new StringValue(input);
|
|
1515
|
+
case "boolean":
|
|
1516
|
+
return new BooleanValue(input);
|
|
1517
|
+
case "object":
|
|
1518
|
+
if (input === null) {
|
|
1519
|
+
return new NullValue();
|
|
1520
|
+
} else if (Array.isArray(input)) {
|
|
1521
|
+
return new ArrayValue(input.map(convertToRuntimeValues));
|
|
1522
|
+
} else {
|
|
1523
|
+
return new ObjectValue(
|
|
1524
|
+
new Map(Object.entries(input).map(([key, value]) => [key, convertToRuntimeValues(value)]))
|
|
1525
|
+
);
|
|
1526
|
+
}
|
|
1527
|
+
case "function":
|
|
1528
|
+
return new FunctionValue((args, _scope) => {
|
|
1529
|
+
const result = input(...args.map((x) => x.value)) ?? null;
|
|
1530
|
+
return convertToRuntimeValues(result);
|
|
1531
|
+
});
|
|
1532
|
+
default:
|
|
1533
|
+
throw new Error(`Cannot convert to runtime value: ${input}`);
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
// src/index.ts
|
|
1538
|
+
var Template = class {
|
|
1539
|
+
parsed;
|
|
1540
|
+
/**
|
|
1541
|
+
* @param {string} template The template string
|
|
1542
|
+
*/
|
|
1543
|
+
constructor(template) {
|
|
1544
|
+
const tokens = tokenize(template, {
|
|
1545
|
+
lstrip_blocks: true,
|
|
1546
|
+
trim_blocks: true
|
|
1547
|
+
});
|
|
1548
|
+
this.parsed = parse(tokens);
|
|
1549
|
+
}
|
|
1550
|
+
render(items) {
|
|
1551
|
+
const env = new Environment();
|
|
1552
|
+
env.set("false", false);
|
|
1553
|
+
env.set("true", true);
|
|
1554
|
+
env.set("raise_exception", (args) => {
|
|
1555
|
+
throw new Error(args);
|
|
1556
|
+
});
|
|
1557
|
+
env.set("range", range);
|
|
1558
|
+
for (const [key, value] of Object.entries(items)) {
|
|
1559
|
+
env.set(key, value);
|
|
1560
|
+
}
|
|
1561
|
+
const interpreter = new Interpreter(env);
|
|
1562
|
+
const result = interpreter.run(this.parsed);
|
|
1563
|
+
return result.value;
|
|
1564
|
+
}
|
|
1565
|
+
};
|
|
1566
|
+
export {
|
|
1567
|
+
Environment,
|
|
1568
|
+
Interpreter,
|
|
1569
|
+
Template,
|
|
1570
|
+
parse,
|
|
1571
|
+
tokenize
|
|
1572
|
+
};
|