@gabrielbryk/jq-ts 1.1.0 → 1.2.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/dist/index.cjs +1840 -534
- package/dist/index.d.cts +171 -3
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +171 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1840 -534
- package/dist/index.mjs.map +1 -1
- package/package.json +26 -33
package/dist/index.mjs
CHANGED
|
@@ -60,11 +60,22 @@ const keywordKinds = {
|
|
|
60
60
|
reduce: "Reduce",
|
|
61
61
|
foreach: "Foreach",
|
|
62
62
|
try: "Try",
|
|
63
|
-
catch: "Catch"
|
|
63
|
+
catch: "Catch",
|
|
64
|
+
def: "Def",
|
|
65
|
+
label: "Label",
|
|
66
|
+
break: "Break"
|
|
64
67
|
};
|
|
65
68
|
|
|
66
69
|
//#endregion
|
|
67
70
|
//#region src/lexer.ts
|
|
71
|
+
/**
|
|
72
|
+
* Tokenizes the input jq string into a list of AST tokens.
|
|
73
|
+
* Handles string interpolation, comments, and operator grouping.
|
|
74
|
+
*
|
|
75
|
+
* @param text - The source code to tokenize.
|
|
76
|
+
* @returns An array of {@link Token}, including an EOF token at the end.
|
|
77
|
+
* @throws {@link LexError} if an invalid character or sequence is encountered.
|
|
78
|
+
*/
|
|
68
79
|
const lex = (text) => {
|
|
69
80
|
const tokens = [];
|
|
70
81
|
const length = text.length;
|
|
@@ -86,6 +97,7 @@ const lex = (text) => {
|
|
|
86
97
|
const isDigit = (ch) => !!ch && ch >= "0" && ch <= "9";
|
|
87
98
|
const isIdentifierStart = (ch) => !!ch && (ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z" || ch === "_");
|
|
88
99
|
const isIdentifierPart = (ch) => isIdentifierStart(ch) || isDigit(ch);
|
|
100
|
+
const modeStack = [0];
|
|
89
101
|
while (pos < length) {
|
|
90
102
|
const ch = peek();
|
|
91
103
|
if (isWhitespace(ch)) {
|
|
@@ -97,10 +109,27 @@ const lex = (text) => {
|
|
|
97
109
|
continue;
|
|
98
110
|
}
|
|
99
111
|
const start = pos;
|
|
112
|
+
if (ch === ")") {
|
|
113
|
+
if (modeStack.length > 1) {
|
|
114
|
+
advance();
|
|
115
|
+
const endPos = readString(start, false);
|
|
116
|
+
const value = readStringValue(start, start + 1, endPos - (peek(-1) === "\"" ? 1 : 2));
|
|
117
|
+
if (text[endPos - 1] === "\"") {
|
|
118
|
+
modeStack.pop();
|
|
119
|
+
pushToken("StringEnd", start, endPos, value);
|
|
120
|
+
} else pushToken("StringMiddle", start, endPos, value);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
100
124
|
switch (ch) {
|
|
101
125
|
case "\"": {
|
|
102
|
-
const endPos = readString(start);
|
|
103
|
-
|
|
126
|
+
const endPos = readString(start, true);
|
|
127
|
+
const isInterp = text.substring(endPos - 2, endPos) === "\\(";
|
|
128
|
+
const value = readStringValue(start, start + 1, endPos - (isInterp ? 2 : 1));
|
|
129
|
+
if (isInterp) {
|
|
130
|
+
modeStack.push(1);
|
|
131
|
+
pushToken("StringStart", start, endPos, value);
|
|
132
|
+
} else pushToken("String", start, endPos, value);
|
|
104
133
|
continue;
|
|
105
134
|
}
|
|
106
135
|
case ".":
|
|
@@ -118,15 +147,28 @@ const lex = (text) => {
|
|
|
118
147
|
advance();
|
|
119
148
|
pushToken("Comma", start, pos);
|
|
120
149
|
continue;
|
|
150
|
+
case "?":
|
|
151
|
+
advance();
|
|
152
|
+
pushToken("Question", start, pos);
|
|
153
|
+
continue;
|
|
121
154
|
case "|":
|
|
122
155
|
advance();
|
|
123
|
-
|
|
156
|
+
if (peek() === "=") {
|
|
157
|
+
advance();
|
|
158
|
+
pushToken("BarEq", start, pos);
|
|
159
|
+
} else pushToken("Pipe", start, pos);
|
|
124
160
|
continue;
|
|
125
161
|
case "/":
|
|
126
162
|
advance();
|
|
127
163
|
if (peek() === "/") {
|
|
128
164
|
advance();
|
|
129
|
-
|
|
165
|
+
if (peek() === "=") {
|
|
166
|
+
advance();
|
|
167
|
+
pushToken("AltEq", start, pos);
|
|
168
|
+
} else pushToken("Alt", start, pos);
|
|
169
|
+
} else if (peek() === "=") {
|
|
170
|
+
advance();
|
|
171
|
+
pushToken("SlashEq", start, pos);
|
|
130
172
|
} else pushToken("Slash", start, pos);
|
|
131
173
|
continue;
|
|
132
174
|
case "(":
|
|
@@ -159,26 +201,38 @@ const lex = (text) => {
|
|
|
159
201
|
continue;
|
|
160
202
|
case "+":
|
|
161
203
|
advance();
|
|
162
|
-
|
|
204
|
+
if (peek() === "=") {
|
|
205
|
+
advance();
|
|
206
|
+
pushToken("PlusEq", start, pos);
|
|
207
|
+
} else pushToken("Plus", start, pos);
|
|
163
208
|
continue;
|
|
164
209
|
case "-":
|
|
165
210
|
advance();
|
|
166
|
-
|
|
211
|
+
if (peek() === "=") {
|
|
212
|
+
advance();
|
|
213
|
+
pushToken("MinusEq", start, pos);
|
|
214
|
+
} else pushToken("Minus", start, pos);
|
|
167
215
|
continue;
|
|
168
216
|
case "*":
|
|
169
217
|
advance();
|
|
170
|
-
|
|
218
|
+
if (peek() === "=") {
|
|
219
|
+
advance();
|
|
220
|
+
pushToken("StarEq", start, pos);
|
|
221
|
+
} else pushToken("Star", start, pos);
|
|
171
222
|
continue;
|
|
172
223
|
case "%":
|
|
173
224
|
advance();
|
|
174
|
-
|
|
225
|
+
if (peek() === "=") {
|
|
226
|
+
advance();
|
|
227
|
+
pushToken("PercentEq", start, pos);
|
|
228
|
+
} else pushToken("Percent", start, pos);
|
|
175
229
|
continue;
|
|
176
230
|
case "=":
|
|
177
231
|
advance();
|
|
178
232
|
if (peek() === "=") {
|
|
179
233
|
advance();
|
|
180
234
|
pushToken("EqualEqual", start, pos);
|
|
181
|
-
} else
|
|
235
|
+
} else pushToken("Eq", start, pos);
|
|
182
236
|
continue;
|
|
183
237
|
case "!":
|
|
184
238
|
advance();
|
|
@@ -246,19 +300,23 @@ const lex = (text) => {
|
|
|
246
300
|
}
|
|
247
301
|
return pos;
|
|
248
302
|
}
|
|
249
|
-
function readString(tokenStart) {
|
|
250
|
-
advance();
|
|
303
|
+
function readString(tokenStart, openQuote) {
|
|
304
|
+
if (openQuote) advance();
|
|
251
305
|
while (pos < length) {
|
|
252
306
|
const current = advance();
|
|
253
307
|
if (current === "\"") return pos;
|
|
254
308
|
if (current === "\\") {
|
|
309
|
+
if (peek() === "(") {
|
|
310
|
+
advance();
|
|
311
|
+
return pos;
|
|
312
|
+
}
|
|
255
313
|
const esc = advance();
|
|
256
314
|
if (!esc) break;
|
|
257
315
|
if ("\"\\/bfnrt".includes(esc)) continue;
|
|
258
316
|
if (esc === "u") {
|
|
259
|
-
for (let i = 0; i < 4; i
|
|
260
|
-
const
|
|
261
|
-
if (!
|
|
317
|
+
for (let i = 0; i < 4; i++) {
|
|
318
|
+
const h = advance();
|
|
319
|
+
if (!h || !isHexDigit(h)) throw new LexError("Invalid Unicode escape", makeSpan(tokenStart, pos));
|
|
262
320
|
}
|
|
263
321
|
continue;
|
|
264
322
|
}
|
|
@@ -274,41 +332,44 @@ const lex = (text) => {
|
|
|
274
332
|
const ch = text[i];
|
|
275
333
|
if (ch === "\\") {
|
|
276
334
|
const next = text[i + 1];
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
case "u": {
|
|
305
|
-
const hex = text.slice(i + 2, i + 6);
|
|
306
|
-
result += String.fromCharCode(Number.parseInt(hex, 16));
|
|
307
|
-
i += 6;
|
|
308
|
-
break;
|
|
335
|
+
if (!next) throw new LexError("Unexpected end of input", makeSpan(tokenStart, pos));
|
|
336
|
+
if ("\"\\/bfnrt".includes(next)) {
|
|
337
|
+
switch (next) {
|
|
338
|
+
case "\"":
|
|
339
|
+
result += "\"";
|
|
340
|
+
break;
|
|
341
|
+
case "\\":
|
|
342
|
+
result += "\\";
|
|
343
|
+
break;
|
|
344
|
+
case "/":
|
|
345
|
+
result += "/";
|
|
346
|
+
break;
|
|
347
|
+
case "b":
|
|
348
|
+
result += "\b";
|
|
349
|
+
break;
|
|
350
|
+
case "f":
|
|
351
|
+
result += "\f";
|
|
352
|
+
break;
|
|
353
|
+
case "n":
|
|
354
|
+
result += "\n";
|
|
355
|
+
break;
|
|
356
|
+
case "r":
|
|
357
|
+
result += "\r";
|
|
358
|
+
break;
|
|
359
|
+
case "t":
|
|
360
|
+
result += " ";
|
|
361
|
+
break;
|
|
309
362
|
}
|
|
310
|
-
|
|
363
|
+
i += 2;
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
if (next === "u") {
|
|
367
|
+
const hex = text.slice(i + 2, i + 6);
|
|
368
|
+
result += String.fromCharCode(parseInt(hex, 16));
|
|
369
|
+
i += 6;
|
|
370
|
+
continue;
|
|
311
371
|
}
|
|
372
|
+
throw new LexError(`Invalid escape sequence "\\${next}"`, makeSpan(tokenStart, pos));
|
|
312
373
|
} else {
|
|
313
374
|
result += ch;
|
|
314
375
|
i += 1;
|
|
@@ -338,16 +399,56 @@ var Parser = class {
|
|
|
338
399
|
this.tokens = tokens;
|
|
339
400
|
}
|
|
340
401
|
parseFilter() {
|
|
341
|
-
const expr = this.
|
|
402
|
+
const expr = this.parseDef();
|
|
342
403
|
this.consume("EOF", "Expected end of expression");
|
|
343
404
|
return expr;
|
|
344
405
|
}
|
|
345
|
-
|
|
346
|
-
|
|
406
|
+
parseDef(allowComma = true) {
|
|
407
|
+
if (this.match("Def")) {
|
|
408
|
+
const startSpan = this.previous().span;
|
|
409
|
+
const nameToken = this.consume("Identifier", "Expected function name");
|
|
410
|
+
const name = String(nameToken.value);
|
|
411
|
+
const args = [];
|
|
412
|
+
if (this.match("LParen")) {
|
|
413
|
+
if (!this.check("RParen")) do {
|
|
414
|
+
const argToken = this.consume("Identifier", "Expected argument name");
|
|
415
|
+
args.push(String(argToken.value));
|
|
416
|
+
} while (this.match("Semicolon"));
|
|
417
|
+
this.consume("RParen", "Expected \")\" after arguments");
|
|
418
|
+
}
|
|
419
|
+
this.consume("Colon", "Expected \":\" after function signature");
|
|
420
|
+
const body = this.parseDef(true);
|
|
421
|
+
this.consume("Semicolon", "Expected \";\" after function body");
|
|
422
|
+
const next = this.parseDef(allowComma);
|
|
423
|
+
return {
|
|
424
|
+
kind: "Def",
|
|
425
|
+
name,
|
|
426
|
+
args,
|
|
427
|
+
body,
|
|
428
|
+
next,
|
|
429
|
+
span: spanBetween(startSpan, next.span)
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
return this.parseBinding(allowComma);
|
|
433
|
+
}
|
|
434
|
+
parseBinding(allowComma = true) {
|
|
435
|
+
if (this.match("Label")) {
|
|
436
|
+
const start = this.previous();
|
|
437
|
+
const varToken = this.consume("Variable", "Expected variable name after \"label\"");
|
|
438
|
+
this.consume("Pipe", "Expected \"|\" after label");
|
|
439
|
+
const body = this.parseBinding(allowComma);
|
|
440
|
+
return {
|
|
441
|
+
kind: "Label",
|
|
442
|
+
label: String(varToken.value),
|
|
443
|
+
body,
|
|
444
|
+
span: spanBetween(start.span, body.span)
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
let expr = allowComma ? this.parseComma() : this.parseAssignment();
|
|
347
448
|
while (this.match("As")) {
|
|
348
449
|
const varToken = this.consume("Variable", "Expected variable name after \"as\"");
|
|
349
450
|
this.consume("Pipe", "Expected \"|\" after variable binding");
|
|
350
|
-
const body = this.parseBinding();
|
|
451
|
+
const body = this.parseBinding(allowComma);
|
|
351
452
|
expr = {
|
|
352
453
|
kind: "As",
|
|
353
454
|
bind: expr,
|
|
@@ -359,9 +460,9 @@ var Parser = class {
|
|
|
359
460
|
return expr;
|
|
360
461
|
}
|
|
361
462
|
parseComma() {
|
|
362
|
-
let expr = this.
|
|
463
|
+
let expr = this.parseAssignment();
|
|
363
464
|
while (this.match("Comma")) {
|
|
364
|
-
const right = this.
|
|
465
|
+
const right = this.parseAssignment();
|
|
365
466
|
expr = {
|
|
366
467
|
kind: "Comma",
|
|
367
468
|
left: expr,
|
|
@@ -371,6 +472,49 @@ var Parser = class {
|
|
|
371
472
|
}
|
|
372
473
|
return expr;
|
|
373
474
|
}
|
|
475
|
+
parseAssignment() {
|
|
476
|
+
const expr = this.parsePipe();
|
|
477
|
+
if (this.match("Eq") || this.match("BarEq") || this.match("PlusEq") || this.match("MinusEq") || this.match("StarEq") || this.match("SlashEq") || this.match("PercentEq") || this.match("AltEq")) {
|
|
478
|
+
const opToken = this.previous();
|
|
479
|
+
const right = this.parseAssignment();
|
|
480
|
+
let op;
|
|
481
|
+
switch (opToken.kind) {
|
|
482
|
+
case "Eq":
|
|
483
|
+
op = "=";
|
|
484
|
+
break;
|
|
485
|
+
case "BarEq":
|
|
486
|
+
op = "|=";
|
|
487
|
+
break;
|
|
488
|
+
case "PlusEq":
|
|
489
|
+
op = "+=";
|
|
490
|
+
break;
|
|
491
|
+
case "MinusEq":
|
|
492
|
+
op = "-=";
|
|
493
|
+
break;
|
|
494
|
+
case "StarEq":
|
|
495
|
+
op = "*=";
|
|
496
|
+
break;
|
|
497
|
+
case "SlashEq":
|
|
498
|
+
op = "/=";
|
|
499
|
+
break;
|
|
500
|
+
case "PercentEq":
|
|
501
|
+
op = "%=";
|
|
502
|
+
break;
|
|
503
|
+
case "AltEq":
|
|
504
|
+
op = "//=";
|
|
505
|
+
break;
|
|
506
|
+
default: throw new Error(`Unknown assignment op: ${opToken.kind}`);
|
|
507
|
+
}
|
|
508
|
+
return {
|
|
509
|
+
kind: "Assignment",
|
|
510
|
+
op,
|
|
511
|
+
left: expr,
|
|
512
|
+
right,
|
|
513
|
+
span: spanBetween(expr.span, right.span)
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
return expr;
|
|
517
|
+
}
|
|
374
518
|
parsePipe() {
|
|
375
519
|
let expr = this.parsePipeOperand();
|
|
376
520
|
while (this.match("Pipe")) {
|
|
@@ -507,6 +651,19 @@ var Parser = class {
|
|
|
507
651
|
continue;
|
|
508
652
|
}
|
|
509
653
|
if (this.match("LBracket")) {
|
|
654
|
+
if (this.match("Colon")) {
|
|
655
|
+
let end = null;
|
|
656
|
+
if (!this.check("RBracket")) end = this.parsePipe();
|
|
657
|
+
const close = this.consume("RBracket", "Expected \"]\" after slice");
|
|
658
|
+
expr = {
|
|
659
|
+
kind: "Slice",
|
|
660
|
+
target: expr,
|
|
661
|
+
start: null,
|
|
662
|
+
end,
|
|
663
|
+
span: spanBetween(expr.span, close.span)
|
|
664
|
+
};
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
510
667
|
if (this.match("RBracket")) {
|
|
511
668
|
const close = this.previous();
|
|
512
669
|
expr = {
|
|
@@ -517,6 +674,19 @@ var Parser = class {
|
|
|
517
674
|
continue;
|
|
518
675
|
}
|
|
519
676
|
const index = this.parsePipe();
|
|
677
|
+
if (this.match("Colon")) {
|
|
678
|
+
let end = null;
|
|
679
|
+
if (!this.check("RBracket")) end = this.parsePipe();
|
|
680
|
+
const close = this.consume("RBracket", "Expected \"]\" after slice");
|
|
681
|
+
expr = {
|
|
682
|
+
kind: "Slice",
|
|
683
|
+
target: expr,
|
|
684
|
+
start: index,
|
|
685
|
+
end,
|
|
686
|
+
span: spanBetween(expr.span, close.span)
|
|
687
|
+
};
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
520
690
|
const closing = this.consume("RBracket", "Expected \"]\" after index expression");
|
|
521
691
|
expr = {
|
|
522
692
|
kind: "IndexAccess",
|
|
@@ -526,6 +696,19 @@ var Parser = class {
|
|
|
526
696
|
};
|
|
527
697
|
continue;
|
|
528
698
|
}
|
|
699
|
+
if (this.match("Question")) {
|
|
700
|
+
const op = this.previous();
|
|
701
|
+
expr = {
|
|
702
|
+
kind: "Try",
|
|
703
|
+
body: expr,
|
|
704
|
+
handler: {
|
|
705
|
+
kind: "Identity",
|
|
706
|
+
span: op.span
|
|
707
|
+
},
|
|
708
|
+
span: spanBetween(expr.span, op.span)
|
|
709
|
+
};
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
529
712
|
break;
|
|
530
713
|
}
|
|
531
714
|
return expr;
|
|
@@ -537,6 +720,7 @@ var Parser = class {
|
|
|
537
720
|
if (this.match("False")) return this.literalNode(false, this.previous().span);
|
|
538
721
|
if (this.match("Number")) return this.literalNode(Number(this.previous().value), this.previous().span);
|
|
539
722
|
if (this.match("String")) return this.literalNode(String(this.previous().value), this.previous().span);
|
|
723
|
+
if (this.match("StringStart")) return this.parseStringInterpolation(this.previous());
|
|
540
724
|
if (this.match("Variable")) {
|
|
541
725
|
const token = this.previous();
|
|
542
726
|
return {
|
|
@@ -549,7 +733,7 @@ var Parser = class {
|
|
|
549
733
|
if (this.match("If")) return this.parseIf(this.previous());
|
|
550
734
|
if (this.match("LParen")) {
|
|
551
735
|
const start = this.previous();
|
|
552
|
-
const expr = this.
|
|
736
|
+
const expr = this.parseDef();
|
|
553
737
|
const close = this.consume("RParen", "Expected \")\" after expression");
|
|
554
738
|
expr.span = spanBetween(start.span, close.span);
|
|
555
739
|
return expr;
|
|
@@ -563,8 +747,21 @@ var Parser = class {
|
|
|
563
747
|
kind: "Recurse",
|
|
564
748
|
span: this.previous().span
|
|
565
749
|
};
|
|
750
|
+
if (this.match("DotDot")) return {
|
|
751
|
+
kind: "Recurse",
|
|
752
|
+
span: this.previous().span
|
|
753
|
+
};
|
|
754
|
+
if (this.match("Break")) return this.parseBreak(this.previous());
|
|
566
755
|
throw this.error(this.peek(), "Unexpected token");
|
|
567
756
|
}
|
|
757
|
+
parseBreak(start) {
|
|
758
|
+
const varToken = this.consume("Variable", "Expected variable after \"break\"");
|
|
759
|
+
return {
|
|
760
|
+
kind: "Break",
|
|
761
|
+
label: String(varToken.value),
|
|
762
|
+
span: spanBetween(start.span, varToken.span)
|
|
763
|
+
};
|
|
764
|
+
}
|
|
568
765
|
parseReduce(start) {
|
|
569
766
|
const source = this.parsePipe();
|
|
570
767
|
this.consume("As", "Expected \"as\" after reduce source");
|
|
@@ -605,11 +802,11 @@ var Parser = class {
|
|
|
605
802
|
};
|
|
606
803
|
}
|
|
607
804
|
parseTry(start) {
|
|
608
|
-
const body = this.
|
|
805
|
+
const body = this.parseDef(true);
|
|
609
806
|
let handler;
|
|
610
807
|
let endSpan = body.span;
|
|
611
808
|
if (this.match("Catch")) {
|
|
612
|
-
handler = this.
|
|
809
|
+
handler = this.parseDef(true);
|
|
613
810
|
endSpan = handler.span;
|
|
614
811
|
}
|
|
615
812
|
return {
|
|
@@ -621,24 +818,24 @@ var Parser = class {
|
|
|
621
818
|
}
|
|
622
819
|
parseIf(start) {
|
|
623
820
|
const branches = [];
|
|
624
|
-
const firstCond = this.
|
|
821
|
+
const firstCond = this.parseDef(true);
|
|
625
822
|
this.consume("Then", "Expected \"then\" after condition");
|
|
626
|
-
const firstThen = this.
|
|
823
|
+
const firstThen = this.parseDef(true);
|
|
627
824
|
branches.push({
|
|
628
825
|
cond: firstCond,
|
|
629
826
|
then: firstThen
|
|
630
827
|
});
|
|
631
828
|
while (this.match("Elif")) {
|
|
632
|
-
const cond = this.
|
|
829
|
+
const cond = this.parseDef(true);
|
|
633
830
|
this.consume("Then", "Expected \"then\" after elif condition");
|
|
634
|
-
const thenBranch = this.
|
|
831
|
+
const thenBranch = this.parseDef(true);
|
|
635
832
|
branches.push({
|
|
636
833
|
cond,
|
|
637
834
|
then: thenBranch
|
|
638
835
|
});
|
|
639
836
|
}
|
|
640
837
|
this.consume("Else", "Expected \"else\" in if expression");
|
|
641
|
-
const elseBranch = this.
|
|
838
|
+
const elseBranch = this.parseDef(true);
|
|
642
839
|
const endToken = this.consume("End", "Expected \"end\" to close if expression");
|
|
643
840
|
return {
|
|
644
841
|
kind: "If",
|
|
@@ -649,26 +846,36 @@ var Parser = class {
|
|
|
649
846
|
}
|
|
650
847
|
parseArray(start) {
|
|
651
848
|
const items = [];
|
|
652
|
-
if (!this.
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
849
|
+
if (!this.match("RBracket")) {
|
|
850
|
+
do
|
|
851
|
+
items.push(this.parseDef(false));
|
|
852
|
+
while (this.match("Comma"));
|
|
853
|
+
this.consume("RBracket", "Expected \"]\" after array elements");
|
|
854
|
+
}
|
|
656
855
|
return {
|
|
657
856
|
kind: "Array",
|
|
658
857
|
items,
|
|
659
|
-
span: spanBetween(start.span,
|
|
858
|
+
span: spanBetween(start.span, this.previous().span)
|
|
660
859
|
};
|
|
661
860
|
}
|
|
662
861
|
parseObject(start) {
|
|
663
862
|
const entries = [];
|
|
664
|
-
if (!this.
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
863
|
+
if (!this.match("RBrace")) {
|
|
864
|
+
do {
|
|
865
|
+
const key = this.parseObjectKey();
|
|
866
|
+
this.consume("Colon", "Expected \":\" after object key");
|
|
867
|
+
const value = this.parseDef(false);
|
|
868
|
+
entries.push({
|
|
869
|
+
key,
|
|
870
|
+
value
|
|
871
|
+
});
|
|
872
|
+
} while (this.match("Comma"));
|
|
873
|
+
this.consume("RBrace", "Expected \"}\" after object entries");
|
|
874
|
+
}
|
|
668
875
|
return {
|
|
669
876
|
kind: "Object",
|
|
670
877
|
entries,
|
|
671
|
-
span: spanBetween(start.span,
|
|
878
|
+
span: spanBetween(start.span, this.previous().span)
|
|
672
879
|
};
|
|
673
880
|
}
|
|
674
881
|
parseObjectEntry() {
|
|
@@ -676,7 +883,7 @@ var Parser = class {
|
|
|
676
883
|
this.consume("Colon", "Expected \":\" after object key");
|
|
677
884
|
return {
|
|
678
885
|
key,
|
|
679
|
-
value: this.
|
|
886
|
+
value: this.parseDef(false)
|
|
680
887
|
};
|
|
681
888
|
}
|
|
682
889
|
parseObjectKey() {
|
|
@@ -740,6 +947,19 @@ var Parser = class {
|
|
|
740
947
|
continue;
|
|
741
948
|
}
|
|
742
949
|
if (this.match("LBracket")) {
|
|
950
|
+
if (this.match("Colon")) {
|
|
951
|
+
let end = null;
|
|
952
|
+
if (!this.check("RBracket")) end = this.parsePipe();
|
|
953
|
+
const close = this.consume("RBracket", "Expected \"]\" after slice");
|
|
954
|
+
expr = {
|
|
955
|
+
kind: "Slice",
|
|
956
|
+
target: expr,
|
|
957
|
+
start: null,
|
|
958
|
+
end,
|
|
959
|
+
span: spanBetween(expr.span, close.span)
|
|
960
|
+
};
|
|
961
|
+
continue;
|
|
962
|
+
}
|
|
743
963
|
if (this.match("RBracket")) {
|
|
744
964
|
const close = this.previous();
|
|
745
965
|
expr = {
|
|
@@ -750,6 +970,19 @@ var Parser = class {
|
|
|
750
970
|
continue;
|
|
751
971
|
}
|
|
752
972
|
const index = this.parsePipe();
|
|
973
|
+
if (this.match("Colon")) {
|
|
974
|
+
let end = null;
|
|
975
|
+
if (!this.check("RBracket")) end = this.parsePipe();
|
|
976
|
+
const close = this.consume("RBracket", "Expected \"]\" after slice");
|
|
977
|
+
expr = {
|
|
978
|
+
kind: "Slice",
|
|
979
|
+
target: expr,
|
|
980
|
+
start: index,
|
|
981
|
+
end,
|
|
982
|
+
span: spanBetween(expr.span, close.span)
|
|
983
|
+
};
|
|
984
|
+
continue;
|
|
985
|
+
}
|
|
753
986
|
const closing = this.consume("RBracket", "Expected \"]\" after index expression");
|
|
754
987
|
expr = {
|
|
755
988
|
kind: "IndexAccess",
|
|
@@ -759,6 +992,19 @@ var Parser = class {
|
|
|
759
992
|
};
|
|
760
993
|
continue;
|
|
761
994
|
}
|
|
995
|
+
if (this.match("Question")) {
|
|
996
|
+
const op = this.previous();
|
|
997
|
+
expr = {
|
|
998
|
+
kind: "Try",
|
|
999
|
+
body: expr,
|
|
1000
|
+
handler: {
|
|
1001
|
+
kind: "Identity",
|
|
1002
|
+
span: op.span
|
|
1003
|
+
},
|
|
1004
|
+
span: spanBetween(expr.span, op.span)
|
|
1005
|
+
};
|
|
1006
|
+
continue;
|
|
1007
|
+
}
|
|
762
1008
|
break;
|
|
763
1009
|
}
|
|
764
1010
|
return expr;
|
|
@@ -785,7 +1031,7 @@ var Parser = class {
|
|
|
785
1031
|
const args = [];
|
|
786
1032
|
if (this.check("RParen")) return args;
|
|
787
1033
|
do
|
|
788
|
-
args.push(this.
|
|
1034
|
+
args.push(this.parseDef(true));
|
|
789
1035
|
while (this.match("Semicolon"));
|
|
790
1036
|
return args;
|
|
791
1037
|
}
|
|
@@ -805,6 +1051,53 @@ var Parser = class {
|
|
|
805
1051
|
span: spanBetween(left.span, right.span)
|
|
806
1052
|
};
|
|
807
1053
|
}
|
|
1054
|
+
parseStringInterpolation(start) {
|
|
1055
|
+
const parts = [{
|
|
1056
|
+
kind: "Literal",
|
|
1057
|
+
value: String(start.value),
|
|
1058
|
+
span: start.span
|
|
1059
|
+
}];
|
|
1060
|
+
while (true) {
|
|
1061
|
+
const expr = this.parseDef();
|
|
1062
|
+
parts.push({
|
|
1063
|
+
kind: "Pipe",
|
|
1064
|
+
left: expr,
|
|
1065
|
+
right: {
|
|
1066
|
+
kind: "Call",
|
|
1067
|
+
name: "tostring",
|
|
1068
|
+
args: [],
|
|
1069
|
+
span: expr.span
|
|
1070
|
+
},
|
|
1071
|
+
span: expr.span
|
|
1072
|
+
});
|
|
1073
|
+
if (this.match("StringMiddle")) {
|
|
1074
|
+
const token = this.previous();
|
|
1075
|
+
parts.push({
|
|
1076
|
+
kind: "Literal",
|
|
1077
|
+
value: String(token.value),
|
|
1078
|
+
span: token.span
|
|
1079
|
+
});
|
|
1080
|
+
continue;
|
|
1081
|
+
}
|
|
1082
|
+
if (this.match("StringEnd")) {
|
|
1083
|
+
const token = this.previous();
|
|
1084
|
+
parts.push({
|
|
1085
|
+
kind: "Literal",
|
|
1086
|
+
value: String(token.value),
|
|
1087
|
+
span: token.span
|
|
1088
|
+
});
|
|
1089
|
+
break;
|
|
1090
|
+
}
|
|
1091
|
+
throw this.error(this.peek(), "Expected closing paren of interpolation or continuation");
|
|
1092
|
+
}
|
|
1093
|
+
return parts.reduce((acc, curr) => ({
|
|
1094
|
+
kind: "Binary",
|
|
1095
|
+
op: "+",
|
|
1096
|
+
left: acc,
|
|
1097
|
+
right: curr,
|
|
1098
|
+
span: spanBetween(acc.span, curr.span)
|
|
1099
|
+
}));
|
|
1100
|
+
}
|
|
808
1101
|
match(kind) {
|
|
809
1102
|
if (this.check(kind)) {
|
|
810
1103
|
this.advance();
|
|
@@ -854,6 +1147,17 @@ const spanBetween = (a, b) => ({
|
|
|
854
1147
|
end: Math.max(a.end, b.end)
|
|
855
1148
|
});
|
|
856
1149
|
|
|
1150
|
+
//#endregion
|
|
1151
|
+
//#region src/builtins/registry.ts
|
|
1152
|
+
const builtins = {};
|
|
1153
|
+
const registerBuiltin = (spec) => {
|
|
1154
|
+
if (!builtins[spec.name]) builtins[spec.name] = [];
|
|
1155
|
+
builtins[spec.name].push(spec);
|
|
1156
|
+
};
|
|
1157
|
+
const registerBuiltins = (specs) => {
|
|
1158
|
+
for (const spec of specs) registerBuiltin(spec);
|
|
1159
|
+
};
|
|
1160
|
+
|
|
857
1161
|
//#endregion
|
|
858
1162
|
//#region src/value.ts
|
|
859
1163
|
/**
|
|
@@ -880,7 +1184,7 @@ const valueEquals = (a, b) => {
|
|
|
880
1184
|
for (let i = 0; i < a.length; i += 1) if (!valueEquals(a[i], b[i])) return false;
|
|
881
1185
|
return true;
|
|
882
1186
|
}
|
|
883
|
-
if (isPlainObject
|
|
1187
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
884
1188
|
const aKeys = Object.keys(a).sort();
|
|
885
1189
|
const bKeys = Object.keys(b).sort();
|
|
886
1190
|
if (aKeys.length !== bKeys.length) return false;
|
|
@@ -917,7 +1221,7 @@ const compareValues = (a, b) => {
|
|
|
917
1221
|
}
|
|
918
1222
|
return a.length < b.length ? -1 : 1;
|
|
919
1223
|
}
|
|
920
|
-
if (isPlainObject
|
|
1224
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
921
1225
|
const aKeys = Object.keys(a).sort();
|
|
922
1226
|
const bKeys = Object.keys(b).sort();
|
|
923
1227
|
const len = Math.min(aKeys.length, bKeys.length);
|
|
@@ -951,7 +1255,14 @@ const typeRank = (value) => {
|
|
|
951
1255
|
* @param value - The value to check.
|
|
952
1256
|
* @returns `true` if `value` is a non-null object (and not an array).
|
|
953
1257
|
*/
|
|
954
|
-
const isPlainObject
|
|
1258
|
+
const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1259
|
+
/**
|
|
1260
|
+
* Checks if a value is a JSON array.
|
|
1261
|
+
*
|
|
1262
|
+
* @param value - The value to check.
|
|
1263
|
+
* @returns `true` if `value` is an array.
|
|
1264
|
+
*/
|
|
1265
|
+
const isValueArray = (value) => Array.isArray(value);
|
|
955
1266
|
/**
|
|
956
1267
|
* Returns the type name of a value as a string (e.g., "null", "boolean", "number", "string", "array", "object").
|
|
957
1268
|
* This corresponds to the output of the `type` builtin.
|
|
@@ -959,7 +1270,7 @@ const isPlainObject$1 = (value) => typeof value === "object" && value !== null &
|
|
|
959
1270
|
* @param value - The value to inspect.
|
|
960
1271
|
* @returns The lower-case type name.
|
|
961
1272
|
*/
|
|
962
|
-
const describeType
|
|
1273
|
+
const describeType = (value) => {
|
|
963
1274
|
if (value === null) return "null";
|
|
964
1275
|
if (typeof value === "boolean") return "boolean";
|
|
965
1276
|
if (typeof value === "number") return "number";
|
|
@@ -969,7 +1280,7 @@ const describeType$1 = (value) => {
|
|
|
969
1280
|
};
|
|
970
1281
|
|
|
971
1282
|
//#endregion
|
|
972
|
-
//#region src/builtins.ts
|
|
1283
|
+
//#region src/builtins/utils.ts
|
|
973
1284
|
const emit$1 = (value, span, tracker) => {
|
|
974
1285
|
tracker.emit(span);
|
|
975
1286
|
return value;
|
|
@@ -986,22 +1297,25 @@ const stableStringify = (value) => {
|
|
|
986
1297
|
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
987
1298
|
return `{${Object.keys(value).sort().map((k) => `${JSON.stringify(k)}:${stableStringify(value[k])}`).join(",")}}`;
|
|
988
1299
|
};
|
|
989
|
-
|
|
990
|
-
|
|
1300
|
+
|
|
1301
|
+
//#endregion
|
|
1302
|
+
//#region src/builtins/std.ts
|
|
1303
|
+
const stdBuiltins = [
|
|
1304
|
+
{
|
|
991
1305
|
name: "type",
|
|
992
1306
|
arity: 0,
|
|
993
1307
|
apply: function* (input, _args, _env, tracker, _eval, span) {
|
|
994
|
-
yield emit$1(describeType
|
|
1308
|
+
yield emit$1(describeType(input), span, tracker);
|
|
995
1309
|
}
|
|
996
1310
|
},
|
|
997
|
-
|
|
1311
|
+
{
|
|
998
1312
|
name: "tostring",
|
|
999
1313
|
arity: 0,
|
|
1000
1314
|
apply: function* (input, _args, _env, tracker, _eval, span) {
|
|
1001
1315
|
yield emit$1(stableStringify(input), span, tracker);
|
|
1002
1316
|
}
|
|
1003
1317
|
},
|
|
1004
|
-
|
|
1318
|
+
{
|
|
1005
1319
|
name: "tonumber",
|
|
1006
1320
|
arity: 0,
|
|
1007
1321
|
apply: function* (input, _args, _env, tracker, _eval, span) {
|
|
@@ -1015,20 +1329,49 @@ const builtins = {
|
|
|
1015
1329
|
yield emit$1(num, span, tracker);
|
|
1016
1330
|
return;
|
|
1017
1331
|
}
|
|
1018
|
-
throw new RuntimeError(`Cannot convert ${describeType
|
|
1332
|
+
throw new RuntimeError(`Cannot convert ${describeType(input)} to number`, span);
|
|
1019
1333
|
}
|
|
1020
1334
|
},
|
|
1021
|
-
|
|
1335
|
+
{
|
|
1022
1336
|
name: "length",
|
|
1023
1337
|
arity: 0,
|
|
1024
1338
|
apply: function* (input, _args, _env, tracker, _eval, span) {
|
|
1025
1339
|
if (typeof input === "string") yield emit$1(Array.from(input).length, span, tracker);
|
|
1026
1340
|
else if (Array.isArray(input)) yield emit$1(input.length, span, tracker);
|
|
1027
1341
|
else if (input !== null && typeof input === "object") yield emit$1(Object.keys(input).length, span, tracker);
|
|
1028
|
-
else throw new RuntimeError(`Cannot take length of ${describeType
|
|
1342
|
+
else throw new RuntimeError(`Cannot take length of ${describeType(input)}`, span);
|
|
1029
1343
|
}
|
|
1030
1344
|
},
|
|
1031
|
-
|
|
1345
|
+
{
|
|
1346
|
+
name: "empty",
|
|
1347
|
+
arity: 0,
|
|
1348
|
+
apply: function* () {}
|
|
1349
|
+
}
|
|
1350
|
+
];
|
|
1351
|
+
|
|
1352
|
+
//#endregion
|
|
1353
|
+
//#region src/builtins/errors.ts
|
|
1354
|
+
const errorBuiltins = [{
|
|
1355
|
+
name: "error",
|
|
1356
|
+
arity: 1,
|
|
1357
|
+
apply: function* (input, args, env, tracker, evaluate$1, span) {
|
|
1358
|
+
for (const msg of evaluate$1(args[0], input, env, tracker)) throw new RuntimeError(typeof msg === "string" ? msg : stableStringify(msg), span);
|
|
1359
|
+
}
|
|
1360
|
+
}];
|
|
1361
|
+
|
|
1362
|
+
//#endregion
|
|
1363
|
+
//#region src/builtins/collections.ts
|
|
1364
|
+
function sortStable(arr, compare$1) {
|
|
1365
|
+
return arr.map((item, index) => ({
|
|
1366
|
+
item,
|
|
1367
|
+
index
|
|
1368
|
+
})).sort((a, b) => {
|
|
1369
|
+
const cmp = compare$1(a.item, b.item);
|
|
1370
|
+
return cmp !== 0 ? cmp : a.index - b.index;
|
|
1371
|
+
}).map((p) => p.item);
|
|
1372
|
+
}
|
|
1373
|
+
const collectionBuiltins = [
|
|
1374
|
+
{
|
|
1032
1375
|
name: "keys",
|
|
1033
1376
|
arity: 0,
|
|
1034
1377
|
apply: function* (input, _args, _env, tracker, _eval, span) {
|
|
@@ -1037,7 +1380,7 @@ const builtins = {
|
|
|
1037
1380
|
else throw new RuntimeError(`keys expects an array or object`, span);
|
|
1038
1381
|
}
|
|
1039
1382
|
},
|
|
1040
|
-
|
|
1383
|
+
{
|
|
1041
1384
|
name: "has",
|
|
1042
1385
|
arity: 1,
|
|
1043
1386
|
apply: function* (input, args, env, tracker, evaluate$1, span) {
|
|
@@ -1054,14 +1397,7 @@ const builtins = {
|
|
|
1054
1397
|
} else throw new RuntimeError(`has() expects an array or object input`, span);
|
|
1055
1398
|
}
|
|
1056
1399
|
},
|
|
1057
|
-
|
|
1058
|
-
name: "error",
|
|
1059
|
-
arity: 1,
|
|
1060
|
-
apply: function* (input, args, env, tracker, evaluate$1, span) {
|
|
1061
|
-
for (const msg of evaluate$1(args[0], input, env, tracker)) throw new RuntimeError(typeof msg === "string" ? msg : stableStringify(msg), span);
|
|
1062
|
-
}
|
|
1063
|
-
},
|
|
1064
|
-
map: {
|
|
1400
|
+
{
|
|
1065
1401
|
name: "map",
|
|
1066
1402
|
arity: 1,
|
|
1067
1403
|
apply: function* (input, args, env, tracker, evaluate$1, span) {
|
|
@@ -1075,7 +1411,7 @@ const builtins = {
|
|
|
1075
1411
|
yield emit$1(result, span, tracker);
|
|
1076
1412
|
}
|
|
1077
1413
|
},
|
|
1078
|
-
|
|
1414
|
+
{
|
|
1079
1415
|
name: "select",
|
|
1080
1416
|
arity: 1,
|
|
1081
1417
|
apply: function* (input, args, env, tracker, evaluate$1, span) {
|
|
@@ -1086,7 +1422,7 @@ const builtins = {
|
|
|
1086
1422
|
}
|
|
1087
1423
|
}
|
|
1088
1424
|
},
|
|
1089
|
-
|
|
1425
|
+
{
|
|
1090
1426
|
name: "sort",
|
|
1091
1427
|
arity: 0,
|
|
1092
1428
|
apply: function* (input, _args, _env, tracker, _eval, span) {
|
|
@@ -1096,7 +1432,7 @@ const builtins = {
|
|
|
1096
1432
|
yield emit$1(sorted, span, tracker);
|
|
1097
1433
|
}
|
|
1098
1434
|
},
|
|
1099
|
-
|
|
1435
|
+
{
|
|
1100
1436
|
name: "sort_by",
|
|
1101
1437
|
arity: 1,
|
|
1102
1438
|
apply: function* (input, args, env, tracker, evaluate$1, span) {
|
|
@@ -1115,7 +1451,7 @@ const builtins = {
|
|
|
1115
1451
|
yield emit$1(sortStable(pairs, (a, b) => compareValues(a.key, b.key)).map((p) => p.val), span, tracker);
|
|
1116
1452
|
}
|
|
1117
1453
|
},
|
|
1118
|
-
|
|
1454
|
+
{
|
|
1119
1455
|
name: "unique",
|
|
1120
1456
|
arity: 0,
|
|
1121
1457
|
apply: function* (input, _args, _env, tracker, _eval, span) {
|
|
@@ -1132,7 +1468,7 @@ const builtins = {
|
|
|
1132
1468
|
yield emit$1(result, span, tracker);
|
|
1133
1469
|
}
|
|
1134
1470
|
},
|
|
1135
|
-
|
|
1471
|
+
{
|
|
1136
1472
|
name: "unique_by",
|
|
1137
1473
|
arity: 1,
|
|
1138
1474
|
apply: function* (input, args, env, tracker, evaluate$1, span) {
|
|
@@ -1153,7 +1489,7 @@ const builtins = {
|
|
|
1153
1489
|
yield emit$1(result, span, tracker);
|
|
1154
1490
|
}
|
|
1155
1491
|
},
|
|
1156
|
-
|
|
1492
|
+
{
|
|
1157
1493
|
name: "to_entries",
|
|
1158
1494
|
arity: 0,
|
|
1159
1495
|
apply: function* (input, _args, _env, tracker, _eval, span) {
|
|
@@ -1168,7 +1504,7 @@ const builtins = {
|
|
|
1168
1504
|
else throw new RuntimeError("to_entries expects array or object", span);
|
|
1169
1505
|
}
|
|
1170
1506
|
},
|
|
1171
|
-
|
|
1507
|
+
{
|
|
1172
1508
|
name: "from_entries",
|
|
1173
1509
|
arity: 0,
|
|
1174
1510
|
apply: function* (input, _args, _env, tracker, _eval, span) {
|
|
@@ -1186,7 +1522,7 @@ const builtins = {
|
|
|
1186
1522
|
yield emit$1(result, span, tracker);
|
|
1187
1523
|
}
|
|
1188
1524
|
},
|
|
1189
|
-
|
|
1525
|
+
{
|
|
1190
1526
|
name: "with_entries",
|
|
1191
1527
|
arity: 1,
|
|
1192
1528
|
apply: function* (input, args, env, tracker, evaluate$1, span) {
|
|
@@ -1217,8 +1553,30 @@ const builtins = {
|
|
|
1217
1553
|
}
|
|
1218
1554
|
yield emit$1(result, span, tracker);
|
|
1219
1555
|
}
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1556
|
+
}
|
|
1557
|
+
];
|
|
1558
|
+
|
|
1559
|
+
//#endregion
|
|
1560
|
+
//#region src/builtins/strings.ts
|
|
1561
|
+
const checkContains = (a, b) => {
|
|
1562
|
+
if (a === b) return true;
|
|
1563
|
+
if (typeof a !== typeof b) return false;
|
|
1564
|
+
if (typeof a === "string" && typeof b === "string") return a.includes(b);
|
|
1565
|
+
if (Array.isArray(a) && Array.isArray(b)) return b.every((bItem) => a.some((aItem) => checkContains(aItem, bItem)));
|
|
1566
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
1567
|
+
const keys = Object.keys(b);
|
|
1568
|
+
for (const key of keys) {
|
|
1569
|
+
if (!Object.prototype.hasOwnProperty.call(a, key)) return false;
|
|
1570
|
+
const valA = a[key];
|
|
1571
|
+
const valB = b[key];
|
|
1572
|
+
if (!checkContains(valA, valB)) return false;
|
|
1573
|
+
}
|
|
1574
|
+
return true;
|
|
1575
|
+
}
|
|
1576
|
+
return valueEquals(a, b);
|
|
1577
|
+
};
|
|
1578
|
+
const stringBuiltins = [
|
|
1579
|
+
{
|
|
1222
1580
|
name: "split",
|
|
1223
1581
|
arity: 1,
|
|
1224
1582
|
apply: function* (input, args, env, tracker, evaluate$1, span) {
|
|
@@ -1230,7 +1588,7 @@ const builtins = {
|
|
|
1230
1588
|
}
|
|
1231
1589
|
}
|
|
1232
1590
|
},
|
|
1233
|
-
|
|
1591
|
+
{
|
|
1234
1592
|
name: "join",
|
|
1235
1593
|
arity: 1,
|
|
1236
1594
|
apply: function* (input, args, env, tracker, evaluate$1, span) {
|
|
@@ -1240,14 +1598,14 @@ const builtins = {
|
|
|
1240
1598
|
if (typeof sep !== "string") throw new RuntimeError("join separator must be a string", span);
|
|
1241
1599
|
const parts = [];
|
|
1242
1600
|
for (const item of input) {
|
|
1243
|
-
if (typeof item !== "string") throw new RuntimeError(`join expects strings, but got ${describeType
|
|
1601
|
+
if (typeof item !== "string") throw new RuntimeError(`join expects strings, but got ${describeType(item)}`, span);
|
|
1244
1602
|
parts.push(item);
|
|
1245
1603
|
}
|
|
1246
1604
|
yield emit$1(parts.join(sep), span, tracker);
|
|
1247
1605
|
}
|
|
1248
1606
|
}
|
|
1249
1607
|
},
|
|
1250
|
-
|
|
1608
|
+
{
|
|
1251
1609
|
name: "startswith",
|
|
1252
1610
|
arity: 1,
|
|
1253
1611
|
apply: function* (input, args, env, tracker, evaluate$1, span) {
|
|
@@ -1259,7 +1617,7 @@ const builtins = {
|
|
|
1259
1617
|
}
|
|
1260
1618
|
}
|
|
1261
1619
|
},
|
|
1262
|
-
|
|
1620
|
+
{
|
|
1263
1621
|
name: "endswith",
|
|
1264
1622
|
arity: 1,
|
|
1265
1623
|
apply: function* (input, args, env, tracker, evaluate$1, span) {
|
|
@@ -1271,7 +1629,7 @@ const builtins = {
|
|
|
1271
1629
|
}
|
|
1272
1630
|
}
|
|
1273
1631
|
},
|
|
1274
|
-
|
|
1632
|
+
{
|
|
1275
1633
|
name: "contains",
|
|
1276
1634
|
arity: 1,
|
|
1277
1635
|
apply: function* (input, args, env, tracker, evaluate$1, span) {
|
|
@@ -1279,72 +1637,189 @@ const builtins = {
|
|
|
1279
1637
|
for (const b of bGen) yield emit$1(checkContains(input, b), span, tracker);
|
|
1280
1638
|
}
|
|
1281
1639
|
},
|
|
1282
|
-
|
|
1283
|
-
name: "
|
|
1284
|
-
arity:
|
|
1285
|
-
apply: function* (input,
|
|
1286
|
-
|
|
1640
|
+
{
|
|
1641
|
+
name: "index",
|
|
1642
|
+
arity: 1,
|
|
1643
|
+
apply: function* (input, args, env, tracker, evaluate$1, span) {
|
|
1644
|
+
const searchGen = evaluate$1(args[0], input, env, tracker);
|
|
1645
|
+
for (const search of searchGen) if (Array.isArray(input)) {
|
|
1646
|
+
let foundIndex = null;
|
|
1647
|
+
for (let i = 0; i < input.length; i++) {
|
|
1648
|
+
const val = input[i];
|
|
1649
|
+
if (val !== void 0 && valueEquals(val, search)) {
|
|
1650
|
+
foundIndex = i;
|
|
1651
|
+
break;
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
yield emit$1(foundIndex, span, tracker);
|
|
1655
|
+
} else if (typeof input === "string") {
|
|
1656
|
+
if (typeof search !== "string") throw new RuntimeError("index expects string search when input is string", span);
|
|
1657
|
+
const idx = input.indexOf(search);
|
|
1658
|
+
yield emit$1(idx === -1 ? null : idx, span, tracker);
|
|
1659
|
+
} else if (input === null) yield emit$1(null, span, tracker);
|
|
1660
|
+
else throw new RuntimeError("index expects string or array", span);
|
|
1287
1661
|
}
|
|
1288
1662
|
},
|
|
1289
|
-
|
|
1290
|
-
name: "
|
|
1663
|
+
{
|
|
1664
|
+
name: "rindex",
|
|
1291
1665
|
arity: 1,
|
|
1292
1666
|
apply: function* (input, args, env, tracker, evaluate$1, span) {
|
|
1293
|
-
|
|
1667
|
+
const searchGen = evaluate$1(args[0], input, env, tracker);
|
|
1668
|
+
for (const search of searchGen) if (Array.isArray(input)) {
|
|
1669
|
+
let foundIndex = null;
|
|
1670
|
+
for (let i = input.length - 1; i >= 0; i--) {
|
|
1671
|
+
const val = input[i];
|
|
1672
|
+
if (val !== void 0 && valueEquals(val, search)) {
|
|
1673
|
+
foundIndex = i;
|
|
1674
|
+
break;
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
yield emit$1(foundIndex, span, tracker);
|
|
1678
|
+
} else if (typeof input === "string") {
|
|
1679
|
+
if (typeof search !== "string") throw new RuntimeError("rindex expects string search when input is string", span);
|
|
1680
|
+
const idx = input.lastIndexOf(search);
|
|
1681
|
+
yield emit$1(idx === -1 ? null : idx, span, tracker);
|
|
1682
|
+
} else if (input === null) yield emit$1(null, span, tracker);
|
|
1683
|
+
else throw new RuntimeError("rindex expects string or array", span);
|
|
1294
1684
|
}
|
|
1295
1685
|
},
|
|
1296
|
-
|
|
1297
|
-
name: "
|
|
1298
|
-
arity:
|
|
1686
|
+
{
|
|
1687
|
+
name: "indices",
|
|
1688
|
+
arity: 1,
|
|
1299
1689
|
apply: function* (input, args, env, tracker, evaluate$1, span) {
|
|
1300
|
-
const
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1690
|
+
const searchGen = evaluate$1(args[0], input, env, tracker);
|
|
1691
|
+
for (const search of searchGen) {
|
|
1692
|
+
const indices = [];
|
|
1693
|
+
if (Array.isArray(input)) for (let i = 0; i < input.length; i++) {
|
|
1694
|
+
const val = input[i];
|
|
1695
|
+
if (val !== void 0 && valueEquals(val, search)) indices.push(i);
|
|
1696
|
+
}
|
|
1697
|
+
else if (typeof input === "string") {
|
|
1698
|
+
if (typeof search !== "string") throw new RuntimeError("indices expects string", span);
|
|
1699
|
+
if (search.length === 0) {} else {
|
|
1700
|
+
let pos = 0;
|
|
1701
|
+
while (pos < input.length) {
|
|
1702
|
+
const idx = input.indexOf(search, pos);
|
|
1703
|
+
if (idx === -1) break;
|
|
1704
|
+
indices.push(idx);
|
|
1705
|
+
pos = idx + 1;
|
|
1706
|
+
pos = idx + search.length;
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
} else {
|
|
1710
|
+
if (input === null) {
|
|
1711
|
+
yield emit$1(null, span, tracker);
|
|
1712
|
+
continue;
|
|
1713
|
+
}
|
|
1714
|
+
throw new RuntimeError("indices expects string or array", span);
|
|
1715
|
+
}
|
|
1716
|
+
yield emit$1(indices, span, tracker);
|
|
1305
1717
|
}
|
|
1306
1718
|
}
|
|
1307
1719
|
},
|
|
1308
|
-
|
|
1309
|
-
name: "
|
|
1310
|
-
arity:
|
|
1311
|
-
apply: function* (input,
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1720
|
+
{
|
|
1721
|
+
name: "explode",
|
|
1722
|
+
arity: 0,
|
|
1723
|
+
apply: function* (input, _args, _env, tracker, _eval, span) {
|
|
1724
|
+
if (typeof input !== "string") throw new RuntimeError("explode expects string", span);
|
|
1725
|
+
yield emit$1(Array.from(input).map((c) => c.codePointAt(0)), span, tracker);
|
|
1726
|
+
}
|
|
1727
|
+
},
|
|
1728
|
+
{
|
|
1729
|
+
name: "implode",
|
|
1730
|
+
arity: 0,
|
|
1731
|
+
apply: function* (input, _args, _env, tracker, _eval, span) {
|
|
1732
|
+
if (!Array.isArray(input)) throw new RuntimeError("implode expects array", span);
|
|
1733
|
+
const chars = [];
|
|
1734
|
+
for (const item of input) {
|
|
1735
|
+
if (typeof item !== "number") throw new RuntimeError("implode item must be number", span);
|
|
1736
|
+
chars.push(String.fromCodePoint(item));
|
|
1315
1737
|
}
|
|
1738
|
+
yield emit$1(chars.join(""), span, tracker);
|
|
1316
1739
|
}
|
|
1317
1740
|
}
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1741
|
+
];
|
|
1742
|
+
|
|
1743
|
+
//#endregion
|
|
1744
|
+
//#region src/eval/path_eval.ts
|
|
1745
|
+
/**
|
|
1746
|
+
* Resolves properties paths for assignment or `path()` built-in.
|
|
1747
|
+
* Returns an array of paths (arrays of strings/numbers) for valid locations.
|
|
1748
|
+
*
|
|
1749
|
+
* @param node - The AST node describing trailing field access/indexing.
|
|
1750
|
+
* @param input - The current input value.
|
|
1751
|
+
* @param env - The environment.
|
|
1752
|
+
* @param tracker - Limits tracker.
|
|
1753
|
+
* @param evaluate - Recursive evaluator.
|
|
1754
|
+
*/
|
|
1755
|
+
const evaluatePath = function* (node, input, env, tracker, evaluate$1) {
|
|
1756
|
+
switch (node.kind) {
|
|
1757
|
+
case "Identity":
|
|
1758
|
+
yield [];
|
|
1759
|
+
return;
|
|
1760
|
+
case "FieldAccess":
|
|
1761
|
+
for (const parent of evaluatePath(node.target, input, env, tracker, evaluate$1)) yield [...parent, node.field];
|
|
1762
|
+
return;
|
|
1763
|
+
case "IndexAccess": {
|
|
1764
|
+
const parentPaths = Array.from(evaluatePath(node.target, input, env, tracker, evaluate$1));
|
|
1765
|
+
const output = evaluate$1(node.index, input, env, tracker);
|
|
1766
|
+
for (const keyVal of output) {
|
|
1767
|
+
let key;
|
|
1768
|
+
if (typeof keyVal === "string") key = keyVal;
|
|
1769
|
+
else if (typeof keyVal === "number" && Number.isInteger(keyVal)) key = keyVal;
|
|
1770
|
+
else throw new RuntimeError("Path index must be string or integer", node.span);
|
|
1771
|
+
for (const parent of parentPaths) yield [...parent, key];
|
|
1772
|
+
}
|
|
1773
|
+
return;
|
|
1331
1774
|
}
|
|
1332
|
-
|
|
1775
|
+
case "Pipe": {
|
|
1776
|
+
const leftPaths = Array.from(evaluatePath(node.left, input, env, tracker, evaluate$1));
|
|
1777
|
+
for (const p of leftPaths) {
|
|
1778
|
+
const val = getPath(input, p) ?? null;
|
|
1779
|
+
for (const q of evaluatePath(node.right, val, env, tracker, evaluate$1)) yield [...p, ...q];
|
|
1780
|
+
}
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
1783
|
+
case "Comma":
|
|
1784
|
+
yield* evaluatePath(node.left, input, env, tracker, evaluate$1);
|
|
1785
|
+
yield* evaluatePath(node.right, input, env, tracker, evaluate$1);
|
|
1786
|
+
return;
|
|
1787
|
+
case "Iterate": {
|
|
1788
|
+
const parentPaths = Array.from(evaluatePath(node.target, input, env, tracker, evaluate$1));
|
|
1789
|
+
for (const p of parentPaths) {
|
|
1790
|
+
const val = getPath(input, p);
|
|
1791
|
+
if (Array.isArray(val)) for (let i = 0; i < val.length; i++) yield [...p, i];
|
|
1792
|
+
else if (val !== null && typeof val === "object") for (const key of Object.keys(val)) yield [...p, key];
|
|
1793
|
+
else {
|
|
1794
|
+
if (val === null) continue;
|
|
1795
|
+
throw new RuntimeError(`Cannot iterate over ${typeof val}`, node.span);
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
return;
|
|
1799
|
+
}
|
|
1800
|
+
case "Call":
|
|
1801
|
+
if (node.name === "select") {
|
|
1802
|
+
const conds = evaluate$1(node.args[0], input, env, tracker);
|
|
1803
|
+
let matched = false;
|
|
1804
|
+
for (const c of conds) if (c !== null && c !== false) matched = true;
|
|
1805
|
+
if (matched) yield [];
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
throw new RuntimeError(`Function ${node.name} not supported in path expression`, node.span);
|
|
1809
|
+
default: throw new RuntimeError("Invalid path expression", node.span);
|
|
1333
1810
|
}
|
|
1334
|
-
return valueEquals(a, b);
|
|
1335
1811
|
};
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
*/
|
|
1812
|
+
|
|
1813
|
+
//#endregion
|
|
1814
|
+
//#region src/builtins/paths.ts
|
|
1340
1815
|
function* traversePaths(root, currentPath, span, tracker) {
|
|
1341
1816
|
tracker.step(span);
|
|
1342
|
-
if (root === null || typeof root !== "object" || Array.isArray(root) && root.length === 0 || isPlainObject
|
|
1817
|
+
if (root === null || typeof root !== "object" || Array.isArray(root) && root.length === 0 || isPlainObject(root) && Object.keys(root).length === 0) {
|
|
1343
1818
|
yield emit$1([...currentPath], span, tracker);
|
|
1344
1819
|
return;
|
|
1345
1820
|
}
|
|
1346
1821
|
if (Array.isArray(root)) for (let i = 0; i < root.length; i++) yield* traversePaths(root[i], [...currentPath, i], span, tracker);
|
|
1347
|
-
else if (isPlainObject
|
|
1822
|
+
else if (isPlainObject(root)) {
|
|
1348
1823
|
const keys = Object.keys(root).sort();
|
|
1349
1824
|
for (const key of keys) yield* traversePaths(root[key], [...currentPath, key], span, tracker);
|
|
1350
1825
|
}
|
|
@@ -1361,7 +1836,7 @@ const getPath = (root, path) => {
|
|
|
1361
1836
|
let curr = root;
|
|
1362
1837
|
for (const part of path) {
|
|
1363
1838
|
if (curr === null) return void 0;
|
|
1364
|
-
if (typeof part === "string" && isPlainObject
|
|
1839
|
+
if (typeof part === "string" && isPlainObject(curr)) {
|
|
1365
1840
|
if (!Object.prototype.hasOwnProperty.call(curr, part)) return void 0;
|
|
1366
1841
|
curr = curr[part];
|
|
1367
1842
|
} else if (typeof part === "number" && Array.isArray(curr)) {
|
|
@@ -1371,18 +1846,14 @@ const getPath = (root, path) => {
|
|
|
1371
1846
|
}
|
|
1372
1847
|
return curr;
|
|
1373
1848
|
};
|
|
1374
|
-
/**
|
|
1375
|
-
* Immutably updates a value at a given path.
|
|
1376
|
-
* Creates intermediate objects/arrays as needed.
|
|
1377
|
-
*/
|
|
1378
1849
|
const updatePath = (root, path, updateFn, span, depth = 0) => {
|
|
1379
1850
|
if (path.length === 0) return updateFn(root);
|
|
1380
1851
|
const [head, ...tail] = path;
|
|
1381
1852
|
if (typeof head === "string") {
|
|
1382
1853
|
let obj = {};
|
|
1383
|
-
if (isPlainObject
|
|
1854
|
+
if (isPlainObject(root)) obj = { ...root };
|
|
1384
1855
|
else if (root === null) obj = {};
|
|
1385
|
-
else throw new RuntimeError(`Cannot index ${describeType
|
|
1856
|
+
else throw new RuntimeError(`Cannot index ${describeType(root)} with string "${head}"`, span);
|
|
1386
1857
|
const newVal = updatePath((Object.prototype.hasOwnProperty.call(obj, head) ? obj[head] : void 0) ?? null, tail, updateFn, span, depth + 1);
|
|
1387
1858
|
if (newVal === void 0) delete obj[head];
|
|
1388
1859
|
else obj[head] = newVal;
|
|
@@ -1392,7 +1863,7 @@ const updatePath = (root, path, updateFn, span, depth = 0) => {
|
|
|
1392
1863
|
let arr = [];
|
|
1393
1864
|
if (Array.isArray(root)) arr = [...root];
|
|
1394
1865
|
else if (root === null) arr = [];
|
|
1395
|
-
else throw new RuntimeError(`Cannot index ${describeType
|
|
1866
|
+
else throw new RuntimeError(`Cannot index ${describeType(root)} with number ${head}`, span);
|
|
1396
1867
|
const idx = head < 0 ? arr.length + head : head;
|
|
1397
1868
|
if (idx < 0) throw new RuntimeError("Invalid negative index", span);
|
|
1398
1869
|
const newVal = updatePath((idx < arr.length ? arr[idx] : null) ?? null, tail, updateFn, span, depth + 1);
|
|
@@ -1410,7 +1881,7 @@ const updatePath = (root, path, updateFn, span, depth = 0) => {
|
|
|
1410
1881
|
};
|
|
1411
1882
|
const deletePaths = (root, paths, span) => {
|
|
1412
1883
|
if (paths.some((p) => p.length === 0)) return null;
|
|
1413
|
-
if (isPlainObject
|
|
1884
|
+
if (isPlainObject(root)) {
|
|
1414
1885
|
const result = { ...root };
|
|
1415
1886
|
const relevantPaths = paths.filter((p) => p.length > 0 && typeof p[0] === "string");
|
|
1416
1887
|
const byKey = {};
|
|
@@ -1447,17 +1918,403 @@ const deletePaths = (root, paths, span) => {
|
|
|
1447
1918
|
}
|
|
1448
1919
|
return root;
|
|
1449
1920
|
};
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
}
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1921
|
+
const pathBuiltins = [
|
|
1922
|
+
{
|
|
1923
|
+
name: "paths",
|
|
1924
|
+
arity: 0,
|
|
1925
|
+
apply: function* (input, _args, _env, tracker, _eval, span) {
|
|
1926
|
+
yield* traversePaths(input, [], span, tracker);
|
|
1927
|
+
}
|
|
1928
|
+
},
|
|
1929
|
+
{
|
|
1930
|
+
name: "getpath",
|
|
1931
|
+
arity: 1,
|
|
1932
|
+
apply: function* (input, args, env, tracker, evaluate$1, span) {
|
|
1933
|
+
for (const pathVal of evaluate$1(args[0], input, env, tracker)) yield emit$1(getPath(input, ensurePath(pathVal, span)) ?? null, span, tracker);
|
|
1934
|
+
}
|
|
1935
|
+
},
|
|
1936
|
+
{
|
|
1937
|
+
name: "setpath",
|
|
1938
|
+
arity: 2,
|
|
1939
|
+
apply: function* (input, args, env, tracker, evaluate$1, span) {
|
|
1940
|
+
const paths = Array.from(evaluate$1(args[0], input, env, tracker));
|
|
1941
|
+
const values = Array.from(evaluate$1(args[1], input, env, tracker));
|
|
1942
|
+
for (const pathVal of paths) {
|
|
1943
|
+
const path = ensurePath(pathVal, span);
|
|
1944
|
+
for (const val of values) yield emit$1(updatePath(input, path, () => val, span) ?? null, span, tracker);
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
},
|
|
1948
|
+
{
|
|
1949
|
+
name: "delpaths",
|
|
1950
|
+
arity: 1,
|
|
1951
|
+
apply: function* (input, args, env, tracker, evaluate$1, span) {
|
|
1952
|
+
for (const pathsVal of evaluate$1(args[0], input, env, tracker)) {
|
|
1953
|
+
if (!Array.isArray(pathsVal)) throw new RuntimeError("delpaths expects an array of paths", span);
|
|
1954
|
+
yield emit$1(deletePaths(input, pathsVal.map((p) => ensurePath(p, span)), span), span, tracker);
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
},
|
|
1958
|
+
{
|
|
1959
|
+
name: "path",
|
|
1960
|
+
arity: 1,
|
|
1961
|
+
apply: function* (input, args, env, tracker, evaluate$1, span) {
|
|
1962
|
+
for (const p of evaluatePath(args[0], input, env, tracker, evaluate$1)) yield emit$1(p, span, tracker);
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
];
|
|
1966
|
+
|
|
1967
|
+
//#endregion
|
|
1968
|
+
//#region src/builtins/iterators.ts
|
|
1969
|
+
const iteratorBuiltins = [
|
|
1970
|
+
{
|
|
1971
|
+
name: "range",
|
|
1972
|
+
arity: 1,
|
|
1973
|
+
apply: function* (input, args, env, tracker, evaluate$1, span) {
|
|
1974
|
+
const ends = evaluate$1(args[0], input, env, tracker);
|
|
1975
|
+
for (const end of ends) {
|
|
1976
|
+
if (typeof end !== "number") throw new RuntimeError("range expects numbers", span);
|
|
1977
|
+
for (let i = 0; i < end; i++) yield emit$1(i, span, tracker);
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
},
|
|
1981
|
+
{
|
|
1982
|
+
name: "range",
|
|
1983
|
+
arity: 2,
|
|
1984
|
+
apply: function* (input, args, env, tracker, evaluate$1, span) {
|
|
1985
|
+
const starts = Array.from(evaluate$1(args[0], input, env, tracker));
|
|
1986
|
+
const ends = Array.from(evaluate$1(args[1], input, env, tracker));
|
|
1987
|
+
for (const start of starts) for (const end of ends) {
|
|
1988
|
+
if (typeof start !== "number" || typeof end !== "number") throw new RuntimeError("range expects numbers", span);
|
|
1989
|
+
if (start < end) for (let i = start; i < end; i++) yield emit$1(i, span, tracker);
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
},
|
|
1993
|
+
{
|
|
1994
|
+
name: "range",
|
|
1995
|
+
arity: 3,
|
|
1996
|
+
apply: function* (input, args, env, tracker, evaluate$1, span) {
|
|
1997
|
+
const starts = Array.from(evaluate$1(args[0], input, env, tracker));
|
|
1998
|
+
const ends = Array.from(evaluate$1(args[1], input, env, tracker));
|
|
1999
|
+
const steps = Array.from(evaluate$1(args[2], input, env, tracker));
|
|
2000
|
+
for (const start of starts) for (const end of ends) for (const step of steps) {
|
|
2001
|
+
if (typeof start !== "number" || typeof end !== "number" || typeof step !== "number") throw new RuntimeError("range expects numbers", span);
|
|
2002
|
+
if (step === 0) throw new RuntimeError("range step cannot be zero", span);
|
|
2003
|
+
if (step > 0) for (let i = start; i < end; i += step) yield emit$1(i, span, tracker);
|
|
2004
|
+
else for (let i = start; i > end; i += step) yield emit$1(i, span, tracker);
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
},
|
|
2008
|
+
{
|
|
2009
|
+
name: "limit",
|
|
2010
|
+
arity: 2,
|
|
2011
|
+
apply: function* (input, args, env, tracker, evaluate$1, span) {
|
|
2012
|
+
const limits = evaluate$1(args[0], input, env, tracker);
|
|
2013
|
+
for (const n of limits) {
|
|
2014
|
+
if (typeof n !== "number") throw new RuntimeError("limit expects number", span);
|
|
2015
|
+
let count = 0;
|
|
2016
|
+
if (n > 0) for (const val of evaluate$1(args[1], input, env, tracker)) {
|
|
2017
|
+
yield val;
|
|
2018
|
+
count++;
|
|
2019
|
+
if (count >= n) break;
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
},
|
|
2024
|
+
{
|
|
2025
|
+
name: "first",
|
|
2026
|
+
arity: 1,
|
|
2027
|
+
apply: function* (input, args, env, tracker, evaluate$1) {
|
|
2028
|
+
for (const val of evaluate$1(args[0], input, env, tracker)) {
|
|
2029
|
+
yield val;
|
|
2030
|
+
break;
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
},
|
|
2034
|
+
{
|
|
2035
|
+
name: "last",
|
|
2036
|
+
arity: 1,
|
|
2037
|
+
apply: function* (input, args, env, tracker, evaluate$1) {
|
|
2038
|
+
let lastVal;
|
|
2039
|
+
let found = false;
|
|
2040
|
+
for (const val of evaluate$1(args[0], input, env, tracker)) {
|
|
2041
|
+
lastVal = val;
|
|
2042
|
+
found = true;
|
|
2043
|
+
}
|
|
2044
|
+
if (found) yield lastVal;
|
|
2045
|
+
}
|
|
2046
|
+
},
|
|
2047
|
+
{
|
|
2048
|
+
name: "nth",
|
|
2049
|
+
arity: 2,
|
|
2050
|
+
apply: function* (input, args, env, tracker, evaluate$1, span) {
|
|
2051
|
+
const indices = evaluate$1(args[0], input, env, tracker);
|
|
2052
|
+
for (const n of indices) {
|
|
2053
|
+
if (typeof n !== "number") throw new RuntimeError("nth expects number", span);
|
|
2054
|
+
let count = 0;
|
|
2055
|
+
for (const val of evaluate$1(args[1], input, env, tracker)) {
|
|
2056
|
+
if (count === n) {
|
|
2057
|
+
yield val;
|
|
2058
|
+
break;
|
|
2059
|
+
}
|
|
2060
|
+
count++;
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
},
|
|
2065
|
+
{
|
|
2066
|
+
name: "isempty",
|
|
2067
|
+
arity: 1,
|
|
2068
|
+
apply: function* (input, args, env, tracker, evaluate$1, span) {
|
|
2069
|
+
let empty = true;
|
|
2070
|
+
for (const _ of evaluate$1(args[0], input, env, tracker)) {
|
|
2071
|
+
empty = false;
|
|
2072
|
+
break;
|
|
2073
|
+
}
|
|
2074
|
+
yield emit$1(empty, span, tracker);
|
|
2075
|
+
}
|
|
2076
|
+
},
|
|
2077
|
+
{
|
|
2078
|
+
name: "all",
|
|
2079
|
+
arity: 1,
|
|
2080
|
+
apply: function* (input, args, env, tracker, evaluate$1, span) {
|
|
2081
|
+
let result = true;
|
|
2082
|
+
for (const val of evaluate$1(args[0], input, env, tracker)) if (!isTruthy(val)) {
|
|
2083
|
+
result = false;
|
|
2084
|
+
break;
|
|
2085
|
+
}
|
|
2086
|
+
yield emit$1(result, span, tracker);
|
|
2087
|
+
}
|
|
2088
|
+
},
|
|
2089
|
+
{
|
|
2090
|
+
name: "any",
|
|
2091
|
+
arity: 1,
|
|
2092
|
+
apply: function* (input, args, env, tracker, evaluate$1, span) {
|
|
2093
|
+
let result = false;
|
|
2094
|
+
for (const val of evaluate$1(args[0], input, env, tracker)) if (isTruthy(val)) {
|
|
2095
|
+
result = true;
|
|
2096
|
+
break;
|
|
2097
|
+
}
|
|
2098
|
+
yield emit$1(result, span, tracker);
|
|
2099
|
+
}
|
|
2100
|
+
},
|
|
2101
|
+
{
|
|
2102
|
+
name: "recurse",
|
|
2103
|
+
arity: 1,
|
|
2104
|
+
apply: function* (input, args, env, tracker, evaluate$1, span) {
|
|
2105
|
+
const rec = function* (curr) {
|
|
2106
|
+
yield emit$1(curr, span, tracker);
|
|
2107
|
+
const nexts = evaluate$1(args[0], curr, env, tracker);
|
|
2108
|
+
for (const next of nexts) yield* rec(next);
|
|
2109
|
+
};
|
|
2110
|
+
yield* rec(input);
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
];
|
|
2114
|
+
|
|
2115
|
+
//#endregion
|
|
2116
|
+
//#region src/builtins/math.ts
|
|
2117
|
+
const mathBuiltins = [
|
|
2118
|
+
{
|
|
2119
|
+
name: "floor",
|
|
2120
|
+
arity: 0,
|
|
2121
|
+
apply: function* (input, _args, _env, tracker, _eval, span) {
|
|
2122
|
+
if (typeof input !== "number") throw new RuntimeError("floor expects number", span);
|
|
2123
|
+
yield emit$1(Math.floor(input), span, tracker);
|
|
2124
|
+
}
|
|
2125
|
+
},
|
|
2126
|
+
{
|
|
2127
|
+
name: "ceil",
|
|
2128
|
+
arity: 0,
|
|
2129
|
+
apply: function* (input, _args, _env, tracker, _eval, span) {
|
|
2130
|
+
if (typeof input !== "number") throw new RuntimeError("ceil expects number", span);
|
|
2131
|
+
yield emit$1(Math.ceil(input), span, tracker);
|
|
2132
|
+
}
|
|
2133
|
+
},
|
|
2134
|
+
{
|
|
2135
|
+
name: "round",
|
|
2136
|
+
arity: 0,
|
|
2137
|
+
apply: function* (input, _args, _env, tracker, _eval, span) {
|
|
2138
|
+
if (typeof input !== "number") throw new RuntimeError("round expects number", span);
|
|
2139
|
+
yield emit$1(Math.round(input), span, tracker);
|
|
2140
|
+
}
|
|
2141
|
+
},
|
|
2142
|
+
{
|
|
2143
|
+
name: "abs",
|
|
2144
|
+
arity: 0,
|
|
2145
|
+
apply: function* (input, _args, _env, tracker, _eval, span) {
|
|
2146
|
+
if (typeof input !== "number") throw new RuntimeError("abs expects number", span);
|
|
2147
|
+
yield emit$1(Math.abs(input), span, tracker);
|
|
2148
|
+
}
|
|
2149
|
+
},
|
|
2150
|
+
{
|
|
2151
|
+
name: "sqrt",
|
|
2152
|
+
arity: 0,
|
|
2153
|
+
apply: function* (input, _args, _env, tracker, _eval, span) {
|
|
2154
|
+
if (typeof input !== "number") throw new RuntimeError("sqrt expects number", span);
|
|
2155
|
+
yield emit$1(Math.sqrt(input), span, tracker);
|
|
2156
|
+
}
|
|
2157
|
+
},
|
|
2158
|
+
{
|
|
2159
|
+
name: "isnan",
|
|
2160
|
+
arity: 0,
|
|
2161
|
+
apply: function* (input, _args, _env, tracker, _eval, span) {
|
|
2162
|
+
if (typeof input !== "number") {
|
|
2163
|
+
yield emit$1(false, span, tracker);
|
|
2164
|
+
return;
|
|
2165
|
+
}
|
|
2166
|
+
yield emit$1(Number.isNaN(input), span, tracker);
|
|
2167
|
+
}
|
|
2168
|
+
},
|
|
2169
|
+
{
|
|
2170
|
+
name: "infinite",
|
|
2171
|
+
arity: 0,
|
|
2172
|
+
apply: function* (input, _args, _env, tracker, _eval, span) {
|
|
2173
|
+
if (typeof input !== "number") {
|
|
2174
|
+
yield emit$1(false, span, tracker);
|
|
2175
|
+
return;
|
|
2176
|
+
}
|
|
2177
|
+
yield emit$1(!Number.isFinite(input) && !Number.isNaN(input), span, tracker);
|
|
2178
|
+
}
|
|
2179
|
+
},
|
|
2180
|
+
{
|
|
2181
|
+
name: "isfinite",
|
|
2182
|
+
arity: 0,
|
|
2183
|
+
apply: function* (input, _args, _env, tracker, _eval, span) {
|
|
2184
|
+
if (typeof input !== "number") {
|
|
2185
|
+
yield emit$1(true, span, tracker);
|
|
2186
|
+
return;
|
|
2187
|
+
}
|
|
2188
|
+
yield emit$1(Number.isFinite(input), span, tracker);
|
|
2189
|
+
}
|
|
2190
|
+
},
|
|
2191
|
+
{
|
|
2192
|
+
name: "normal",
|
|
2193
|
+
arity: 0,
|
|
2194
|
+
apply: function* (input, _args, _env, tracker, _eval, span) {
|
|
2195
|
+
if (typeof input !== "number") {
|
|
2196
|
+
yield emit$1(false, span, tracker);
|
|
2197
|
+
return;
|
|
2198
|
+
}
|
|
2199
|
+
if (!Number.isFinite(input) || Number.isNaN(input) || input === 0) {
|
|
2200
|
+
yield emit$1(false, span, tracker);
|
|
2201
|
+
return;
|
|
2202
|
+
}
|
|
2203
|
+
yield emit$1(true, span, tracker);
|
|
2204
|
+
}
|
|
2205
|
+
},
|
|
2206
|
+
{
|
|
2207
|
+
name: "subnormal",
|
|
2208
|
+
arity: 0,
|
|
2209
|
+
apply: function* (input, _args, _env, tracker, _eval, span) {
|
|
2210
|
+
if (typeof input !== "number") {
|
|
2211
|
+
yield emit$1(false, span, tracker);
|
|
2212
|
+
return;
|
|
2213
|
+
}
|
|
2214
|
+
yield emit$1(false, span, tracker);
|
|
2215
|
+
}
|
|
2216
|
+
},
|
|
2217
|
+
{
|
|
2218
|
+
name: "min",
|
|
2219
|
+
arity: 0,
|
|
2220
|
+
apply: function* (input, _args, _env, tracker, _eval, span) {
|
|
2221
|
+
if (!Array.isArray(input)) throw new RuntimeError("min expects an array", span);
|
|
2222
|
+
if (input.length === 0) {
|
|
2223
|
+
yield emit$1(null, span, tracker);
|
|
2224
|
+
return;
|
|
2225
|
+
}
|
|
2226
|
+
let minVal = input[0];
|
|
2227
|
+
for (let i = 1; i < input.length; i++) if (compareValues(input[i], minVal) < 0) minVal = input[i];
|
|
2228
|
+
yield emit$1(minVal, span, tracker);
|
|
2229
|
+
}
|
|
2230
|
+
},
|
|
2231
|
+
{
|
|
2232
|
+
name: "max",
|
|
2233
|
+
arity: 0,
|
|
2234
|
+
apply: function* (input, _args, _env, tracker, _eval, span) {
|
|
2235
|
+
if (!Array.isArray(input)) throw new RuntimeError("max expects an array", span);
|
|
2236
|
+
if (input.length === 0) {
|
|
2237
|
+
yield emit$1(null, span, tracker);
|
|
2238
|
+
return;
|
|
2239
|
+
}
|
|
2240
|
+
let maxVal = input[0];
|
|
2241
|
+
for (let i = 1; i < input.length; i++) if (compareValues(input[i], maxVal) > 0) maxVal = input[i];
|
|
2242
|
+
yield emit$1(maxVal, span, tracker);
|
|
2243
|
+
}
|
|
2244
|
+
},
|
|
2245
|
+
{
|
|
2246
|
+
name: "min_by",
|
|
2247
|
+
arity: 1,
|
|
2248
|
+
apply: function* (input, args, env, tracker, evaluate$1, span) {
|
|
2249
|
+
if (!Array.isArray(input)) throw new RuntimeError("min_by expects an array", span);
|
|
2250
|
+
if (input.length === 0) {
|
|
2251
|
+
yield emit$1(null, span, tracker);
|
|
2252
|
+
return;
|
|
2253
|
+
}
|
|
2254
|
+
let minItem = input[0];
|
|
2255
|
+
let minKey;
|
|
2256
|
+
const keys0 = Array.from(evaluate$1(args[0], minItem, env, tracker));
|
|
2257
|
+
if (keys0.length !== 1) throw new RuntimeError("min_by key must return one value", span);
|
|
2258
|
+
minKey = keys0[0];
|
|
2259
|
+
for (let i = 1; i < input.length; i++) {
|
|
2260
|
+
const item = input[i];
|
|
2261
|
+
const keys = Array.from(evaluate$1(args[0], item, env, tracker));
|
|
2262
|
+
if (keys.length !== 1) throw new RuntimeError("min_by key must return one value", span);
|
|
2263
|
+
const key = keys[0];
|
|
2264
|
+
if (compareValues(key, minKey) < 0) {
|
|
2265
|
+
minKey = key;
|
|
2266
|
+
minItem = item;
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
yield emit$1(minItem, span, tracker);
|
|
2270
|
+
}
|
|
2271
|
+
},
|
|
2272
|
+
{
|
|
2273
|
+
name: "max_by",
|
|
2274
|
+
arity: 1,
|
|
2275
|
+
apply: function* (input, args, env, tracker, evaluate$1, span) {
|
|
2276
|
+
if (!Array.isArray(input)) throw new RuntimeError("max_by expects an array", span);
|
|
2277
|
+
if (input.length === 0) {
|
|
2278
|
+
yield emit$1(null, span, tracker);
|
|
2279
|
+
return;
|
|
2280
|
+
}
|
|
2281
|
+
let maxItem = input[0];
|
|
2282
|
+
let maxKey;
|
|
2283
|
+
const keys0 = Array.from(evaluate$1(args[0], maxItem, env, tracker));
|
|
2284
|
+
if (keys0.length !== 1) throw new RuntimeError("max_by key must return one value", span);
|
|
2285
|
+
maxKey = keys0[0];
|
|
2286
|
+
for (let i = 1; i < input.length; i++) {
|
|
2287
|
+
const item = input[i];
|
|
2288
|
+
const keys = Array.from(evaluate$1(args[0], item, env, tracker));
|
|
2289
|
+
if (keys.length !== 1) throw new RuntimeError("max_by key must return one value", span);
|
|
2290
|
+
const key = keys[0];
|
|
2291
|
+
if (compareValues(key, maxKey) > 0) {
|
|
2292
|
+
maxKey = key;
|
|
2293
|
+
maxItem = item;
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
yield emit$1(maxItem, span, tracker);
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
];
|
|
2300
|
+
|
|
2301
|
+
//#endregion
|
|
2302
|
+
//#region src/builtins/index.ts
|
|
2303
|
+
const registerAllBuiltins = () => {
|
|
2304
|
+
registerBuiltins(stdBuiltins);
|
|
2305
|
+
registerBuiltins(errorBuiltins);
|
|
2306
|
+
registerBuiltins(collectionBuiltins);
|
|
2307
|
+
registerBuiltins(stringBuiltins);
|
|
2308
|
+
registerBuiltins(pathBuiltins);
|
|
2309
|
+
registerBuiltins(iteratorBuiltins);
|
|
2310
|
+
registerBuiltins(mathBuiltins);
|
|
2311
|
+
};
|
|
2312
|
+
|
|
2313
|
+
//#endregion
|
|
2314
|
+
//#region src/builtins.ts
|
|
2315
|
+
registerAllBuiltins();
|
|
2316
|
+
|
|
2317
|
+
//#endregion
|
|
1461
2318
|
//#region src/validate.ts
|
|
1462
2319
|
/**
|
|
1463
2320
|
* Validates the AST for correctness and supported features.
|
|
@@ -1467,82 +2324,124 @@ function sortStable(arr, compare) {
|
|
|
1467
2324
|
* @throws {ValidationError} If validation fails.
|
|
1468
2325
|
*/
|
|
1469
2326
|
const validate = (node) => {
|
|
1470
|
-
visit(node);
|
|
2327
|
+
visit(node, []);
|
|
1471
2328
|
};
|
|
1472
|
-
const visit = (node) => {
|
|
2329
|
+
const visit = (node, scope) => {
|
|
1473
2330
|
switch (node.kind) {
|
|
1474
2331
|
case "Identity":
|
|
1475
2332
|
case "Literal":
|
|
1476
2333
|
case "Var": return;
|
|
1477
2334
|
case "FieldAccess":
|
|
1478
|
-
visit(node.target);
|
|
2335
|
+
visit(node.target, scope);
|
|
1479
2336
|
return;
|
|
1480
2337
|
case "IndexAccess":
|
|
1481
|
-
visit(node.target);
|
|
1482
|
-
visit(node.index);
|
|
2338
|
+
visit(node.target, scope);
|
|
2339
|
+
visit(node.index, scope);
|
|
1483
2340
|
return;
|
|
1484
2341
|
case "Array":
|
|
1485
|
-
node.items.forEach(visit);
|
|
2342
|
+
node.items.forEach((n) => visit(n, scope));
|
|
1486
2343
|
return;
|
|
1487
2344
|
case "Object":
|
|
1488
2345
|
node.entries.forEach((entry) => {
|
|
1489
|
-
if (entry.key.kind === "KeyExpr") visit(entry.key.expr);
|
|
1490
|
-
visit(entry.value);
|
|
2346
|
+
if (entry.key.kind === "KeyExpr") visit(entry.key.expr, scope);
|
|
2347
|
+
visit(entry.value, scope);
|
|
1491
2348
|
});
|
|
1492
2349
|
return;
|
|
1493
2350
|
case "Pipe":
|
|
1494
2351
|
case "Comma":
|
|
1495
2352
|
case "Alt":
|
|
1496
|
-
visit(node.left);
|
|
1497
|
-
visit(node.right);
|
|
2353
|
+
visit(node.left, scope);
|
|
2354
|
+
visit(node.right, scope);
|
|
1498
2355
|
return;
|
|
1499
2356
|
case "Binary":
|
|
1500
2357
|
case "Bool":
|
|
1501
|
-
visit(node.left);
|
|
1502
|
-
visit(node.right);
|
|
2358
|
+
visit(node.left, scope);
|
|
2359
|
+
visit(node.right, scope);
|
|
1503
2360
|
return;
|
|
1504
2361
|
case "Unary":
|
|
1505
|
-
visit(node.expr);
|
|
2362
|
+
visit(node.expr, scope);
|
|
1506
2363
|
return;
|
|
1507
2364
|
case "If":
|
|
1508
2365
|
node.branches.forEach((branch) => {
|
|
1509
|
-
visit(branch.cond);
|
|
1510
|
-
visit(branch.then);
|
|
2366
|
+
visit(branch.cond, scope);
|
|
2367
|
+
visit(branch.then, scope);
|
|
1511
2368
|
});
|
|
1512
|
-
visit(node.else);
|
|
2369
|
+
visit(node.else, scope);
|
|
1513
2370
|
return;
|
|
1514
2371
|
case "As":
|
|
1515
|
-
visit(node.bind);
|
|
1516
|
-
visit(node.body);
|
|
2372
|
+
visit(node.bind, scope);
|
|
2373
|
+
visit(node.body, scope);
|
|
1517
2374
|
return;
|
|
1518
2375
|
case "Call": {
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
2376
|
+
for (let i = scope.length - 1; i >= 0; i--) if (scope[i].has(node.name)) {
|
|
2377
|
+
for (const arg of node.args) visit(arg, scope);
|
|
2378
|
+
return;
|
|
2379
|
+
}
|
|
2380
|
+
const specs = builtins[node.name];
|
|
2381
|
+
if (!specs || specs.length === 0) throw new ValidationError(`Unknown function: ${node.name}`, node.span);
|
|
2382
|
+
if (!specs.find((s) => s.arity === node.args.length)) {
|
|
2383
|
+
const arities = specs.map((s) => s.arity).join(" or ");
|
|
2384
|
+
throw new ValidationError(`Function ${node.name} expects ${arities} arguments, but got ${node.args.length}`, node.span);
|
|
2385
|
+
}
|
|
2386
|
+
for (const arg of node.args) visit(arg, scope);
|
|
2387
|
+
return;
|
|
2388
|
+
}
|
|
2389
|
+
case "Assignment":
|
|
2390
|
+
visit(node.left, scope);
|
|
2391
|
+
visit(node.right, scope);
|
|
2392
|
+
return;
|
|
2393
|
+
case "Def": {
|
|
2394
|
+
const bodyScope = new Set(node.args);
|
|
2395
|
+
bodyScope.add(node.name);
|
|
2396
|
+
visit(node.body, [...scope, bodyScope]);
|
|
2397
|
+
const nextScope = new Set([node.name]);
|
|
2398
|
+
visit(node.next, [...scope, nextScope]);
|
|
1523
2399
|
return;
|
|
1524
2400
|
}
|
|
1525
2401
|
case "Reduce":
|
|
1526
|
-
visit(node.source);
|
|
1527
|
-
visit(node.init);
|
|
1528
|
-
visit(node.update);
|
|
2402
|
+
visit(node.source, scope);
|
|
2403
|
+
visit(node.init, scope);
|
|
2404
|
+
visit(node.update, scope);
|
|
1529
2405
|
return;
|
|
1530
2406
|
case "Foreach":
|
|
1531
|
-
visit(node.source);
|
|
1532
|
-
visit(node.init);
|
|
1533
|
-
visit(node.update);
|
|
1534
|
-
if (node.extract) visit(node.extract);
|
|
2407
|
+
visit(node.source, scope);
|
|
2408
|
+
visit(node.init, scope);
|
|
2409
|
+
visit(node.update, scope);
|
|
2410
|
+
if (node.extract) visit(node.extract, scope);
|
|
1535
2411
|
return;
|
|
1536
2412
|
case "Try":
|
|
1537
|
-
visit(node.body);
|
|
1538
|
-
if (node.handler) visit(node.handler);
|
|
2413
|
+
visit(node.body, scope);
|
|
2414
|
+
if (node.handler) visit(node.handler, scope);
|
|
1539
2415
|
return;
|
|
1540
2416
|
case "Recurse":
|
|
1541
|
-
case "Iterate":
|
|
2417
|
+
case "Iterate":
|
|
2418
|
+
case "Break": return;
|
|
2419
|
+
case "Label":
|
|
2420
|
+
visit(node.body, scope);
|
|
2421
|
+
return;
|
|
2422
|
+
case "Slice":
|
|
2423
|
+
visit(node.target, scope);
|
|
2424
|
+
if (node.start) visit(node.start, scope);
|
|
2425
|
+
if (node.end) visit(node.end, scope);
|
|
2426
|
+
return;
|
|
1542
2427
|
default: return node;
|
|
1543
2428
|
}
|
|
1544
2429
|
};
|
|
1545
2430
|
|
|
2431
|
+
//#endregion
|
|
2432
|
+
//#region src/eval/break.ts
|
|
2433
|
+
/**
|
|
2434
|
+
* Internal error class used to implement control flow breaks (`break $label`).
|
|
2435
|
+
* Caught by `evalLabel` or `evalForeach`/`evalReduce` if labels match.
|
|
2436
|
+
*/
|
|
2437
|
+
var BreakSignal = class BreakSignal extends Error {
|
|
2438
|
+
constructor(label) {
|
|
2439
|
+
super(`Break: ${label}`);
|
|
2440
|
+
this.label = label;
|
|
2441
|
+
Object.setPrototypeOf(this, BreakSignal.prototype);
|
|
2442
|
+
}
|
|
2443
|
+
};
|
|
2444
|
+
|
|
1546
2445
|
//#endregion
|
|
1547
2446
|
//#region src/limits.ts
|
|
1548
2447
|
const DEFAULT_LIMITS = {
|
|
@@ -1605,167 +2504,298 @@ var LimitTracker = class {
|
|
|
1605
2504
|
};
|
|
1606
2505
|
|
|
1607
2506
|
//#endregion
|
|
1608
|
-
//#region src/eval.ts
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
2507
|
+
//#region src/eval/ops.ts
|
|
2508
|
+
/**
|
|
2509
|
+
* Applies a unary negation (`-`).
|
|
2510
|
+
*
|
|
2511
|
+
* @param value - The value to negate.
|
|
2512
|
+
* @param span - Source span for errors.
|
|
2513
|
+
* @returns The negated value.
|
|
2514
|
+
*/
|
|
2515
|
+
const applyUnaryNeg = (value, span) => {
|
|
2516
|
+
if (typeof value === "number") return -value;
|
|
2517
|
+
throw new RuntimeError(`Invalid operand for unary -: ${describeType(value)}`, span);
|
|
1613
2518
|
};
|
|
1614
2519
|
/**
|
|
1615
|
-
*
|
|
1616
|
-
* This is a generator function that yields results lazily.
|
|
2520
|
+
* Applies a binary operator.
|
|
1617
2521
|
*
|
|
1618
|
-
*
|
|
1619
|
-
*
|
|
1620
|
-
* @param
|
|
1621
|
-
* @param
|
|
2522
|
+
* Supports arithmetic (`+`, `-`, `*`, `/`, `%`) and comparators.
|
|
2523
|
+
*
|
|
2524
|
+
* @param op - The operator string.
|
|
2525
|
+
* @param left - The left operand.
|
|
2526
|
+
* @param right - The right operand.
|
|
2527
|
+
* @param span - Source span for errors.
|
|
2528
|
+
* @returns The result of the operation.
|
|
1622
2529
|
*/
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
const condValues = Array.from(evaluate(branch.cond, input, env, tracker));
|
|
1678
|
-
if (condValues.length > 1) throw new RuntimeError("Condition produced multiple values", branch.cond.span);
|
|
1679
|
-
if (condValues.length === 1 && isTruthy(condValues[0])) {
|
|
1680
|
-
yield* evaluate(branch.then, input, env, tracker);
|
|
1681
|
-
return;
|
|
1682
|
-
}
|
|
1683
|
-
}
|
|
1684
|
-
yield* evaluate(node.else, input, env, tracker);
|
|
1685
|
-
return;
|
|
1686
|
-
case "As": {
|
|
1687
|
-
const boundValues = Array.from(evaluate(node.bind, input, env, tracker));
|
|
1688
|
-
for (const value of boundValues) {
|
|
1689
|
-
pushBinding(env, node.name, value);
|
|
1690
|
-
try {
|
|
1691
|
-
yield* evaluate(node.body, input, env, tracker);
|
|
1692
|
-
} finally {
|
|
1693
|
-
popBinding(env);
|
|
1694
|
-
}
|
|
1695
|
-
}
|
|
1696
|
-
return;
|
|
1697
|
-
}
|
|
1698
|
-
case "Call": {
|
|
1699
|
-
const builtin = builtins[node.name];
|
|
1700
|
-
if (!builtin) throw new RuntimeError(`Unknown function: ${node.name}/${node.args.length}`, node.span);
|
|
1701
|
-
yield* builtin.apply(input, node.args, env, tracker, evaluate, node.span);
|
|
1702
|
-
return;
|
|
1703
|
-
}
|
|
1704
|
-
case "Reduce":
|
|
1705
|
-
yield* evalReduce(node, input, env, tracker);
|
|
1706
|
-
return;
|
|
1707
|
-
case "Foreach":
|
|
1708
|
-
yield* evalForeach(node, input, env, tracker);
|
|
1709
|
-
return;
|
|
1710
|
-
case "Try":
|
|
1711
|
-
yield* evalTry(node, input, env, tracker);
|
|
1712
|
-
return;
|
|
1713
|
-
case "Recurse":
|
|
1714
|
-
yield* evalRecurse(node, input, env, tracker);
|
|
1715
|
-
return;
|
|
1716
|
-
case "Iterate":
|
|
1717
|
-
yield* evalIterate(node, input, env, tracker);
|
|
1718
|
-
return;
|
|
2530
|
+
const applyBinaryOp = (op, left, right, span) => {
|
|
2531
|
+
switch (op) {
|
|
2532
|
+
case "+": return add(left, right, span);
|
|
2533
|
+
case "-": return sub(left, right, span);
|
|
2534
|
+
case "*": return mul(left, right, span);
|
|
2535
|
+
case "/": return div(left, right, span);
|
|
2536
|
+
case "%": return mod(left, right, span);
|
|
2537
|
+
case "Eq": return isEqual(left, right);
|
|
2538
|
+
case "Neq": return !isEqual(left, right);
|
|
2539
|
+
case "Lt": return compare(left, right) < 0;
|
|
2540
|
+
case "Lte": return compare(left, right) <= 0;
|
|
2541
|
+
case "Gt": return compare(left, right) > 0;
|
|
2542
|
+
case "Gte": return compare(left, right) >= 0;
|
|
2543
|
+
default: throw new RuntimeError(`Unknown binary operator: ${op}`, span);
|
|
2544
|
+
}
|
|
2545
|
+
};
|
|
2546
|
+
function isEqual(a, b) {
|
|
2547
|
+
if (a === b) return true;
|
|
2548
|
+
if (a === null || b === null) return false;
|
|
2549
|
+
if (typeof a !== typeof b) return false;
|
|
2550
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
2551
|
+
if (a.length !== b.length) return false;
|
|
2552
|
+
return a.every((v, i) => isEqual(v, b[i]));
|
|
2553
|
+
}
|
|
2554
|
+
if (isPlainObject$1(a) && isPlainObject$1(b)) {
|
|
2555
|
+
const ka = Object.keys(a).sort();
|
|
2556
|
+
const kb = Object.keys(b).sort();
|
|
2557
|
+
if (ka.length !== kb.length) return false;
|
|
2558
|
+
if (!ka.every((k, i) => k === kb[i])) return false;
|
|
2559
|
+
return ka.every((k) => isEqual(a[k], b[k]));
|
|
2560
|
+
}
|
|
2561
|
+
return false;
|
|
2562
|
+
}
|
|
2563
|
+
function compare(a, b) {
|
|
2564
|
+
if (a === b) return 0;
|
|
2565
|
+
const typeOrder = (v) => {
|
|
2566
|
+
if (v === null) return 0;
|
|
2567
|
+
if (typeof v === "boolean") return 1;
|
|
2568
|
+
if (typeof v === "number") return 2;
|
|
2569
|
+
if (typeof v === "string") return 3;
|
|
2570
|
+
if (Array.isArray(v)) return 4;
|
|
2571
|
+
if (isPlainObject$1(v)) return 5;
|
|
2572
|
+
return 6;
|
|
2573
|
+
};
|
|
2574
|
+
const ta = typeOrder(a);
|
|
2575
|
+
const tb = typeOrder(b);
|
|
2576
|
+
if (ta !== tb) return ta - tb;
|
|
2577
|
+
if (typeof a === "boolean" && typeof b === "boolean") return (a ? 1 : 0) - (b ? 1 : 0);
|
|
2578
|
+
if (typeof a === "number" && typeof b === "number") return a - b;
|
|
2579
|
+
if (typeof a === "string" && typeof b === "string") return a < b ? -1 : 1;
|
|
2580
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
2581
|
+
for (let i = 0; i < Math.min(a.length, b.length); i++) {
|
|
2582
|
+
const c = compare(a[i], b[i]);
|
|
2583
|
+
if (c !== 0) return c;
|
|
1719
2584
|
}
|
|
1720
|
-
|
|
1721
|
-
|
|
2585
|
+
return a.length - b.length;
|
|
2586
|
+
}
|
|
2587
|
+
if (isPlainObject$1(a) && isPlainObject$1(b)) {
|
|
2588
|
+
const keysA = Object.keys(a).sort();
|
|
2589
|
+
const keysB = Object.keys(b).sort();
|
|
2590
|
+
for (let i = 0; i < Math.min(keysA.length, keysB.length); i++) {
|
|
2591
|
+
const kA = keysA[i];
|
|
2592
|
+
const kB = keysB[i];
|
|
2593
|
+
if (kA !== kB) return kA < kB ? -1 : 1;
|
|
2594
|
+
const c = compare(a[kA], b[kB]);
|
|
2595
|
+
if (c !== 0) return c;
|
|
2596
|
+
}
|
|
2597
|
+
return keysA.length - keysB.length;
|
|
2598
|
+
}
|
|
2599
|
+
return 0;
|
|
2600
|
+
}
|
|
2601
|
+
function add(left, right, span) {
|
|
2602
|
+
if (left === null && right === null) return null;
|
|
2603
|
+
if (left === null && typeof right === "number") return right;
|
|
2604
|
+
if (typeof left === "number" && right === null) return left;
|
|
2605
|
+
if (typeof left === "number" && typeof right === "number") return left + right;
|
|
2606
|
+
if (typeof left === "string" && typeof right === "string") return left + right;
|
|
2607
|
+
if (Array.isArray(left) && Array.isArray(right)) return [...left, ...right];
|
|
2608
|
+
if (isPlainObject$1(left) && isPlainObject$1(right)) return {
|
|
2609
|
+
...left,
|
|
2610
|
+
...right
|
|
2611
|
+
};
|
|
2612
|
+
throw new RuntimeError(`Cannot add ${describeType(left)} and ${describeType(right)}`, span);
|
|
2613
|
+
}
|
|
2614
|
+
function sub(left, right, span) {
|
|
2615
|
+
if (typeof left === "number" && typeof right === "number") return left - right;
|
|
2616
|
+
if (Array.isArray(left) && Array.isArray(right)) {
|
|
2617
|
+
const toRemove = new Set(right.map(stableStringify));
|
|
2618
|
+
return left.filter((x) => !toRemove.has(stableStringify(x)));
|
|
1722
2619
|
}
|
|
2620
|
+
throw new RuntimeError(`Cannot subtract ${describeType(right)} from ${describeType(left)}`, span);
|
|
2621
|
+
}
|
|
2622
|
+
function mul(left, right, span) {
|
|
2623
|
+
if (typeof left === "number" && typeof right === "number") return left * right;
|
|
2624
|
+
if (typeof left === "string" && typeof right === "number") return repeatString(left, right);
|
|
2625
|
+
if (typeof left === "number" && typeof right === "string") return repeatString(right, left);
|
|
2626
|
+
if (isPlainObject$1(left) && isPlainObject$1(right)) return mergeDeep(left, right);
|
|
2627
|
+
throw new RuntimeError(`Cannot multiply ${describeType(left)} by ${describeType(right)}`, span);
|
|
2628
|
+
}
|
|
2629
|
+
function div(left, right, span) {
|
|
2630
|
+
if (typeof left === "number" && typeof right === "number") {
|
|
2631
|
+
if (right === 0) throw new RuntimeError("Division by zero", span);
|
|
2632
|
+
return left / right;
|
|
2633
|
+
}
|
|
2634
|
+
if (typeof left === "string" && typeof right === "string") return left.split(right);
|
|
2635
|
+
throw new RuntimeError(`Cannot divide ${describeType(left)} by ${describeType(right)}`, span);
|
|
2636
|
+
}
|
|
2637
|
+
function mod(left, right, span) {
|
|
2638
|
+
if (typeof left === "number" && typeof right === "number") {
|
|
2639
|
+
if (right === 0) throw new RuntimeError("Modulo by zero", span);
|
|
2640
|
+
return left % right;
|
|
2641
|
+
}
|
|
2642
|
+
throw new RuntimeError(`Cannot modulo ${describeType(left)} by ${describeType(right)}`, span);
|
|
2643
|
+
}
|
|
2644
|
+
function repeatString(str, count) {
|
|
2645
|
+
if (count <= 0) return null;
|
|
2646
|
+
const n = Math.floor(count);
|
|
2647
|
+
if (n <= 0) return null;
|
|
2648
|
+
return str.repeat(n);
|
|
2649
|
+
}
|
|
2650
|
+
function mergeDeep(target, source) {
|
|
2651
|
+
const result = { ...target };
|
|
2652
|
+
for (const key of Object.keys(source)) {
|
|
2653
|
+
const sVal = source[key];
|
|
2654
|
+
const tVal = result[key];
|
|
2655
|
+
if (isPlainObject$1(sVal) && tVal !== void 0 && isPlainObject$1(tVal)) result[key] = mergeDeep(tVal, sVal);
|
|
2656
|
+
else result[key] = sVal;
|
|
2657
|
+
}
|
|
2658
|
+
return result;
|
|
2659
|
+
}
|
|
2660
|
+
function isPlainObject$1(v) {
|
|
2661
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1723
2662
|
}
|
|
2663
|
+
|
|
2664
|
+
//#endregion
|
|
2665
|
+
//#region src/eval/env.ts
|
|
2666
|
+
/**
|
|
2667
|
+
* Retrieves a variable value from the environment stack.
|
|
2668
|
+
* Searches from the current (top) frame down to the global frame.
|
|
2669
|
+
*/
|
|
2670
|
+
const getVar = (env, name) => {
|
|
2671
|
+
for (let i = env.length - 1; i >= 0; i--) if (env[i].vars.has(name)) return env[i].vars.get(name);
|
|
2672
|
+
};
|
|
2673
|
+
/**
|
|
2674
|
+
* Bounds a value to a variable name in the current scope frame.
|
|
2675
|
+
*/
|
|
2676
|
+
const pushBinding = (env, name, value) => {
|
|
2677
|
+
env[env.length - 1].vars.set(name, value);
|
|
2678
|
+
};
|
|
2679
|
+
/**
|
|
2680
|
+
* Removes a variable binding from the current scope frame.
|
|
2681
|
+
*/
|
|
2682
|
+
const popBinding = (env, name) => {
|
|
2683
|
+
env[env.length - 1].vars.delete(name);
|
|
2684
|
+
};
|
|
2685
|
+
|
|
2686
|
+
//#endregion
|
|
2687
|
+
//#region src/eval/common.ts
|
|
2688
|
+
/**
|
|
2689
|
+
* Emits a value, recording it in the limits tracker.
|
|
2690
|
+
*
|
|
2691
|
+
* @param value - The value to yield.
|
|
2692
|
+
* @param span - The source span responsible for this value.
|
|
2693
|
+
* @param tracker - The limits tracker.
|
|
2694
|
+
* @returns The value itself.
|
|
2695
|
+
*/
|
|
1724
2696
|
const emit = (value, span, tracker) => {
|
|
1725
2697
|
tracker.emit(span);
|
|
1726
2698
|
return value;
|
|
1727
2699
|
};
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
2700
|
+
/**
|
|
2701
|
+
* Ensures a value is an integer. Throws a RuntimeError otherwise.
|
|
2702
|
+
*
|
|
2703
|
+
* @param value - The value to check.
|
|
2704
|
+
* @param span - To report error.
|
|
2705
|
+
* @returns The integer value.
|
|
2706
|
+
*/
|
|
2707
|
+
const ensureInteger = (value, span) => {
|
|
2708
|
+
if (typeof value === "number" && Number.isInteger(value)) return value;
|
|
2709
|
+
throw new RuntimeError("Expected integer", span);
|
|
2710
|
+
};
|
|
2711
|
+
|
|
2712
|
+
//#endregion
|
|
2713
|
+
//#region src/eval/assignment.ts
|
|
2714
|
+
/**
|
|
2715
|
+
* Evaluates assignment and update operators (`=`, `|=`, `+=`, etc.).
|
|
2716
|
+
*
|
|
2717
|
+
* Implements the complex path update logic of jq.
|
|
2718
|
+
* It first resolves the path(s) on the left-hand side, then computes new values,
|
|
2719
|
+
* and finally reconstructs the object with the updated values.
|
|
2720
|
+
*
|
|
2721
|
+
* @param node - The assignment AST node.
|
|
2722
|
+
* @param input - The current input value.
|
|
2723
|
+
* @param env - The environment.
|
|
2724
|
+
* @param tracker - Limits tracker.
|
|
2725
|
+
* @param evaluate - Recursive evaluator.
|
|
2726
|
+
*/
|
|
2727
|
+
const evalAssignment = function* (node, input, env, tracker, evaluate$1) {
|
|
2728
|
+
const paths = Array.from(evaluatePath(node.left, input, env, tracker, evaluate$1));
|
|
2729
|
+
if (paths.length === 0) {
|
|
2730
|
+
yield emit(input, node.span, tracker);
|
|
2731
|
+
return;
|
|
2732
|
+
}
|
|
2733
|
+
if (node.op === "=") {
|
|
2734
|
+
const rhsValues = Array.from(evaluate$1(node.right, input, env, tracker));
|
|
2735
|
+
if (rhsValues.length === 0) return;
|
|
2736
|
+
for (const rhsVal of rhsValues) {
|
|
2737
|
+
let current = input;
|
|
2738
|
+
for (const path of paths) current = updatePath(current, path, () => rhsVal, node.span) ?? current;
|
|
2739
|
+
yield emit(current, node.span, tracker);
|
|
1737
2740
|
}
|
|
1738
|
-
|
|
2741
|
+
return;
|
|
1739
2742
|
}
|
|
2743
|
+
yield* applyUpdates(input, paths, 0, node.op, node.right, input, env, tracker, evaluate$1);
|
|
1740
2744
|
};
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
2745
|
+
function* applyUpdates(current, paths, index, op, rhsNode, contextInput, env, tracker, evaluate$1) {
|
|
2746
|
+
if (index >= paths.length) {
|
|
2747
|
+
yield current;
|
|
2748
|
+
return;
|
|
2749
|
+
}
|
|
2750
|
+
const path = paths[index];
|
|
2751
|
+
const oldValue = getPath(current, path) ?? null;
|
|
2752
|
+
let newValues = [];
|
|
2753
|
+
if (op === "|=") newValues = Array.from(evaluate$1(rhsNode, oldValue, env, tracker));
|
|
2754
|
+
else {
|
|
2755
|
+
const rhsResults = Array.from(evaluate$1(rhsNode, contextInput, env, tracker));
|
|
2756
|
+
for (const rhs of rhsResults) {
|
|
2757
|
+
let res;
|
|
2758
|
+
switch (op) {
|
|
2759
|
+
case "+=":
|
|
2760
|
+
res = applyBinaryOp("+", oldValue, rhs, rhsNode.span);
|
|
2761
|
+
break;
|
|
2762
|
+
case "-=":
|
|
2763
|
+
res = applyBinaryOp("-", oldValue, rhs, rhsNode.span);
|
|
2764
|
+
break;
|
|
2765
|
+
case "*=":
|
|
2766
|
+
res = applyBinaryOp("*", oldValue, rhs, rhsNode.span);
|
|
2767
|
+
break;
|
|
2768
|
+
case "/=":
|
|
2769
|
+
res = applyBinaryOp("/", oldValue, rhs, rhsNode.span);
|
|
2770
|
+
break;
|
|
2771
|
+
case "%=":
|
|
2772
|
+
res = applyBinaryOp("%", oldValue, rhs, rhsNode.span);
|
|
2773
|
+
break;
|
|
2774
|
+
case "//=":
|
|
2775
|
+
res = oldValue !== null && oldValue !== false ? oldValue : rhs;
|
|
2776
|
+
break;
|
|
2777
|
+
default: throw new RuntimeError(`Unknown assignment op: ${op}`, rhsNode.span);
|
|
1754
2778
|
}
|
|
1755
|
-
|
|
2779
|
+
newValues.push(res);
|
|
1756
2780
|
}
|
|
1757
|
-
if (isPlainObject(container)) {
|
|
1758
|
-
for (const keyValue of indexValues) {
|
|
1759
|
-
if (typeof keyValue !== "string") throw new RuntimeError(`Cannot index object with ${describeType(keyValue)}`, node.span);
|
|
1760
|
-
yield emit(Object.prototype.hasOwnProperty.call(container, keyValue) ? container[keyValue] : null, node.span, tracker);
|
|
1761
|
-
}
|
|
1762
|
-
continue;
|
|
1763
|
-
}
|
|
1764
|
-
throw new RuntimeError(`Cannot index ${describeType(container)}`, node.span);
|
|
1765
2781
|
}
|
|
1766
|
-
|
|
1767
|
-
const
|
|
1768
|
-
|
|
2782
|
+
if (newValues.length === 0) return;
|
|
2783
|
+
for (const val of newValues) yield* applyUpdates(updatePath(current, path, () => val, rhsNode.span) ?? current, paths, index + 1, op, rhsNode, contextInput, env, tracker, evaluate$1);
|
|
2784
|
+
}
|
|
2785
|
+
|
|
2786
|
+
//#endregion
|
|
2787
|
+
//#region src/eval/iterators.ts
|
|
2788
|
+
/**
|
|
2789
|
+
* Iterates over the values of an array or object (`.[]`).
|
|
2790
|
+
*
|
|
2791
|
+
* @param node - The iterate AST node.
|
|
2792
|
+
* @param input - The current input value.
|
|
2793
|
+
* @param env - The environment.
|
|
2794
|
+
* @param tracker - Limits tracker.
|
|
2795
|
+
* @param evaluate - Recursive evaluator.
|
|
2796
|
+
*/
|
|
2797
|
+
const evalIterate = function* (node, input, env, tracker, evaluate$1) {
|
|
2798
|
+
for (const container of evaluate$1(node.target, input, env, tracker)) {
|
|
1769
2799
|
if (container === null) continue;
|
|
1770
2800
|
if (isValueArray(container)) {
|
|
1771
2801
|
for (const item of container) yield emit(item, node.span, tracker);
|
|
@@ -1779,232 +2809,508 @@ const evalIterate = function* (node, input, env, tracker) {
|
|
|
1779
2809
|
throw new RuntimeError(`Cannot iterate over ${describeType(container)}`, node.span);
|
|
1780
2810
|
}
|
|
1781
2811
|
};
|
|
1782
|
-
|
|
1783
|
-
|
|
2812
|
+
/**
|
|
2813
|
+
* Executes a `reduce` operation.
|
|
2814
|
+
*
|
|
2815
|
+
* `reduce inputs as $var (init; update)`
|
|
2816
|
+
*
|
|
2817
|
+
* @param node - The reduce AST node.
|
|
2818
|
+
* @param input - The current input value.
|
|
2819
|
+
* @param env - The environment.
|
|
2820
|
+
* @param tracker - Limits tracker.
|
|
2821
|
+
* @param evaluate - Recursive evaluator.
|
|
2822
|
+
*/
|
|
2823
|
+
const evalReduce = function* (node, input, env, tracker, evaluate$1) {
|
|
2824
|
+
const initValues = Array.from(evaluate$1(node.init, input, env, tracker));
|
|
1784
2825
|
if (initValues.length !== 1) throw new RuntimeError("Reduce init must single value", node.init.span);
|
|
1785
2826
|
let acc = initValues[0];
|
|
1786
|
-
for (const item of evaluate(node.source, input, env, tracker)) {
|
|
2827
|
+
for (const item of evaluate$1(node.source, input, env, tracker)) {
|
|
1787
2828
|
tracker.step(node.span);
|
|
1788
2829
|
pushBinding(env, node.var, item);
|
|
1789
2830
|
try {
|
|
1790
|
-
const updates = Array.from(evaluate(node.update, acc, env, tracker));
|
|
2831
|
+
const updates = Array.from(evaluate$1(node.update, acc, env, tracker));
|
|
1791
2832
|
if (updates.length !== 1) throw new RuntimeError("Reduce update must produce single value", node.update.span);
|
|
1792
2833
|
acc = updates[0];
|
|
1793
2834
|
} finally {
|
|
1794
|
-
popBinding(env);
|
|
2835
|
+
popBinding(env, node.var);
|
|
1795
2836
|
}
|
|
1796
2837
|
}
|
|
1797
2838
|
yield emit(acc, node.span, tracker);
|
|
1798
2839
|
};
|
|
1799
|
-
|
|
1800
|
-
|
|
2840
|
+
/**
|
|
2841
|
+
* Executes a `foreach` operation.
|
|
2842
|
+
*
|
|
2843
|
+
* `foreach inputs as $var (init; update; extract)`
|
|
2844
|
+
*
|
|
2845
|
+
* @param node - The foreach AST node.
|
|
2846
|
+
* @param input - The current input value.
|
|
2847
|
+
* @param env - The environment.
|
|
2848
|
+
* @param tracker - Limits tracker.
|
|
2849
|
+
* @param evaluate - Recursive evaluator.
|
|
2850
|
+
*/
|
|
2851
|
+
const evalForeach = function* (node, input, env, tracker, evaluate$1) {
|
|
2852
|
+
const initValues = Array.from(evaluate$1(node.init, input, env, tracker));
|
|
1801
2853
|
if (initValues.length !== 1) throw new RuntimeError("Foreach init must single value", node.init.span);
|
|
1802
2854
|
let acc = initValues[0];
|
|
1803
|
-
for (const item of evaluate(node.source, input, env, tracker)) {
|
|
2855
|
+
for (const item of evaluate$1(node.source, input, env, tracker)) {
|
|
1804
2856
|
tracker.step(node.span);
|
|
1805
2857
|
pushBinding(env, node.var, item);
|
|
1806
2858
|
try {
|
|
1807
|
-
const updates = Array.from(evaluate(node.update, acc, env, tracker));
|
|
2859
|
+
const updates = Array.from(evaluate$1(node.update, acc, env, tracker));
|
|
1808
2860
|
if (updates.length !== 1) throw new RuntimeError("Foreach update must produce single value", node.update.span);
|
|
1809
2861
|
acc = updates[0];
|
|
1810
|
-
if (node.extract) for (const extracted of evaluate(node.extract, acc, env, tracker)) yield emit(extracted, node.span, tracker);
|
|
2862
|
+
if (node.extract) for (const extracted of evaluate$1(node.extract, acc, env, tracker)) yield emit(extracted, node.span, tracker);
|
|
1811
2863
|
else yield emit(acc, node.span, tracker);
|
|
1812
2864
|
} finally {
|
|
1813
|
-
popBinding(env);
|
|
2865
|
+
popBinding(env, node.var);
|
|
1814
2866
|
}
|
|
1815
2867
|
}
|
|
1816
2868
|
};
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
2869
|
+
/**
|
|
2870
|
+
* Evaluates the recursive operator `..`.
|
|
2871
|
+
* Deprecated node type kept for compatibility; currently implements `..` logic.
|
|
2872
|
+
*
|
|
2873
|
+
* @param node - The recurse AST node.
|
|
2874
|
+
* @param input - The current input value.
|
|
2875
|
+
* @param env - The environment.
|
|
2876
|
+
* @param tracker - Limits tracker.
|
|
2877
|
+
* @param evaluate - Recursive evaluator.
|
|
2878
|
+
*/
|
|
2879
|
+
const evalRecurse = function* (node, input, env, tracker, evaluate$1) {
|
|
1827
2880
|
yield emit(input, node.span, tracker);
|
|
1828
|
-
|
|
1829
|
-
if (isValueArray(input))
|
|
2881
|
+
const children = [];
|
|
2882
|
+
if (isValueArray(input)) children.push(...input);
|
|
1830
2883
|
else if (isPlainObject(input)) {
|
|
1831
2884
|
const keys = Object.keys(input).sort();
|
|
1832
|
-
for (const key of keys)
|
|
2885
|
+
for (const key of keys) children.push(input[key]);
|
|
1833
2886
|
}
|
|
2887
|
+
for (const child of children) yield* evalRecurse(node, child, env, tracker, evaluate$1);
|
|
1834
2888
|
};
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
for (const
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
});
|
|
1859
|
-
partials = next;
|
|
1860
|
-
if (partials.length === 0) return;
|
|
2889
|
+
|
|
2890
|
+
//#endregion
|
|
2891
|
+
//#region src/eval/access.ts
|
|
2892
|
+
/**
|
|
2893
|
+
* Evaluates field access (`.foo`).
|
|
2894
|
+
*
|
|
2895
|
+
* @param node - The field access AST node.
|
|
2896
|
+
* @param input - The current input value.
|
|
2897
|
+
* @param env - The environment.
|
|
2898
|
+
* @param tracker - Limits tracker.
|
|
2899
|
+
* @param evaluate - Recursive evaluator.
|
|
2900
|
+
*/
|
|
2901
|
+
const evalField = function* (node, input, env, tracker, evaluate$1) {
|
|
2902
|
+
for (const container of evaluate$1(node.target, input, env, tracker)) {
|
|
2903
|
+
if (container === null) {
|
|
2904
|
+
yield emit(null, node.span, tracker);
|
|
2905
|
+
continue;
|
|
2906
|
+
}
|
|
2907
|
+
if (isPlainObject(container)) {
|
|
2908
|
+
yield emit(Object.prototype.hasOwnProperty.call(container, node.field) ? container[node.field] : null, node.span, tracker);
|
|
2909
|
+
continue;
|
|
2910
|
+
}
|
|
2911
|
+
throw new RuntimeError(`Cannot index ${describeType(container)} with string`, node.span);
|
|
1861
2912
|
}
|
|
1862
|
-
for (const obj of partials) yield emit(obj, node.span, tracker);
|
|
1863
|
-
};
|
|
1864
|
-
const resolveObjectKeys = (key, input, env, tracker) => {
|
|
1865
|
-
if (key.kind === "KeyIdentifier") return [key.name];
|
|
1866
|
-
if (key.kind === "KeyString") return [key.value];
|
|
1867
|
-
return Array.from(evaluate(key.expr, input, env, tracker)).map((value) => {
|
|
1868
|
-
if (typeof value !== "string") throw new RuntimeError("Object key expression must produce strings", key.span);
|
|
1869
|
-
return value;
|
|
1870
|
-
});
|
|
1871
2913
|
};
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
2914
|
+
/**
|
|
2915
|
+
* Evaluates index access (`.[0]`, `.["foo"]`).
|
|
2916
|
+
* Supports negative indices for arrays.
|
|
2917
|
+
*
|
|
2918
|
+
* @param node - The index access AST node.
|
|
2919
|
+
* @param input - The current input value.
|
|
2920
|
+
* @param env - The environment.
|
|
2921
|
+
* @param tracker - Limits tracker.
|
|
2922
|
+
* @param evaluate - Recursive evaluator.
|
|
2923
|
+
*/
|
|
2924
|
+
const evalIndex = function* (node, input, env, tracker, evaluate$1) {
|
|
2925
|
+
const indexValues = Array.from(evaluate$1(node.index, input, env, tracker));
|
|
2926
|
+
for (const container of evaluate$1(node.target, input, env, tracker)) {
|
|
2927
|
+
if (container === null) {
|
|
2928
|
+
yield emit(null, node.span, tracker);
|
|
2929
|
+
continue;
|
|
2930
|
+
}
|
|
2931
|
+
if (isValueArray(container)) {
|
|
2932
|
+
for (const idxValue of indexValues) {
|
|
2933
|
+
const index = ensureInteger(idxValue, node.span);
|
|
2934
|
+
const resolved = index < 0 ? container.length + index : index;
|
|
2935
|
+
if (resolved < 0 || resolved >= container.length) yield emit(null, node.span, tracker);
|
|
2936
|
+
else yield emit(container[resolved], node.span, tracker);
|
|
2937
|
+
}
|
|
2938
|
+
continue;
|
|
2939
|
+
}
|
|
2940
|
+
if (isPlainObject(container)) {
|
|
2941
|
+
for (const keyValue of indexValues) {
|
|
2942
|
+
if (typeof keyValue !== "string") throw new RuntimeError(`Cannot index object with ${describeType(keyValue)}`, node.span);
|
|
2943
|
+
yield emit(Object.prototype.hasOwnProperty.call(container, keyValue) ? container[keyValue] : null, node.span, tracker);
|
|
2944
|
+
}
|
|
2945
|
+
continue;
|
|
2946
|
+
}
|
|
2947
|
+
throw new RuntimeError(`Cannot index ${describeType(container)}`, node.span);
|
|
1891
2948
|
}
|
|
1892
2949
|
};
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
const
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
}
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
const
|
|
1919
|
-
|
|
1920
|
-
if (right === 0) throw new RuntimeError("Division by zero", span);
|
|
1921
|
-
return left / right;
|
|
1922
|
-
};
|
|
1923
|
-
const applyModulo = (left, right, span) => {
|
|
1924
|
-
if (typeof left !== "number" || typeof right !== "number") throw new RuntimeError("Modulo expects two numbers", span);
|
|
1925
|
-
if (right === 0) throw new RuntimeError("Modulo by zero", span);
|
|
1926
|
-
return left % right;
|
|
2950
|
+
/**
|
|
2951
|
+
* Evaluates array slicing (`.[start:end]`).
|
|
2952
|
+
* Supports optional start/end (defaults to 0/length) and negative indices (via JS slice).
|
|
2953
|
+
*
|
|
2954
|
+
* @param node - The slice AST node.
|
|
2955
|
+
* @param input - The current input value.
|
|
2956
|
+
* @param env - The environment.
|
|
2957
|
+
* @param tracker - Limits tracker.
|
|
2958
|
+
* @param evaluate - Recursive evaluator.
|
|
2959
|
+
*/
|
|
2960
|
+
const evalSlice = function* (node, input, env, tracker, evaluate$1) {
|
|
2961
|
+
for (const target of evaluate$1(node.target, input, env, tracker)) {
|
|
2962
|
+
if (typeof target !== "string" && !Array.isArray(target)) throw new RuntimeError("Slice expected string or array", node.span);
|
|
2963
|
+
const starts = [];
|
|
2964
|
+
if (node.start) for (const s of evaluate$1(node.start, input, env, tracker)) {
|
|
2965
|
+
if (typeof s !== "number") throw new RuntimeError("Slice start must be number", node.span);
|
|
2966
|
+
starts.push(s);
|
|
2967
|
+
}
|
|
2968
|
+
else starts.push(0);
|
|
2969
|
+
const ends = [];
|
|
2970
|
+
if (node.end) for (const e of evaluate$1(node.end, input, env, tracker)) {
|
|
2971
|
+
if (typeof e !== "number") throw new RuntimeError("Slice end must be number", node.span);
|
|
2972
|
+
ends.push(e);
|
|
2973
|
+
}
|
|
2974
|
+
else ends.push(target.length);
|
|
2975
|
+
for (const s of starts) for (const e of ends) yield emit(target.slice(s, e), node.span, tracker);
|
|
2976
|
+
}
|
|
1927
2977
|
};
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
2978
|
+
|
|
2979
|
+
//#endregion
|
|
2980
|
+
//#region src/eval/constructors.ts
|
|
2981
|
+
/**
|
|
2982
|
+
* constructs arrays from the input.
|
|
2983
|
+
*
|
|
2984
|
+
* `[.foo, .bar]` -> `[foo_val, bar_val]`
|
|
2985
|
+
* It computes the Cartesian product of all items in the array definition.
|
|
2986
|
+
*
|
|
2987
|
+
* @param node - The array definition AST node.
|
|
2988
|
+
* @param input - The current input value.
|
|
2989
|
+
* @param env - The environment.
|
|
2990
|
+
* @param tracker - Limits tracker.
|
|
2991
|
+
* @param evaluate - Recursive evaluator.
|
|
2992
|
+
*/
|
|
2993
|
+
const buildArray = function* (node, input, env, tracker, evaluate$1) {
|
|
2994
|
+
const result = [];
|
|
2995
|
+
for (const itemNode of node.items) for (const itemVal of evaluate$1(itemNode, input, env, tracker)) result.push(itemVal);
|
|
2996
|
+
yield result;
|
|
1932
2997
|
};
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
2998
|
+
/**
|
|
2999
|
+
* constructs objects from the input.
|
|
3000
|
+
*
|
|
3001
|
+
* `{a: .foo, b: .bar}`
|
|
3002
|
+
* Computes the Cartesian product of all key-value pairs.
|
|
3003
|
+
*
|
|
3004
|
+
* @param node - The object definition AST node.
|
|
3005
|
+
* @param input - The current input value.
|
|
3006
|
+
* @param env - The environment.
|
|
3007
|
+
* @param tracker - Limits tracker.
|
|
3008
|
+
* @param evaluate - Recursive evaluator.
|
|
3009
|
+
*/
|
|
3010
|
+
const buildObjects = function* (node, input, env, tracker, evaluate$1) {
|
|
3011
|
+
yield* fillObject(node.entries, 0, {}, input, env, tracker, evaluate$1);
|
|
1947
3012
|
};
|
|
1948
|
-
|
|
1949
|
-
if (
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
Object.keys(value).forEach((key) => {
|
|
1953
|
-
const child = value[key];
|
|
1954
|
-
if (child !== void 0) result[key] = cloneValue(child);
|
|
1955
|
-
});
|
|
1956
|
-
return result;
|
|
3013
|
+
function* fillObject(entries, index, current, input, env, tracker, evaluate$1) {
|
|
3014
|
+
if (index >= entries.length) {
|
|
3015
|
+
yield { ...current };
|
|
3016
|
+
return;
|
|
1957
3017
|
}
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
if (
|
|
3018
|
+
const entry = entries[index];
|
|
3019
|
+
let keys = [];
|
|
3020
|
+
if (entry.key.kind === "KeyIdentifier") keys = [entry.key.name];
|
|
3021
|
+
else if (entry.key.kind === "KeyString") keys = [entry.key.value];
|
|
3022
|
+
else for (const k of evaluate$1(entry.key.expr, input, env, tracker)) {
|
|
3023
|
+
if (typeof k !== "string") throw new RuntimeError("Object key must be a string", entry.key.span);
|
|
3024
|
+
keys.push(k);
|
|
1964
3025
|
}
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
3026
|
+
for (const key of keys) for (const val of evaluate$1(entry.value, input, env, tracker)) {
|
|
3027
|
+
current[key] = val;
|
|
3028
|
+
yield* fillObject(entries, index + 1, current, input, env, tracker, evaluate$1);
|
|
3029
|
+
delete current[key];
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
|
|
3033
|
+
//#endregion
|
|
3034
|
+
//#region src/eval/functions.ts
|
|
3035
|
+
/**
|
|
3036
|
+
* Evaluates a function call.
|
|
3037
|
+
*
|
|
3038
|
+
* Checks for:
|
|
3039
|
+
* 1. User-defined functions in the current environment stack (local scope).
|
|
3040
|
+
* 2. Standard library built-ins.
|
|
3041
|
+
*
|
|
3042
|
+
* Handles closure creation for arguments and recursion.
|
|
3043
|
+
*
|
|
3044
|
+
* @param node - The call AST node.
|
|
3045
|
+
* @param input - The current input value.
|
|
3046
|
+
* @param env - The environment.
|
|
3047
|
+
* @param tracker - Limits tracker.
|
|
3048
|
+
* @param evaluate - Recursive evaluator.
|
|
3049
|
+
*/
|
|
3050
|
+
const evalCall = function* (node, input, env, tracker, evaluate$1) {
|
|
3051
|
+
for (let i = env.length - 1; i >= 0; i--) {
|
|
3052
|
+
const funcs = env[i].funcs.get(node.name);
|
|
3053
|
+
if (funcs) {
|
|
3054
|
+
const def = funcs.find((f) => f.args.length === node.args.length);
|
|
3055
|
+
if (def) {
|
|
3056
|
+
const newFrame = {
|
|
3057
|
+
vars: /* @__PURE__ */ new Map(),
|
|
3058
|
+
funcs: /* @__PURE__ */ new Map()
|
|
3059
|
+
};
|
|
3060
|
+
for (let j = 0; j < node.args.length; j++) {
|
|
3061
|
+
const argName = def.args[j];
|
|
3062
|
+
const argBody = node.args[j];
|
|
3063
|
+
const argDefs = newFrame.funcs.get(argName) || [];
|
|
3064
|
+
argDefs.push({
|
|
3065
|
+
args: [],
|
|
3066
|
+
body: argBody,
|
|
3067
|
+
closure: env
|
|
3068
|
+
});
|
|
3069
|
+
newFrame.funcs.set(argName, argDefs);
|
|
3070
|
+
}
|
|
3071
|
+
const newStack = [...def.closure, newFrame];
|
|
3072
|
+
yield* evaluate$1(def.body, input, newStack, tracker);
|
|
3073
|
+
return;
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
const specs = builtins[node.name];
|
|
3078
|
+
if (!specs) throw new RuntimeError(`Unknown function: ${node.name}`, node.span);
|
|
3079
|
+
const builtin = specs.find((s) => s.arity === node.args.length);
|
|
3080
|
+
if (!builtin) throw new RuntimeError(`Function ${node.name} does not accept ${node.args.length} arguments`, node.span);
|
|
3081
|
+
yield* builtin.apply(input, node.args, env, tracker, evaluate$1, node.span);
|
|
1969
3082
|
};
|
|
1970
|
-
|
|
1971
|
-
|
|
3083
|
+
/**
|
|
3084
|
+
* Defines a new function in the environment.
|
|
3085
|
+
*
|
|
3086
|
+
* Adds the function definition to the current scope and executes the `next` expression
|
|
3087
|
+
* with the updated environment.
|
|
3088
|
+
*
|
|
3089
|
+
* @param node - The function definition AST node.
|
|
3090
|
+
* @param input - The current input value.
|
|
3091
|
+
* @param env - The environment.
|
|
3092
|
+
* @param tracker - Limits tracker.
|
|
3093
|
+
* @param evaluate - Recursive evaluator.
|
|
3094
|
+
*/
|
|
3095
|
+
const evalDef = function* (node, input, env, tracker, evaluate$1) {
|
|
3096
|
+
const newFrame = {
|
|
3097
|
+
vars: /* @__PURE__ */ new Map(),
|
|
3098
|
+
funcs: /* @__PURE__ */ new Map()
|
|
3099
|
+
};
|
|
3100
|
+
const currentDefs = newFrame.funcs.get(node.name) || [];
|
|
3101
|
+
const funDef = {
|
|
3102
|
+
args: node.args,
|
|
3103
|
+
body: node.body,
|
|
3104
|
+
closure: []
|
|
3105
|
+
};
|
|
3106
|
+
currentDefs.push(funDef);
|
|
3107
|
+
newFrame.funcs.set(node.name, currentDefs);
|
|
3108
|
+
const newStack = [...env, newFrame];
|
|
3109
|
+
funDef.closure = newStack;
|
|
3110
|
+
yield* evaluate$1(node.next, input, newStack, tracker);
|
|
1972
3111
|
};
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
3112
|
+
|
|
3113
|
+
//#endregion
|
|
3114
|
+
//#region src/eval/control_flow.ts
|
|
3115
|
+
/**
|
|
3116
|
+
* Evaluates an `if-then-else` expression.
|
|
3117
|
+
*
|
|
3118
|
+
* @param node - The If AST node.
|
|
3119
|
+
* @param input - The current input value.
|
|
3120
|
+
* @param env - The environment.
|
|
3121
|
+
* @param tracker - Limits tracker.
|
|
3122
|
+
* @param evaluate - Recursive evaluator.
|
|
3123
|
+
*/
|
|
3124
|
+
const evalIf = function* (node, input, env, tracker, evaluate$1) {
|
|
3125
|
+
yield* evalIfBranch(node, 0, input, env, tracker, evaluate$1);
|
|
1976
3126
|
};
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
3127
|
+
function* evalIfBranch(node, branchIndex, input, env, tracker, evaluate$1) {
|
|
3128
|
+
if (branchIndex >= node.branches.length) {
|
|
3129
|
+
yield* evaluate$1(node.else, input, env, tracker);
|
|
3130
|
+
return;
|
|
3131
|
+
}
|
|
3132
|
+
const branch = node.branches[branchIndex];
|
|
3133
|
+
for (const cond of evaluate$1(branch.cond, input, env, tracker)) if (isTruthy(cond)) yield* evaluate$1(branch.then, input, env, tracker);
|
|
3134
|
+
else yield* evalIfBranch(node, branchIndex + 1, input, env, tracker, evaluate$1);
|
|
3135
|
+
}
|
|
3136
|
+
/**
|
|
3137
|
+
* Evaluates a `try-catch` expression.
|
|
3138
|
+
*
|
|
3139
|
+
* @param node - The Try AST node.
|
|
3140
|
+
* @param input - The current input value.
|
|
3141
|
+
* @param env - The environment.
|
|
3142
|
+
* @param tracker - Limits tracker.
|
|
3143
|
+
* @param evaluate - Recursive evaluator.
|
|
3144
|
+
*/
|
|
3145
|
+
const evalTry = function* (node, input, env, tracker, evaluate$1) {
|
|
3146
|
+
try {
|
|
3147
|
+
yield* evaluate$1(node.body, input, env, tracker);
|
|
3148
|
+
} catch (err) {
|
|
3149
|
+
if (err instanceof RuntimeError) {
|
|
3150
|
+
if (node.handler) yield* evaluate$1(node.handler, err.message, env, tracker);
|
|
3151
|
+
} else throw err;
|
|
3152
|
+
}
|
|
1985
3153
|
};
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
3154
|
+
/**
|
|
3155
|
+
* Evaluates a `label` expression, establishing a target for `break`.
|
|
3156
|
+
*
|
|
3157
|
+
* @param node - The Label AST node.
|
|
3158
|
+
* @param input - The current input value.
|
|
3159
|
+
* @param env - The environment.
|
|
3160
|
+
* @param tracker - Limits tracker.
|
|
3161
|
+
* @param evaluate - Recursive evaluator.
|
|
3162
|
+
*/
|
|
3163
|
+
const evalLabel = function* (node, input, env, tracker, evaluate$1) {
|
|
3164
|
+
try {
|
|
3165
|
+
yield* evaluate$1(node.body, input, env, tracker);
|
|
3166
|
+
} catch (e) {
|
|
3167
|
+
if (e instanceof BreakSignal) {
|
|
3168
|
+
if (e.label === node.label) return;
|
|
3169
|
+
}
|
|
3170
|
+
throw e;
|
|
3171
|
+
}
|
|
1997
3172
|
};
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
3173
|
+
|
|
3174
|
+
//#endregion
|
|
3175
|
+
//#region src/eval/dispatch.ts
|
|
3176
|
+
/**
|
|
3177
|
+
* Runs a jq AST against an input value.
|
|
3178
|
+
*
|
|
3179
|
+
* @param ast - The parsed AST.
|
|
3180
|
+
* @param input - The initial input value (JSON).
|
|
3181
|
+
* @param options - Execution options.
|
|
3182
|
+
* @returns An array of all values yielded by the filter.
|
|
3183
|
+
*/
|
|
3184
|
+
const runAst = (ast, input, options = {}) => {
|
|
3185
|
+
const tracker = new LimitTracker(resolveLimits(options.limits));
|
|
3186
|
+
const env = [{
|
|
3187
|
+
vars: /* @__PURE__ */ new Map(),
|
|
3188
|
+
funcs: /* @__PURE__ */ new Map()
|
|
3189
|
+
}];
|
|
3190
|
+
return Array.from(evaluate(ast, input, env, tracker));
|
|
2007
3191
|
};
|
|
3192
|
+
/**
|
|
3193
|
+
* The core evaluation generator.
|
|
3194
|
+
*
|
|
3195
|
+
* This function dispatches execution to specific handlers based on the AST node kind.
|
|
3196
|
+
* It uses a generator to support jq's streaming nature (backtracking and multiple outputs).
|
|
3197
|
+
*
|
|
3198
|
+
* @param node - The current AST node to evaluate.
|
|
3199
|
+
* @param input - The current input value (context).
|
|
3200
|
+
* @param env - The current environment (variables and functions).
|
|
3201
|
+
* @param tracker - The limit tracker for safety.
|
|
3202
|
+
* @yields The output values produced by the filter.
|
|
3203
|
+
*/
|
|
3204
|
+
function* evaluate(node, input, env, tracker) {
|
|
3205
|
+
if (!node) throw new Error("evaluate called with undefined node");
|
|
3206
|
+
tracker.step(node.span);
|
|
3207
|
+
tracker.enter(node.span);
|
|
3208
|
+
try {
|
|
3209
|
+
switch (node.kind) {
|
|
3210
|
+
case "Identity":
|
|
3211
|
+
yield emit(input, node.span, tracker);
|
|
3212
|
+
return;
|
|
3213
|
+
case "Literal":
|
|
3214
|
+
yield emit(node.value, node.span, tracker);
|
|
3215
|
+
return;
|
|
3216
|
+
case "Var": {
|
|
3217
|
+
const val = getVar(env, node.name);
|
|
3218
|
+
if (val === void 0) throw new RuntimeError(`Undefined variable: ${node.name}`, node.span);
|
|
3219
|
+
yield emit(val, node.span, tracker);
|
|
3220
|
+
return;
|
|
3221
|
+
}
|
|
3222
|
+
case "FieldAccess":
|
|
3223
|
+
yield* evalField(node, input, env, tracker, evaluate);
|
|
3224
|
+
return;
|
|
3225
|
+
case "IndexAccess":
|
|
3226
|
+
yield* evalIndex(node, input, env, tracker, evaluate);
|
|
3227
|
+
return;
|
|
3228
|
+
case "Slice":
|
|
3229
|
+
yield* evalSlice(node, input, env, tracker, evaluate);
|
|
3230
|
+
return;
|
|
3231
|
+
case "Array":
|
|
3232
|
+
yield* buildArray(node, input, env, tracker, evaluate);
|
|
3233
|
+
return;
|
|
3234
|
+
case "Object":
|
|
3235
|
+
yield* buildObjects(node, input, env, tracker, evaluate);
|
|
3236
|
+
return;
|
|
3237
|
+
case "Label":
|
|
3238
|
+
yield* evalLabel(node, input, env, tracker, evaluate);
|
|
3239
|
+
return;
|
|
3240
|
+
case "Break": throw new BreakSignal(node.label);
|
|
3241
|
+
case "Pipe":
|
|
3242
|
+
for (const leftVal of evaluate(node.left, input, env, tracker)) yield* evaluate(node.right, leftVal, env, tracker);
|
|
3243
|
+
return;
|
|
3244
|
+
case "Comma":
|
|
3245
|
+
yield* evaluate(node.left, input, env, tracker);
|
|
3246
|
+
yield* evaluate(node.right, input, env, tracker);
|
|
3247
|
+
return;
|
|
3248
|
+
case "Alt": {
|
|
3249
|
+
const valid = Array.from(evaluate(node.left, input, env, tracker)).filter((v) => v !== null && v !== false);
|
|
3250
|
+
if (valid.length > 0) for (const v of valid) yield v;
|
|
3251
|
+
else yield* evaluate(node.right, input, env, tracker);
|
|
3252
|
+
return;
|
|
3253
|
+
}
|
|
3254
|
+
case "Unary":
|
|
3255
|
+
if (node.op === "Not") for (const value of evaluate(node.expr, input, env, tracker)) yield emit(!isTruthy(value), node.span, tracker);
|
|
3256
|
+
else for (const value of evaluate(node.expr, input, env, tracker)) yield emit(applyUnaryNeg(value, node.span), node.span, tracker);
|
|
3257
|
+
return;
|
|
3258
|
+
case "Bool":
|
|
3259
|
+
for (const l of evaluate(node.left, input, env, tracker)) if (node.op === "And") if (!isTruthy(l)) yield emit(false, node.span, tracker);
|
|
3260
|
+
else for (const r of evaluate(node.right, input, env, tracker)) yield emit(isTruthy(l) && isTruthy(r), node.span, tracker);
|
|
3261
|
+
else if (isTruthy(l)) yield emit(true, node.span, tracker);
|
|
3262
|
+
else for (const r of evaluate(node.right, input, env, tracker)) yield emit(isTruthy(l) || isTruthy(r), node.span, tracker);
|
|
3263
|
+
return;
|
|
3264
|
+
case "Binary": {
|
|
3265
|
+
const leftRes = Array.from(evaluate(node.left, input, env, tracker));
|
|
3266
|
+
const rightRes = Array.from(evaluate(node.right, input, env, tracker));
|
|
3267
|
+
for (const l of leftRes) for (const r of rightRes) yield emit(applyBinaryOp(node.op, l, r, node.span), node.span, tracker);
|
|
3268
|
+
return;
|
|
3269
|
+
}
|
|
3270
|
+
case "If":
|
|
3271
|
+
yield* evalIf(node, input, env, tracker, evaluate);
|
|
3272
|
+
return;
|
|
3273
|
+
case "Try":
|
|
3274
|
+
yield* evalTry(node, input, env, tracker, evaluate);
|
|
3275
|
+
return;
|
|
3276
|
+
case "Recurse":
|
|
3277
|
+
yield* evalRecurse(node, input, env, tracker, evaluate);
|
|
3278
|
+
return;
|
|
3279
|
+
case "Iterate":
|
|
3280
|
+
yield* evalIterate(node, input, env, tracker, evaluate);
|
|
3281
|
+
return;
|
|
3282
|
+
case "Assignment":
|
|
3283
|
+
yield* evalAssignment(node, input, env, tracker, evaluate);
|
|
3284
|
+
return;
|
|
3285
|
+
case "Reduce":
|
|
3286
|
+
yield* evalReduce(node, input, env, tracker, evaluate);
|
|
3287
|
+
return;
|
|
3288
|
+
case "Foreach":
|
|
3289
|
+
yield* evalForeach(node, input, env, tracker, evaluate);
|
|
3290
|
+
return;
|
|
3291
|
+
case "As": {
|
|
3292
|
+
const values = Array.from(evaluate(node.bind, input, env, tracker));
|
|
3293
|
+
for (const val of values) {
|
|
3294
|
+
const newFrame = {
|
|
3295
|
+
vars: new Map([[node.name, val]]),
|
|
3296
|
+
funcs: /* @__PURE__ */ new Map()
|
|
3297
|
+
};
|
|
3298
|
+
const newEnv = [...env, newFrame];
|
|
3299
|
+
yield* evaluate(node.body, input, newEnv, tracker);
|
|
3300
|
+
}
|
|
3301
|
+
return;
|
|
3302
|
+
}
|
|
3303
|
+
case "Call":
|
|
3304
|
+
yield* evalCall(node, input, env, tracker, evaluate);
|
|
3305
|
+
return;
|
|
3306
|
+
case "Def":
|
|
3307
|
+
yield* evalDef(node, input, env, tracker, evaluate);
|
|
3308
|
+
return;
|
|
3309
|
+
}
|
|
3310
|
+
} finally {
|
|
3311
|
+
tracker.exit();
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
2008
3314
|
|
|
2009
3315
|
//#endregion
|
|
2010
3316
|
//#region src/index.ts
|