bonsai-js 0.1.0
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/CHANGELOG.md +92 -0
- package/LICENSE +21 -0
- package/README.md +478 -0
- package/dist/errors-t5i9AEaf.mjs +165 -0
- package/dist/errors-t5i9AEaf.mjs.map +1 -0
- package/dist/index.d.mts +77 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2226 -0
- package/dist/index.mjs.map +1 -0
- package/dist/stdlib/index.d.mts +23 -0
- package/dist/stdlib/index.d.mts.map +1 -0
- package/dist/stdlib/index.mjs +178 -0
- package/dist/stdlib/index.mjs.map +1 -0
- package/dist/types-Cbouo-QC.d.mts +244 -0
- package/dist/types-Cbouo-QC.d.mts.map +1 -0
- package/package.json +58 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2226 @@
|
|
|
1
|
+
import { a as attachLocation, c as offsetToPosition, i as ExpressionError, l as suggest, n as BonsaiSecurityError, o as formatBonsaiError, r as BonsaiTypeError, s as formatError, t as BonsaiReferenceError } from "./errors-t5i9AEaf.mjs";
|
|
2
|
+
//#region src/lexer.ts
|
|
3
|
+
const KEYWORDS = new Map([
|
|
4
|
+
["true", {
|
|
5
|
+
type: "Boolean",
|
|
6
|
+
value: "true"
|
|
7
|
+
}],
|
|
8
|
+
["false", {
|
|
9
|
+
type: "Boolean",
|
|
10
|
+
value: "false"
|
|
11
|
+
}],
|
|
12
|
+
["null", {
|
|
13
|
+
type: "Null",
|
|
14
|
+
value: "null"
|
|
15
|
+
}],
|
|
16
|
+
["undefined", {
|
|
17
|
+
type: "Undefined",
|
|
18
|
+
value: "undefined"
|
|
19
|
+
}],
|
|
20
|
+
["in", {
|
|
21
|
+
type: "Operator",
|
|
22
|
+
value: "in"
|
|
23
|
+
}],
|
|
24
|
+
["not", {
|
|
25
|
+
type: "Operator",
|
|
26
|
+
value: "not"
|
|
27
|
+
}]
|
|
28
|
+
]);
|
|
29
|
+
function isDigit(ch) {
|
|
30
|
+
return ch >= "0" && ch <= "9";
|
|
31
|
+
}
|
|
32
|
+
function isAlpha(ch) {
|
|
33
|
+
return ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z" || ch === "_" || ch === "$";
|
|
34
|
+
}
|
|
35
|
+
function isAlphaNumeric(ch) {
|
|
36
|
+
return isAlpha(ch) || isDigit(ch);
|
|
37
|
+
}
|
|
38
|
+
function isHexDigit(ch) {
|
|
39
|
+
return isDigit(ch) || ch >= "a" && ch <= "f" || ch >= "A" && ch <= "F";
|
|
40
|
+
}
|
|
41
|
+
function tokenize(source) {
|
|
42
|
+
const tokens = [];
|
|
43
|
+
let i = 0;
|
|
44
|
+
while (i < source.length) {
|
|
45
|
+
const ch = source[i];
|
|
46
|
+
if (ch === " " || ch === " " || ch === "\n" || ch === "\r") {
|
|
47
|
+
i++;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (isDigit(ch)) {
|
|
51
|
+
const start = i;
|
|
52
|
+
if (ch === "0" && i + 1 < source.length && (source[i + 1] === "x" || source[i + 1] === "X")) {
|
|
53
|
+
i += 2;
|
|
54
|
+
while (i < source.length && (isHexDigit(source[i]) || source[i] === "_")) i++;
|
|
55
|
+
tokens.push({
|
|
56
|
+
type: "Number",
|
|
57
|
+
value: source.slice(start, i),
|
|
58
|
+
start,
|
|
59
|
+
end: i
|
|
60
|
+
});
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (ch === "0" && i + 1 < source.length && (source[i + 1] === "b" || source[i + 1] === "B")) {
|
|
64
|
+
i += 2;
|
|
65
|
+
while (i < source.length && (source[i] === "0" || source[i] === "1" || source[i] === "_")) i++;
|
|
66
|
+
tokens.push({
|
|
67
|
+
type: "Number",
|
|
68
|
+
value: source.slice(start, i),
|
|
69
|
+
start,
|
|
70
|
+
end: i
|
|
71
|
+
});
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (ch === "0" && i + 1 < source.length && (source[i + 1] === "o" || source[i + 1] === "O")) {
|
|
75
|
+
i += 2;
|
|
76
|
+
while (i < source.length && (source[i] >= "0" && source[i] <= "7" || source[i] === "_")) i++;
|
|
77
|
+
tokens.push({
|
|
78
|
+
type: "Number",
|
|
79
|
+
value: source.slice(start, i),
|
|
80
|
+
start,
|
|
81
|
+
end: i
|
|
82
|
+
});
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
while (i < source.length && (isDigit(source[i]) || source[i] === "_")) i++;
|
|
86
|
+
if (i < source.length && source[i] === "." && i + 1 < source.length && isDigit(source[i + 1])) {
|
|
87
|
+
i++;
|
|
88
|
+
while (i < source.length && (isDigit(source[i]) || source[i] === "_")) i++;
|
|
89
|
+
}
|
|
90
|
+
if (i < source.length && (source[i] === "e" || source[i] === "E")) {
|
|
91
|
+
i++;
|
|
92
|
+
if (i < source.length && (source[i] === "+" || source[i] === "-")) i++;
|
|
93
|
+
while (i < source.length && (isDigit(source[i]) || source[i] === "_")) i++;
|
|
94
|
+
}
|
|
95
|
+
tokens.push({
|
|
96
|
+
type: "Number",
|
|
97
|
+
value: source.slice(start, i),
|
|
98
|
+
start,
|
|
99
|
+
end: i
|
|
100
|
+
});
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (ch === "\"" || ch === "'") {
|
|
104
|
+
const quote = ch;
|
|
105
|
+
const start = i;
|
|
106
|
+
i++;
|
|
107
|
+
let value = "";
|
|
108
|
+
while (i < source.length && source[i] !== quote) {
|
|
109
|
+
if (source[i] === "\\") {
|
|
110
|
+
i++;
|
|
111
|
+
const escaped = source[i];
|
|
112
|
+
switch (escaped) {
|
|
113
|
+
case "n":
|
|
114
|
+
value += "\n";
|
|
115
|
+
break;
|
|
116
|
+
case "t":
|
|
117
|
+
value += " ";
|
|
118
|
+
break;
|
|
119
|
+
case "r":
|
|
120
|
+
value += "\r";
|
|
121
|
+
break;
|
|
122
|
+
case "\\":
|
|
123
|
+
value += "\\";
|
|
124
|
+
break;
|
|
125
|
+
case "0":
|
|
126
|
+
value += "\0";
|
|
127
|
+
break;
|
|
128
|
+
case "x": {
|
|
129
|
+
const hex = source.slice(i + 1, i + 3);
|
|
130
|
+
if (hex.length < 2 || !isHexDigit(hex[0]) || !isHexDigit(hex[1])) throw new ExpressionError("Invalid \\x escape: expected 2 hex digits", {
|
|
131
|
+
source,
|
|
132
|
+
start,
|
|
133
|
+
end: i + 3
|
|
134
|
+
});
|
|
135
|
+
value += String.fromCharCode(parseInt(hex, 16));
|
|
136
|
+
i += 2;
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
case "u":
|
|
140
|
+
if (i + 1 < source.length && source[i + 1] === "{") {
|
|
141
|
+
const closeBrace = source.indexOf("}", i + 2);
|
|
142
|
+
if (closeBrace === -1) throw new ExpressionError("Invalid \\u{} escape: missing closing brace", {
|
|
143
|
+
source,
|
|
144
|
+
start,
|
|
145
|
+
end: source.length
|
|
146
|
+
});
|
|
147
|
+
const hex = source.slice(i + 2, closeBrace);
|
|
148
|
+
if (hex.length === 0) throw new ExpressionError("Invalid \\u{} escape: empty code point", {
|
|
149
|
+
source,
|
|
150
|
+
start,
|
|
151
|
+
end: closeBrace + 1
|
|
152
|
+
});
|
|
153
|
+
for (const c of hex) if (!isHexDigit(c)) throw new ExpressionError(`Invalid \\u{} escape: non-hex digit "${c}"`, {
|
|
154
|
+
source,
|
|
155
|
+
start,
|
|
156
|
+
end: closeBrace + 1
|
|
157
|
+
});
|
|
158
|
+
const MAX_UNICODE_CODE_POINT = 1114111;
|
|
159
|
+
const codePoint = parseInt(hex, 16);
|
|
160
|
+
if (codePoint > MAX_UNICODE_CODE_POINT) throw new ExpressionError("Invalid \\u{} escape: code point out of range", {
|
|
161
|
+
source,
|
|
162
|
+
start,
|
|
163
|
+
end: closeBrace + 1
|
|
164
|
+
});
|
|
165
|
+
value += String.fromCodePoint(codePoint);
|
|
166
|
+
i = closeBrace;
|
|
167
|
+
} else {
|
|
168
|
+
const UNICODE_ESCAPE_LEN = 4;
|
|
169
|
+
const hex = source.slice(i + 1, i + 1 + UNICODE_ESCAPE_LEN);
|
|
170
|
+
if (hex.length < UNICODE_ESCAPE_LEN) throw new ExpressionError("Invalid \\u escape: expected 4 hex digits", {
|
|
171
|
+
source,
|
|
172
|
+
start,
|
|
173
|
+
end: i + 1 + UNICODE_ESCAPE_LEN
|
|
174
|
+
});
|
|
175
|
+
for (const c of hex) if (!isHexDigit(c)) throw new ExpressionError("Invalid \\u escape: expected 4 hex digits", {
|
|
176
|
+
source,
|
|
177
|
+
start,
|
|
178
|
+
end: i + 1 + UNICODE_ESCAPE_LEN
|
|
179
|
+
});
|
|
180
|
+
value += String.fromCharCode(parseInt(hex, 16));
|
|
181
|
+
i += UNICODE_ESCAPE_LEN;
|
|
182
|
+
}
|
|
183
|
+
break;
|
|
184
|
+
default:
|
|
185
|
+
value += escaped;
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
} else value += source[i];
|
|
189
|
+
i++;
|
|
190
|
+
}
|
|
191
|
+
if (i >= source.length) throw new ExpressionError("Unterminated string", {
|
|
192
|
+
source,
|
|
193
|
+
start,
|
|
194
|
+
end: i
|
|
195
|
+
});
|
|
196
|
+
i++;
|
|
197
|
+
tokens.push({
|
|
198
|
+
type: "String",
|
|
199
|
+
value,
|
|
200
|
+
start,
|
|
201
|
+
end: i
|
|
202
|
+
});
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (ch === "`") {
|
|
206
|
+
const start = i;
|
|
207
|
+
i++;
|
|
208
|
+
let raw = "`";
|
|
209
|
+
while (i < source.length && source[i] !== "`") if (source[i] === "$" && i + 1 < source.length && source[i + 1] === "{") {
|
|
210
|
+
raw += "${";
|
|
211
|
+
i += 2;
|
|
212
|
+
let depth = 1;
|
|
213
|
+
while (i < source.length && depth > 0) {
|
|
214
|
+
const ic = source[i];
|
|
215
|
+
if (ic === "\"" || ic === "'") {
|
|
216
|
+
raw += ic;
|
|
217
|
+
i++;
|
|
218
|
+
while (i < source.length && source[i] !== ic) {
|
|
219
|
+
if (source[i] === "\\" && i + 1 < source.length) {
|
|
220
|
+
raw += source[i] + source[i + 1];
|
|
221
|
+
i += 2;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
raw += source[i];
|
|
225
|
+
i++;
|
|
226
|
+
}
|
|
227
|
+
if (i < source.length) {
|
|
228
|
+
raw += source[i];
|
|
229
|
+
i++;
|
|
230
|
+
}
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (ic === "{") depth++;
|
|
234
|
+
else if (ic === "}") depth--;
|
|
235
|
+
if (depth > 0) raw += ic;
|
|
236
|
+
i++;
|
|
237
|
+
}
|
|
238
|
+
raw += "}";
|
|
239
|
+
} else {
|
|
240
|
+
raw += source[i];
|
|
241
|
+
i++;
|
|
242
|
+
}
|
|
243
|
+
if (i >= source.length) throw new ExpressionError("Unterminated template literal", {
|
|
244
|
+
source,
|
|
245
|
+
start,
|
|
246
|
+
end: i
|
|
247
|
+
});
|
|
248
|
+
raw += "`";
|
|
249
|
+
i++;
|
|
250
|
+
tokens.push({
|
|
251
|
+
type: "TemplateLiteral",
|
|
252
|
+
value: raw,
|
|
253
|
+
start,
|
|
254
|
+
end: i
|
|
255
|
+
});
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (isAlpha(ch)) {
|
|
259
|
+
const start = i;
|
|
260
|
+
while (i < source.length && isAlphaNumeric(source[i])) i++;
|
|
261
|
+
const value = source.slice(start, i);
|
|
262
|
+
const kw = KEYWORDS.get(value);
|
|
263
|
+
if (kw) tokens.push({
|
|
264
|
+
type: kw.type,
|
|
265
|
+
value: kw.value,
|
|
266
|
+
start,
|
|
267
|
+
end: i
|
|
268
|
+
});
|
|
269
|
+
else tokens.push({
|
|
270
|
+
type: "Identifier",
|
|
271
|
+
value,
|
|
272
|
+
start,
|
|
273
|
+
end: i
|
|
274
|
+
});
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
const start = i;
|
|
278
|
+
if (ch === "." && i + 2 < source.length && source[i + 1] === "." && source[i + 2] === ".") {
|
|
279
|
+
tokens.push({
|
|
280
|
+
type: "Spread",
|
|
281
|
+
value: "...",
|
|
282
|
+
start,
|
|
283
|
+
end: i + 3
|
|
284
|
+
});
|
|
285
|
+
i += 3;
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (ch === ".") {
|
|
289
|
+
tokens.push({
|
|
290
|
+
type: "Punctuation",
|
|
291
|
+
value: ".",
|
|
292
|
+
start,
|
|
293
|
+
end: i + 1
|
|
294
|
+
});
|
|
295
|
+
i++;
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
if (ch === "|") {
|
|
299
|
+
if (i + 1 < source.length && source[i + 1] === ">") {
|
|
300
|
+
tokens.push({
|
|
301
|
+
type: "Pipe",
|
|
302
|
+
value: "|>",
|
|
303
|
+
start,
|
|
304
|
+
end: i + 2
|
|
305
|
+
});
|
|
306
|
+
i += 2;
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
if (i + 1 < source.length && source[i + 1] === "|") {
|
|
310
|
+
tokens.push({
|
|
311
|
+
type: "Operator",
|
|
312
|
+
value: "||",
|
|
313
|
+
start,
|
|
314
|
+
end: i + 2
|
|
315
|
+
});
|
|
316
|
+
i += 2;
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (ch === "&" && i + 1 < source.length && source[i + 1] === "&") {
|
|
321
|
+
tokens.push({
|
|
322
|
+
type: "Operator",
|
|
323
|
+
value: "&&",
|
|
324
|
+
start,
|
|
325
|
+
end: i + 2
|
|
326
|
+
});
|
|
327
|
+
i += 2;
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
if (ch === "=" && i + 1 < source.length && source[i + 1] === "=") {
|
|
331
|
+
tokens.push({
|
|
332
|
+
type: "Operator",
|
|
333
|
+
value: "==",
|
|
334
|
+
start,
|
|
335
|
+
end: i + 2
|
|
336
|
+
});
|
|
337
|
+
i += 2;
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
if (ch === "!" && i + 1 < source.length && source[i + 1] === "=") {
|
|
341
|
+
tokens.push({
|
|
342
|
+
type: "Operator",
|
|
343
|
+
value: "!=",
|
|
344
|
+
start,
|
|
345
|
+
end: i + 2
|
|
346
|
+
});
|
|
347
|
+
i += 2;
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
if (ch === "!") {
|
|
351
|
+
tokens.push({
|
|
352
|
+
type: "Operator",
|
|
353
|
+
value: "!",
|
|
354
|
+
start,
|
|
355
|
+
end: i + 1
|
|
356
|
+
});
|
|
357
|
+
i++;
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
if (ch === "<") {
|
|
361
|
+
if (i + 1 < source.length && source[i + 1] === "=") {
|
|
362
|
+
tokens.push({
|
|
363
|
+
type: "Operator",
|
|
364
|
+
value: "<=",
|
|
365
|
+
start,
|
|
366
|
+
end: i + 2
|
|
367
|
+
});
|
|
368
|
+
i += 2;
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
tokens.push({
|
|
372
|
+
type: "Operator",
|
|
373
|
+
value: "<",
|
|
374
|
+
start,
|
|
375
|
+
end: i + 1
|
|
376
|
+
});
|
|
377
|
+
i++;
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
if (ch === ">") {
|
|
381
|
+
if (i + 1 < source.length && source[i + 1] === "=") {
|
|
382
|
+
tokens.push({
|
|
383
|
+
type: "Operator",
|
|
384
|
+
value: ">=",
|
|
385
|
+
start,
|
|
386
|
+
end: i + 2
|
|
387
|
+
});
|
|
388
|
+
i += 2;
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
tokens.push({
|
|
392
|
+
type: "Operator",
|
|
393
|
+
value: ">",
|
|
394
|
+
start,
|
|
395
|
+
end: i + 1
|
|
396
|
+
});
|
|
397
|
+
i++;
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
if (ch === "*" && i + 1 < source.length && source[i + 1] === "*") {
|
|
401
|
+
tokens.push({
|
|
402
|
+
type: "Operator",
|
|
403
|
+
value: "**",
|
|
404
|
+
start,
|
|
405
|
+
end: i + 2
|
|
406
|
+
});
|
|
407
|
+
i += 2;
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
if (ch === "?") {
|
|
411
|
+
if (i + 1 < source.length && source[i + 1] === ".") {
|
|
412
|
+
tokens.push({
|
|
413
|
+
type: "OptionalChain",
|
|
414
|
+
value: "?.",
|
|
415
|
+
start,
|
|
416
|
+
end: i + 2
|
|
417
|
+
});
|
|
418
|
+
i += 2;
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
if (i + 1 < source.length && source[i + 1] === "?") {
|
|
422
|
+
tokens.push({
|
|
423
|
+
type: "NullishCoalescing",
|
|
424
|
+
value: "??",
|
|
425
|
+
start,
|
|
426
|
+
end: i + 2
|
|
427
|
+
});
|
|
428
|
+
i += 2;
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
tokens.push({
|
|
432
|
+
type: "Punctuation",
|
|
433
|
+
value: "?",
|
|
434
|
+
start,
|
|
435
|
+
end: i + 1
|
|
436
|
+
});
|
|
437
|
+
i++;
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
if (ch === "+" || ch === "-" || ch === "*" || ch === "/" || ch === "%") {
|
|
441
|
+
tokens.push({
|
|
442
|
+
type: "Operator",
|
|
443
|
+
value: ch,
|
|
444
|
+
start,
|
|
445
|
+
end: i + 1
|
|
446
|
+
});
|
|
447
|
+
i++;
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
if ("()[]{},:".includes(ch)) {
|
|
451
|
+
tokens.push({
|
|
452
|
+
type: "Punctuation",
|
|
453
|
+
value: ch,
|
|
454
|
+
start,
|
|
455
|
+
end: i + 1
|
|
456
|
+
});
|
|
457
|
+
i++;
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
throw new ExpressionError(`Unexpected character "${ch}"`, {
|
|
461
|
+
source,
|
|
462
|
+
start,
|
|
463
|
+
end: i + 1
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
tokens.push({
|
|
467
|
+
type: "EOF",
|
|
468
|
+
value: "",
|
|
469
|
+
start: i,
|
|
470
|
+
end: i
|
|
471
|
+
});
|
|
472
|
+
return tokens;
|
|
473
|
+
}
|
|
474
|
+
//#endregion
|
|
475
|
+
//#region src/parser.ts
|
|
476
|
+
const PIPE_PRECEDENCE = 5;
|
|
477
|
+
const PRECEDENCE = {
|
|
478
|
+
"||": 10,
|
|
479
|
+
"&&": 20,
|
|
480
|
+
"??": 25,
|
|
481
|
+
"==": 30,
|
|
482
|
+
"!=": 30,
|
|
483
|
+
"<": 40,
|
|
484
|
+
">": 40,
|
|
485
|
+
"<=": 40,
|
|
486
|
+
">=": 40,
|
|
487
|
+
"in": 40,
|
|
488
|
+
"+": 50,
|
|
489
|
+
"-": 50,
|
|
490
|
+
"*": 60,
|
|
491
|
+
"/": 60,
|
|
492
|
+
"%": 60,
|
|
493
|
+
"**": 70,
|
|
494
|
+
"!": void 0,
|
|
495
|
+
"not": void 0
|
|
496
|
+
};
|
|
497
|
+
const MAX_PARSE_DEPTH = 32;
|
|
498
|
+
function parse(source, _depth = 0) {
|
|
499
|
+
if (_depth > MAX_PARSE_DEPTH) throw new ExpressionError("Maximum template nesting depth exceeded", {
|
|
500
|
+
source,
|
|
501
|
+
start: 0,
|
|
502
|
+
end: source.length
|
|
503
|
+
});
|
|
504
|
+
const tokens = tokenize(source);
|
|
505
|
+
let pos = 0;
|
|
506
|
+
function current() {
|
|
507
|
+
return tokens[pos];
|
|
508
|
+
}
|
|
509
|
+
function advance() {
|
|
510
|
+
return tokens[pos++];
|
|
511
|
+
}
|
|
512
|
+
function expect(type, value) {
|
|
513
|
+
const tok = current();
|
|
514
|
+
if (tok.type !== type || value !== void 0 && tok.value !== value) throw new ExpressionError(`Expected ${value ? `"${value}"` : type} but got ${tok.value ? `"${tok.value}"` : tok.type}`, {
|
|
515
|
+
source,
|
|
516
|
+
start: tok.start,
|
|
517
|
+
end: tok.end
|
|
518
|
+
});
|
|
519
|
+
return advance();
|
|
520
|
+
}
|
|
521
|
+
function parseExpression(minPrec = 0) {
|
|
522
|
+
let left = parseUnary();
|
|
523
|
+
if (current().type === "Punctuation" && current().value === "?" && minPrec === 0) {
|
|
524
|
+
advance();
|
|
525
|
+
const consequent = parseExpression(0);
|
|
526
|
+
expect("Punctuation", ":");
|
|
527
|
+
const alternate = parseExpression(0);
|
|
528
|
+
left = {
|
|
529
|
+
type: "ConditionalExpression",
|
|
530
|
+
test: left,
|
|
531
|
+
consequent,
|
|
532
|
+
alternate,
|
|
533
|
+
start: left.start,
|
|
534
|
+
end: alternate.end
|
|
535
|
+
};
|
|
536
|
+
return left;
|
|
537
|
+
}
|
|
538
|
+
while (current().type === "Pipe" && minPrec <= PIPE_PRECEDENCE) {
|
|
539
|
+
advance();
|
|
540
|
+
const transform = parsePipeTransform();
|
|
541
|
+
left = {
|
|
542
|
+
type: "PipeExpression",
|
|
543
|
+
input: left,
|
|
544
|
+
transform,
|
|
545
|
+
start: left.start,
|
|
546
|
+
end: transform.end
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
while (true) {
|
|
550
|
+
const tok = current();
|
|
551
|
+
let prec;
|
|
552
|
+
if (tok.type === "Operator") {
|
|
553
|
+
prec = PRECEDENCE[tok.value];
|
|
554
|
+
if (tok.value === "not") {
|
|
555
|
+
const nextTok = tokens[pos + 1];
|
|
556
|
+
if (nextTok && nextTok.type === "Operator" && nextTok.value === "in") prec = PRECEDENCE["in"];
|
|
557
|
+
}
|
|
558
|
+
} else if (tok.type === "NullishCoalescing") prec = PRECEDENCE["??"];
|
|
559
|
+
if (prec === void 0 || prec < minPrec) break;
|
|
560
|
+
const op = advance();
|
|
561
|
+
let opValue;
|
|
562
|
+
if (op.type === "NullishCoalescing") opValue = "??";
|
|
563
|
+
else if (op.value === "not") {
|
|
564
|
+
advance();
|
|
565
|
+
opValue = "not in";
|
|
566
|
+
} else opValue = op.value;
|
|
567
|
+
const right = parseExpression(opValue === "**" ? prec : prec + 1);
|
|
568
|
+
left = {
|
|
569
|
+
type: "BinaryExpression",
|
|
570
|
+
operator: opValue,
|
|
571
|
+
left,
|
|
572
|
+
right,
|
|
573
|
+
start: left.start,
|
|
574
|
+
end: right.end
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
if (current().type === "Punctuation" && current().value === "?" && minPrec === 0) {
|
|
578
|
+
advance();
|
|
579
|
+
const consequent = parseExpression(0);
|
|
580
|
+
expect("Punctuation", ":");
|
|
581
|
+
const alternate = parseExpression(0);
|
|
582
|
+
left = {
|
|
583
|
+
type: "ConditionalExpression",
|
|
584
|
+
test: left,
|
|
585
|
+
consequent,
|
|
586
|
+
alternate,
|
|
587
|
+
start: left.start,
|
|
588
|
+
end: alternate.end
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
while (current().type === "Pipe" && minPrec <= PIPE_PRECEDENCE) {
|
|
592
|
+
advance();
|
|
593
|
+
const transform = parsePipeTransform();
|
|
594
|
+
left = {
|
|
595
|
+
type: "PipeExpression",
|
|
596
|
+
input: left,
|
|
597
|
+
transform,
|
|
598
|
+
start: left.start,
|
|
599
|
+
end: transform.end
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
return left;
|
|
603
|
+
}
|
|
604
|
+
function parsePipeTransform() {
|
|
605
|
+
let node = parsePrimary();
|
|
606
|
+
if (current().type === "Punctuation" && current().value === "(") {
|
|
607
|
+
advance();
|
|
608
|
+
const args = [];
|
|
609
|
+
while (!(current().type === "Punctuation" && current().value === ")")) {
|
|
610
|
+
if (args.length > 0) expect("Punctuation", ",");
|
|
611
|
+
if (current().type === "Punctuation" && current().value === ")") break;
|
|
612
|
+
args.push(parseExpression(0));
|
|
613
|
+
}
|
|
614
|
+
const end = expect("Punctuation", ")").end;
|
|
615
|
+
node = {
|
|
616
|
+
type: "CallExpression",
|
|
617
|
+
callee: node,
|
|
618
|
+
args,
|
|
619
|
+
start: node.start,
|
|
620
|
+
end
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
return node;
|
|
624
|
+
}
|
|
625
|
+
function parseUnary() {
|
|
626
|
+
const tok = current();
|
|
627
|
+
if (tok.type === "Operator" && (tok.value === "!" || tok.value === "-" || tok.value === "+")) {
|
|
628
|
+
advance();
|
|
629
|
+
const operand = parseUnary();
|
|
630
|
+
return {
|
|
631
|
+
type: "UnaryExpression",
|
|
632
|
+
operator: tok.value,
|
|
633
|
+
operand,
|
|
634
|
+
start: tok.start,
|
|
635
|
+
end: operand.end
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
return parsePostfix();
|
|
639
|
+
}
|
|
640
|
+
function parsePostfix() {
|
|
641
|
+
let node = parsePrimary();
|
|
642
|
+
while (true) {
|
|
643
|
+
const tok = current();
|
|
644
|
+
if (tok.type === "Punctuation" && tok.value === ".") {
|
|
645
|
+
advance();
|
|
646
|
+
const prop = expect("Identifier");
|
|
647
|
+
node = {
|
|
648
|
+
type: "MemberExpression",
|
|
649
|
+
object: node,
|
|
650
|
+
property: {
|
|
651
|
+
type: "Identifier",
|
|
652
|
+
name: prop.value,
|
|
653
|
+
start: prop.start,
|
|
654
|
+
end: prop.end
|
|
655
|
+
},
|
|
656
|
+
computed: false,
|
|
657
|
+
start: node.start,
|
|
658
|
+
end: prop.end
|
|
659
|
+
};
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
if (tok.type === "Punctuation" && tok.value === "[") {
|
|
663
|
+
advance();
|
|
664
|
+
const property = parseExpression(0);
|
|
665
|
+
const end = expect("Punctuation", "]").end;
|
|
666
|
+
node = {
|
|
667
|
+
type: "MemberExpression",
|
|
668
|
+
object: node,
|
|
669
|
+
property,
|
|
670
|
+
computed: true,
|
|
671
|
+
start: node.start,
|
|
672
|
+
end
|
|
673
|
+
};
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
676
|
+
if (tok.type === "OptionalChain") {
|
|
677
|
+
advance();
|
|
678
|
+
if (current().type === "Punctuation" && current().value === "[") {
|
|
679
|
+
advance();
|
|
680
|
+
const property = parseExpression(0);
|
|
681
|
+
const end = expect("Punctuation", "]").end;
|
|
682
|
+
node = {
|
|
683
|
+
type: "OptionalMemberExpression",
|
|
684
|
+
object: node,
|
|
685
|
+
property,
|
|
686
|
+
computed: true,
|
|
687
|
+
start: node.start,
|
|
688
|
+
end
|
|
689
|
+
};
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
692
|
+
const prop = expect("Identifier");
|
|
693
|
+
node = {
|
|
694
|
+
type: "OptionalMemberExpression",
|
|
695
|
+
object: node,
|
|
696
|
+
property: {
|
|
697
|
+
type: "Identifier",
|
|
698
|
+
name: prop.value,
|
|
699
|
+
start: prop.start,
|
|
700
|
+
end: prop.end
|
|
701
|
+
},
|
|
702
|
+
computed: false,
|
|
703
|
+
start: node.start,
|
|
704
|
+
end: prop.end
|
|
705
|
+
};
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
708
|
+
if (tok.type === "Punctuation" && tok.value === "(") {
|
|
709
|
+
advance();
|
|
710
|
+
const args = [];
|
|
711
|
+
while (!(current().type === "Punctuation" && current().value === ")")) {
|
|
712
|
+
if (args.length > 0) expect("Punctuation", ",");
|
|
713
|
+
if (current().type === "Punctuation" && current().value === ")") break;
|
|
714
|
+
args.push(parseExpression(0));
|
|
715
|
+
}
|
|
716
|
+
const end = expect("Punctuation", ")").end;
|
|
717
|
+
node = {
|
|
718
|
+
type: "CallExpression",
|
|
719
|
+
callee: node,
|
|
720
|
+
args,
|
|
721
|
+
start: node.start,
|
|
722
|
+
end
|
|
723
|
+
};
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
726
|
+
break;
|
|
727
|
+
}
|
|
728
|
+
return node;
|
|
729
|
+
}
|
|
730
|
+
function parsePrimary() {
|
|
731
|
+
const tok = current();
|
|
732
|
+
if (tok.type === "Number") {
|
|
733
|
+
advance();
|
|
734
|
+
const raw = tok.value.replace(/_/g, "");
|
|
735
|
+
return {
|
|
736
|
+
type: "NumberLiteral",
|
|
737
|
+
value: Number(raw),
|
|
738
|
+
start: tok.start,
|
|
739
|
+
end: tok.end
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
if (tok.type === "String") {
|
|
743
|
+
advance();
|
|
744
|
+
return {
|
|
745
|
+
type: "StringLiteral",
|
|
746
|
+
value: tok.value,
|
|
747
|
+
start: tok.start,
|
|
748
|
+
end: tok.end
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
if (tok.type === "Boolean") {
|
|
752
|
+
advance();
|
|
753
|
+
return {
|
|
754
|
+
type: "BooleanLiteral",
|
|
755
|
+
value: tok.value === "true",
|
|
756
|
+
start: tok.start,
|
|
757
|
+
end: tok.end
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
if (tok.type === "Null") {
|
|
761
|
+
advance();
|
|
762
|
+
return {
|
|
763
|
+
type: "NullLiteral",
|
|
764
|
+
value: null,
|
|
765
|
+
start: tok.start,
|
|
766
|
+
end: tok.end
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
if (tok.type === "Undefined") {
|
|
770
|
+
advance();
|
|
771
|
+
return {
|
|
772
|
+
type: "UndefinedLiteral",
|
|
773
|
+
value: void 0,
|
|
774
|
+
start: tok.start,
|
|
775
|
+
end: tok.end
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
if (tok.type === "TemplateLiteral") return parseTemplateLiteral();
|
|
779
|
+
if (tok.type === "Identifier") {
|
|
780
|
+
advance();
|
|
781
|
+
return {
|
|
782
|
+
type: "Identifier",
|
|
783
|
+
name: tok.value,
|
|
784
|
+
start: tok.start,
|
|
785
|
+
end: tok.end
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
if (tok.type === "Punctuation" && tok.value === "(") {
|
|
789
|
+
advance();
|
|
790
|
+
const expr = parseExpression(0);
|
|
791
|
+
expect("Punctuation", ")");
|
|
792
|
+
return expr;
|
|
793
|
+
}
|
|
794
|
+
if (tok.type === "Punctuation" && tok.value === "[") return parseArrayLiteral();
|
|
795
|
+
if (tok.type === "Punctuation" && tok.value === "{") return parseObjectLiteral();
|
|
796
|
+
if (tok.type === "Punctuation" && tok.value === ".") return parseLambdaExpression();
|
|
797
|
+
if (tok.type === "Spread") {
|
|
798
|
+
advance();
|
|
799
|
+
const argument = parseExpression(0);
|
|
800
|
+
return {
|
|
801
|
+
type: "SpreadElement",
|
|
802
|
+
argument,
|
|
803
|
+
start: tok.start,
|
|
804
|
+
end: argument.end
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
throw new ExpressionError(`Unexpected ${tok.type === "EOF" ? "end of expression" : `token "${tok.value}"`}`, {
|
|
808
|
+
source,
|
|
809
|
+
start: tok.start,
|
|
810
|
+
end: tok.end
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
function parseLambdaExpression() {
|
|
814
|
+
const start = current().start;
|
|
815
|
+
advance();
|
|
816
|
+
const prop = expect("Identifier");
|
|
817
|
+
let node = {
|
|
818
|
+
type: "LambdaAccessor",
|
|
819
|
+
property: prop.value,
|
|
820
|
+
start,
|
|
821
|
+
end: prop.end
|
|
822
|
+
};
|
|
823
|
+
while (true) {
|
|
824
|
+
const tok = current();
|
|
825
|
+
if (tok.type === "Punctuation" && tok.value === ".") {
|
|
826
|
+
advance();
|
|
827
|
+
const nextProp = expect("Identifier");
|
|
828
|
+
node = {
|
|
829
|
+
type: "MemberExpression",
|
|
830
|
+
object: node,
|
|
831
|
+
property: {
|
|
832
|
+
type: "Identifier",
|
|
833
|
+
name: nextProp.value,
|
|
834
|
+
start: nextProp.start,
|
|
835
|
+
end: nextProp.end
|
|
836
|
+
},
|
|
837
|
+
computed: false,
|
|
838
|
+
start: node.start,
|
|
839
|
+
end: nextProp.end
|
|
840
|
+
};
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
if (tok.type === "OptionalChain") {
|
|
844
|
+
advance();
|
|
845
|
+
const nextProp = expect("Identifier");
|
|
846
|
+
node = {
|
|
847
|
+
type: "OptionalMemberExpression",
|
|
848
|
+
object: node,
|
|
849
|
+
property: {
|
|
850
|
+
type: "Identifier",
|
|
851
|
+
name: nextProp.value,
|
|
852
|
+
start: nextProp.start,
|
|
853
|
+
end: nextProp.end
|
|
854
|
+
},
|
|
855
|
+
computed: false,
|
|
856
|
+
start: node.start,
|
|
857
|
+
end: nextProp.end
|
|
858
|
+
};
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
861
|
+
if (tok.type === "Punctuation" && tok.value === "[") {
|
|
862
|
+
advance();
|
|
863
|
+
const property = parseExpression(0);
|
|
864
|
+
const end = expect("Punctuation", "]").end;
|
|
865
|
+
node = {
|
|
866
|
+
type: "MemberExpression",
|
|
867
|
+
object: node,
|
|
868
|
+
property,
|
|
869
|
+
computed: true,
|
|
870
|
+
start: node.start,
|
|
871
|
+
end
|
|
872
|
+
};
|
|
873
|
+
continue;
|
|
874
|
+
}
|
|
875
|
+
if (tok.type === "Punctuation" && tok.value === "(") {
|
|
876
|
+
advance();
|
|
877
|
+
const args = [];
|
|
878
|
+
while (!(current().type === "Punctuation" && current().value === ")")) {
|
|
879
|
+
if (args.length > 0) expect("Punctuation", ",");
|
|
880
|
+
if (current().type === "Punctuation" && current().value === ")") break;
|
|
881
|
+
args.push(parseExpression(0));
|
|
882
|
+
}
|
|
883
|
+
const end = expect("Punctuation", ")").end;
|
|
884
|
+
node = {
|
|
885
|
+
type: "CallExpression",
|
|
886
|
+
callee: node,
|
|
887
|
+
args,
|
|
888
|
+
start: node.start,
|
|
889
|
+
end
|
|
890
|
+
};
|
|
891
|
+
continue;
|
|
892
|
+
}
|
|
893
|
+
break;
|
|
894
|
+
}
|
|
895
|
+
const tok = current();
|
|
896
|
+
let prec;
|
|
897
|
+
if (tok.type === "Operator") {
|
|
898
|
+
prec = PRECEDENCE[tok.value];
|
|
899
|
+
if (tok.value === "not") {
|
|
900
|
+
const nextTok = tokens[pos + 1];
|
|
901
|
+
if (nextTok && nextTok.type === "Operator" && nextTok.value === "in") prec = PRECEDENCE["in"];
|
|
902
|
+
}
|
|
903
|
+
} else if (tok.type === "NullishCoalescing") prec = PRECEDENCE["??"];
|
|
904
|
+
if (prec !== void 0) {
|
|
905
|
+
let left = node;
|
|
906
|
+
while (true) {
|
|
907
|
+
const opTok = current();
|
|
908
|
+
let opPrec;
|
|
909
|
+
if (opTok.type === "Operator") {
|
|
910
|
+
opPrec = PRECEDENCE[opTok.value];
|
|
911
|
+
if (opTok.value === "not") {
|
|
912
|
+
const nextTok = tokens[pos + 1];
|
|
913
|
+
if (nextTok && nextTok.type === "Operator" && nextTok.value === "in") opPrec = PRECEDENCE["in"];
|
|
914
|
+
}
|
|
915
|
+
} else if (opTok.type === "NullishCoalescing") opPrec = PRECEDENCE["??"];
|
|
916
|
+
if (opPrec === void 0) break;
|
|
917
|
+
const op = advance();
|
|
918
|
+
let opValue;
|
|
919
|
+
if (op.type === "NullishCoalescing") opValue = "??";
|
|
920
|
+
else if (op.value === "not") {
|
|
921
|
+
advance();
|
|
922
|
+
opValue = "not in";
|
|
923
|
+
} else opValue = op.value;
|
|
924
|
+
const right = parseExpression(opValue === "**" ? opPrec : opPrec + 1);
|
|
925
|
+
left = {
|
|
926
|
+
type: "BinaryExpression",
|
|
927
|
+
operator: opValue,
|
|
928
|
+
left,
|
|
929
|
+
right,
|
|
930
|
+
start: left.start,
|
|
931
|
+
end: right.end
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
node = left;
|
|
935
|
+
}
|
|
936
|
+
if (current().type === "Punctuation" && current().value === "?") {
|
|
937
|
+
advance();
|
|
938
|
+
const consequent = parseExpression(0);
|
|
939
|
+
expect("Punctuation", ":");
|
|
940
|
+
const alternate = parseExpression(0);
|
|
941
|
+
return {
|
|
942
|
+
type: "LambdaExpression",
|
|
943
|
+
body: {
|
|
944
|
+
type: "ConditionalExpression",
|
|
945
|
+
test: node,
|
|
946
|
+
consequent,
|
|
947
|
+
alternate,
|
|
948
|
+
start,
|
|
949
|
+
end: alternate.end
|
|
950
|
+
},
|
|
951
|
+
start,
|
|
952
|
+
end: alternate.end
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
if (node.type === "LambdaAccessor") return node;
|
|
956
|
+
return {
|
|
957
|
+
type: "LambdaExpression",
|
|
958
|
+
body: node,
|
|
959
|
+
start,
|
|
960
|
+
end: node.end
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
function parseArrayLiteral() {
|
|
964
|
+
const start = expect("Punctuation", "[").start;
|
|
965
|
+
const elements = [];
|
|
966
|
+
while (!(current().type === "Punctuation" && current().value === "]")) {
|
|
967
|
+
if (elements.length > 0) expect("Punctuation", ",");
|
|
968
|
+
if (current().type === "Punctuation" && current().value === "]") break;
|
|
969
|
+
elements.push(parseExpression(0));
|
|
970
|
+
}
|
|
971
|
+
return {
|
|
972
|
+
type: "ArrayLiteral",
|
|
973
|
+
elements,
|
|
974
|
+
start,
|
|
975
|
+
end: expect("Punctuation", "]").end
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
function parseObjectLiteral() {
|
|
979
|
+
const start = expect("Punctuation", "{").start;
|
|
980
|
+
const properties = [];
|
|
981
|
+
while (!(current().type === "Punctuation" && current().value === "}")) {
|
|
982
|
+
if (properties.length > 0) expect("Punctuation", ",");
|
|
983
|
+
if (current().type === "Punctuation" && current().value === "}") break;
|
|
984
|
+
let key;
|
|
985
|
+
let computed = false;
|
|
986
|
+
if (current().type === "Punctuation" && current().value === "[") {
|
|
987
|
+
advance();
|
|
988
|
+
key = parseExpression(0);
|
|
989
|
+
expect("Punctuation", "]");
|
|
990
|
+
computed = true;
|
|
991
|
+
} else {
|
|
992
|
+
const tok = current();
|
|
993
|
+
if (tok.type === "Identifier") {
|
|
994
|
+
advance();
|
|
995
|
+
key = {
|
|
996
|
+
type: "Identifier",
|
|
997
|
+
name: tok.value,
|
|
998
|
+
start: tok.start,
|
|
999
|
+
end: tok.end
|
|
1000
|
+
};
|
|
1001
|
+
const next = current();
|
|
1002
|
+
if (next.type === "Punctuation" && (next.value === "," || next.value === "}")) {
|
|
1003
|
+
const value = {
|
|
1004
|
+
type: "Identifier",
|
|
1005
|
+
name: tok.value,
|
|
1006
|
+
start: tok.start,
|
|
1007
|
+
end: tok.end
|
|
1008
|
+
};
|
|
1009
|
+
properties.push({
|
|
1010
|
+
type: "ObjectProperty",
|
|
1011
|
+
key,
|
|
1012
|
+
value,
|
|
1013
|
+
computed: false,
|
|
1014
|
+
start: key.start,
|
|
1015
|
+
end: value.end
|
|
1016
|
+
});
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
} else if (tok.type === "String") {
|
|
1020
|
+
advance();
|
|
1021
|
+
key = {
|
|
1022
|
+
type: "StringLiteral",
|
|
1023
|
+
value: tok.value,
|
|
1024
|
+
start: tok.start,
|
|
1025
|
+
end: tok.end
|
|
1026
|
+
};
|
|
1027
|
+
} else throw new ExpressionError(`Expected property name but got "${tok.value}"`, {
|
|
1028
|
+
source,
|
|
1029
|
+
start: tok.start,
|
|
1030
|
+
end: tok.end
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
expect("Punctuation", ":");
|
|
1034
|
+
const value = parseExpression(0);
|
|
1035
|
+
properties.push({
|
|
1036
|
+
type: "ObjectProperty",
|
|
1037
|
+
key,
|
|
1038
|
+
value,
|
|
1039
|
+
computed,
|
|
1040
|
+
start: key.start,
|
|
1041
|
+
end: value.end
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
return {
|
|
1045
|
+
type: "ObjectLiteral",
|
|
1046
|
+
properties,
|
|
1047
|
+
start,
|
|
1048
|
+
end: expect("Punctuation", "}").end
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
function parseTemplateLiteral() {
|
|
1052
|
+
const tok = advance();
|
|
1053
|
+
const raw = tok.value.slice(1, -1);
|
|
1054
|
+
const parts = [];
|
|
1055
|
+
let i = 0;
|
|
1056
|
+
let textStart = 0;
|
|
1057
|
+
while (i < raw.length) if (raw[i] === "$" && i + 1 < raw.length && raw[i + 1] === "{") {
|
|
1058
|
+
if (i > textStart) {
|
|
1059
|
+
const text = raw.slice(textStart, i);
|
|
1060
|
+
parts.push({
|
|
1061
|
+
type: "StringLiteral",
|
|
1062
|
+
value: text,
|
|
1063
|
+
start: tok.start,
|
|
1064
|
+
end: tok.end
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
i += 2;
|
|
1068
|
+
let depth = 1;
|
|
1069
|
+
const exprStart = i;
|
|
1070
|
+
while (i < raw.length && depth > 0) {
|
|
1071
|
+
if (raw[i] === "{") depth++;
|
|
1072
|
+
else if (raw[i] === "}") depth--;
|
|
1073
|
+
if (depth > 0) i++;
|
|
1074
|
+
}
|
|
1075
|
+
const exprSource = raw.slice(exprStart, i);
|
|
1076
|
+
i++;
|
|
1077
|
+
textStart = i;
|
|
1078
|
+
const exprAst = parse(exprSource, _depth + 1);
|
|
1079
|
+
parts.push(exprAst);
|
|
1080
|
+
} else i++;
|
|
1081
|
+
if (textStart < raw.length) {
|
|
1082
|
+
const text = raw.slice(textStart);
|
|
1083
|
+
parts.push({
|
|
1084
|
+
type: "StringLiteral",
|
|
1085
|
+
value: text,
|
|
1086
|
+
start: tok.start,
|
|
1087
|
+
end: tok.end
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
return {
|
|
1091
|
+
type: "TemplateLiteral",
|
|
1092
|
+
parts,
|
|
1093
|
+
start: tok.start,
|
|
1094
|
+
end: tok.end
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
const result = parseExpression(0);
|
|
1098
|
+
if (current().type !== "EOF") {
|
|
1099
|
+
const tok = current();
|
|
1100
|
+
throw new ExpressionError(`Unexpected token "${tok.value}"`, {
|
|
1101
|
+
source,
|
|
1102
|
+
start: tok.start,
|
|
1103
|
+
end: tok.end
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
1106
|
+
return result;
|
|
1107
|
+
}
|
|
1108
|
+
//#endregion
|
|
1109
|
+
//#region src/compiler.ts
|
|
1110
|
+
function compile(ast) {
|
|
1111
|
+
return optimize(ast);
|
|
1112
|
+
}
|
|
1113
|
+
function optimize(node) {
|
|
1114
|
+
switch (node.type) {
|
|
1115
|
+
case "BinaryExpression": {
|
|
1116
|
+
const left = optimize(node.left);
|
|
1117
|
+
const right = optimize(node.right);
|
|
1118
|
+
if (isConstant(left) && isConstant(right)) {
|
|
1119
|
+
const result = evalConstant(node.operator, valueOf(left), valueOf(right));
|
|
1120
|
+
if (result !== void 0) return makeConstant(result, node.start, node.end);
|
|
1121
|
+
}
|
|
1122
|
+
return {
|
|
1123
|
+
...node,
|
|
1124
|
+
left,
|
|
1125
|
+
right
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
case "ConditionalExpression": {
|
|
1129
|
+
const test = optimize(node.test);
|
|
1130
|
+
if (isConstant(test)) return valueOf(test) ? optimize(node.consequent) : optimize(node.alternate);
|
|
1131
|
+
return {
|
|
1132
|
+
...node,
|
|
1133
|
+
test,
|
|
1134
|
+
consequent: optimize(node.consequent),
|
|
1135
|
+
alternate: optimize(node.alternate)
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
case "UnaryExpression": {
|
|
1139
|
+
const operand = optimize(node.operand);
|
|
1140
|
+
if (isConstant(operand)) {
|
|
1141
|
+
const val = valueOf(operand);
|
|
1142
|
+
if (node.operator === "!" && typeof val === "boolean") return {
|
|
1143
|
+
type: "BooleanLiteral",
|
|
1144
|
+
value: !val,
|
|
1145
|
+
start: node.start,
|
|
1146
|
+
end: node.end
|
|
1147
|
+
};
|
|
1148
|
+
if (node.operator === "-" && typeof val === "number") return {
|
|
1149
|
+
type: "NumberLiteral",
|
|
1150
|
+
value: -val,
|
|
1151
|
+
start: node.start,
|
|
1152
|
+
end: node.end
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
return {
|
|
1156
|
+
...node,
|
|
1157
|
+
operand
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
case "PipeExpression": return {
|
|
1161
|
+
...node,
|
|
1162
|
+
input: optimize(node.input),
|
|
1163
|
+
transform: optimize(node.transform)
|
|
1164
|
+
};
|
|
1165
|
+
case "ArrayLiteral": return {
|
|
1166
|
+
...node,
|
|
1167
|
+
elements: node.elements.map((e) => optimize(e))
|
|
1168
|
+
};
|
|
1169
|
+
case "CallExpression": return {
|
|
1170
|
+
...node,
|
|
1171
|
+
args: node.args.map((a) => optimize(a))
|
|
1172
|
+
};
|
|
1173
|
+
case "MemberExpression":
|
|
1174
|
+
case "OptionalMemberExpression": return {
|
|
1175
|
+
...node,
|
|
1176
|
+
object: optimize(node.object),
|
|
1177
|
+
property: node.computed ? optimize(node.property) : node.property
|
|
1178
|
+
};
|
|
1179
|
+
case "LambdaExpression": return {
|
|
1180
|
+
...node,
|
|
1181
|
+
body: optimize(node.body)
|
|
1182
|
+
};
|
|
1183
|
+
default: return node;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
function isConstant(node) {
|
|
1187
|
+
return node.type === "NumberLiteral" || node.type === "StringLiteral" || node.type === "BooleanLiteral" || node.type === "NullLiteral";
|
|
1188
|
+
}
|
|
1189
|
+
function valueOf(node) {
|
|
1190
|
+
if (node.type === "NumberLiteral") return node.value;
|
|
1191
|
+
if (node.type === "StringLiteral") return node.value;
|
|
1192
|
+
if (node.type === "BooleanLiteral") return node.value;
|
|
1193
|
+
if (node.type === "NullLiteral") return null;
|
|
1194
|
+
}
|
|
1195
|
+
function evalConstant(op, left, right) {
|
|
1196
|
+
if (typeof left === "number" && typeof right === "number") switch (op) {
|
|
1197
|
+
case "+": return left + right;
|
|
1198
|
+
case "-": return left - right;
|
|
1199
|
+
case "*": return left * right;
|
|
1200
|
+
case "/": return left / right;
|
|
1201
|
+
case "%": return left % right;
|
|
1202
|
+
case "**": return left ** right;
|
|
1203
|
+
case "<": return left < right;
|
|
1204
|
+
case ">": return left > right;
|
|
1205
|
+
case "<=": return left <= right;
|
|
1206
|
+
case ">=": return left >= right;
|
|
1207
|
+
case "==": return left === right;
|
|
1208
|
+
case "!=": return left !== right;
|
|
1209
|
+
}
|
|
1210
|
+
if (typeof left === "string" && typeof right === "string" && op === "+") return left + right;
|
|
1211
|
+
if (typeof left === "boolean" && typeof right === "boolean") {
|
|
1212
|
+
if (op === "&&") return left && right;
|
|
1213
|
+
if (op === "||") return left || right;
|
|
1214
|
+
}
|
|
1215
|
+
if (op === "==") return left === right;
|
|
1216
|
+
if (op === "!=") return left !== right;
|
|
1217
|
+
}
|
|
1218
|
+
function makeConstant(value, start, end) {
|
|
1219
|
+
if (typeof value === "number") return {
|
|
1220
|
+
type: "NumberLiteral",
|
|
1221
|
+
value,
|
|
1222
|
+
start,
|
|
1223
|
+
end
|
|
1224
|
+
};
|
|
1225
|
+
if (typeof value === "string") return {
|
|
1226
|
+
type: "StringLiteral",
|
|
1227
|
+
value,
|
|
1228
|
+
start,
|
|
1229
|
+
end
|
|
1230
|
+
};
|
|
1231
|
+
if (typeof value === "boolean") return {
|
|
1232
|
+
type: "BooleanLiteral",
|
|
1233
|
+
value,
|
|
1234
|
+
start,
|
|
1235
|
+
end
|
|
1236
|
+
};
|
|
1237
|
+
if (value === null) return {
|
|
1238
|
+
type: "NullLiteral",
|
|
1239
|
+
value: null,
|
|
1240
|
+
start,
|
|
1241
|
+
end
|
|
1242
|
+
};
|
|
1243
|
+
return {
|
|
1244
|
+
type: "UndefinedLiteral",
|
|
1245
|
+
value: void 0,
|
|
1246
|
+
start,
|
|
1247
|
+
end
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
//#endregion
|
|
1251
|
+
//#region src/eval-ops.ts
|
|
1252
|
+
/** Receiver type check for safe methods. */
|
|
1253
|
+
function isAllowedReceiver(obj, methodName) {
|
|
1254
|
+
const t = typeof obj;
|
|
1255
|
+
switch (methodName) {
|
|
1256
|
+
case "startsWith":
|
|
1257
|
+
case "endsWith":
|
|
1258
|
+
case "substring":
|
|
1259
|
+
case "charAt":
|
|
1260
|
+
case "charCodeAt":
|
|
1261
|
+
case "repeat":
|
|
1262
|
+
case "trimStart":
|
|
1263
|
+
case "trimEnd":
|
|
1264
|
+
case "replace":
|
|
1265
|
+
case "replaceAll": return t === "string";
|
|
1266
|
+
case "includes":
|
|
1267
|
+
case "indexOf":
|
|
1268
|
+
case "slice":
|
|
1269
|
+
case "at": return t === "string" || Array.isArray(obj);
|
|
1270
|
+
case "toFixed": return t === "number";
|
|
1271
|
+
case "toString": return t === "string" || t === "number";
|
|
1272
|
+
default: return false;
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
function toPropertyKey(value) {
|
|
1276
|
+
return String(value);
|
|
1277
|
+
}
|
|
1278
|
+
function applyBinaryOp(operator, left, right) {
|
|
1279
|
+
switch (operator) {
|
|
1280
|
+
case "+": return left + right;
|
|
1281
|
+
case "-": return left - right;
|
|
1282
|
+
case "*": return left * right;
|
|
1283
|
+
case "/": return left / right;
|
|
1284
|
+
case "%": return left % right;
|
|
1285
|
+
case "**": return left ** right;
|
|
1286
|
+
case "==": return left === right;
|
|
1287
|
+
case "!=": return left !== right;
|
|
1288
|
+
case "<": return left < right;
|
|
1289
|
+
case ">": return left > right;
|
|
1290
|
+
case "<=": return left <= right;
|
|
1291
|
+
case ">=": return left >= right;
|
|
1292
|
+
case "in":
|
|
1293
|
+
if (typeof right === "string") return right.includes(left);
|
|
1294
|
+
if (Array.isArray(right)) return right.includes(left);
|
|
1295
|
+
throw new BonsaiTypeError("in", "a string or array", right);
|
|
1296
|
+
case "not in":
|
|
1297
|
+
if (typeof right === "string") return !right.includes(left);
|
|
1298
|
+
if (Array.isArray(right)) return !right.includes(left);
|
|
1299
|
+
throw new BonsaiTypeError("not in", "a string or array", right);
|
|
1300
|
+
default: throw new Error(`Unknown binary operator: ${operator}`);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
function applyUnaryOp(operator, operand) {
|
|
1304
|
+
switch (operator) {
|
|
1305
|
+
case "!": return !operand;
|
|
1306
|
+
case "-": return -operand;
|
|
1307
|
+
case "+": return Number(operand);
|
|
1308
|
+
default: throw new Error(`Unknown unary operator: ${operator}`);
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
function validateMethodCall(obj, methodName, guard) {
|
|
1312
|
+
guard.checkNameAccess(methodName, "method");
|
|
1313
|
+
if (obj == null) throw new BonsaiTypeError(methodName, "a non-null value", obj);
|
|
1314
|
+
if (!isAllowedReceiver(obj, methodName)) throw new BonsaiSecurityError("METHOD_NOT_ALLOWED", `Method "${methodName}" is not allowed on ${typeof obj}`);
|
|
1315
|
+
const method = obj[methodName];
|
|
1316
|
+
if (typeof method !== "function") throw new BonsaiTypeError(methodName, "a method", method);
|
|
1317
|
+
return method;
|
|
1318
|
+
}
|
|
1319
|
+
function resolveTransform(name, transforms) {
|
|
1320
|
+
if (!Object.hasOwn(transforms, name)) throw new BonsaiReferenceError("transform", name, suggest(name, Object.keys(transforms)));
|
|
1321
|
+
return transforms[name];
|
|
1322
|
+
}
|
|
1323
|
+
function resolveFunction(name, functions) {
|
|
1324
|
+
if (!Object.hasOwn(functions, name)) throw new BonsaiReferenceError("function", name, suggest(name, Object.keys(functions)));
|
|
1325
|
+
return functions[name];
|
|
1326
|
+
}
|
|
1327
|
+
function getIdentifierName(node, message = "Expected identifier") {
|
|
1328
|
+
if (node.type !== "Identifier") throw new Error(message);
|
|
1329
|
+
return node.name;
|
|
1330
|
+
}
|
|
1331
|
+
function getObjectLiteralKeyName(node, message = "Expected identifier or string literal") {
|
|
1332
|
+
if (node.type === "Identifier" || node.type === "StringLiteral") return node.type === "Identifier" ? node.name : node.value;
|
|
1333
|
+
throw new Error(message);
|
|
1334
|
+
}
|
|
1335
|
+
function expandSpreadValue(value, maxLength) {
|
|
1336
|
+
if (Array.isArray(value)) {
|
|
1337
|
+
if (maxLength !== void 0 && value.length > maxLength) throw new BonsaiSecurityError("MAX_ARRAY_LENGTH", `Spread source length (${value.length}) exceeds maximum (${maxLength})`);
|
|
1338
|
+
return value;
|
|
1339
|
+
}
|
|
1340
|
+
if (value != null) {
|
|
1341
|
+
if (typeof value[Symbol.iterator] === "function") {
|
|
1342
|
+
const result = [];
|
|
1343
|
+
for (const item of value) {
|
|
1344
|
+
result.push(item);
|
|
1345
|
+
if (maxLength !== void 0 && result.length > maxLength) throw new BonsaiSecurityError("MAX_ARRAY_LENGTH", `Spread exceeds maximum array length (${maxLength})`);
|
|
1346
|
+
}
|
|
1347
|
+
return result;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
throw new BonsaiTypeError("spread", "an iterable value", value);
|
|
1351
|
+
}
|
|
1352
|
+
const REPLACE_METHODS = new Set(["replace", "replaceAll"]);
|
|
1353
|
+
const MAX_REPEAT_COUNT = 1e5;
|
|
1354
|
+
function validateMethodArgs(methodName, args) {
|
|
1355
|
+
if (REPLACE_METHODS.has(methodName)) for (const arg of args) {
|
|
1356
|
+
if (typeof arg === "function") throw new BonsaiTypeError(methodName, "string arguments (callbacks are not allowed)", arg);
|
|
1357
|
+
if (arg instanceof RegExp) throw new BonsaiTypeError(methodName, "string arguments (RegExp is not allowed)", arg);
|
|
1358
|
+
if (arg != null && typeof arg === "object") throw new BonsaiTypeError(methodName, "string arguments (objects are not allowed)", arg);
|
|
1359
|
+
}
|
|
1360
|
+
if (methodName === "repeat") {
|
|
1361
|
+
const count = Number(args[0]);
|
|
1362
|
+
if (!Number.isFinite(count) || count < 0 || count > MAX_REPEAT_COUNT) throw new BonsaiTypeError("repeat", `a count between 0 and ${MAX_REPEAT_COUNT}`, args[0]);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
function accessMember(object, propertyNode, computed, computedValue, guard) {
|
|
1366
|
+
const key = computed ? toPropertyKey(computedValue) : getIdentifierName(propertyNode, "Expected identifier property");
|
|
1367
|
+
guard.checkNameAccess(key, "member");
|
|
1368
|
+
return object?.[key];
|
|
1369
|
+
}
|
|
1370
|
+
//#endregion
|
|
1371
|
+
//#region src/evaluator.ts
|
|
1372
|
+
function rejectPromise(value, kind, name) {
|
|
1373
|
+
if (value instanceof Promise) throw new BonsaiTypeError(name, `a synchronous ${kind} result — use evaluate() instead of evaluateSync() for async`, value);
|
|
1374
|
+
return value;
|
|
1375
|
+
}
|
|
1376
|
+
function evaluate(node, context, transforms, functions, guard, source) {
|
|
1377
|
+
return evalNode(node, {
|
|
1378
|
+
ctx: context,
|
|
1379
|
+
tr: transforms,
|
|
1380
|
+
fn: functions,
|
|
1381
|
+
g: guard,
|
|
1382
|
+
s: source
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
function evalNode(node, env) {
|
|
1386
|
+
const { ctx, g, s } = env;
|
|
1387
|
+
g.enterDepth();
|
|
1388
|
+
g.step();
|
|
1389
|
+
try {
|
|
1390
|
+
switch (node.type) {
|
|
1391
|
+
case "NumberLiteral":
|
|
1392
|
+
case "StringLiteral":
|
|
1393
|
+
case "BooleanLiteral": return node.value;
|
|
1394
|
+
case "NullLiteral": return null;
|
|
1395
|
+
case "UndefinedLiteral": return;
|
|
1396
|
+
case "Identifier":
|
|
1397
|
+
g.checkNameAccess(node.name, "identifier");
|
|
1398
|
+
return Object.hasOwn(ctx, node.name) ? ctx[node.name] : void 0;
|
|
1399
|
+
case "UnaryExpression": return applyUnaryOp(node.operator, evalNode(node.operand, env));
|
|
1400
|
+
case "BinaryExpression": {
|
|
1401
|
+
const op = node.operator;
|
|
1402
|
+
if (op === "&&") {
|
|
1403
|
+
const left = evalNode(node.left, env);
|
|
1404
|
+
return left ? evalNode(node.right, env) : left;
|
|
1405
|
+
}
|
|
1406
|
+
if (op === "||") {
|
|
1407
|
+
const left = evalNode(node.left, env);
|
|
1408
|
+
return left ? left : evalNode(node.right, env);
|
|
1409
|
+
}
|
|
1410
|
+
if (op === "??") {
|
|
1411
|
+
const left = evalNode(node.left, env);
|
|
1412
|
+
return left != null ? left : evalNode(node.right, env);
|
|
1413
|
+
}
|
|
1414
|
+
return applyBinaryOp(op, evalNode(node.left, env), evalNode(node.right, env));
|
|
1415
|
+
}
|
|
1416
|
+
case "ConditionalExpression": return evalNode(node.test, env) ? evalNode(node.consequent, env) : evalNode(node.alternate, env);
|
|
1417
|
+
case "MemberExpression": {
|
|
1418
|
+
const object = evalNode(node.object, env);
|
|
1419
|
+
const computedValue = node.computed ? evalNode(node.property, env) : void 0;
|
|
1420
|
+
try {
|
|
1421
|
+
return accessMember(object, node.property, node.computed, computedValue, g);
|
|
1422
|
+
} catch (e) {
|
|
1423
|
+
if (s) attachLocation(e, s, node.start, node.end);
|
|
1424
|
+
throw e;
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
case "OptionalMemberExpression": {
|
|
1428
|
+
const object = evalNode(node.object, env);
|
|
1429
|
+
if (object == null) return void 0;
|
|
1430
|
+
const computedValue = node.computed ? evalNode(node.property, env) : void 0;
|
|
1431
|
+
try {
|
|
1432
|
+
return accessMember(object, node.property, node.computed, computedValue, g);
|
|
1433
|
+
} catch (e) {
|
|
1434
|
+
if (s) attachLocation(e, s, node.start, node.end);
|
|
1435
|
+
throw e;
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
case "ArrayLiteral": {
|
|
1439
|
+
const elements = [];
|
|
1440
|
+
for (const el of node.elements) if (el.type === "SpreadElement") elements.push(...expandSpreadValue(evalNode(el.argument, env), g.policy.maxArrayLength));
|
|
1441
|
+
else elements.push(evalNode(el, env));
|
|
1442
|
+
g.checkArrayLength(elements.length);
|
|
1443
|
+
return elements;
|
|
1444
|
+
}
|
|
1445
|
+
case "ObjectLiteral": {
|
|
1446
|
+
const obj = Object.create(null);
|
|
1447
|
+
for (const prop of node.properties) {
|
|
1448
|
+
const key = getObjectPropertyKey$1(prop, env);
|
|
1449
|
+
obj[key] = evalNode(prop.value, env);
|
|
1450
|
+
}
|
|
1451
|
+
return obj;
|
|
1452
|
+
}
|
|
1453
|
+
case "CallExpression": return evalCallExpression(node, env);
|
|
1454
|
+
case "PipeExpression": {
|
|
1455
|
+
const input = evalNode(node.input, env);
|
|
1456
|
+
try {
|
|
1457
|
+
return evalPipe(input, node.transform, env);
|
|
1458
|
+
} catch (e) {
|
|
1459
|
+
if (s) attachLocation(e, s, node.transform.start, node.transform.end);
|
|
1460
|
+
throw e;
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
case "TemplateLiteral": {
|
|
1464
|
+
let result = "";
|
|
1465
|
+
for (const part of node.parts) result += part.type === "StringLiteral" ? part.value : String(evalNode(part, env));
|
|
1466
|
+
return result;
|
|
1467
|
+
}
|
|
1468
|
+
case "SpreadElement": return evalNode(node.argument, env);
|
|
1469
|
+
case "LambdaAccessor": return makeLambdaAccessor$1(node.property, g);
|
|
1470
|
+
case "LambdaExpression": return (item) => evalLambdaBody(node.body, item, env);
|
|
1471
|
+
default: throw new Error(`Unknown node type: ${node.type}`);
|
|
1472
|
+
}
|
|
1473
|
+
} finally {
|
|
1474
|
+
g.exitDepth();
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
function evalCallExpression(node, env) {
|
|
1478
|
+
const { fn, g, s } = env;
|
|
1479
|
+
if (node.callee.type === "MemberExpression" || node.callee.type === "OptionalMemberExpression") {
|
|
1480
|
+
const obj = evalNode(node.callee.object, env);
|
|
1481
|
+
if (node.callee.type === "OptionalMemberExpression" && obj == null) return void 0;
|
|
1482
|
+
const computedValue = node.callee.computed ? evalNode(node.callee.property, env) : void 0;
|
|
1483
|
+
const methodName = node.callee.computed ? String(computedValue) : getIdentifierName(node.callee.property, "Expected method name");
|
|
1484
|
+
try {
|
|
1485
|
+
const method = validateMethodCall(obj, methodName, g);
|
|
1486
|
+
const args = [];
|
|
1487
|
+
for (const arg of node.args) pushCallArgument(args, arg, env);
|
|
1488
|
+
validateMethodArgs(methodName, args);
|
|
1489
|
+
return rejectPromise(method.call(obj, ...args), "method", methodName);
|
|
1490
|
+
} catch (e) {
|
|
1491
|
+
if (s) attachLocation(e, s, node.start, node.end);
|
|
1492
|
+
throw e;
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
if (node.callee.type === "Identifier") try {
|
|
1496
|
+
const func = resolveFunction(node.callee.name, fn);
|
|
1497
|
+
const args = [];
|
|
1498
|
+
for (const arg of node.args) pushCallArgument(args, arg, env);
|
|
1499
|
+
return rejectPromise(func(...args), "function", node.callee.name);
|
|
1500
|
+
} catch (e) {
|
|
1501
|
+
if (s) attachLocation(e, s, node.start, node.end);
|
|
1502
|
+
throw e;
|
|
1503
|
+
}
|
|
1504
|
+
throw new Error("Cannot call non-identifier");
|
|
1505
|
+
}
|
|
1506
|
+
function pushCallArgument(args, node, env) {
|
|
1507
|
+
if (node.type === "SpreadElement") {
|
|
1508
|
+
args.push(...expandSpreadValue(evalNode(node.argument, env), env.g.policy.maxArrayLength));
|
|
1509
|
+
return;
|
|
1510
|
+
}
|
|
1511
|
+
args.push(evalArg(node, env));
|
|
1512
|
+
}
|
|
1513
|
+
function evalArg(node, env) {
|
|
1514
|
+
if (node.type === "LambdaAccessor") return makeLambdaAccessor$1(node.property, env.g);
|
|
1515
|
+
return evalNode(node, env);
|
|
1516
|
+
}
|
|
1517
|
+
function evalPipe(input, transformNode, env) {
|
|
1518
|
+
const { tr } = env;
|
|
1519
|
+
if (transformNode.type === "CallExpression") {
|
|
1520
|
+
const calleeName = getIdentifierName(transformNode.callee, "Transform must be an identifier");
|
|
1521
|
+
const func = resolveTransform(calleeName, tr);
|
|
1522
|
+
const args = [];
|
|
1523
|
+
for (const arg of transformNode.args) pushCallArgument(args, arg, env);
|
|
1524
|
+
return rejectPromise(func(input, ...args), "transform", calleeName);
|
|
1525
|
+
}
|
|
1526
|
+
if (transformNode.type === "Identifier") return rejectPromise(resolveTransform(transformNode.name, tr)(input), "transform", transformNode.name);
|
|
1527
|
+
throw new Error("Invalid transform expression");
|
|
1528
|
+
}
|
|
1529
|
+
function evalLambdaBody(node, item, env) {
|
|
1530
|
+
const { g } = env;
|
|
1531
|
+
g.enterDepth();
|
|
1532
|
+
g.step();
|
|
1533
|
+
let ownDepth = true;
|
|
1534
|
+
try {
|
|
1535
|
+
switch (node.type) {
|
|
1536
|
+
case "LambdaAccessor":
|
|
1537
|
+
g.checkNameAccess(node.property, "member");
|
|
1538
|
+
return item?.[node.property];
|
|
1539
|
+
case "MemberExpression": {
|
|
1540
|
+
const object = evalLambdaBody(node.object, item, env);
|
|
1541
|
+
const computedValue = node.computed ? evalNode(node.property, env) : void 0;
|
|
1542
|
+
return accessMember(object, node.property, node.computed, computedValue, g);
|
|
1543
|
+
}
|
|
1544
|
+
case "OptionalMemberExpression": {
|
|
1545
|
+
const object = evalLambdaBody(node.object, item, env);
|
|
1546
|
+
if (object == null) return void 0;
|
|
1547
|
+
const computedValue = node.computed ? evalNode(node.property, env) : void 0;
|
|
1548
|
+
return accessMember(object, node.property, node.computed, computedValue, g);
|
|
1549
|
+
}
|
|
1550
|
+
case "CallExpression":
|
|
1551
|
+
if (node.callee.type === "MemberExpression" || node.callee.type === "OptionalMemberExpression") {
|
|
1552
|
+
const obj = evalLambdaBody(node.callee.object, item, env);
|
|
1553
|
+
if (node.callee.type === "OptionalMemberExpression" && obj == null) return void 0;
|
|
1554
|
+
const computedValue = node.callee.computed ? evalNode(node.callee.property, env) : void 0;
|
|
1555
|
+
const methodName = node.callee.computed ? String(computedValue) : getIdentifierName(node.callee.property, "Expected method name");
|
|
1556
|
+
const method = validateMethodCall(obj, methodName, g);
|
|
1557
|
+
const args = [];
|
|
1558
|
+
for (const arg of node.args) if (arg.type === "SpreadElement") args.push(...expandSpreadValue(evalNode(arg.argument, env), g.policy.maxArrayLength));
|
|
1559
|
+
else args.push(evalNode(arg, env));
|
|
1560
|
+
validateMethodArgs(methodName, args);
|
|
1561
|
+
return rejectPromise(method.call(obj, ...args), "method", methodName);
|
|
1562
|
+
}
|
|
1563
|
+
ownDepth = false;
|
|
1564
|
+
g.exitDepth();
|
|
1565
|
+
return evalNode(node, env);
|
|
1566
|
+
case "BinaryExpression": {
|
|
1567
|
+
const op = node.operator;
|
|
1568
|
+
if (op === "&&") {
|
|
1569
|
+
const left = evalLambdaBody(node.left, item, env);
|
|
1570
|
+
return left ? evalLambdaBody(node.right, item, env) : left;
|
|
1571
|
+
}
|
|
1572
|
+
if (op === "||") {
|
|
1573
|
+
const left = evalLambdaBody(node.left, item, env);
|
|
1574
|
+
return left ? left : evalLambdaBody(node.right, item, env);
|
|
1575
|
+
}
|
|
1576
|
+
if (op === "??") {
|
|
1577
|
+
const left = evalLambdaBody(node.left, item, env);
|
|
1578
|
+
return left != null ? left : evalLambdaBody(node.right, item, env);
|
|
1579
|
+
}
|
|
1580
|
+
return applyBinaryOp(op, evalLambdaBody(node.left, item, env), evalLambdaBody(node.right, item, env));
|
|
1581
|
+
}
|
|
1582
|
+
case "UnaryExpression": return applyUnaryOp(node.operator, evalLambdaBody(node.operand, item, env));
|
|
1583
|
+
case "ConditionalExpression": return evalLambdaBody(node.test, item, env) ? evalLambdaBody(node.consequent, item, env) : evalLambdaBody(node.alternate, item, env);
|
|
1584
|
+
case "LambdaExpression": return evalLambdaBody(node.body, item, env);
|
|
1585
|
+
default:
|
|
1586
|
+
ownDepth = false;
|
|
1587
|
+
g.exitDepth();
|
|
1588
|
+
return evalNode(node, env);
|
|
1589
|
+
}
|
|
1590
|
+
} finally {
|
|
1591
|
+
if (ownDepth) g.exitDepth();
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
function getObjectPropertyKey$1(prop, env) {
|
|
1595
|
+
const key = prop.computed ? String(evalNode(prop.key, env)) : getObjectLiteralKeyName(prop.key);
|
|
1596
|
+
env.g.checkNameAccess(key, "object-key");
|
|
1597
|
+
return key;
|
|
1598
|
+
}
|
|
1599
|
+
function makeLambdaAccessor$1(property, guard) {
|
|
1600
|
+
return (item) => {
|
|
1601
|
+
guard.checkNameAccess(property, "member");
|
|
1602
|
+
return item?.[property];
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1605
|
+
//#endregion
|
|
1606
|
+
//#region src/evaluator-async.ts
|
|
1607
|
+
async function evaluateAsync(node, context, transforms, functions, guard, source) {
|
|
1608
|
+
const result = await evalNodeAsync(node, {
|
|
1609
|
+
ctx: context,
|
|
1610
|
+
tr: transforms,
|
|
1611
|
+
fn: functions,
|
|
1612
|
+
g: guard,
|
|
1613
|
+
s: source
|
|
1614
|
+
});
|
|
1615
|
+
guard.checkTimeout();
|
|
1616
|
+
return result;
|
|
1617
|
+
}
|
|
1618
|
+
async function evalNodeAsync(node, env) {
|
|
1619
|
+
const { ctx, g, s } = env;
|
|
1620
|
+
g.enterDepth();
|
|
1621
|
+
g.step();
|
|
1622
|
+
try {
|
|
1623
|
+
switch (node.type) {
|
|
1624
|
+
case "NumberLiteral":
|
|
1625
|
+
case "StringLiteral":
|
|
1626
|
+
case "BooleanLiteral": return node.value;
|
|
1627
|
+
case "NullLiteral": return null;
|
|
1628
|
+
case "UndefinedLiteral": return;
|
|
1629
|
+
case "Identifier":
|
|
1630
|
+
g.checkNameAccess(node.name, "identifier");
|
|
1631
|
+
return Object.hasOwn(ctx, node.name) ? ctx[node.name] : void 0;
|
|
1632
|
+
case "UnaryExpression": return applyUnaryOp(node.operator, await evalNodeAsync(node.operand, env));
|
|
1633
|
+
case "BinaryExpression": {
|
|
1634
|
+
const op = node.operator;
|
|
1635
|
+
if (op === "&&") {
|
|
1636
|
+
const left = await evalNodeAsync(node.left, env);
|
|
1637
|
+
return left ? await evalNodeAsync(node.right, env) : left;
|
|
1638
|
+
}
|
|
1639
|
+
if (op === "||") {
|
|
1640
|
+
const left = await evalNodeAsync(node.left, env);
|
|
1641
|
+
return left ? left : await evalNodeAsync(node.right, env);
|
|
1642
|
+
}
|
|
1643
|
+
if (op === "??") {
|
|
1644
|
+
const left = await evalNodeAsync(node.left, env);
|
|
1645
|
+
return left != null ? left : await evalNodeAsync(node.right, env);
|
|
1646
|
+
}
|
|
1647
|
+
return applyBinaryOp(op, await evalNodeAsync(node.left, env), await evalNodeAsync(node.right, env));
|
|
1648
|
+
}
|
|
1649
|
+
case "ConditionalExpression": return await evalNodeAsync(node.test, env) ? await evalNodeAsync(node.consequent, env) : await evalNodeAsync(node.alternate, env);
|
|
1650
|
+
case "MemberExpression": {
|
|
1651
|
+
const object = await evalNodeAsync(node.object, env);
|
|
1652
|
+
const computedValue = node.computed ? await evalNodeAsync(node.property, env) : void 0;
|
|
1653
|
+
try {
|
|
1654
|
+
return accessMember(object, node.property, node.computed, computedValue, g);
|
|
1655
|
+
} catch (e) {
|
|
1656
|
+
if (s) attachLocation(e, s, node.start, node.end);
|
|
1657
|
+
throw e;
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
case "OptionalMemberExpression": {
|
|
1661
|
+
const object = await evalNodeAsync(node.object, env);
|
|
1662
|
+
if (object == null) return void 0;
|
|
1663
|
+
const computedValue = node.computed ? await evalNodeAsync(node.property, env) : void 0;
|
|
1664
|
+
try {
|
|
1665
|
+
return accessMember(object, node.property, node.computed, computedValue, g);
|
|
1666
|
+
} catch (e) {
|
|
1667
|
+
if (s) attachLocation(e, s, node.start, node.end);
|
|
1668
|
+
throw e;
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
case "ArrayLiteral": {
|
|
1672
|
+
const elements = [];
|
|
1673
|
+
for (const el of node.elements) if (el.type === "SpreadElement") elements.push(...expandSpreadValue(await evalNodeAsync(el.argument, env), g.policy.maxArrayLength));
|
|
1674
|
+
else elements.push(await evalNodeAsync(el, env));
|
|
1675
|
+
g.checkArrayLength(elements.length);
|
|
1676
|
+
return elements;
|
|
1677
|
+
}
|
|
1678
|
+
case "ObjectLiteral": {
|
|
1679
|
+
const obj = Object.create(null);
|
|
1680
|
+
for (const prop of node.properties) {
|
|
1681
|
+
const key = await getObjectPropertyKey(prop, env);
|
|
1682
|
+
obj[key] = await evalNodeAsync(prop.value, env);
|
|
1683
|
+
}
|
|
1684
|
+
return obj;
|
|
1685
|
+
}
|
|
1686
|
+
case "CallExpression": return await evalCallExpressionAsync(node, env);
|
|
1687
|
+
case "PipeExpression": {
|
|
1688
|
+
const input = await evalNodeAsync(node.input, env);
|
|
1689
|
+
try {
|
|
1690
|
+
return await evalPipeAsync(input, node.transform, env);
|
|
1691
|
+
} catch (e) {
|
|
1692
|
+
if (s) attachLocation(e, s, node.transform.start, node.transform.end);
|
|
1693
|
+
throw e;
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
case "TemplateLiteral": {
|
|
1697
|
+
let result = "";
|
|
1698
|
+
for (const part of node.parts) result += part.type === "StringLiteral" ? part.value : String(await evalNodeAsync(part, env));
|
|
1699
|
+
return result;
|
|
1700
|
+
}
|
|
1701
|
+
case "SpreadElement": return await evalNodeAsync(node.argument, env);
|
|
1702
|
+
case "LambdaAccessor": return makeLambdaAccessor(node.property, g);
|
|
1703
|
+
case "LambdaExpression": return (item) => evalLambdaBodyAsync(node.body, item, env);
|
|
1704
|
+
default: throw new Error(`Unknown node type: ${node.type}`);
|
|
1705
|
+
}
|
|
1706
|
+
} finally {
|
|
1707
|
+
g.exitDepth();
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
async function evalCallExpressionAsync(node, env) {
|
|
1711
|
+
const { fn, g, s } = env;
|
|
1712
|
+
if (node.callee.type === "MemberExpression" || node.callee.type === "OptionalMemberExpression") {
|
|
1713
|
+
const obj = await evalNodeAsync(node.callee.object, env);
|
|
1714
|
+
if (node.callee.type === "OptionalMemberExpression" && obj == null) return void 0;
|
|
1715
|
+
const computedValue = node.callee.computed ? await evalNodeAsync(node.callee.property, env) : void 0;
|
|
1716
|
+
const methodName = node.callee.computed ? String(computedValue) : getIdentifierName(node.callee.property, "Expected method name");
|
|
1717
|
+
try {
|
|
1718
|
+
const method = validateMethodCall(obj, methodName, g);
|
|
1719
|
+
const args = [];
|
|
1720
|
+
for (const arg of node.args) await pushCallArgumentAsync(args, arg, env);
|
|
1721
|
+
validateMethodArgs(methodName, args);
|
|
1722
|
+
const result = await method.call(obj, ...args);
|
|
1723
|
+
g.checkTimeout();
|
|
1724
|
+
return result;
|
|
1725
|
+
} catch (e) {
|
|
1726
|
+
if (s) attachLocation(e, s, node.start, node.end);
|
|
1727
|
+
throw e;
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
if (node.callee.type === "Identifier") try {
|
|
1731
|
+
const func = resolveFunction(node.callee.name, fn);
|
|
1732
|
+
const args = [];
|
|
1733
|
+
for (const arg of node.args) await pushCallArgumentAsync(args, arg, env);
|
|
1734
|
+
const result = await func(...args);
|
|
1735
|
+
g.checkTimeout();
|
|
1736
|
+
return result;
|
|
1737
|
+
} catch (e) {
|
|
1738
|
+
if (s) attachLocation(e, s, node.start, node.end);
|
|
1739
|
+
throw e;
|
|
1740
|
+
}
|
|
1741
|
+
throw new Error("Cannot call non-identifier");
|
|
1742
|
+
}
|
|
1743
|
+
async function pushCallArgumentAsync(args, node, env) {
|
|
1744
|
+
if (node.type === "SpreadElement") {
|
|
1745
|
+
args.push(...expandSpreadValue(await evalNodeAsync(node.argument, env), env.g.policy.maxArrayLength));
|
|
1746
|
+
return;
|
|
1747
|
+
}
|
|
1748
|
+
args.push(await evalArgAsync(node, env));
|
|
1749
|
+
}
|
|
1750
|
+
async function evalArgAsync(node, env) {
|
|
1751
|
+
if (node.type === "LambdaAccessor") return makeLambdaAccessor(node.property, env.g);
|
|
1752
|
+
return await evalNodeAsync(node, env);
|
|
1753
|
+
}
|
|
1754
|
+
async function evalPipeAsync(input, transformNode, env) {
|
|
1755
|
+
const { tr, g } = env;
|
|
1756
|
+
if (transformNode.type === "CallExpression") {
|
|
1757
|
+
const func = resolveTransform(getIdentifierName(transformNode.callee, "Transform must be an identifier"), tr);
|
|
1758
|
+
const args = [];
|
|
1759
|
+
for (const arg of transformNode.args) await pushCallArgumentAsync(args, arg, env);
|
|
1760
|
+
const result = await func(input, ...args);
|
|
1761
|
+
g.checkTimeout();
|
|
1762
|
+
return result;
|
|
1763
|
+
}
|
|
1764
|
+
if (transformNode.type === "Identifier") {
|
|
1765
|
+
const result = await resolveTransform(transformNode.name, tr)(input);
|
|
1766
|
+
g.checkTimeout();
|
|
1767
|
+
return result;
|
|
1768
|
+
}
|
|
1769
|
+
throw new Error("Invalid transform expression");
|
|
1770
|
+
}
|
|
1771
|
+
async function evalLambdaBodyAsync(node, item, env) {
|
|
1772
|
+
const { g } = env;
|
|
1773
|
+
g.enterDepth();
|
|
1774
|
+
g.step();
|
|
1775
|
+
let ownDepth = true;
|
|
1776
|
+
try {
|
|
1777
|
+
switch (node.type) {
|
|
1778
|
+
case "LambdaAccessor":
|
|
1779
|
+
g.checkNameAccess(node.property, "member");
|
|
1780
|
+
return item?.[node.property];
|
|
1781
|
+
case "MemberExpression": {
|
|
1782
|
+
const object = await evalLambdaBodyAsync(node.object, item, env);
|
|
1783
|
+
const computedValue = node.computed ? await evalNodeAsync(node.property, env) : void 0;
|
|
1784
|
+
return accessMember(object, node.property, node.computed, computedValue, g);
|
|
1785
|
+
}
|
|
1786
|
+
case "OptionalMemberExpression": {
|
|
1787
|
+
const object = await evalLambdaBodyAsync(node.object, item, env);
|
|
1788
|
+
if (object == null) return void 0;
|
|
1789
|
+
const computedValue = node.computed ? await evalNodeAsync(node.property, env) : void 0;
|
|
1790
|
+
return accessMember(object, node.property, node.computed, computedValue, g);
|
|
1791
|
+
}
|
|
1792
|
+
case "CallExpression":
|
|
1793
|
+
if (node.callee.type === "MemberExpression" || node.callee.type === "OptionalMemberExpression") {
|
|
1794
|
+
const obj = await evalLambdaBodyAsync(node.callee.object, item, env);
|
|
1795
|
+
if (node.callee.type === "OptionalMemberExpression" && obj == null) return void 0;
|
|
1796
|
+
const computedValue = node.callee.computed ? await evalNodeAsync(node.callee.property, env) : void 0;
|
|
1797
|
+
const methodName = node.callee.computed ? String(computedValue) : getIdentifierName(node.callee.property, "Expected method name");
|
|
1798
|
+
const method = validateMethodCall(obj, methodName, g);
|
|
1799
|
+
const args = [];
|
|
1800
|
+
for (const arg of node.args) if (arg.type === "SpreadElement") args.push(...expandSpreadValue(await evalNodeAsync(arg.argument, env), g.policy.maxArrayLength));
|
|
1801
|
+
else args.push(await evalNodeAsync(arg, env));
|
|
1802
|
+
validateMethodArgs(methodName, args);
|
|
1803
|
+
return await method.call(obj, ...args);
|
|
1804
|
+
}
|
|
1805
|
+
ownDepth = false;
|
|
1806
|
+
g.exitDepth();
|
|
1807
|
+
return await evalNodeAsync(node, env);
|
|
1808
|
+
case "BinaryExpression": {
|
|
1809
|
+
const op = node.operator;
|
|
1810
|
+
if (op === "&&") {
|
|
1811
|
+
const left = await evalLambdaBodyAsync(node.left, item, env);
|
|
1812
|
+
return left ? await evalLambdaBodyAsync(node.right, item, env) : left;
|
|
1813
|
+
}
|
|
1814
|
+
if (op === "||") {
|
|
1815
|
+
const left = await evalLambdaBodyAsync(node.left, item, env);
|
|
1816
|
+
return left ? left : await evalLambdaBodyAsync(node.right, item, env);
|
|
1817
|
+
}
|
|
1818
|
+
if (op === "??") {
|
|
1819
|
+
const left = await evalLambdaBodyAsync(node.left, item, env);
|
|
1820
|
+
return left != null ? left : await evalLambdaBodyAsync(node.right, item, env);
|
|
1821
|
+
}
|
|
1822
|
+
return applyBinaryOp(op, await evalLambdaBodyAsync(node.left, item, env), await evalLambdaBodyAsync(node.right, item, env));
|
|
1823
|
+
}
|
|
1824
|
+
case "UnaryExpression": return applyUnaryOp(node.operator, await evalLambdaBodyAsync(node.operand, item, env));
|
|
1825
|
+
case "ConditionalExpression": return await evalLambdaBodyAsync(node.test, item, env) ? await evalLambdaBodyAsync(node.consequent, item, env) : await evalLambdaBodyAsync(node.alternate, item, env);
|
|
1826
|
+
case "LambdaExpression": return await evalLambdaBodyAsync(node.body, item, env);
|
|
1827
|
+
default:
|
|
1828
|
+
ownDepth = false;
|
|
1829
|
+
g.exitDepth();
|
|
1830
|
+
return await evalNodeAsync(node, env);
|
|
1831
|
+
}
|
|
1832
|
+
} finally {
|
|
1833
|
+
if (ownDepth) g.exitDepth();
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
async function getObjectPropertyKey(prop, env) {
|
|
1837
|
+
const key = prop.computed ? String(await evalNodeAsync(prop.key, env)) : getObjectLiteralKeyName(prop.key);
|
|
1838
|
+
env.g.checkNameAccess(key, "object-key");
|
|
1839
|
+
return key;
|
|
1840
|
+
}
|
|
1841
|
+
function makeLambdaAccessor(property, guard) {
|
|
1842
|
+
return (item) => {
|
|
1843
|
+
guard.checkNameAccess(property, "member");
|
|
1844
|
+
return item?.[property];
|
|
1845
|
+
};
|
|
1846
|
+
}
|
|
1847
|
+
//#endregion
|
|
1848
|
+
//#region src/execution-context.ts
|
|
1849
|
+
const BLOCKED_PROPERTIES = new Set([
|
|
1850
|
+
"__proto__",
|
|
1851
|
+
"constructor",
|
|
1852
|
+
"prototype"
|
|
1853
|
+
]);
|
|
1854
|
+
const MAX_INDEX_DIGITS = 10;
|
|
1855
|
+
function isCanonicalIndex(key) {
|
|
1856
|
+
if (key.length === 0 || key.length > MAX_INDEX_DIGITS) return false;
|
|
1857
|
+
const n = Number(key);
|
|
1858
|
+
return Number.isInteger(n) && n >= 0 && String(n) === key;
|
|
1859
|
+
}
|
|
1860
|
+
const TIMEOUT_CHECK_INTERVAL = 1e3;
|
|
1861
|
+
const DEFAULT_MAX_DEPTH = 100;
|
|
1862
|
+
const DEFAULT_MAX_ARRAY_LENGTH = 1e5;
|
|
1863
|
+
/** Immutable per-instance security configuration derived from BonsaiOptions. */
|
|
1864
|
+
var SecurityPolicy = class {
|
|
1865
|
+
maxDepth;
|
|
1866
|
+
maxArrayLength;
|
|
1867
|
+
timeout;
|
|
1868
|
+
allowedProperties;
|
|
1869
|
+
deniedProperties;
|
|
1870
|
+
constructor(options = {}) {
|
|
1871
|
+
this.maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
1872
|
+
this.maxArrayLength = options.maxArrayLength ?? DEFAULT_MAX_ARRAY_LENGTH;
|
|
1873
|
+
this.timeout = options.timeout ?? 0;
|
|
1874
|
+
this.allowedProperties = options.allowedProperties ? new Set(options.allowedProperties) : void 0;
|
|
1875
|
+
this.deniedProperties = options.deniedProperties ? new Set(options.deniedProperties) : void 0;
|
|
1876
|
+
}
|
|
1877
|
+
};
|
|
1878
|
+
/** Mutable per-evaluation state: tracks depth, step count, and deadline for a single evaluation. */
|
|
1879
|
+
var ExecutionContext = class {
|
|
1880
|
+
stepCount = 0;
|
|
1881
|
+
depth = 0;
|
|
1882
|
+
deadline;
|
|
1883
|
+
policy;
|
|
1884
|
+
now;
|
|
1885
|
+
constructor(policy, now = Date.now) {
|
|
1886
|
+
this.policy = policy;
|
|
1887
|
+
this.now = now;
|
|
1888
|
+
this.deadline = policy.timeout ? now() + policy.timeout : 0;
|
|
1889
|
+
}
|
|
1890
|
+
step() {
|
|
1891
|
+
if (this.deadline) {
|
|
1892
|
+
this.stepCount++;
|
|
1893
|
+
if (this.stepCount % TIMEOUT_CHECK_INTERVAL === 0) this.checkTimeout();
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
checkTimeout() {
|
|
1897
|
+
if (this.deadline && this.now() >= this.deadline) throw new BonsaiSecurityError("TIMEOUT", `Expression timeout: exceeded ${this.policy.timeout}ms`);
|
|
1898
|
+
}
|
|
1899
|
+
enterDepth() {
|
|
1900
|
+
if (++this.depth > this.policy.maxDepth) throw new BonsaiSecurityError("MAX_DEPTH", `Maximum expression depth (${this.policy.maxDepth}) exceeded`);
|
|
1901
|
+
}
|
|
1902
|
+
exitDepth() {
|
|
1903
|
+
this.depth--;
|
|
1904
|
+
}
|
|
1905
|
+
withDepth(fn) {
|
|
1906
|
+
this.enterDepth();
|
|
1907
|
+
try {
|
|
1908
|
+
return fn();
|
|
1909
|
+
} finally {
|
|
1910
|
+
this.exitDepth();
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
checkNameAccess(key, kind) {
|
|
1914
|
+
if (BLOCKED_PROPERTIES.has(key)) throw new BonsaiSecurityError("BLOCKED_PROPERTY", `Blocked: access to "${key}" is not allowed`);
|
|
1915
|
+
if (kind === "identifier" || kind === "object-key") return;
|
|
1916
|
+
if (isCanonicalIndex(key)) return;
|
|
1917
|
+
if (this.policy.allowedProperties) {
|
|
1918
|
+
if (!this.policy.allowedProperties.has(key)) throw new BonsaiSecurityError("PROPERTY_NOT_ALLOWED", `Blocked: "${key}" is not in allowed properties`);
|
|
1919
|
+
}
|
|
1920
|
+
if (this.policy.deniedProperties) {
|
|
1921
|
+
if (this.policy.deniedProperties.has(key)) throw new BonsaiSecurityError("PROPERTY_DENIED", `Blocked: "${key}" is in denied properties`);
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
checkArrayLength(length) {
|
|
1925
|
+
if (length > this.policy.maxArrayLength) throw new BonsaiSecurityError("MAX_ARRAY_LENGTH", `Array length (${length}) exceeds maximum (${this.policy.maxArrayLength})`);
|
|
1926
|
+
}
|
|
1927
|
+
};
|
|
1928
|
+
//#endregion
|
|
1929
|
+
//#region src/cache.ts
|
|
1930
|
+
var LRUCache = class {
|
|
1931
|
+
max;
|
|
1932
|
+
map = /* @__PURE__ */ new Map();
|
|
1933
|
+
constructor(max) {
|
|
1934
|
+
this.max = max > 0 ? max : 0;
|
|
1935
|
+
}
|
|
1936
|
+
get(key) {
|
|
1937
|
+
if (!this.map.has(key)) return void 0;
|
|
1938
|
+
const value = this.map.get(key);
|
|
1939
|
+
this.map.delete(key);
|
|
1940
|
+
this.map.set(key, value);
|
|
1941
|
+
return value;
|
|
1942
|
+
}
|
|
1943
|
+
set(key, value) {
|
|
1944
|
+
if (this.max === 0) return;
|
|
1945
|
+
if (this.map.has(key)) this.map.delete(key);
|
|
1946
|
+
else if (this.map.size >= this.max) {
|
|
1947
|
+
const oldest = this.map.keys().next().value;
|
|
1948
|
+
this.map.delete(oldest);
|
|
1949
|
+
}
|
|
1950
|
+
this.map.set(key, value);
|
|
1951
|
+
}
|
|
1952
|
+
get size() {
|
|
1953
|
+
return this.map.size;
|
|
1954
|
+
}
|
|
1955
|
+
clear() {
|
|
1956
|
+
this.map.clear();
|
|
1957
|
+
}
|
|
1958
|
+
};
|
|
1959
|
+
//#endregion
|
|
1960
|
+
//#region src/plugins.ts
|
|
1961
|
+
function createPluginRegistry() {
|
|
1962
|
+
const transformMap = /* @__PURE__ */ new Map();
|
|
1963
|
+
const functionMap = /* @__PURE__ */ new Map();
|
|
1964
|
+
let transformsCache = {};
|
|
1965
|
+
let functionsCache = {};
|
|
1966
|
+
let transformsDirty = false;
|
|
1967
|
+
let functionsDirty = false;
|
|
1968
|
+
const registry = {
|
|
1969
|
+
addTransform(name, fn) {
|
|
1970
|
+
transformMap.set(name, fn);
|
|
1971
|
+
transformsDirty = true;
|
|
1972
|
+
},
|
|
1973
|
+
addFunction(name, fn) {
|
|
1974
|
+
functionMap.set(name, fn);
|
|
1975
|
+
functionsDirty = true;
|
|
1976
|
+
},
|
|
1977
|
+
removeTransform(name) {
|
|
1978
|
+
const r = transformMap.delete(name);
|
|
1979
|
+
if (r) transformsDirty = true;
|
|
1980
|
+
return r;
|
|
1981
|
+
},
|
|
1982
|
+
removeFunction(name) {
|
|
1983
|
+
const r = functionMap.delete(name);
|
|
1984
|
+
if (r) functionsDirty = true;
|
|
1985
|
+
return r;
|
|
1986
|
+
},
|
|
1987
|
+
getTransform(name) {
|
|
1988
|
+
return transformMap.get(name);
|
|
1989
|
+
},
|
|
1990
|
+
getFunction(name) {
|
|
1991
|
+
return functionMap.get(name);
|
|
1992
|
+
},
|
|
1993
|
+
getTransformNames() {
|
|
1994
|
+
return [...transformMap.keys()];
|
|
1995
|
+
},
|
|
1996
|
+
getFunctionNames() {
|
|
1997
|
+
return [...functionMap.keys()];
|
|
1998
|
+
},
|
|
1999
|
+
use(plugin) {
|
|
2000
|
+
plugin(registry);
|
|
2001
|
+
},
|
|
2002
|
+
get transforms() {
|
|
2003
|
+
if (transformsDirty) {
|
|
2004
|
+
transformsCache = Object.fromEntries(transformMap);
|
|
2005
|
+
transformsDirty = false;
|
|
2006
|
+
}
|
|
2007
|
+
return transformsCache;
|
|
2008
|
+
},
|
|
2009
|
+
get functions() {
|
|
2010
|
+
if (functionsDirty) {
|
|
2011
|
+
functionsCache = Object.fromEntries(functionMap);
|
|
2012
|
+
functionsDirty = false;
|
|
2013
|
+
}
|
|
2014
|
+
return functionsCache;
|
|
2015
|
+
}
|
|
2016
|
+
};
|
|
2017
|
+
return registry;
|
|
2018
|
+
}
|
|
2019
|
+
//#endregion
|
|
2020
|
+
//#region src/index.ts
|
|
2021
|
+
const DEFAULT_CACHE_SIZE = 256;
|
|
2022
|
+
let _shared;
|
|
2023
|
+
/**
|
|
2024
|
+
* Evaluate a single expression with default options. Uses a shared instance internally.
|
|
2025
|
+
* For repeated evaluation or custom configuration, use `bonsai()` to create a dedicated instance.
|
|
2026
|
+
*/
|
|
2027
|
+
function evaluateExpression(expression, context) {
|
|
2028
|
+
if (!_shared) _shared = bonsai();
|
|
2029
|
+
return _shared.evaluateSync(expression, context);
|
|
2030
|
+
}
|
|
2031
|
+
/**
|
|
2032
|
+
* Create a new Bonsai instance with optional safety and caching configuration.
|
|
2033
|
+
* Register transforms and functions via `.use()`, `.addTransform()`, and `.addFunction()`.
|
|
2034
|
+
*
|
|
2035
|
+
* @example
|
|
2036
|
+
* ```ts
|
|
2037
|
+
* const expr = bonsai({ timeout: 50 })
|
|
2038
|
+
* expr.use(strings)
|
|
2039
|
+
* expr.evaluateSync('name |> trim |> upper', { name: ' hello ' }) // "HELLO"
|
|
2040
|
+
* ```
|
|
2041
|
+
*/
|
|
2042
|
+
function bonsai(options = {}) {
|
|
2043
|
+
const registry = createPluginRegistry();
|
|
2044
|
+
const cache = new LRUCache(options.cacheSize ?? DEFAULT_CACHE_SIZE);
|
|
2045
|
+
const astCache = new LRUCache(options.cacheSize ?? DEFAULT_CACHE_SIZE);
|
|
2046
|
+
const policy = new SecurityPolicy(options);
|
|
2047
|
+
function createExecutionContext() {
|
|
2048
|
+
return new ExecutionContext(policy);
|
|
2049
|
+
}
|
|
2050
|
+
function getAst(source) {
|
|
2051
|
+
let ast = astCache.get(source);
|
|
2052
|
+
if (ast) return ast;
|
|
2053
|
+
ast = compile(parse(source));
|
|
2054
|
+
astCache.set(source, ast);
|
|
2055
|
+
return ast;
|
|
2056
|
+
}
|
|
2057
|
+
function compileExpr(source) {
|
|
2058
|
+
const cached = cache.get(source);
|
|
2059
|
+
if (cached) return cached;
|
|
2060
|
+
const optimized = getAst(source);
|
|
2061
|
+
const compiled = {
|
|
2062
|
+
ast: optimized,
|
|
2063
|
+
source,
|
|
2064
|
+
async evaluate(context = {}) {
|
|
2065
|
+
return evaluateAsync(optimized, context, registry.transforms, registry.functions, createExecutionContext(), source);
|
|
2066
|
+
},
|
|
2067
|
+
evaluateSync(context = {}) {
|
|
2068
|
+
return evaluate(optimized, context, registry.transforms, registry.functions, createExecutionContext(), source);
|
|
2069
|
+
}
|
|
2070
|
+
};
|
|
2071
|
+
cache.set(source, compiled);
|
|
2072
|
+
return compiled;
|
|
2073
|
+
}
|
|
2074
|
+
const instance = {
|
|
2075
|
+
use(plugin) {
|
|
2076
|
+
plugin(instance);
|
|
2077
|
+
return instance;
|
|
2078
|
+
},
|
|
2079
|
+
addTransform(name, fn) {
|
|
2080
|
+
registry.addTransform(name, fn);
|
|
2081
|
+
return instance;
|
|
2082
|
+
},
|
|
2083
|
+
addFunction(name, fn) {
|
|
2084
|
+
registry.addFunction(name, fn);
|
|
2085
|
+
return instance;
|
|
2086
|
+
},
|
|
2087
|
+
removeTransform(name) {
|
|
2088
|
+
return registry.removeTransform(name);
|
|
2089
|
+
},
|
|
2090
|
+
removeFunction(name) {
|
|
2091
|
+
return registry.removeFunction(name);
|
|
2092
|
+
},
|
|
2093
|
+
hasTransform(name) {
|
|
2094
|
+
return registry.getTransform(name) !== void 0;
|
|
2095
|
+
},
|
|
2096
|
+
hasFunction(name) {
|
|
2097
|
+
return registry.getFunction(name) !== void 0;
|
|
2098
|
+
},
|
|
2099
|
+
listTransforms() {
|
|
2100
|
+
return registry.getTransformNames();
|
|
2101
|
+
},
|
|
2102
|
+
listFunctions() {
|
|
2103
|
+
return registry.getFunctionNames();
|
|
2104
|
+
},
|
|
2105
|
+
clearCache() {
|
|
2106
|
+
cache.clear();
|
|
2107
|
+
astCache.clear();
|
|
2108
|
+
},
|
|
2109
|
+
compile(expression) {
|
|
2110
|
+
return compileExpr(expression);
|
|
2111
|
+
},
|
|
2112
|
+
async evaluate(expression, context = {}) {
|
|
2113
|
+
return evaluateAsync(getAst(expression), context, registry.transforms, registry.functions, createExecutionContext(), expression);
|
|
2114
|
+
},
|
|
2115
|
+
evaluateSync(expression, context = {}) {
|
|
2116
|
+
return evaluate(getAst(expression), context, registry.transforms, registry.functions, createExecutionContext(), expression);
|
|
2117
|
+
},
|
|
2118
|
+
validate(expression) {
|
|
2119
|
+
try {
|
|
2120
|
+
const ast = parse(expression);
|
|
2121
|
+
return {
|
|
2122
|
+
valid: true,
|
|
2123
|
+
errors: [],
|
|
2124
|
+
ast,
|
|
2125
|
+
references: extractReferences(ast)
|
|
2126
|
+
};
|
|
2127
|
+
} catch (error) {
|
|
2128
|
+
let message;
|
|
2129
|
+
if (error instanceof ExpressionError) message = error.rawMessage;
|
|
2130
|
+
else if (error instanceof Error) message = error.message;
|
|
2131
|
+
else message = String(error);
|
|
2132
|
+
const { start, end } = error instanceof ExpressionError ? error : {
|
|
2133
|
+
start: 0,
|
|
2134
|
+
end: 1
|
|
2135
|
+
};
|
|
2136
|
+
const position = offsetToPosition(expression, start);
|
|
2137
|
+
return {
|
|
2138
|
+
valid: false,
|
|
2139
|
+
errors: [{
|
|
2140
|
+
message,
|
|
2141
|
+
position: {
|
|
2142
|
+
line: position.line,
|
|
2143
|
+
column: position.column
|
|
2144
|
+
},
|
|
2145
|
+
formatted: error instanceof ExpressionError ? error.message : formatError(message, {
|
|
2146
|
+
source: expression,
|
|
2147
|
+
start,
|
|
2148
|
+
end
|
|
2149
|
+
})
|
|
2150
|
+
}]
|
|
2151
|
+
};
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
};
|
|
2155
|
+
return instance;
|
|
2156
|
+
}
|
|
2157
|
+
function extractReferences(node) {
|
|
2158
|
+
const identifiers = /* @__PURE__ */ new Set();
|
|
2159
|
+
const transforms = /* @__PURE__ */ new Set();
|
|
2160
|
+
const functions = /* @__PURE__ */ new Set();
|
|
2161
|
+
function walk(n) {
|
|
2162
|
+
switch (n.type) {
|
|
2163
|
+
case "Identifier":
|
|
2164
|
+
identifiers.add(n.name);
|
|
2165
|
+
break;
|
|
2166
|
+
case "PipeExpression":
|
|
2167
|
+
walk(n.input);
|
|
2168
|
+
if (n.transform.type === "Identifier") transforms.add(n.transform.name);
|
|
2169
|
+
else if (n.transform.type === "CallExpression") {
|
|
2170
|
+
if (n.transform.callee.type === "Identifier") transforms.add(getIdentifierName(n.transform.callee));
|
|
2171
|
+
n.transform.args.forEach(walk);
|
|
2172
|
+
}
|
|
2173
|
+
break;
|
|
2174
|
+
case "CallExpression":
|
|
2175
|
+
if (n.callee.type === "Identifier") functions.add(n.callee.name);
|
|
2176
|
+
else walk(n.callee);
|
|
2177
|
+
n.args.forEach(walk);
|
|
2178
|
+
break;
|
|
2179
|
+
case "BinaryExpression":
|
|
2180
|
+
walk(n.left);
|
|
2181
|
+
walk(n.right);
|
|
2182
|
+
break;
|
|
2183
|
+
case "UnaryExpression":
|
|
2184
|
+
walk(n.operand);
|
|
2185
|
+
break;
|
|
2186
|
+
case "ConditionalExpression":
|
|
2187
|
+
walk(n.test);
|
|
2188
|
+
walk(n.consequent);
|
|
2189
|
+
walk(n.alternate);
|
|
2190
|
+
break;
|
|
2191
|
+
case "MemberExpression":
|
|
2192
|
+
case "OptionalMemberExpression":
|
|
2193
|
+
walk(n.object);
|
|
2194
|
+
if (n.computed) walk(n.property);
|
|
2195
|
+
break;
|
|
2196
|
+
case "ArrayLiteral":
|
|
2197
|
+
n.elements.forEach(walk);
|
|
2198
|
+
break;
|
|
2199
|
+
case "ObjectLiteral":
|
|
2200
|
+
n.properties.forEach((p) => {
|
|
2201
|
+
walk(p.key);
|
|
2202
|
+
walk(p.value);
|
|
2203
|
+
});
|
|
2204
|
+
break;
|
|
2205
|
+
case "TemplateLiteral":
|
|
2206
|
+
n.parts.forEach(walk);
|
|
2207
|
+
break;
|
|
2208
|
+
case "SpreadElement":
|
|
2209
|
+
walk(n.argument);
|
|
2210
|
+
break;
|
|
2211
|
+
case "LambdaExpression":
|
|
2212
|
+
walk(n.body);
|
|
2213
|
+
break;
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
walk(node);
|
|
2217
|
+
return {
|
|
2218
|
+
identifiers: [...identifiers],
|
|
2219
|
+
transforms: [...transforms],
|
|
2220
|
+
functions: [...functions]
|
|
2221
|
+
};
|
|
2222
|
+
}
|
|
2223
|
+
//#endregion
|
|
2224
|
+
export { BonsaiReferenceError, BonsaiSecurityError, BonsaiTypeError, ExpressionError, bonsai, compile, evaluateExpression, formatBonsaiError, formatError, parse, tokenize };
|
|
2225
|
+
|
|
2226
|
+
//# sourceMappingURL=index.mjs.map
|