@gabrielbryk/jq-ts 1.3.4 → 1.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -279,7 +279,7 @@ const lex = (text) => {
279
279
  const raw = text.slice(start, pos);
280
280
  const keyword = keywordKinds[raw];
281
281
  if (keyword === "Null" || keyword === "True" || keyword === "False") pushToken(keyword, start, pos);
282
- else if (keyword) pushToken(keyword, start, pos);
282
+ else if (keyword) pushToken(keyword, start, pos, raw);
283
283
  else pushToken("Identifier", start, pos, raw);
284
284
  continue;
285
285
  }
@@ -445,7 +445,7 @@ var Parser = class {
445
445
  span: spanBetween(start.span, body.span)
446
446
  };
447
447
  }
448
- let expr = allowComma ? this.parseComma() : this.parseAssignment();
448
+ let expr = this.parsePipe(allowComma);
449
449
  while (this.match("As")) {
450
450
  const varToken = this.consume("Variable", "Expected variable name after \"as\"");
451
451
  this.consume("Pipe", "Expected \"|\" after variable binding");
@@ -460,8 +460,22 @@ var Parser = class {
460
460
  }
461
461
  return expr;
462
462
  }
463
- parseComma() {
463
+ parsePipe(allowComma = true) {
464
+ let expr = this.parseComma(allowComma);
465
+ while (this.match("Pipe")) {
466
+ const right = this.parseComma(allowComma);
467
+ expr = {
468
+ kind: "Pipe",
469
+ left: expr,
470
+ right,
471
+ span: spanBetween(expr.span, right.span)
472
+ };
473
+ }
474
+ return expr;
475
+ }
476
+ parseComma(allowComma = true) {
464
477
  let expr = this.parseAssignment();
478
+ if (!allowComma) return expr;
465
479
  while (this.match("Comma")) {
466
480
  const right = this.parseAssignment();
467
481
  expr = {
@@ -474,7 +488,7 @@ var Parser = class {
474
488
  return expr;
475
489
  }
476
490
  parseAssignment() {
477
- const expr = this.parsePipe();
491
+ const expr = this.parseAlt();
478
492
  if (this.match("Eq") || this.match("BarEq") || this.match("PlusEq") || this.match("MinusEq") || this.match("StarEq") || this.match("SlashEq") || this.match("PercentEq") || this.match("AltEq")) {
479
493
  const opToken = this.previous();
480
494
  const right = this.parseAssignment();
@@ -516,19 +530,6 @@ var Parser = class {
516
530
  }
517
531
  return expr;
518
532
  }
519
- parsePipe() {
520
- let expr = this.parsePipeOperand();
521
- while (this.match("Pipe")) {
522
- const right = this.parsePipeOperand();
523
- expr = {
524
- kind: "Pipe",
525
- left: expr,
526
- right,
527
- span: spanBetween(expr.span, right.span)
528
- };
529
- }
530
- return expr;
531
- }
532
533
  parsePipeOperand() {
533
534
  return this.parseAlt();
534
535
  }
@@ -622,16 +623,6 @@ var Parser = class {
622
623
  return expr;
623
624
  }
624
625
  parseUnary() {
625
- if (this.match("Not")) {
626
- const op = this.previous();
627
- const expr = this.parseUnary();
628
- return {
629
- kind: "Unary",
630
- op: "Not",
631
- expr,
632
- span: spanBetween(op.span, expr.span)
633
- };
634
- }
635
626
  if (this.match("Minus")) {
636
627
  const op = this.previous();
637
628
  const expr = this.parseUnary();
@@ -748,11 +739,8 @@ var Parser = class {
748
739
  kind: "Recurse",
749
740
  span: this.previous().span
750
741
  };
751
- if (this.match("DotDot")) return {
752
- kind: "Recurse",
753
- span: this.previous().span
754
- };
755
742
  if (this.match("Break")) return this.parseBreak(this.previous());
743
+ if (this.match("Not")) return this.finishIdentifier(this.previous());
756
744
  throw this.error(this.peek(), "Unexpected token");
757
745
  }
758
746
  parseBreak(start) {
@@ -1306,7 +1294,10 @@ const emit$1 = (value, span, tracker) => {
1306
1294
  return value;
1307
1295
  };
1308
1296
  const ensureIndex = (val) => {
1309
- if (typeof val === "number" && Number.isInteger(val)) return val;
1297
+ if (typeof val === "number") {
1298
+ if (!Number.isFinite(val)) return void 0;
1299
+ return Math.trunc(val);
1300
+ }
1310
1301
  if (typeof val === "string" && /^-?\d+$/.test(val)) return parseInt(val, 10);
1311
1302
  };
1312
1303
  const stableStringify = (value) => {
@@ -1378,6 +1369,13 @@ const stdBuiltins = [
1378
1369
  yield emit$1(isTruthy(input), span, tracker);
1379
1370
  }
1380
1371
  },
1372
+ {
1373
+ name: "not",
1374
+ arity: 0,
1375
+ apply: function* (input, _args, _env, tracker, _eval, span) {
1376
+ yield emit$1(!isTruthy(input), span, tracker);
1377
+ }
1378
+ },
1381
1379
  {
1382
1380
  name: "walk",
1383
1381
  arity: 1,
@@ -2119,6 +2117,29 @@ const evaluatePath = function* (node, input, env, tracker, evaluate$1) {
2119
2117
  return;
2120
2118
  }
2121
2119
  throw new RuntimeError(`Function ${node.name} not supported in path expression`, node.span);
2120
+ case "Slice": {
2121
+ const parentPaths = Array.from(evaluatePath(node.target, input, env, tracker, evaluate$1));
2122
+ const startRes = node.start ? Array.from(evaluate$1(node.start, input, env, tracker)) : [null];
2123
+ const endRes = node.end ? Array.from(evaluate$1(node.end, input, env, tracker)) : [null];
2124
+ for (const startVal of startRes) for (const endVal of endRes) {
2125
+ const sliceSpec = {
2126
+ start: typeof startVal === "number" ? startVal : null,
2127
+ end: typeof endVal === "number" ? endVal : null
2128
+ };
2129
+ for (const p of parentPaths) yield [...p, sliceSpec];
2130
+ }
2131
+ return;
2132
+ }
2133
+ case "Try":
2134
+ try {
2135
+ yield* evaluatePath(node.body, input, env, tracker, evaluate$1);
2136
+ } catch (e) {
2137
+ if (!(e instanceof RuntimeError)) throw e;
2138
+ }
2139
+ return;
2140
+ case "Var":
2141
+ yield [];
2142
+ return;
2122
2143
  default: throw new RuntimeError("Invalid path expression", node.span);
2123
2144
  }
2124
2145
  };
@@ -2139,11 +2160,11 @@ function* traversePaths(root, currentPath, span, tracker) {
2139
2160
  }
2140
2161
  const isPath = (val) => {
2141
2162
  if (!Array.isArray(val)) return false;
2142
- return val.every((p) => typeof p === "string" || typeof p === "number" && Number.isInteger(p));
2163
+ return val.every((p) => typeof p === "string" || typeof p === "number" && Number.isInteger(p) || isPlainObject(p) && ("start" in p || "end" in p));
2143
2164
  };
2144
2165
  const ensurePath = (val, span) => {
2145
2166
  if (isPath(val)) return val;
2146
- throw new RuntimeError("Path must be an array of strings or integers", span);
2167
+ throw new RuntimeError("Path must be an array of strings, integers, or slice objects", span);
2147
2168
  };
2148
2169
  const getPath = (root, path) => {
2149
2170
  let curr = root;
@@ -2190,7 +2211,17 @@ const updatePath = (root, path, updateFn, span, depth = 0) => {
2190
2211
  } else arr[idx] = newVal;
2191
2212
  return arr;
2192
2213
  }
2193
- throw new RuntimeError(`Path segment must be string or integer`, span);
2214
+ if (typeof head === "object" && head !== null) {
2215
+ if (!Array.isArray(root)) throw new RuntimeError(`Cannot slice ${describeType(root)}`, span);
2216
+ const arr = [...root];
2217
+ const start = head.start ?? 0;
2218
+ const end = head.end ?? arr.length;
2219
+ const newSliceVal = updateFn(arr.slice(start, end));
2220
+ if (!Array.isArray(newSliceVal)) throw new RuntimeError("Assignment to a slice must be an array", span);
2221
+ arr.splice(start, end - start, ...newSliceVal);
2222
+ return arr;
2223
+ }
2224
+ throw new RuntimeError(`Path segment must be string, integer, or slice object`, span);
2194
2225
  };
2195
2226
  const deletePaths = (root, paths, span) => {
2196
2227
  if (paths.some((p) => p.length === 0)) return null;
@@ -3065,15 +3096,21 @@ const emit = (value, span, tracker) => {
3065
3096
  return value;
3066
3097
  };
3067
3098
  /**
3068
- * Ensures a value is an integer. Throws a RuntimeError otherwise.
3099
+ * Converts a value to an array index.
3100
+ * Truncates floats to integers. Returns null if input is null.
3101
+ * Throws a RuntimeError for non-numeric types.
3069
3102
  *
3070
- * @param value - The value to check.
3103
+ * @param value - The value to convert.
3071
3104
  * @param span - To report error.
3072
- * @returns The integer value.
3105
+ * @returns The integer index or null.
3073
3106
  */
3074
- const ensureInteger = (value, span) => {
3075
- if (typeof value === "number" && Number.isInteger(value)) return value;
3076
- throw new RuntimeError("Expected integer", span);
3107
+ const toIndex = (value, span) => {
3108
+ if (value === null) return null;
3109
+ if (typeof value === "number") {
3110
+ if (!Number.isFinite(value)) return null;
3111
+ return Math.trunc(value);
3112
+ }
3113
+ throw new RuntimeError("Expected numeric index", span);
3077
3114
  };
3078
3115
 
3079
3116
  //#endregion
@@ -3301,7 +3338,11 @@ const evalIndex = function* (node, input, env, tracker, evaluate$1) {
3301
3338
  }
3302
3339
  if (isValueArray(container)) {
3303
3340
  for (const idxValue of indexValues) {
3304
- const index = ensureInteger(idxValue, node.span);
3341
+ const index = toIndex(idxValue, node.span);
3342
+ if (index === null) {
3343
+ yield emit(null, node.span, tracker);
3344
+ continue;
3345
+ }
3305
3346
  const resolved = index < 0 ? container.length + index : index;
3306
3347
  if (resolved < 0 || resolved >= container.length) yield emit(null, node.span, tracker);
3307
3348
  else yield emit(container[resolved], node.span, tracker);
package/dist/index.mjs CHANGED
@@ -278,7 +278,7 @@ const lex = (text) => {
278
278
  const raw = text.slice(start, pos);
279
279
  const keyword = keywordKinds[raw];
280
280
  if (keyword === "Null" || keyword === "True" || keyword === "False") pushToken(keyword, start, pos);
281
- else if (keyword) pushToken(keyword, start, pos);
281
+ else if (keyword) pushToken(keyword, start, pos, raw);
282
282
  else pushToken("Identifier", start, pos, raw);
283
283
  continue;
284
284
  }
@@ -444,7 +444,7 @@ var Parser = class {
444
444
  span: spanBetween(start.span, body.span)
445
445
  };
446
446
  }
447
- let expr = allowComma ? this.parseComma() : this.parseAssignment();
447
+ let expr = this.parsePipe(allowComma);
448
448
  while (this.match("As")) {
449
449
  const varToken = this.consume("Variable", "Expected variable name after \"as\"");
450
450
  this.consume("Pipe", "Expected \"|\" after variable binding");
@@ -459,8 +459,22 @@ var Parser = class {
459
459
  }
460
460
  return expr;
461
461
  }
462
- parseComma() {
462
+ parsePipe(allowComma = true) {
463
+ let expr = this.parseComma(allowComma);
464
+ while (this.match("Pipe")) {
465
+ const right = this.parseComma(allowComma);
466
+ expr = {
467
+ kind: "Pipe",
468
+ left: expr,
469
+ right,
470
+ span: spanBetween(expr.span, right.span)
471
+ };
472
+ }
473
+ return expr;
474
+ }
475
+ parseComma(allowComma = true) {
463
476
  let expr = this.parseAssignment();
477
+ if (!allowComma) return expr;
464
478
  while (this.match("Comma")) {
465
479
  const right = this.parseAssignment();
466
480
  expr = {
@@ -473,7 +487,7 @@ var Parser = class {
473
487
  return expr;
474
488
  }
475
489
  parseAssignment() {
476
- const expr = this.parsePipe();
490
+ const expr = this.parseAlt();
477
491
  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
492
  const opToken = this.previous();
479
493
  const right = this.parseAssignment();
@@ -515,19 +529,6 @@ var Parser = class {
515
529
  }
516
530
  return expr;
517
531
  }
518
- parsePipe() {
519
- let expr = this.parsePipeOperand();
520
- while (this.match("Pipe")) {
521
- const right = this.parsePipeOperand();
522
- expr = {
523
- kind: "Pipe",
524
- left: expr,
525
- right,
526
- span: spanBetween(expr.span, right.span)
527
- };
528
- }
529
- return expr;
530
- }
531
532
  parsePipeOperand() {
532
533
  return this.parseAlt();
533
534
  }
@@ -621,16 +622,6 @@ var Parser = class {
621
622
  return expr;
622
623
  }
623
624
  parseUnary() {
624
- if (this.match("Not")) {
625
- const op = this.previous();
626
- const expr = this.parseUnary();
627
- return {
628
- kind: "Unary",
629
- op: "Not",
630
- expr,
631
- span: spanBetween(op.span, expr.span)
632
- };
633
- }
634
625
  if (this.match("Minus")) {
635
626
  const op = this.previous();
636
627
  const expr = this.parseUnary();
@@ -747,11 +738,8 @@ var Parser = class {
747
738
  kind: "Recurse",
748
739
  span: this.previous().span
749
740
  };
750
- if (this.match("DotDot")) return {
751
- kind: "Recurse",
752
- span: this.previous().span
753
- };
754
741
  if (this.match("Break")) return this.parseBreak(this.previous());
742
+ if (this.match("Not")) return this.finishIdentifier(this.previous());
755
743
  throw this.error(this.peek(), "Unexpected token");
756
744
  }
757
745
  parseBreak(start) {
@@ -1305,7 +1293,10 @@ const emit$1 = (value, span, tracker) => {
1305
1293
  return value;
1306
1294
  };
1307
1295
  const ensureIndex = (val) => {
1308
- if (typeof val === "number" && Number.isInteger(val)) return val;
1296
+ if (typeof val === "number") {
1297
+ if (!Number.isFinite(val)) return void 0;
1298
+ return Math.trunc(val);
1299
+ }
1309
1300
  if (typeof val === "string" && /^-?\d+$/.test(val)) return parseInt(val, 10);
1310
1301
  };
1311
1302
  const stableStringify = (value) => {
@@ -1377,6 +1368,13 @@ const stdBuiltins = [
1377
1368
  yield emit$1(isTruthy(input), span, tracker);
1378
1369
  }
1379
1370
  },
1371
+ {
1372
+ name: "not",
1373
+ arity: 0,
1374
+ apply: function* (input, _args, _env, tracker, _eval, span) {
1375
+ yield emit$1(!isTruthy(input), span, tracker);
1376
+ }
1377
+ },
1380
1378
  {
1381
1379
  name: "walk",
1382
1380
  arity: 1,
@@ -2118,6 +2116,29 @@ const evaluatePath = function* (node, input, env, tracker, evaluate$1) {
2118
2116
  return;
2119
2117
  }
2120
2118
  throw new RuntimeError(`Function ${node.name} not supported in path expression`, node.span);
2119
+ case "Slice": {
2120
+ const parentPaths = Array.from(evaluatePath(node.target, input, env, tracker, evaluate$1));
2121
+ const startRes = node.start ? Array.from(evaluate$1(node.start, input, env, tracker)) : [null];
2122
+ const endRes = node.end ? Array.from(evaluate$1(node.end, input, env, tracker)) : [null];
2123
+ for (const startVal of startRes) for (const endVal of endRes) {
2124
+ const sliceSpec = {
2125
+ start: typeof startVal === "number" ? startVal : null,
2126
+ end: typeof endVal === "number" ? endVal : null
2127
+ };
2128
+ for (const p of parentPaths) yield [...p, sliceSpec];
2129
+ }
2130
+ return;
2131
+ }
2132
+ case "Try":
2133
+ try {
2134
+ yield* evaluatePath(node.body, input, env, tracker, evaluate$1);
2135
+ } catch (e) {
2136
+ if (!(e instanceof RuntimeError)) throw e;
2137
+ }
2138
+ return;
2139
+ case "Var":
2140
+ yield [];
2141
+ return;
2121
2142
  default: throw new RuntimeError("Invalid path expression", node.span);
2122
2143
  }
2123
2144
  };
@@ -2138,11 +2159,11 @@ function* traversePaths(root, currentPath, span, tracker) {
2138
2159
  }
2139
2160
  const isPath = (val) => {
2140
2161
  if (!Array.isArray(val)) return false;
2141
- return val.every((p) => typeof p === "string" || typeof p === "number" && Number.isInteger(p));
2162
+ return val.every((p) => typeof p === "string" || typeof p === "number" && Number.isInteger(p) || isPlainObject(p) && ("start" in p || "end" in p));
2142
2163
  };
2143
2164
  const ensurePath = (val, span) => {
2144
2165
  if (isPath(val)) return val;
2145
- throw new RuntimeError("Path must be an array of strings or integers", span);
2166
+ throw new RuntimeError("Path must be an array of strings, integers, or slice objects", span);
2146
2167
  };
2147
2168
  const getPath = (root, path) => {
2148
2169
  let curr = root;
@@ -2189,7 +2210,17 @@ const updatePath = (root, path, updateFn, span, depth = 0) => {
2189
2210
  } else arr[idx] = newVal;
2190
2211
  return arr;
2191
2212
  }
2192
- throw new RuntimeError(`Path segment must be string or integer`, span);
2213
+ if (typeof head === "object" && head !== null) {
2214
+ if (!Array.isArray(root)) throw new RuntimeError(`Cannot slice ${describeType(root)}`, span);
2215
+ const arr = [...root];
2216
+ const start = head.start ?? 0;
2217
+ const end = head.end ?? arr.length;
2218
+ const newSliceVal = updateFn(arr.slice(start, end));
2219
+ if (!Array.isArray(newSliceVal)) throw new RuntimeError("Assignment to a slice must be an array", span);
2220
+ arr.splice(start, end - start, ...newSliceVal);
2221
+ return arr;
2222
+ }
2223
+ throw new RuntimeError(`Path segment must be string, integer, or slice object`, span);
2193
2224
  };
2194
2225
  const deletePaths = (root, paths, span) => {
2195
2226
  if (paths.some((p) => p.length === 0)) return null;
@@ -3064,15 +3095,21 @@ const emit = (value, span, tracker) => {
3064
3095
  return value;
3065
3096
  };
3066
3097
  /**
3067
- * Ensures a value is an integer. Throws a RuntimeError otherwise.
3098
+ * Converts a value to an array index.
3099
+ * Truncates floats to integers. Returns null if input is null.
3100
+ * Throws a RuntimeError for non-numeric types.
3068
3101
  *
3069
- * @param value - The value to check.
3102
+ * @param value - The value to convert.
3070
3103
  * @param span - To report error.
3071
- * @returns The integer value.
3104
+ * @returns The integer index or null.
3072
3105
  */
3073
- const ensureInteger = (value, span) => {
3074
- if (typeof value === "number" && Number.isInteger(value)) return value;
3075
- throw new RuntimeError("Expected integer", span);
3106
+ const toIndex = (value, span) => {
3107
+ if (value === null) return null;
3108
+ if (typeof value === "number") {
3109
+ if (!Number.isFinite(value)) return null;
3110
+ return Math.trunc(value);
3111
+ }
3112
+ throw new RuntimeError("Expected numeric index", span);
3076
3113
  };
3077
3114
 
3078
3115
  //#endregion
@@ -3300,7 +3337,11 @@ const evalIndex = function* (node, input, env, tracker, evaluate$1) {
3300
3337
  }
3301
3338
  if (isValueArray(container)) {
3302
3339
  for (const idxValue of indexValues) {
3303
- const index = ensureInteger(idxValue, node.span);
3340
+ const index = toIndex(idxValue, node.span);
3341
+ if (index === null) {
3342
+ yield emit(null, node.span, tracker);
3343
+ continue;
3344
+ }
3304
3345
  const resolved = index < 0 ? container.length + index : index;
3305
3346
  if (resolved < 0 || resolved >= container.length) yield emit(null, node.span, tracker);
3306
3347
  else yield emit(container[resolved], node.span, tracker);