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