@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 +83 -42
- package/dist/index.mjs +83 -42
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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 =
|
|
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
|
-
|
|
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.
|
|
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"
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
3103
|
+
* @param value - The value to convert.
|
|
3071
3104
|
* @param span - To report error.
|
|
3072
|
-
* @returns The integer
|
|
3105
|
+
* @returns The integer index or null.
|
|
3073
3106
|
*/
|
|
3074
|
-
const
|
|
3075
|
-
if (
|
|
3076
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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"
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
3102
|
+
* @param value - The value to convert.
|
|
3070
3103
|
* @param span - To report error.
|
|
3071
|
-
* @returns The integer
|
|
3104
|
+
* @returns The integer index or null.
|
|
3072
3105
|
*/
|
|
3073
|
-
const
|
|
3074
|
-
if (
|
|
3075
|
-
|
|
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 =
|
|
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);
|