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