@elaraai/east 1.0.84 → 1.0.85
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/src/analyze.d.ts.map +1 -1
- package/dist/src/analyze.js +48 -0
- package/dist/src/analyze.js.map +1 -1
- package/dist/src/ast_to_ir.coerce_to.spec.js +4 -4
- package/dist/src/ast_to_ir.coerce_to.spec.js.map +1 -1
- package/dist/src/ast_to_ir.d.ts +6 -1
- package/dist/src/ast_to_ir.d.ts.map +1 -1
- package/dist/src/ast_to_ir.js +20 -7
- package/dist/src/ast_to_ir.js.map +1 -1
- package/dist/src/builtins.d.ts +1 -1
- package/dist/src/builtins.d.ts.map +1 -1
- package/dist/src/builtins.js +52 -0
- package/dist/src/builtins.js.map +1 -1
- package/dist/src/codegen/canonical.d.ts +37 -0
- package/dist/src/codegen/canonical.d.ts.map +1 -0
- package/dist/src/codegen/canonical.js +208 -0
- package/dist/src/codegen/canonical.js.map +1 -0
- package/dist/src/codegen/codegen.spec.js +132 -201
- package/dist/src/codegen/codegen.spec.js.map +1 -1
- package/dist/src/codegen/printer.d.ts.map +1 -1
- package/dist/src/codegen/printer.js +142 -24
- package/dist/src/codegen/printer.js.map +1 -1
- package/dist/src/codegen/spellings.d.ts +4 -1
- package/dist/src/codegen/spellings.d.ts.map +1 -1
- package/dist/src/codegen/spellings.js +5 -1
- package/dist/src/codegen/spellings.js.map +1 -1
- package/dist/src/compile/builtins/index.d.ts.map +1 -1
- package/dist/src/compile/builtins/index.js +2 -0
- package/dist/src/compile/builtins/index.js.map +1 -1
- package/dist/src/compile/builtins/primitives.d.ts.map +1 -1
- package/dist/src/compile/builtins/primitives.js +2 -1
- package/dist/src/compile/builtins/primitives.js.map +1 -1
- package/dist/src/compile/builtins/query.d.ts +10 -0
- package/dist/src/compile/builtins/query.d.ts.map +1 -0
- package/dist/src/compile/builtins/query.js +5 -0
- package/dist/src/compile/builtins/query.js.map +1 -0
- package/dist/src/expr/block.d.ts.map +1 -1
- package/dist/src/expr/block.js +2 -1
- package/dist/src/expr/block.js.map +1 -1
- package/dist/src/expr/index.d.ts +75 -47
- package/dist/src/expr/index.d.ts.map +1 -1
- package/dist/src/expr/index.js +31 -0
- package/dist/src/expr/index.js.map +1 -1
- package/dist/src/expr/libs/datetime.d.ts +20 -20
- package/dist/src/expr/libs/datetime.d.ts.map +1 -1
- package/dist/src/expr/libs/datetime.js +208 -167
- package/dist/src/expr/libs/datetime.js.map +1 -1
- package/dist/src/expr/libs/float.d.ts +15 -15
- package/dist/src/expr/libs/float.d.ts.map +1 -1
- package/dist/src/expr/libs/float.js +298 -267
- package/dist/src/expr/libs/float.js.map +1 -1
- package/dist/src/expr/libs/integer.d.ts +12 -12
- package/dist/src/expr/libs/integer.d.ts.map +1 -1
- package/dist/src/expr/libs/integer.js +219 -194
- package/dist/src/expr/libs/integer.js.map +1 -1
- package/dist/src/expr/libs/lazy.d.ts +28 -0
- package/dist/src/expr/libs/lazy.d.ts.map +1 -0
- package/dist/src/expr/libs/lazy.js +55 -0
- package/dist/src/expr/libs/lazy.js.map +1 -0
- package/dist/src/expr/libs/lazy.spec.d.ts +6 -0
- package/dist/src/expr/libs/lazy.spec.d.ts.map +1 -0
- package/dist/src/expr/libs/lazy.spec.js +102 -0
- package/dist/src/expr/libs/lazy.spec.js.map +1 -0
- package/dist/src/expr/query.d.ts +60 -0
- package/dist/src/expr/query.d.ts.map +1 -0
- package/dist/src/expr/query.js +84 -0
- package/dist/src/expr/query.js.map +1 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/internal.d.ts +4 -0
- package/dist/src/internal.d.ts.map +1 -1
- package/dist/src/internal.js +9 -0
- package/dist/src/internal.js.map +1 -1
- package/dist/src/location.d.ts +22 -0
- package/dist/src/location.d.ts.map +1 -1
- package/dist/src/location.js +31 -0
- package/dist/src/location.js.map +1 -1
- package/dist/src/naming.d.ts +5 -3
- package/dist/src/naming.d.ts.map +1 -1
- package/dist/src/naming.js.map +1 -1
- package/dist/src/portable.spec.d.ts +6 -0
- package/dist/src/portable.spec.d.ts.map +1 -0
- package/dist/src/portable.spec.js +181 -0
- package/dist/src/portable.spec.js.map +1 -0
- package/dist/src/query/evaluate.d.ts +98 -0
- package/dist/src/query/evaluate.d.ts.map +1 -0
- package/dist/src/query/evaluate.js +193 -0
- package/dist/src/query/evaluate.js.map +1 -0
- package/dist/src/query/index.d.ts +17 -0
- package/dist/src/query/index.d.ts.map +1 -0
- package/dist/src/query/index.js +17 -0
- package/dist/src/query/index.js.map +1 -0
- package/dist/src/query/jq/catalog.d.ts +174 -0
- package/dist/src/query/jq/catalog.d.ts.map +1 -0
- package/dist/src/query/jq/catalog.js +1740 -0
- package/dist/src/query/jq/catalog.js.map +1 -0
- package/dist/src/query/jq/check.d.ts +210 -0
- package/dist/src/query/jq/check.d.ts.map +1 -0
- package/dist/src/query/jq/check.js +2261 -0
- package/dist/src/query/jq/check.js.map +1 -0
- package/dist/src/query/jq/complete.d.ts +89 -0
- package/dist/src/query/jq/complete.d.ts.map +1 -0
- package/dist/src/query/jq/complete.js +320 -0
- package/dist/src/query/jq/complete.js.map +1 -0
- package/dist/src/query/jq/describe.d.ts +53 -0
- package/dist/src/query/jq/describe.d.ts.map +1 -0
- package/dist/src/query/jq/describe.js +149 -0
- package/dist/src/query/jq/describe.js.map +1 -0
- package/dist/src/query/jq/lex.d.ts +118 -0
- package/dist/src/query/jq/lex.d.ts.map +1 -0
- package/dist/src/query/jq/lex.js +371 -0
- package/dist/src/query/jq/lex.js.map +1 -0
- package/dist/src/query/jq/literals.d.ts +49 -0
- package/dist/src/query/jq/literals.d.ts.map +1 -0
- package/dist/src/query/jq/literals.js +78 -0
- package/dist/src/query/jq/literals.js.map +1 -0
- package/dist/src/query/jq/messages.d.ts +126 -0
- package/dist/src/query/jq/messages.d.ts.map +1 -0
- package/dist/src/query/jq/messages.js +172 -0
- package/dist/src/query/jq/messages.js.map +1 -0
- package/dist/src/query/jq/parse.d.ts +70 -0
- package/dist/src/query/jq/parse.d.ts.map +1 -0
- package/dist/src/query/jq/parse.js +1013 -0
- package/dist/src/query/jq/parse.js.map +1 -0
- package/dist/src/query/jq/print.d.ts +59 -0
- package/dist/src/query/jq/print.d.ts.map +1 -0
- package/dist/src/query/jq/print.js +587 -0
- package/dist/src/query/jq/print.js.map +1 -0
- package/dist/src/query/jq/shapes.d.ts +345 -0
- package/dist/src/query/jq/shapes.d.ts.map +1 -0
- package/dist/src/query/jq/shapes.js +584 -0
- package/dist/src/query/jq/shapes.js.map +1 -0
- package/dist/src/query/jq/spans.d.ts +133 -0
- package/dist/src/query/jq/spans.d.ts.map +1 -0
- package/dist/src/query/jq/spans.js +195 -0
- package/dist/src/query/jq/spans.js.map +1 -0
- package/dist/src/query/jq/split.d.ts +334 -0
- package/dist/src/query/jq/split.d.ts.map +1 -0
- package/dist/src/query/jq/split.js +2108 -0
- package/dist/src/query/jq/split.js.map +1 -0
- package/dist/src/query/jq/strftime.d.ts +20 -0
- package/dist/src/query/jq/strftime.d.ts.map +1 -0
- package/dist/src/query/jq/strftime.js +65 -0
- package/dist/src/query/jq/strftime.js.map +1 -0
- package/dist/src/query/jq/summary.d.ts +139 -0
- package/dist/src/query/jq/summary.d.ts.map +1 -0
- package/dist/src/query/jq/summary.js +222 -0
- package/dist/src/query/jq/summary.js.map +1 -0
- package/dist/src/query/jq/translate-builtins.d.ts +21 -0
- package/dist/src/query/jq/translate-builtins.d.ts.map +1 -0
- package/dist/src/query/jq/translate-builtins.js +1873 -0
- package/dist/src/query/jq/translate-builtins.js.map +1 -0
- package/dist/src/query/jq/translate.d.ts +627 -0
- package/dist/src/query/jq/translate.d.ts.map +1 -0
- package/dist/src/query/jq/translate.js +3364 -0
- package/dist/src/query/jq/translate.js.map +1 -0
- package/dist/src/query/types.d.ts +505 -0
- package/dist/src/query/types.d.ts.map +1 -0
- package/dist/src/query/types.js +231 -0
- package/dist/src/query/types.js.map +1 -0
- package/dist/src/runner_exec.d.ts +134 -0
- package/dist/src/runner_exec.d.ts.map +1 -0
- package/dist/src/runner_exec.js +371 -0
- package/dist/src/runner_exec.js.map +1 -0
- package/dist/src/runner_exec.spec.d.ts +6 -0
- package/dist/src/runner_exec.spec.d.ts.map +1 -0
- package/dist/src/runner_exec.spec.js +198 -0
- package/dist/src/runner_exec.spec.js.map +1 -0
- package/dist/src/runner_io.d.ts +370 -0
- package/dist/src/runner_io.d.ts.map +1 -0
- package/dist/src/runner_io.js +563 -0
- package/dist/src/runner_io.js.map +1 -0
- package/dist/src/runner_io.spec.d.ts +6 -0
- package/dist/src/runner_io.spec.d.ts.map +1 -0
- package/dist/src/runner_io.spec.js +224 -0
- package/dist/src/runner_io.spec.js.map +1 -0
- package/dist/src/runner_merge.d.ts +95 -0
- package/dist/src/runner_merge.d.ts.map +1 -0
- package/dist/src/runner_merge.js +238 -0
- package/dist/src/runner_merge.js.map +1 -0
- package/dist/src/runner_protocol.d.ts +11 -4
- package/dist/src/runner_protocol.d.ts.map +1 -1
- package/dist/src/runner_protocol.js +11 -4
- package/dist/src/runner_protocol.js.map +1 -1
- package/dist/src/serialization/beast2/index.d.ts +1 -1
- package/dist/src/serialization/beast2/index.d.ts.map +1 -1
- package/dist/src/serialization/beast2/index.js.map +1 -1
- package/dist/src/serialization/beast2/v5/frames.d.ts +10 -6
- package/dist/src/serialization/beast2/v5/frames.d.ts.map +1 -1
- package/dist/src/serialization/beast2/v5/frames.js +10 -6
- package/dist/src/serialization/beast2/v5/frames.js.map +1 -1
- package/dist/src/serialization/beast2/v5/frames.spec.d.ts +6 -0
- package/dist/src/serialization/beast2/v5/frames.spec.d.ts.map +1 -0
- package/dist/src/serialization/beast2/v5/frames.spec.js +42 -0
- package/dist/src/serialization/beast2/v5/frames.spec.js.map +1 -0
- package/dist/src/serialization/beast2/v5/index.spec.js +108 -0
- package/dist/src/serialization/beast2/v5/index.spec.js.map +1 -1
- package/dist/src/serialization/beast2/v5/lazy.d.ts +12 -7
- package/dist/src/serialization/beast2/v5/lazy.d.ts.map +1 -1
- package/dist/src/serialization/beast2/v5/lazy.js +16 -6
- package/dist/src/serialization/beast2/v5/lazy.js.map +1 -1
- package/dist/src/serialization/beast2/v5/lazy.spec.js +159 -18
- package/dist/src/serialization/beast2/v5/lazy.spec.js.map +1 -1
- package/dist/src/serialization/beast2/v5/stream.d.ts +110 -21
- package/dist/src/serialization/beast2/v5/stream.d.ts.map +1 -1
- package/dist/src/serialization/beast2/v5/stream.js +153 -42
- package/dist/src/serialization/beast2/v5/stream.js.map +1 -1
- package/dist/src/serialization/beast2/v5/weight.d.ts +36 -0
- package/dist/src/serialization/beast2/v5/weight.d.ts.map +1 -0
- package/dist/src/serialization/beast2/v5/weight.js +238 -0
- package/dist/src/serialization/beast2/v5/weight.js.map +1 -0
- package/dist/src/serialization/crc32.d.ts +38 -0
- package/dist/src/serialization/crc32.d.ts.map +1 -0
- package/dist/src/serialization/crc32.js +70 -0
- package/dist/src/serialization/crc32.js.map +1 -0
- package/dist/src/serialization/crc32.spec.d.ts +6 -0
- package/dist/src/serialization/crc32.spec.d.ts.map +1 -0
- package/dist/src/serialization/crc32.spec.js +92 -0
- package/dist/src/serialization/crc32.spec.js.map +1 -0
- package/dist/src/serialization/east-inferred.spec.js +4 -0
- package/dist/src/serialization/east-inferred.spec.js.map +1 -1
- package/dist/src/serialization/east.d.ts.map +1 -1
- package/dist/src/serialization/east.js +134 -110
- package/dist/src/serialization/east.js.map +1 -1
- package/dist/src/serialization/east.spec.js +185 -4
- package/dist/src/serialization/east.spec.js.map +1 -1
- package/dist/src/serialization/east_text.d.ts +25 -0
- package/dist/src/serialization/east_text.d.ts.map +1 -0
- package/dist/src/serialization/east_text.js +29 -0
- package/dist/src/serialization/east_text.js.map +1 -0
- package/dist/src/serialization/index.d.ts +1 -1
- package/dist/src/serialization/index.d.ts.map +1 -1
- package/dist/src/types.d.ts +1 -1
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js +7 -6
- package/dist/src/types.js.map +1 -1
- package/package.json +6 -3
|
@@ -0,0 +1,1873 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Elara AI Pty Ltd
|
|
3
|
+
* Dual-licensed under AGPL-3.0 and commercial license. See LICENSE for details.
|
|
4
|
+
*/
|
|
5
|
+
import { SortedSet } from "../../containers/sortedset.js";
|
|
6
|
+
import { variant } from "../../containers/variant.js";
|
|
7
|
+
import { compareFor } from "../../comparison.js";
|
|
8
|
+
import { DateTimeFormatTokenType } from "../../datetime_format/types.js";
|
|
9
|
+
import { UNKNOWN_LOC_ID } from "../../location.js";
|
|
10
|
+
import { ArrayType, BlobType, BooleanType, DateTimeType, DictType, FloatType, FunctionType, IntegerType, NullType, OptionType, RefType, SetType, StringType, StructType, isTypeEqual, printType, } from "../../types.js";
|
|
11
|
+
import { BUILTINS, delPaths } from "./catalog.js";
|
|
12
|
+
import { jqTypeNames, nullablePayload, unify, unwrap } from "./shapes.js";
|
|
13
|
+
import { childPath, jqChildren } from "./spans.js";
|
|
14
|
+
import { formatTokens } from "./strftime.js";
|
|
15
|
+
import { isRoot, parts } from "./translate.js";
|
|
16
|
+
/** Builds a rule table entry for each of some names. */
|
|
17
|
+
const rules = {};
|
|
18
|
+
function rule(names, r) {
|
|
19
|
+
for (const name of typeof names === "string" ? [names] : names)
|
|
20
|
+
rules[name] = r;
|
|
21
|
+
}
|
|
22
|
+
/** The call's input, as an expression. */
|
|
23
|
+
function input(t, c) {
|
|
24
|
+
return t.expr(c.x, c.path);
|
|
25
|
+
}
|
|
26
|
+
/** The instance a call's argument is typed in, for an input. */
|
|
27
|
+
function argEnv(t, c, i, x) {
|
|
28
|
+
return t.envFor(c.argPaths[i], c.env, x);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Generates value arguments, each output as its collected type: the first's
|
|
32
|
+
* outputs slowest, as jq binds `$a` then `$b`.
|
|
33
|
+
*/
|
|
34
|
+
function values(t, c, indices, $, then, x = c.x) {
|
|
35
|
+
const step = ($2, k, acc) => {
|
|
36
|
+
if (k === indices.length) {
|
|
37
|
+
then($2, acc);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const i = indices[k];
|
|
41
|
+
t.collected(c.args[i], c.argPaths[i], $2, x, c.env, ($3, v) => step($3, k + 1, [...acc, v]));
|
|
42
|
+
};
|
|
43
|
+
step($, 0, []);
|
|
44
|
+
}
|
|
45
|
+
/** Generates a filter argument on an input. */
|
|
46
|
+
function arg(t, c, i, $, x, emit) {
|
|
47
|
+
t.gen(c.args[i], c.argPaths[i], $, x, c.env, emit);
|
|
48
|
+
}
|
|
49
|
+
/** The one type an argument's outputs share on an input. */
|
|
50
|
+
function argType(t, c, i, x) {
|
|
51
|
+
return t.typeAt(c.argPaths[i], argEnv(t, c, i, x));
|
|
52
|
+
}
|
|
53
|
+
/** Whether an argument gives exactly one output on an input. */
|
|
54
|
+
function argIsOne(t, c, i, x) {
|
|
55
|
+
const r = t.result(c.argPaths[i], argEnv(t, c, i, x));
|
|
56
|
+
return r !== null && r.mult.lo === 1 && r.mult.hi === 1;
|
|
57
|
+
}
|
|
58
|
+
/** The call's own output type, as the checker gave it. */
|
|
59
|
+
function outputType(t, c) {
|
|
60
|
+
return t.typeAt(c.path, c.env);
|
|
61
|
+
}
|
|
62
|
+
/** Raises jq's error for an input a builtin cannot take: reached only where the checker allowed it, inside `try` or after `?`. */
|
|
63
|
+
function cannot(t, c, $, v, what) {
|
|
64
|
+
t.raise($, `${printKind(t, v)} cannot be used with ${c.name}: it needs ${what}`, c.path);
|
|
65
|
+
}
|
|
66
|
+
function printKind(t, v) {
|
|
67
|
+
return jqTypeNames(t.type(v))[0] ?? "a value";
|
|
68
|
+
}
|
|
69
|
+
/** A number as a Float. */
|
|
70
|
+
function float(t, $, v, path) {
|
|
71
|
+
return t.widenTo($, t.open(v), FloatType, path);
|
|
72
|
+
}
|
|
73
|
+
/** Whether a type is a number. */
|
|
74
|
+
function isNumber(type) {
|
|
75
|
+
return type.type === "Integer" || type.type === "Float";
|
|
76
|
+
}
|
|
77
|
+
// ─── Selection and streams ───────────────────────────────────────────────
|
|
78
|
+
rule("empty", () => { });
|
|
79
|
+
rule("error", (t, c) => {
|
|
80
|
+
const raise = ($, v) => t.raise($, message(t, $, v, c.path), c.path);
|
|
81
|
+
if (c.args.length === 0) {
|
|
82
|
+
raise(c.$, input(t, c));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
arg(t, c, 0, c.$, c.x, raise);
|
|
86
|
+
});
|
|
87
|
+
/** An error's message: a string as it is, anything else as its East text (§13.14). */
|
|
88
|
+
function message(t, $, v, path) {
|
|
89
|
+
return t.tostring($, v, path);
|
|
90
|
+
}
|
|
91
|
+
rule("not", (t, c) => c.emit(c.$, t.asExpr(negate(t, t.truthy(input(t, c), c.path), c.path))));
|
|
92
|
+
function negate(t, v, path) {
|
|
93
|
+
return typeof v === "boolean" ? !v : t.not(v, path);
|
|
94
|
+
}
|
|
95
|
+
rule("select", (t, c) => {
|
|
96
|
+
const x = input(t, c);
|
|
97
|
+
arg(t, c, 0, c.$, c.x, ($, condition) => t.branch($, t.truthy(condition, c.path), $2 => c.emit($2, x), () => { }, c.path));
|
|
98
|
+
});
|
|
99
|
+
rule("first", (t, c) => {
|
|
100
|
+
if (c.args.length === 1) {
|
|
101
|
+
t.once(c.$, ($, label) => arg(t, c, 0, $, c.x, ($2, v) => { c.emit($2, v); t.brk($2, label); }), c.path);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
t.indexOf(c.$, input(t, c), t.int(0), false, c.path, c.emit);
|
|
105
|
+
});
|
|
106
|
+
rule("last", (t, c) => {
|
|
107
|
+
if (c.args.length === 1) {
|
|
108
|
+
const type = argType(t, c, 0, c.x);
|
|
109
|
+
const option = OptionType(type);
|
|
110
|
+
const last = t.declare(c.$, t.none(option), "last");
|
|
111
|
+
arg(t, c, 0, c.$, c.x, ($, v) => t.assign($, last, t.some(t.widenTo($, v, type, c.path), option)));
|
|
112
|
+
t.match(c.$, last, { some: c.emit }, c.path);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
t.indexOf(c.$, input(t, c), t.int(-1), false, c.path, c.emit);
|
|
116
|
+
});
|
|
117
|
+
rule("nth", (t, c) => {
|
|
118
|
+
if (c.args.length === 1) {
|
|
119
|
+
values(t, c, [0], c.$, ($, [n]) => t.indexOf($, input(t, c), n, false, c.path, c.emit));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
values(t, c, [0], c.$, ($, [n]) => {
|
|
123
|
+
t.ifElse($, t.lt(n, t.int(0), c.path), $2 => t.raise($2, "nth doesn't support negative indices", c.path), $2 => {
|
|
124
|
+
const count = t.declare($2, t.int(0), "count");
|
|
125
|
+
t.once($2, ($3, label) => arg(t, c, 1, $3, c.x, ($4, v) => {
|
|
126
|
+
t.ifElse($4, t.eq(count, n, c.path), $5 => { c.emit($5, v); t.brk($5, label); }, undefined, c.path);
|
|
127
|
+
t.assign($4, count, t.add(count, t.int(1), c.path));
|
|
128
|
+
}), c.path);
|
|
129
|
+
}, c.path);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
rule("limit", (t, c) => values(t, c, [0], c.$, ($, [n]) => {
|
|
133
|
+
t.ifElse($, t.lt(n, t.int(0), c.path), $2 => t.raise($2, "limit doesn't support negative count", c.path), $2 => {
|
|
134
|
+
t.ifElse($2, t.lt(t.int(0), n, c.path), $3 => {
|
|
135
|
+
const count = t.declare($3, t.int(0), "count");
|
|
136
|
+
t.once($3, ($4, label) => arg(t, c, 1, $4, c.x, ($5, v) => {
|
|
137
|
+
c.emit($5, v);
|
|
138
|
+
t.assign($5, count, t.add(count, t.int(1), c.path));
|
|
139
|
+
t.ifElse($5, t.b("GreaterEqual", [IntegerType], [count, n], BooleanType, c.path), $6 => t.brk($6, label), undefined, c.path);
|
|
140
|
+
}), c.path);
|
|
141
|
+
}, undefined, c.path);
|
|
142
|
+
}, c.path);
|
|
143
|
+
}));
|
|
144
|
+
rule("skip", (t, c) => values(t, c, [0], c.$, ($, [n]) => {
|
|
145
|
+
t.ifElse($, t.lt(n, t.int(0), c.path), $2 => t.raise($2, "skip doesn't support negative count", c.path), $2 => {
|
|
146
|
+
const count = t.declare($2, t.int(0), "count");
|
|
147
|
+
arg(t, c, 1, $2, c.x, ($3, v) => {
|
|
148
|
+
t.ifElse($3, t.lt(count, n, c.path), $4 => t.assign($4, count, t.add(count, t.int(1), c.path)), $4 => c.emit($4, v), c.path);
|
|
149
|
+
});
|
|
150
|
+
}, c.path);
|
|
151
|
+
}));
|
|
152
|
+
rule("isempty", (t, c) => {
|
|
153
|
+
const empty = t.declare(c.$, t.bool(true), "empty");
|
|
154
|
+
t.once(c.$, ($, label) => arg(t, c, 0, $, c.x, $2 => { t.assign($2, empty, t.bool(false)); t.brk($2, label); }), c.path);
|
|
155
|
+
c.emit(c.$, empty);
|
|
156
|
+
});
|
|
157
|
+
rule("range", (t, c) => {
|
|
158
|
+
const type = outputType(t, c);
|
|
159
|
+
const indices = c.args.map((_, i) => i);
|
|
160
|
+
values(t, c, indices, c.$, ($, args) => {
|
|
161
|
+
const as = (v) => t.widenTo($, v, type, c.path);
|
|
162
|
+
const zero = type.type === "Float" ? t.float(0) : t.int(0);
|
|
163
|
+
const one = type.type === "Float" ? t.float(1) : t.int(1);
|
|
164
|
+
const [from, upto, by] = args.length === 1 ? [zero, as(args[0]), one] : args.length === 2 ? [as(args[0]), as(args[1]), one] : [as(args[0]), as(args[1]), as(args[2])];
|
|
165
|
+
const i = t.declare($, from, "i");
|
|
166
|
+
const byValue = t.constant(by)?.value;
|
|
167
|
+
const less = (a, b) => t.b("Less", [type], [a, b], BooleanType, c.path);
|
|
168
|
+
// Up while below the end for a positive step, down while above it for a negative one; a zero step gives nothing.
|
|
169
|
+
const going = byValue === undefined
|
|
170
|
+
? t.b("BooleanOr", [], [
|
|
171
|
+
t.b("BooleanAnd", [], [less(zero, by), less(i, upto)], BooleanType, c.path),
|
|
172
|
+
t.b("BooleanAnd", [], [less(by, zero), less(upto, i)], BooleanType, c.path),
|
|
173
|
+
], BooleanType, c.path)
|
|
174
|
+
: byValue > 0 ? less(i, upto) : byValue < 0 ? less(upto, i) : undefined;
|
|
175
|
+
if (going === undefined)
|
|
176
|
+
return;
|
|
177
|
+
t.whileLoop($, going, $2 => {
|
|
178
|
+
const value = t.declare($2, i, "value", false);
|
|
179
|
+
t.assign($2, i, t.b(type.type === "Float" ? "FloatAdd" : "IntegerAdd", [], [i, by], type, c.path));
|
|
180
|
+
c.emit($2, value);
|
|
181
|
+
}, c.path);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
rule("recurse", (t, c) => {
|
|
185
|
+
if (c.args.length === 0) {
|
|
186
|
+
// `recurse` is `..`: jq defines both as `recurse(.[]?)`.
|
|
187
|
+
t.descend(c.$, input(t, c), c.path, c.emit);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
// `recurse(f)`, `recurse(f; cond)`: the value, then f of it, and so on, depth first.
|
|
191
|
+
const type = outputType(t, c);
|
|
192
|
+
const start = t.widenTo(c.$, input(t, c), type, c.path);
|
|
193
|
+
t.walkStack(c.$, start, [type], c.path, ($, v, push) => {
|
|
194
|
+
c.emit($, v);
|
|
195
|
+
arg(t, c, 0, $, v, ($2, child) => {
|
|
196
|
+
const next = t.bind($2, t.widenTo($2, child, type, c.path), "child");
|
|
197
|
+
if (c.args.length === 2)
|
|
198
|
+
arg(t, c, 1, $2, next, ($3, condition) => t.branch($3, t.truthy(condition, c.path), $4 => push($4, next), () => { }, c.path));
|
|
199
|
+
else
|
|
200
|
+
push($2, next);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
rule("repeat", (t, c) => {
|
|
205
|
+
const type = outputType(t, c);
|
|
206
|
+
const start = t.widenTo(c.$, input(t, c), type, c.path);
|
|
207
|
+
t.walkStack(c.$, start, [type], c.path, ($, v, push) => {
|
|
208
|
+
c.emit($, v);
|
|
209
|
+
arg(t, c, 0, $, v, ($2, next) => push($2, t.widenTo($2, next, type, c.path)));
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
rule("while", (t, c) => {
|
|
213
|
+
const type = outputType(t, c);
|
|
214
|
+
const start = t.widenTo(c.$, input(t, c), type, c.path);
|
|
215
|
+
t.walkStack(c.$, start, [type], c.path, ($, v, push) => {
|
|
216
|
+
arg(t, c, 0, $, v, ($2, condition) => t.branch($2, t.truthy(condition, c.path), $3 => {
|
|
217
|
+
c.emit($3, v);
|
|
218
|
+
arg(t, c, 1, $3, v, ($4, next) => push($4, t.widenTo($4, next, type, c.path)));
|
|
219
|
+
}, () => { }, c.path));
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
rule("until", (t, c) => {
|
|
223
|
+
const type = outputType(t, c);
|
|
224
|
+
const start = t.widenTo(c.$, input(t, c), type, c.path);
|
|
225
|
+
t.walkStack(c.$, start, [type], c.path, ($, v, push) => {
|
|
226
|
+
arg(t, c, 0, $, v, ($2, condition) => t.branch($2, t.truthy(condition, c.path), $3 => c.emit($3, v), $3 => arg(t, c, 1, $3, v, ($4, next) => push($4, t.widenTo($4, next, type, c.path))), c.path));
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
rule("combinations", (t, c) => {
|
|
230
|
+
const run = ($, lists) => {
|
|
231
|
+
const row = parts(t.type(lists).value);
|
|
232
|
+
const element = row.value;
|
|
233
|
+
const n = t.declare($, t.size(lists, c.path), "count", false);
|
|
234
|
+
const anyEmpty = t.declare($, t.bool(false), "anyEmpty");
|
|
235
|
+
t.forEach($, lists, ($2, list) => t.ifElse($2, t.eq(t.size(list, c.path), t.int(0), c.path), $3 => t.assign($3, anyEmpty, t.bool(true)), undefined, c.path), c.path, "list");
|
|
236
|
+
t.ifElse($, t.not(anyEmpty, c.path), $2 => {
|
|
237
|
+
const index = t.declare($2, t.emptyArray(IntegerType), "positions");
|
|
238
|
+
t.forEach($2, lists, $3 => t.push($3, index, t.int(0), c.path), c.path, "list");
|
|
239
|
+
t.whileLoop($2, true, ($3, label) => {
|
|
240
|
+
const combo = t.declare($3, t.emptyArray(element), "combination");
|
|
241
|
+
t.forEach($3, lists, ($4, list, k) => {
|
|
242
|
+
t.push($4, combo, t.b("ArrayGet", [element], [list, t.b("ArrayGet", [IntegerType], [index, k], IntegerType, c.path)], element, c.path), c.path);
|
|
243
|
+
}, c.path, "list");
|
|
244
|
+
c.emit($3, combo);
|
|
245
|
+
// The next combination: the last position fastest.
|
|
246
|
+
const k = t.declare($3, t.add(n, t.int(-1), c.path), "k");
|
|
247
|
+
const carry = t.declare($3, t.bool(true), "carry");
|
|
248
|
+
t.whileLoop($3, t.b("BooleanAnd", [], [carry, t.b("GreaterEqual", [IntegerType], [k, t.int(0)], BooleanType, c.path)], BooleanType, c.path), $4 => {
|
|
249
|
+
const next = t.declare($4, t.add(t.b("ArrayGet", [IntegerType], [index, k], IntegerType, c.path), t.int(1), c.path), "next", false);
|
|
250
|
+
t.ifElse($4, t.lt(next, t.size(t.b("ArrayGet", [row], [lists, k], row, c.path), c.path), c.path), $5 => {
|
|
251
|
+
t.stmt($5, t.b("ArrayUpdate", [IntegerType], [index, k, next], NullType, c.path));
|
|
252
|
+
t.assign($5, carry, t.bool(false));
|
|
253
|
+
}, $5 => {
|
|
254
|
+
t.stmt($5, t.b("ArrayUpdate", [IntegerType], [index, k, t.int(0)], NullType, c.path));
|
|
255
|
+
t.assign($5, k, t.add(k, t.int(-1), c.path));
|
|
256
|
+
}, c.path);
|
|
257
|
+
}, c.path);
|
|
258
|
+
t.ifElse($3, carry, $4 => t.brk($4, label), undefined, c.path);
|
|
259
|
+
}, c.path);
|
|
260
|
+
}, undefined, c.path);
|
|
261
|
+
};
|
|
262
|
+
if (c.args.length === 0) {
|
|
263
|
+
run(c.$, t.bind(c.$, t.open(input(t, c)), "lists"));
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
values(t, c, [0], c.$, ($, [n]) => {
|
|
267
|
+
const list = t.bind($, t.open(input(t, c)), "list");
|
|
268
|
+
const lists = t.declare($, t.emptyArray(t.type(list)), "lists");
|
|
269
|
+
const i = t.declare($, t.int(0), "i");
|
|
270
|
+
t.whileLoop($, t.lt(i, n, c.path), $2 => { t.push($2, lists, list, c.path); t.assign($2, i, t.add(i, t.int(1), c.path)); }, c.path);
|
|
271
|
+
run($, lists);
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
rule("walk", (t, c) => {
|
|
275
|
+
const type = outputType(t, c);
|
|
276
|
+
// Bottom up: a value's parts are walked first, then f runs on it rebuilt.
|
|
277
|
+
const cells = new Map();
|
|
278
|
+
// Where the functions that walk recursive values are declared: before the walk, so every loop sees them.
|
|
279
|
+
const start = c.$.statements.length;
|
|
280
|
+
let declared = 0;
|
|
281
|
+
const walk = ($, v) => {
|
|
282
|
+
const vt = t.type(v);
|
|
283
|
+
if (vt.type === "Recursive")
|
|
284
|
+
return walkRecursive($, v);
|
|
285
|
+
const rebuilt = rebuild($, v);
|
|
286
|
+
return apply($, rebuilt);
|
|
287
|
+
};
|
|
288
|
+
const apply = ($, v) => {
|
|
289
|
+
const out = t.one(c.args[0], c.argPaths[0], $, v, c.env, argType(t, c, 0, v));
|
|
290
|
+
return t.bind($, out, "walked");
|
|
291
|
+
};
|
|
292
|
+
const rebuild = ($, v) => {
|
|
293
|
+
const o = t.open(v);
|
|
294
|
+
const ot = t.type(o);
|
|
295
|
+
// An option's value's parts, where it holds one; f runs once, on the whole.
|
|
296
|
+
if (nullablePayload(ot) !== undefined)
|
|
297
|
+
return t.matchAuto(o, { none: () => t.null(c.path), some: ($2, p) => rebuild($2, p) }, c.path);
|
|
298
|
+
// A variant's payload is walked; its case's name is its type.
|
|
299
|
+
if (ot.type === "Variant")
|
|
300
|
+
return t.mapPayloads(o, ($2, _name, payload) => walk($2, payload), c.path);
|
|
301
|
+
// A collection of Never is empty: nothing to walk.
|
|
302
|
+
if ((ot.type === "Array" || ot.type === "Dict") && ot.value.type === "Never")
|
|
303
|
+
return o;
|
|
304
|
+
if (ot.type === "Array") {
|
|
305
|
+
let out;
|
|
306
|
+
const decl = t.block();
|
|
307
|
+
t.forEach($, o, ($2, item) => {
|
|
308
|
+
const w = walk($2, item);
|
|
309
|
+
out ??= t.declare(decl, t.emptyArray(t.type(w)), "array");
|
|
310
|
+
t.push($2, out, w, c.path);
|
|
311
|
+
}, c.path);
|
|
312
|
+
$.statements.splice($.statements.length - 1, 0, ...decl.statements);
|
|
313
|
+
return out ?? o;
|
|
314
|
+
}
|
|
315
|
+
if (ot.type === "Dict") {
|
|
316
|
+
let out;
|
|
317
|
+
const decl = t.block();
|
|
318
|
+
t.forEach($, o, ($2, item, key) => {
|
|
319
|
+
const w = walk($2, item);
|
|
320
|
+
out ??= t.declare(decl, t.value(new Map(), DictType(ot.key, t.type(w))), "dict");
|
|
321
|
+
t.stmt($2, t.b("DictInsert", [ot.key, t.type(w)], [out, key, w], NullType, c.path));
|
|
322
|
+
}, c.path);
|
|
323
|
+
$.statements.splice($.statements.length - 1, 0, ...decl.statements);
|
|
324
|
+
return out ?? o;
|
|
325
|
+
}
|
|
326
|
+
if (ot.type === "Struct") {
|
|
327
|
+
const s = t.bind($, o, "struct");
|
|
328
|
+
const values = {};
|
|
329
|
+
const types = {};
|
|
330
|
+
for (const name of Object.keys(ot.fields)) {
|
|
331
|
+
values[name] = walk($, t.field(s, name));
|
|
332
|
+
types[name] = t.type(values[name]);
|
|
333
|
+
}
|
|
334
|
+
// A recursive value keeps its type; a reference's value is read through it.
|
|
335
|
+
const vt = t.type(v);
|
|
336
|
+
const rebuiltType = vt.type === "Recursive" && isTypeEqual(StructType(types), ot) ? vt : StructType(types);
|
|
337
|
+
return t.struct(rebuiltType, values);
|
|
338
|
+
}
|
|
339
|
+
return v;
|
|
340
|
+
};
|
|
341
|
+
// A recursive value is walked by a function its own parts call, through a reference.
|
|
342
|
+
const walkRecursive = ($, v) => {
|
|
343
|
+
const rt = t.type(v);
|
|
344
|
+
const fnType = FunctionType([rt], rt);
|
|
345
|
+
let cell = cells.get(rt);
|
|
346
|
+
if (cell === undefined) {
|
|
347
|
+
const header = t.block();
|
|
348
|
+
const placeholder = t.lambda([rt], rt, ["value"], (_$f, value) => value, c.path);
|
|
349
|
+
const made = t.declare(header, t.mk({ ast_type: "NewRef", type: RefType(fnType), loc_id: UNKNOWN_LOC_ID, value: t.ast(placeholder) }), "walk", false);
|
|
350
|
+
cell = made;
|
|
351
|
+
cells.set(rt, made);
|
|
352
|
+
const fn = t.lambda([rt], rt, ["value"], ($f, value) => {
|
|
353
|
+
const rebuilt = rebuild($f, value);
|
|
354
|
+
return t.widenTo($f, apply($f, t.widenTo($f, rebuilt, rt, c.path)), rt, c.path);
|
|
355
|
+
}, c.path);
|
|
356
|
+
t.stmt(header, t.b("RefUpdate", [fnType], [made, fn], NullType, c.path));
|
|
357
|
+
c.$.statements.splice(start + declared, 0, ...header.statements);
|
|
358
|
+
declared += header.statements.length;
|
|
359
|
+
}
|
|
360
|
+
return t.bind($, t.callFn(t.b("RefGet", [fnType], [cell], fnType, c.path), [v], c.path), "walked");
|
|
361
|
+
};
|
|
362
|
+
c.emit(c.$, t.widenTo(c.$, walk(c.$, input(t, c)), type, c.path));
|
|
363
|
+
});
|
|
364
|
+
rule("IN", (t, c) => {
|
|
365
|
+
const found = t.declare(c.$, t.bool(false), "found");
|
|
366
|
+
const test = ($, a, b, label) => {
|
|
367
|
+
t.ifElse($, t.compare($, "==", a, b, c.path), $2 => { t.assign($2, found, t.bool(true)); t.brk($2, label); }, undefined, c.path);
|
|
368
|
+
};
|
|
369
|
+
t.once(c.$, ($, label) => {
|
|
370
|
+
if (c.args.length === 1) {
|
|
371
|
+
const x = input(t, c);
|
|
372
|
+
arg(t, c, 0, $, c.x, ($2, v) => test($2, v, x, label));
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
// `IN(src; s)` is `any(src == s; .)`: s's outputs slowest.
|
|
376
|
+
arg(t, c, 1, $, c.x, ($2, b) => {
|
|
377
|
+
const bound = t.bind($2, b, "candidate");
|
|
378
|
+
arg(t, c, 0, $2, c.x, ($3, a) => test($3, a, bound, label));
|
|
379
|
+
});
|
|
380
|
+
}, c.path);
|
|
381
|
+
c.emit(c.$, found);
|
|
382
|
+
});
|
|
383
|
+
rule("INDEX", (t, c) => {
|
|
384
|
+
const type = outputType(t, c);
|
|
385
|
+
const dict = t.declare(c.$, t.value(new Map(), type), "index");
|
|
386
|
+
const add = ($, row) => {
|
|
387
|
+
const bound = t.bind($, row, "row");
|
|
388
|
+
arg(t, c, c.args.length - 1, $, bound, ($2, key) => t.put($2, dict, key, bound, c.path));
|
|
389
|
+
};
|
|
390
|
+
if (c.args.length === 2)
|
|
391
|
+
arg(t, c, 0, c.$, c.x, add);
|
|
392
|
+
else
|
|
393
|
+
t.iterate(c.$, input(t, c), false, c.path, add);
|
|
394
|
+
c.emit(c.$, dict);
|
|
395
|
+
});
|
|
396
|
+
rule("del", (t, c) => {
|
|
397
|
+
const paths = delPaths(c.args[0], c.argPaths[0]);
|
|
398
|
+
if (paths === undefined)
|
|
399
|
+
throw t.gap(`del(${t.text(c.argPaths[0])})`);
|
|
400
|
+
c.emit(c.$, deleteAt(t, c, c.$, input(t, c), paths));
|
|
401
|
+
});
|
|
402
|
+
/**
|
|
403
|
+
* A value without what some paths name, all deleted at once as jq deletes
|
|
404
|
+
* them: each path's keys and bounds are taken on the `del`'s input first.
|
|
405
|
+
*/
|
|
406
|
+
function deleteAt(t, c, $, v, paths) {
|
|
407
|
+
if (paths.length === 0)
|
|
408
|
+
return v;
|
|
409
|
+
if (paths.some(p => p.length === 0))
|
|
410
|
+
return t.null(c.path);
|
|
411
|
+
const e = t.open(v);
|
|
412
|
+
const type = t.type(e);
|
|
413
|
+
if (type.type === "Null")
|
|
414
|
+
return e;
|
|
415
|
+
const payload = nullablePayload(type);
|
|
416
|
+
if (payload !== undefined) {
|
|
417
|
+
// An option: the deletion inside its value, when it has one.
|
|
418
|
+
const inner = t.type(deleteAt(t, c, t.block(), t.placeholder(payload), paths));
|
|
419
|
+
const option = OptionType(inner);
|
|
420
|
+
return t.matchValue(e, { none: () => t.none(option), some: ($2, p) => t.some(deleteAt(t, c, $2, p, paths), option) }, option, c.path);
|
|
421
|
+
}
|
|
422
|
+
// A select as the whole path: the value is deleted, becoming null, where the condition holds.
|
|
423
|
+
const selects = paths.filter(p => p.length === 1 && p[0].kind === "select");
|
|
424
|
+
if (selects.length > 0) {
|
|
425
|
+
const kept = t.bind($, deleteAt(t, c, $, e, paths.filter(p => !selects.includes(p))), "kept");
|
|
426
|
+
const deleted = t.declare($, t.bool(false), "deleted");
|
|
427
|
+
for (const p of selects) {
|
|
428
|
+
const step = p[0];
|
|
429
|
+
t.gen(nodeAt(c, step.path), step.path, $, e, c.env, ($2, condition) => t.branch($2, t.truthy(condition, c.path), $3 => t.assign($3, deleted, t.bool(true)), () => { }, c.path));
|
|
430
|
+
}
|
|
431
|
+
const option = OptionType(t.type(kept));
|
|
432
|
+
return t.ifValue(deleted, () => t.none(option), () => t.some(kept, option), option, c.path);
|
|
433
|
+
}
|
|
434
|
+
const nameOf = (step) => {
|
|
435
|
+
if (step.kind === "field")
|
|
436
|
+
return step.name;
|
|
437
|
+
if (step.kind === "index" && type.type !== "Dict") {
|
|
438
|
+
const literal = t.literalOf(step.node);
|
|
439
|
+
if (literal?.type.type === "String")
|
|
440
|
+
return literal.value;
|
|
441
|
+
}
|
|
442
|
+
return undefined;
|
|
443
|
+
};
|
|
444
|
+
if (type.type === "Struct") {
|
|
445
|
+
const fields = type.fields;
|
|
446
|
+
const s = t.bind($, e, "struct");
|
|
447
|
+
const removed = new Set();
|
|
448
|
+
const nested = new Map();
|
|
449
|
+
for (const p of paths) {
|
|
450
|
+
const [first, ...rest] = p;
|
|
451
|
+
const names = first.kind === "iterate" ? Object.keys(fields) : [nameOf(first)];
|
|
452
|
+
for (const name of names) {
|
|
453
|
+
if (name === undefined)
|
|
454
|
+
throw t.gap(`del of ${first.kind} on ${printType(type)}`);
|
|
455
|
+
if (!(name in fields))
|
|
456
|
+
continue;
|
|
457
|
+
if (rest.length === 0)
|
|
458
|
+
removed.add(name);
|
|
459
|
+
else
|
|
460
|
+
nested.set(name, [...(nested.get(name) ?? []), rest]);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
const values = {};
|
|
464
|
+
const types = {};
|
|
465
|
+
for (const name of Object.keys(fields)) {
|
|
466
|
+
if (removed.has(name))
|
|
467
|
+
continue;
|
|
468
|
+
const rests = nested.get(name);
|
|
469
|
+
values[name] = rests === undefined ? t.field(s, name) : t.bind($, deleteAt(t, c, $, t.field(s, name), rests), name);
|
|
470
|
+
types[name] = t.type(values[name]);
|
|
471
|
+
}
|
|
472
|
+
return t.struct(StructType(types), values);
|
|
473
|
+
}
|
|
474
|
+
if (type.type === "Array" || type.type === "Dict") {
|
|
475
|
+
const isArray = type.type === "Array";
|
|
476
|
+
const d = t.bind($, e, "container");
|
|
477
|
+
const size = isArray ? t.declare($, t.size(d, c.path), "size", false) : undefined;
|
|
478
|
+
const targets = paths.map((p) => {
|
|
479
|
+
const first = p[0];
|
|
480
|
+
if (first.kind === "iterate")
|
|
481
|
+
return undefined;
|
|
482
|
+
if (first.kind === "slice") {
|
|
483
|
+
const node = nodeAt(c, first.path);
|
|
484
|
+
const bound = (option, step, fallback) => option.type === "none"
|
|
485
|
+
? t.boundOf($, undefined, fallback, size, c.path)
|
|
486
|
+
: t.boundOf($, t.one(option.value, childPath(first.path, step), $, c.x, c.env, argTypeAt(t, c, childPath(first.path, step))), fallback, size, c.path);
|
|
487
|
+
return { from: bound(node.value.from, "slice.from.some", t.int(0)), to: bound(node.value.to, "slice.to.some", size) };
|
|
488
|
+
}
|
|
489
|
+
const K = isArray ? IntegerType : type.key;
|
|
490
|
+
const keys = t.declare($, t.emptyArray(K), "keys");
|
|
491
|
+
const add = ($2, key) => {
|
|
492
|
+
const k = t.bind($2, t.widenTo($2, key, K, c.path), "key");
|
|
493
|
+
// A negative index counts from the end.
|
|
494
|
+
t.push($2, keys, isArray ? t.ifValue(t.lt(k, t.int(0), c.path), () => t.add(k, size, c.path), () => k, IntegerType, c.path) : k, c.path);
|
|
495
|
+
};
|
|
496
|
+
const name = nameOf(first);
|
|
497
|
+
if (name !== undefined)
|
|
498
|
+
add($, t.str(name, c.path));
|
|
499
|
+
else {
|
|
500
|
+
const step = first;
|
|
501
|
+
t.collected(step.node, step.path, $, c.x, c.env, add);
|
|
502
|
+
}
|
|
503
|
+
return { keys };
|
|
504
|
+
});
|
|
505
|
+
// Paths through every element to something inside it change each alike, and may change its type: they apply first.
|
|
506
|
+
const throughEach = (p) => p[0].kind === "iterate" && p.length > 1 && !(p.length === 2 && p[1].kind === "select");
|
|
507
|
+
const everywhere = paths.filter(throughEach).map(p => p.slice(1));
|
|
508
|
+
let out;
|
|
509
|
+
const declared = t.block();
|
|
510
|
+
t.forEach($, d, ($2, item, key) => {
|
|
511
|
+
const current = everywhere.length === 0 ? item : t.bind($2, deleteAt(t, c, $2, item, everywhere), "element");
|
|
512
|
+
const cell = t.declare($2, current, "element");
|
|
513
|
+
const dropped = t.declare($2, t.bool(false), "dropped");
|
|
514
|
+
paths.forEach((p, k) => {
|
|
515
|
+
if (throughEach(p))
|
|
516
|
+
return;
|
|
517
|
+
const rest = p.slice(1);
|
|
518
|
+
const handle = ($3) => {
|
|
519
|
+
if (rest.length === 0) {
|
|
520
|
+
t.assign($3, dropped, t.bool(true));
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
if (rest.length === 1 && rest[0].kind === "select") {
|
|
524
|
+
const step = rest[0];
|
|
525
|
+
t.gen(nodeAt(c, step.path), step.path, $3, cell, c.env, ($4, condition) => t.branch($4, t.truthy(condition, c.path), $5 => t.assign($5, dropped, t.bool(true)), () => { }, c.path));
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
t.assign($3, cell, t.widenTo($3, deleteAt(t, c, $3, cell, [rest]), t.type(cell), c.path));
|
|
529
|
+
};
|
|
530
|
+
const target = targets[k];
|
|
531
|
+
if (target === undefined) {
|
|
532
|
+
handle($2);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
if ("from" in target) {
|
|
536
|
+
const inRange = t.b("BooleanAnd", [], [t.b("LessEqual", [IntegerType], [target.from, key], BooleanType, c.path), t.lt(key, target.to, c.path)], BooleanType, c.path);
|
|
537
|
+
t.ifElse($2, inRange, handle, undefined, c.path);
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
t.forEach($2, target.keys, ($3, k, _i, label) => t.ifElse($3, t.eq(k, key, c.path), $4 => { handle($4); t.brk($4, label); }, undefined, c.path), c.path, "key");
|
|
541
|
+
});
|
|
542
|
+
const element = t.type(cell);
|
|
543
|
+
out ??= t.declare(declared, isArray ? t.emptyArray(element) : t.value(new Map(), DictType(type.key, element)), isArray ? "array" : "dict");
|
|
544
|
+
const result = out;
|
|
545
|
+
t.ifElse($2, t.not(dropped, c.path), $3 => {
|
|
546
|
+
if (isArray)
|
|
547
|
+
t.push($3, result, cell, c.path);
|
|
548
|
+
else
|
|
549
|
+
t.stmt($3, t.b("DictInsert", [type.key, element], [result, key, cell], NullType, c.path));
|
|
550
|
+
}, undefined, c.path);
|
|
551
|
+
}, c.path);
|
|
552
|
+
// The result is declared before the loop, once the elements' type after deletion is known.
|
|
553
|
+
$.statements.splice($.statements.length - 1, 0, ...declared.statements);
|
|
554
|
+
return out ?? d;
|
|
555
|
+
}
|
|
556
|
+
if (type.type === "String") {
|
|
557
|
+
t.raise($, "Cannot delete fields from string", c.path);
|
|
558
|
+
return e;
|
|
559
|
+
}
|
|
560
|
+
throw t.gap(`del on ${printType(type)}`);
|
|
561
|
+
}
|
|
562
|
+
/** The node at a path inside the call's first argument. */
|
|
563
|
+
function nodeAt(c, path) {
|
|
564
|
+
const walk = (n, at) => {
|
|
565
|
+
if (at === path)
|
|
566
|
+
return n;
|
|
567
|
+
for (const child of jqChildren(n)) {
|
|
568
|
+
if (child.node === undefined)
|
|
569
|
+
continue;
|
|
570
|
+
const childAt = childPath(at, child.step);
|
|
571
|
+
if (path === childAt || path.startsWith(`${childAt}.`))
|
|
572
|
+
return walk(child.node, childAt);
|
|
573
|
+
}
|
|
574
|
+
return undefined;
|
|
575
|
+
};
|
|
576
|
+
const found = walk(c.args[0], c.argPaths[0]);
|
|
577
|
+
if (found === undefined)
|
|
578
|
+
throw new Error(`translateJq: no node at ${path}`);
|
|
579
|
+
return found;
|
|
580
|
+
}
|
|
581
|
+
/** The collected type of a node inside the call's argument. */
|
|
582
|
+
function argTypeAt(t, c, path) {
|
|
583
|
+
return t.typeAt(path, t.envFor(path, c.env, c.x));
|
|
584
|
+
}
|
|
585
|
+
rule("to_entries", (t, c) => {
|
|
586
|
+
const type = outputType(t, c);
|
|
587
|
+
const entry = parts(type.value);
|
|
588
|
+
const valueType = entry.fields["value"];
|
|
589
|
+
const out = t.declare(c.$, t.emptyArray(entry), "entries");
|
|
590
|
+
const x = t.open(input(t, c));
|
|
591
|
+
const xt = t.type(x);
|
|
592
|
+
if (xt.type === "Struct") {
|
|
593
|
+
const s = t.bind(c.$, x, "struct");
|
|
594
|
+
for (const name of Object.keys(xt.fields)) {
|
|
595
|
+
t.push(c.$, out, t.struct(entry, { key: t.str(name, c.path), value: t.widenTo(c.$, t.field(s, name), valueType, c.path) }), c.path);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
else if (xt.type === "Dict") {
|
|
599
|
+
t.forEach(c.$, x, ($, value, key) => t.push($, out, t.struct(entry, { key: key, value: t.widenTo($, value, valueType, c.path) }), c.path), c.path);
|
|
600
|
+
}
|
|
601
|
+
else {
|
|
602
|
+
const array = t.asArray(c.$, x, c.path);
|
|
603
|
+
if (array === undefined) {
|
|
604
|
+
cannot(t, c, c.$, x, "a dict, a struct or an array");
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
t.forEach(c.$, array, ($, value, index) => t.push($, out, t.struct(entry, { key: index, value: t.widenTo($, value, valueType, c.path) }), c.path), c.path);
|
|
608
|
+
}
|
|
609
|
+
c.emit(c.$, out);
|
|
610
|
+
});
|
|
611
|
+
/** The key and value fields a from_entries entry can have, as jq accepts them. */
|
|
612
|
+
const KEY_FIELDS = ["key", "k", "name", "Name", "K", "Key"];
|
|
613
|
+
const VALUE_FIELDS = ["value", "v", "Value", "V"];
|
|
614
|
+
/** Adds `{key, value}` entries to a dict. */
|
|
615
|
+
function addEntry(t, c, $, dict, entry) {
|
|
616
|
+
const e = t.open(entry);
|
|
617
|
+
const fields = t.type(e).fields;
|
|
618
|
+
const key = KEY_FIELDS.find(k => k in fields);
|
|
619
|
+
const value = VALUE_FIELDS.find(v => v in fields);
|
|
620
|
+
if (key === undefined)
|
|
621
|
+
throw t.gap("a from_entries entry without a key");
|
|
622
|
+
const s = t.bind($, e, "entry");
|
|
623
|
+
t.put($, dict, t.field(s, key), value === undefined ? t.null(c.path) : t.field(s, value), c.path);
|
|
624
|
+
}
|
|
625
|
+
rule("from_entries", (t, c) => {
|
|
626
|
+
const dict = t.declare(c.$, t.value(new Map(), outputType(t, c)), "dict");
|
|
627
|
+
t.forEach(c.$, input(t, c), ($, entry) => addEntry(t, c, $, dict, entry), c.path, "entry");
|
|
628
|
+
c.emit(c.$, dict);
|
|
629
|
+
});
|
|
630
|
+
rule("with_entries", (t, c) => {
|
|
631
|
+
const dict = t.declare(c.$, t.value(new Map(), outputType(t, c)), "dict");
|
|
632
|
+
const x = t.open(input(t, c));
|
|
633
|
+
const xt = t.type(x);
|
|
634
|
+
const each = ($, entry) => arg(t, c, 0, $, entry, ($2, mapped) => addEntry(t, c, $2, dict, mapped));
|
|
635
|
+
if (xt.type === "Dict") {
|
|
636
|
+
const entryType = StructType({ key: xt.key, value: xt.value });
|
|
637
|
+
t.forEach(c.$, x, ($, value, key) => each($, t.bind($, t.struct(entryType, { key: key, value }), "entry")), c.path);
|
|
638
|
+
}
|
|
639
|
+
else if (xt.type === "Struct") {
|
|
640
|
+
let valueType;
|
|
641
|
+
for (const f of Object.values(xt.fields))
|
|
642
|
+
valueType = valueType === undefined ? f : unifyOrFail(t, valueType, f);
|
|
643
|
+
const entryType = StructType({ key: StringType, value: valueType ?? NullType });
|
|
644
|
+
const s = t.bind(c.$, x, "struct");
|
|
645
|
+
for (const name of Object.keys(xt.fields)) {
|
|
646
|
+
each(c.$, t.bind(c.$, t.struct(entryType, { key: t.str(name, c.path), value: t.widenTo(c.$, t.field(s, name), valueType, c.path) }), "entry"));
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
cannot(t, c, c.$, x, "a dict or a struct");
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
c.emit(c.$, dict);
|
|
654
|
+
});
|
|
655
|
+
/** The one type two types unify to. */
|
|
656
|
+
function unifyOrFail(t, a, b) {
|
|
657
|
+
const u = unify(a, b);
|
|
658
|
+
if (u === undefined)
|
|
659
|
+
throw t.gap(`${printType(a)} and ${printType(b)} have no one type`);
|
|
660
|
+
return u;
|
|
661
|
+
}
|
|
662
|
+
rule("pick", (t, c) => {
|
|
663
|
+
let node = c.args[0];
|
|
664
|
+
const names = [];
|
|
665
|
+
while (node.type === "field") {
|
|
666
|
+
names.unshift(node.value.name);
|
|
667
|
+
node = node.value.target;
|
|
668
|
+
}
|
|
669
|
+
const build = ($, v, rest) => {
|
|
670
|
+
// `null`'s fields are null.
|
|
671
|
+
const inner = t.type(v).type === "Null" ? t.null(c.path) : t.field(v, rest[0]);
|
|
672
|
+
const picked = rest.length === 1 ? inner : build($, t.bind($, inner, rest[0]), rest.slice(1));
|
|
673
|
+
return t.struct(StructType({ [rest[0]]: t.type(picked) }), { [rest[0]]: picked });
|
|
674
|
+
};
|
|
675
|
+
c.emit(c.$, build(c.$, t.bind(c.$, t.open(input(t, c)), "value"), names));
|
|
676
|
+
});
|
|
677
|
+
rule("transpose", (t, c) => {
|
|
678
|
+
const type = outputType(t, c);
|
|
679
|
+
// No rows, so nothing to transpose.
|
|
680
|
+
if (parts(t.type(t.open(input(t, c)))).value.type === "Never") {
|
|
681
|
+
c.emit(c.$, t.value([], type, c.path));
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
const row = parts(type.value);
|
|
685
|
+
const cell = row.value;
|
|
686
|
+
const rows = t.bind(c.$, t.open(input(t, c)), "rows");
|
|
687
|
+
const width = t.declare(c.$, t.int(0), "width");
|
|
688
|
+
t.forEach(c.$, rows, ($, r) => t.ifElse($, t.lt(width, t.size(r, c.path), c.path), $2 => t.assign($2, width, t.size(r, c.path)), undefined, c.path), c.path, "row");
|
|
689
|
+
const out = t.declare(c.$, t.emptyArray(row), "transposed");
|
|
690
|
+
const i = t.declare(c.$, t.int(0), "column");
|
|
691
|
+
t.whileLoop(c.$, t.lt(i, width, c.path), $ => {
|
|
692
|
+
const column = t.declare($, t.emptyArray(cell), "cells");
|
|
693
|
+
t.forEach($, rows, ($2, r) => {
|
|
694
|
+
const inner = t.type(r).value;
|
|
695
|
+
t.push($2, column, t.widenTo($2, t.b("ArrayTryGet", [inner], [r, i], OptionType(inner), c.path), cell, c.path), c.path);
|
|
696
|
+
}, c.path, "row");
|
|
697
|
+
t.push($, out, column, c.path);
|
|
698
|
+
t.assign($, i, t.add(i, t.int(1), c.path));
|
|
699
|
+
}, c.path);
|
|
700
|
+
c.emit(c.$, out);
|
|
701
|
+
});
|
|
702
|
+
// ─── Collections ─────────────────────────────────────────────────────────
|
|
703
|
+
rule("length", (t, c) => {
|
|
704
|
+
const x = t.open(input(t, c));
|
|
705
|
+
const length = ($, v, emit) => {
|
|
706
|
+
const o = t.open(v);
|
|
707
|
+
const ty = t.type(o);
|
|
708
|
+
switch (ty.type) {
|
|
709
|
+
case "Null":
|
|
710
|
+
emit($, t.int(0));
|
|
711
|
+
return;
|
|
712
|
+
case "String":
|
|
713
|
+
emit($, t.b("StringLength", [], [o], IntegerType, c.path));
|
|
714
|
+
return;
|
|
715
|
+
case "Array":
|
|
716
|
+
emit($, t.size(o, c.path));
|
|
717
|
+
return;
|
|
718
|
+
case "Set":
|
|
719
|
+
emit($, t.b("SetSize", [ty.key], [o], IntegerType, c.path));
|
|
720
|
+
return;
|
|
721
|
+
case "Dict":
|
|
722
|
+
emit($, t.b("DictSize", [ty.key, ty.value], [o], IntegerType, c.path));
|
|
723
|
+
return;
|
|
724
|
+
case "Vector":
|
|
725
|
+
emit($, t.b("VectorLength", [ty.element], [o], IntegerType, c.path));
|
|
726
|
+
return;
|
|
727
|
+
case "Matrix":
|
|
728
|
+
emit($, t.b("MatrixRows", [ty.element], [o], IntegerType, c.path));
|
|
729
|
+
return;
|
|
730
|
+
case "Blob":
|
|
731
|
+
emit($, t.b("BlobSize", [], [o], IntegerType, c.path));
|
|
732
|
+
return;
|
|
733
|
+
case "Struct":
|
|
734
|
+
emit($, t.int(Object.keys(ty.fields).length));
|
|
735
|
+
return;
|
|
736
|
+
case "Integer":
|
|
737
|
+
emit($, t.b("IntegerAbs", [], [o], IntegerType, c.path));
|
|
738
|
+
return;
|
|
739
|
+
case "Float":
|
|
740
|
+
emit($, t.b("FloatAbs", [], [o], FloatType, c.path));
|
|
741
|
+
return;
|
|
742
|
+
case "Variant":
|
|
743
|
+
if (nullablePayload(ty) !== undefined) {
|
|
744
|
+
t.match($, o, { none: $2 => emit($2, t.int(0)), some: ($2, p) => length($2, p, emit) }, c.path);
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
// jq sees a variant as {type, value}.
|
|
748
|
+
emit($, t.int(2));
|
|
749
|
+
return;
|
|
750
|
+
default:
|
|
751
|
+
cannot(t, c, $, o, "a value with a length");
|
|
752
|
+
}
|
|
753
|
+
};
|
|
754
|
+
length(c.$, x, c.emit);
|
|
755
|
+
});
|
|
756
|
+
rule("utf8bytelength", (t, c) => {
|
|
757
|
+
c.emit(c.$, t.b("BlobSize", [], [t.b("StringEncodeUtf8", [], [t.open(input(t, c))], BlobType, c.path)], IntegerType, c.path));
|
|
758
|
+
});
|
|
759
|
+
const compareString = compareFor(StringType);
|
|
760
|
+
/** The type of the root a program was checked against: a struct of datasets. */
|
|
761
|
+
function rootType(t) {
|
|
762
|
+
return parts(unwrap(t.checked.inputType));
|
|
763
|
+
}
|
|
764
|
+
/** A struct's field names, as `keys` gives them (sorted) and `keys_unsorted` (in declared order, §13.12). */
|
|
765
|
+
function fieldNames(t, c, type) {
|
|
766
|
+
const names = Object.keys(type.fields);
|
|
767
|
+
return t.value(c.name === "keys" ? [...names].sort(compareString) : names, ArrayType(StringType), c.path);
|
|
768
|
+
}
|
|
769
|
+
rule(["keys", "keys_unsorted"], (t, c) => {
|
|
770
|
+
// A root's names are its type's: every dataset in it, none read.
|
|
771
|
+
if (isRoot(c.x)) {
|
|
772
|
+
c.emit(c.$, fieldNames(t, c, rootType(t)));
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
const x = t.open(input(t, c));
|
|
776
|
+
const xt = t.type(x);
|
|
777
|
+
if (xt.type === "Dict") {
|
|
778
|
+
const K = xt.key;
|
|
779
|
+
const out = t.declare(c.$, t.emptyArray(K), "keys");
|
|
780
|
+
t.forEach(c.$, x, ($, _value, key) => t.push($, out, key, c.path), c.path);
|
|
781
|
+
c.emit(c.$, out);
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
if (xt.type === "Struct") {
|
|
785
|
+
c.emit(c.$, fieldNames(t, c, xt));
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
if (xt.type === "Variant") {
|
|
789
|
+
c.emit(c.$, t.value(["type", "value"], ArrayType(StringType), c.path));
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
const array = t.asArray(c.$, x, c.path);
|
|
793
|
+
if (array === undefined) {
|
|
794
|
+
cannot(t, c, c.$, x, "a dict, a struct or an array");
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
c.emit(c.$, t.b("ArrayRange", [], [t.int(0), t.size(array, c.path), t.int(1)], ArrayType(IntegerType), c.path));
|
|
798
|
+
});
|
|
799
|
+
/** Whether a struct type has a field of a name: a constant for a name the query writes. */
|
|
800
|
+
function hasField(t, c, type, key) {
|
|
801
|
+
const names = Object.keys(type.fields);
|
|
802
|
+
const known = t.constant(key)?.value;
|
|
803
|
+
if (typeof known === "string")
|
|
804
|
+
return t.bool(names.includes(known), c.path);
|
|
805
|
+
return t.b("SetHas", [StringType], [t.value(new SortedSet(names, compareString), SetType(StringType), c.path), key], BooleanType, c.path);
|
|
806
|
+
}
|
|
807
|
+
/** Whether a container has a key: a dict's key, a struct's field name, an array's index. */
|
|
808
|
+
function has(t, c, $, container, key) {
|
|
809
|
+
const o = t.open(container);
|
|
810
|
+
const ot = t.type(o);
|
|
811
|
+
if (ot.type === "Dict")
|
|
812
|
+
return t.b("DictHas", [ot.key, ot.value], [o, t.widenTo($, key, ot.key, c.path)], BooleanType, c.path);
|
|
813
|
+
if (ot.type === "Struct")
|
|
814
|
+
return hasField(t, c, ot, key);
|
|
815
|
+
const array = t.asArray($, o, c.path);
|
|
816
|
+
if (array === undefined)
|
|
817
|
+
return undefined;
|
|
818
|
+
const k = t.bind($, key, "index");
|
|
819
|
+
return t.b("BooleanAnd", [], [t.b("LessEqual", [IntegerType], [t.int(0), k], BooleanType, c.path), t.lt(k, t.size(array, c.path), c.path)], BooleanType, c.path);
|
|
820
|
+
}
|
|
821
|
+
rule("has", (t, c) => values(t, c, [0], c.$, ($, [key]) => {
|
|
822
|
+
// A root's names are its type's: every dataset in it, none read.
|
|
823
|
+
if (isRoot(c.x)) {
|
|
824
|
+
c.emit($, hasField(t, c, rootType(t), key));
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
const x = input(t, c);
|
|
828
|
+
const result = has(t, c, $, x, key);
|
|
829
|
+
if (result === undefined) {
|
|
830
|
+
cannot(t, c, $, x, "a dict, a struct or an array");
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
c.emit($, result);
|
|
834
|
+
}));
|
|
835
|
+
rule("in", (t, c) => {
|
|
836
|
+
const x = input(t, c);
|
|
837
|
+
arg(t, c, 0, c.$, c.x, ($, container) => {
|
|
838
|
+
const result = has(t, c, $, container, x);
|
|
839
|
+
if (result === undefined) {
|
|
840
|
+
cannot(t, c, $, container, "a dict, a struct or an array");
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
c.emit($, result);
|
|
844
|
+
});
|
|
845
|
+
});
|
|
846
|
+
rule("map", (t, c) => {
|
|
847
|
+
const element = outputType(t, c).value;
|
|
848
|
+
const out = t.declare(c.$, t.emptyArray(element), "mapped");
|
|
849
|
+
t.iterate(c.$, input(t, c), false, c.path, ($, item) => arg(t, c, 0, $, item, ($2, v) => t.push($2, out, v, c.path)));
|
|
850
|
+
c.emit(c.$, out);
|
|
851
|
+
});
|
|
852
|
+
rule("map_values", (t, c) => {
|
|
853
|
+
const x = t.open(input(t, c));
|
|
854
|
+
const xt = t.type(x);
|
|
855
|
+
const type = outputType(t, c);
|
|
856
|
+
const first = ($, v, then) => {
|
|
857
|
+
t.once($, ($2, label) => arg(t, c, 0, $2, v, ($3, w) => { then($3, w); t.brk($3, label); }), c.path);
|
|
858
|
+
};
|
|
859
|
+
if (xt.type === "Array") {
|
|
860
|
+
const out = t.declare(c.$, t.emptyArray(type.value), "mapped");
|
|
861
|
+
t.forEach(c.$, x, ($, item) => first($, item, ($2, w) => t.push($2, out, w, c.path)), c.path);
|
|
862
|
+
c.emit(c.$, out);
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
if (xt.type === "Dict") {
|
|
866
|
+
const out = t.declare(c.$, t.value(new Map(), type), "mapped");
|
|
867
|
+
t.forEach(c.$, x, ($, item, key) => first($, item, ($2, w) => t.stmt($2, t.b("DictInsert", [type.key, type.value], [out, key, t.widenTo($2, w, type.value, c.path)], NullType, c.path))), c.path);
|
|
868
|
+
c.emit(c.$, out);
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
if (xt.type === "Struct") {
|
|
872
|
+
const s = t.bind(c.$, x, "struct");
|
|
873
|
+
const fields = {};
|
|
874
|
+
for (const [name, f] of Object.entries(type.fields)) {
|
|
875
|
+
const value = t.field(s, name);
|
|
876
|
+
fields[name] = t.bind(c.$, t.one(c.args[0], c.argPaths[0], c.$, value, c.env, f), name);
|
|
877
|
+
}
|
|
878
|
+
c.emit(c.$, t.struct(type, fields));
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
cannot(t, c, c.$, x, "an array, a dict or a struct");
|
|
882
|
+
});
|
|
883
|
+
/**
|
|
884
|
+
* `a + b` for `add`, as the checker types it: numbers, strings, arrays, dicts;
|
|
885
|
+
* struct merges. Also how a split query's pieces add up an `add` (`split.ts`).
|
|
886
|
+
*
|
|
887
|
+
* @internal
|
|
888
|
+
*/
|
|
889
|
+
export function addInto(t, c, $, acc, v, type) {
|
|
890
|
+
const o = t.open(v);
|
|
891
|
+
const ot = t.type(o);
|
|
892
|
+
if (ot.type === "Null")
|
|
893
|
+
return;
|
|
894
|
+
if (nullablePayload(ot) !== undefined) {
|
|
895
|
+
t.match($, o, { some: ($2, p) => addInto(t, c, $2, acc, p, type) }, c.path);
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
switch (type.type) {
|
|
899
|
+
case "Integer":
|
|
900
|
+
t.assign($, acc, t.b("IntegerAdd", [], [acc, o], IntegerType, c.path));
|
|
901
|
+
return;
|
|
902
|
+
case "Float":
|
|
903
|
+
t.assign($, acc, t.b("FloatAdd", [], [acc, float(t, $, o, c.path)], FloatType, c.path));
|
|
904
|
+
return;
|
|
905
|
+
case "String":
|
|
906
|
+
t.assign($, acc, t.concat(acc, o, c.path));
|
|
907
|
+
return;
|
|
908
|
+
case "Array":
|
|
909
|
+
t.stmt($, t.b("ArrayAppend", [type.value], [acc, t.widenTo($, o, type, c.path)], NullType, c.path));
|
|
910
|
+
return;
|
|
911
|
+
case "Dict":
|
|
912
|
+
t.forEach($, t.widenTo($, o, type, c.path), ($2, value, key) => t.put($2, acc, key, value, c.path), c.path);
|
|
913
|
+
return;
|
|
914
|
+
default: {
|
|
915
|
+
// Structs of one type: the later one's fields win, which is all of them.
|
|
916
|
+
const payload = nullablePayload(type);
|
|
917
|
+
if (payload !== undefined) {
|
|
918
|
+
t.assign($, acc, t.some(t.widenTo($, o, payload, c.path), type));
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
throw t.gap(`add of ${printType(ot)}`);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
rule("add", (t, c) => {
|
|
926
|
+
const type = outputType(t, c);
|
|
927
|
+
if (type.type === "Null") {
|
|
928
|
+
// The values' type has no identity to add: jq's null.
|
|
929
|
+
if (c.args.length === 1)
|
|
930
|
+
arg(t, c, 0, c.$, c.x, () => { });
|
|
931
|
+
c.emit(c.$, t.null(c.path));
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
const start = type.type === "Integer" ? t.int(0) : type.type === "Float" ? t.float(0) : type.type === "String" ? t.str("")
|
|
935
|
+
: type.type === "Array" ? t.emptyArray(type.value) : type.type === "Dict" ? t.value(new Map(), type) : t.none(type);
|
|
936
|
+
const acc = t.declare(c.$, start, "sum");
|
|
937
|
+
const each = ($, v) => addInto(t, c, $, acc, v, type);
|
|
938
|
+
if (c.args.length === 1)
|
|
939
|
+
arg(t, c, 0, c.$, c.x, each);
|
|
940
|
+
else
|
|
941
|
+
t.iterate(c.$, input(t, c), false, c.path, each);
|
|
942
|
+
c.emit(c.$, acc);
|
|
943
|
+
});
|
|
944
|
+
rule(["any", "all"], (t, c) => {
|
|
945
|
+
const any = c.name === "any";
|
|
946
|
+
// any: true at the first true; all: false at the first false.
|
|
947
|
+
const result = t.declare(c.$, t.bool(!any), "result");
|
|
948
|
+
const test = ($, v, label) => {
|
|
949
|
+
const truth = t.truthy(v, c.path);
|
|
950
|
+
t.branch($, any ? truth : negate(t, truth, c.path), $2 => { t.assign($2, result, t.bool(any)); t.brk($2, label); }, () => { }, c.path);
|
|
951
|
+
};
|
|
952
|
+
t.once(c.$, ($, label) => {
|
|
953
|
+
if (c.args.length === 2) {
|
|
954
|
+
arg(t, c, 0, $, c.x, ($2, g) => arg(t, c, 1, $2, g, ($3, v) => test($3, v, label)));
|
|
955
|
+
}
|
|
956
|
+
else if (c.args.length === 1) {
|
|
957
|
+
t.iterate($, input(t, c), false, c.path, ($2, item) => arg(t, c, 0, $2, item, ($3, v) => test($3, v, label)));
|
|
958
|
+
}
|
|
959
|
+
else {
|
|
960
|
+
t.iterate($, input(t, c), false, c.path, ($2, item) => test($2, item, label));
|
|
961
|
+
}
|
|
962
|
+
}, c.path);
|
|
963
|
+
c.emit(c.$, result);
|
|
964
|
+
});
|
|
965
|
+
rule("flatten", (t, c) => {
|
|
966
|
+
const type = outputType(t, c);
|
|
967
|
+
const element = type.value;
|
|
968
|
+
const out = t.declare(c.$, t.emptyArray(element), "flat");
|
|
969
|
+
const into = ($, v) => {
|
|
970
|
+
const vt = t.type(t.open(v));
|
|
971
|
+
if (isTypeEqual(vt, element) || vt.type !== "Array") {
|
|
972
|
+
t.push($, out, v, c.path);
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
t.forEach($, v, ($2, item) => into($2, item), c.path);
|
|
976
|
+
};
|
|
977
|
+
t.forEach(c.$, input(t, c), ($, item) => into($, item), c.path);
|
|
978
|
+
c.emit(c.$, out);
|
|
979
|
+
});
|
|
980
|
+
/** An array's elements as an array. */
|
|
981
|
+
function elementsOf(t, c, $) {
|
|
982
|
+
return t.asArray($, t.open(input(t, c)), c.path);
|
|
983
|
+
}
|
|
984
|
+
/** Sorts an array by East's total order of a key the function gives each element. */
|
|
985
|
+
function sortBy(t, c, array, keyType, key) {
|
|
986
|
+
const element = t.type(array).value;
|
|
987
|
+
const fn = t.lambda([element], keyType, ["item"], (_$f, item) => key(item), c.path);
|
|
988
|
+
return t.b("ArraySort", [element, keyType], [array, fn], t.type(array), c.path);
|
|
989
|
+
}
|
|
990
|
+
rule("sort", (t, c) => {
|
|
991
|
+
const array = elementsOf(t, c, c.$);
|
|
992
|
+
if (array === undefined) {
|
|
993
|
+
cannot(t, c, c.$, input(t, c), "an array");
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
const element = t.type(array).value;
|
|
997
|
+
c.emit(c.$, sortBy(t, c, array, element, item => item));
|
|
998
|
+
});
|
|
999
|
+
/**
|
|
1000
|
+
* Each element with the key a filter gives it, as jq keys `sort_by(f)`: the
|
|
1001
|
+
* filter's one output, or the array of its outputs.
|
|
1002
|
+
*/
|
|
1003
|
+
function keyed(t, c, $) {
|
|
1004
|
+
const array = elementsOf(t, c, $);
|
|
1005
|
+
if (array === undefined) {
|
|
1006
|
+
cannot(t, c, $, input(t, c), "an array");
|
|
1007
|
+
return undefined;
|
|
1008
|
+
}
|
|
1009
|
+
const element = t.type(array).value;
|
|
1010
|
+
const sample = t.placeholder(element);
|
|
1011
|
+
const one = argIsOne(t, c, 0, sample);
|
|
1012
|
+
const outputKey = argType(t, c, 0, sample);
|
|
1013
|
+
const keyType = one ? outputKey : ArrayType(outputKey);
|
|
1014
|
+
const Pair = StructType({ key: keyType, value: element });
|
|
1015
|
+
const pairs = t.declare($, t.emptyArray(Pair), "keyed");
|
|
1016
|
+
t.forEach($, array, ($2, item) => {
|
|
1017
|
+
if (one) {
|
|
1018
|
+
const key = t.one(c.args[0], c.argPaths[0], $2, item, c.env, keyType);
|
|
1019
|
+
t.push($2, pairs, t.struct(Pair, { key, value: item }), c.path);
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
const keys = t.declare($2, t.emptyArray(outputKey), "keys");
|
|
1023
|
+
arg(t, c, 0, $2, item, ($3, k) => t.push($3, keys, k, c.path));
|
|
1024
|
+
t.push($2, pairs, t.struct(Pair, { key: keys, value: item }), c.path);
|
|
1025
|
+
}, c.path);
|
|
1026
|
+
return { pairs, keyType, element };
|
|
1027
|
+
}
|
|
1028
|
+
/** Pairs sorted by their keys (stable). */
|
|
1029
|
+
function sortPairs(t, c, pairs, keyType) {
|
|
1030
|
+
return sortBy(t, c, pairs, keyType, pair => t.field(pair, "key"));
|
|
1031
|
+
}
|
|
1032
|
+
rule("sort_by", (t, c) => {
|
|
1033
|
+
const k = keyed(t, c, c.$);
|
|
1034
|
+
if (k === undefined)
|
|
1035
|
+
return;
|
|
1036
|
+
const sorted = t.bind(c.$, sortPairs(t, c, k.pairs, k.keyType), "sorted");
|
|
1037
|
+
const out = t.declare(c.$, t.emptyArray(k.element), "sorted");
|
|
1038
|
+
t.forEach(c.$, sorted, ($, pair) => t.push($, out, t.field(pair, "value"), c.path), c.path, "pair");
|
|
1039
|
+
c.emit(c.$, out);
|
|
1040
|
+
});
|
|
1041
|
+
/** Runs of equal keys in sorted pairs, as arrays of their values, in key order. */
|
|
1042
|
+
function groups(t, c, $, k, each) {
|
|
1043
|
+
const sorted = t.bind($, sortPairs(t, c, k.pairs, k.keyType), "sorted");
|
|
1044
|
+
const group = t.declare($, t.emptyArray(k.element), "group");
|
|
1045
|
+
const last = t.declare($, t.none(OptionType(k.keyType)), "key");
|
|
1046
|
+
t.forEach($, sorted, ($2, pair) => {
|
|
1047
|
+
const key = t.bind($2, t.field(pair, "key"), "key");
|
|
1048
|
+
const same = t.matchValue(last, { none: () => t.bool(false), some: (_$3, previous) => t.eq(previous, key, c.path) }, BooleanType, c.path);
|
|
1049
|
+
t.ifElse($2, t.not(same, c.path), $3 => {
|
|
1050
|
+
t.ifElse($3, t.lt(t.int(0), t.size(group, c.path), c.path), $4 => {
|
|
1051
|
+
each($4, t.bind($4, t.b("ArrayCopy", [k.element], [group], ArrayType(k.element), c.path), "group"));
|
|
1052
|
+
t.stmt($4, t.b("ArrayClear", [k.element], [group], NullType, c.path));
|
|
1053
|
+
}, undefined, c.path);
|
|
1054
|
+
t.assign($3, last, t.some(key, OptionType(k.keyType)));
|
|
1055
|
+
}, undefined, c.path);
|
|
1056
|
+
t.push($2, group, t.field(pair, "value"), c.path);
|
|
1057
|
+
}, c.path, "pair");
|
|
1058
|
+
t.ifElse($, t.lt(t.int(0), t.size(group, c.path), c.path), $2 => each($2, group), undefined, c.path);
|
|
1059
|
+
}
|
|
1060
|
+
rule("group_by", (t, c) => {
|
|
1061
|
+
const k = keyed(t, c, c.$);
|
|
1062
|
+
if (k === undefined)
|
|
1063
|
+
return;
|
|
1064
|
+
const out = t.declare(c.$, t.emptyArray(ArrayType(k.element)), "groups");
|
|
1065
|
+
groups(t, c, c.$, k, ($, group) => t.push($, out, group, c.path));
|
|
1066
|
+
c.emit(c.$, out);
|
|
1067
|
+
});
|
|
1068
|
+
rule("unique_by", (t, c) => {
|
|
1069
|
+
const k = keyed(t, c, c.$);
|
|
1070
|
+
if (k === undefined)
|
|
1071
|
+
return;
|
|
1072
|
+
const out = t.declare(c.$, t.emptyArray(k.element), "unique");
|
|
1073
|
+
groups(t, c, c.$, k, ($, group) => t.push($, out, t.b("ArrayGet", [k.element], [group, t.int(0)], k.element, c.path), c.path));
|
|
1074
|
+
c.emit(c.$, out);
|
|
1075
|
+
});
|
|
1076
|
+
rule(["min_by", "max_by"], (t, c) => {
|
|
1077
|
+
const k = keyed(t, c, c.$);
|
|
1078
|
+
if (k === undefined)
|
|
1079
|
+
return;
|
|
1080
|
+
const min = c.name === "min_by";
|
|
1081
|
+
const Pair = t.type(k.pairs).value;
|
|
1082
|
+
const best = t.declare(c.$, t.none(OptionType(Pair)), "best");
|
|
1083
|
+
t.forEach(c.$, k.pairs, ($, pair) => {
|
|
1084
|
+
// The first least for min_by, the last greatest for max_by, as jq keeps them.
|
|
1085
|
+
const better = t.matchValue(best, {
|
|
1086
|
+
none: () => t.bool(true),
|
|
1087
|
+
some: (_$2, b) => min ? t.lt(t.field(pair, "key"), t.field(b, "key"), c.path) : t.b("GreaterEqual", [k.keyType], [t.field(pair, "key"), t.field(b, "key")], BooleanType, c.path),
|
|
1088
|
+
}, BooleanType, c.path);
|
|
1089
|
+
t.ifElse($, better, $2 => t.assign($2, best, t.some(pair, OptionType(Pair))), undefined, c.path);
|
|
1090
|
+
}, c.path, "pair");
|
|
1091
|
+
const option = OptionType(k.element);
|
|
1092
|
+
const out = outputType(t, c);
|
|
1093
|
+
if (nullablePayload(option) !== undefined) {
|
|
1094
|
+
c.emit(c.$, t.matchValue(best, { none: () => t.none(option), some: (_$, b) => t.some(t.field(b, "value"), option) }, option, c.path));
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
// An element that can be null already is the answer as it is, and no element is its null.
|
|
1098
|
+
c.emit(c.$, t.matchValue(best, { none: $ => t.widenTo($, t.null(c.path), out, c.path), some: ($, b) => t.widenTo($, t.field(b, "value"), out, c.path) }, out, c.path));
|
|
1099
|
+
});
|
|
1100
|
+
rule("unique", (t, c) => {
|
|
1101
|
+
const array = elementsOf(t, c, c.$);
|
|
1102
|
+
if (array === undefined) {
|
|
1103
|
+
cannot(t, c, c.$, input(t, c), "an array");
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
const element = t.type(array).value;
|
|
1107
|
+
const sorted = t.bind(c.$, sortBy(t, c, array, element, item => item), "sorted");
|
|
1108
|
+
const out = t.declare(c.$, t.emptyArray(element), "unique");
|
|
1109
|
+
t.forEach(c.$, sorted, ($, item) => {
|
|
1110
|
+
const size = t.bind($, t.size(out, c.path), "size");
|
|
1111
|
+
const repeat = t.ifValue(t.lt(t.int(0), size, c.path), () => t.eq(t.b("ArrayGet", [element], [out, t.add(size, t.int(-1), c.path)], element, c.path), item, c.path), () => t.bool(false), BooleanType, c.path);
|
|
1112
|
+
t.ifElse($, t.not(repeat, c.path), $2 => t.push($2, out, item, c.path), undefined, c.path);
|
|
1113
|
+
}, c.path);
|
|
1114
|
+
c.emit(c.$, out);
|
|
1115
|
+
});
|
|
1116
|
+
rule(["min", "max"], (t, c) => {
|
|
1117
|
+
const array = elementsOf(t, c, c.$);
|
|
1118
|
+
if (array === undefined) {
|
|
1119
|
+
cannot(t, c, c.$, input(t, c), "an array");
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
const element = t.type(array).value;
|
|
1123
|
+
const option = OptionType(element);
|
|
1124
|
+
const best = t.declare(c.$, t.none(option), "best");
|
|
1125
|
+
t.forEach(c.$, array, ($, item) => {
|
|
1126
|
+
const better = t.matchValue(best, {
|
|
1127
|
+
none: () => t.bool(true),
|
|
1128
|
+
some: (_$2, b) => c.name === "min" ? t.lt(item, b, c.path) : t.b("GreaterEqual", [element], [item, b], BooleanType, c.path),
|
|
1129
|
+
}, BooleanType, c.path);
|
|
1130
|
+
t.ifElse($, better, $2 => t.assign($2, best, t.some(item, option)), undefined, c.path);
|
|
1131
|
+
}, c.path);
|
|
1132
|
+
const out = outputType(t, c);
|
|
1133
|
+
if (nullablePayload(option) !== undefined) {
|
|
1134
|
+
c.emit(c.$, t.widenTo(c.$, best, out, c.path));
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
// An element that can be null already is the answer as it is, and no element is its null.
|
|
1138
|
+
c.emit(c.$, t.matchValue(best, { none: $ => t.widenTo($, t.null(c.path), out, c.path), some: ($, b) => t.widenTo($, b, out, c.path) }, out, c.path));
|
|
1139
|
+
});
|
|
1140
|
+
rule("reverse", (t, c) => {
|
|
1141
|
+
const x = t.open(input(t, c));
|
|
1142
|
+
const xt = t.type(x);
|
|
1143
|
+
if (xt.type === "Null") {
|
|
1144
|
+
c.emit(c.$, t.emptyArray(outputType(t, c).value));
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
if (xt.type === "String") {
|
|
1148
|
+
const codePoints = t.b("StringSplit", [], [x, t.str("")], ArrayType(StringType), c.path);
|
|
1149
|
+
c.emit(c.$, t.b("ArrayStringJoin", [], [t.b("ArrayReverse", [StringType], [codePoints], ArrayType(StringType), c.path), t.str("")], StringType, c.path));
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
const array = t.asArray(c.$, x, c.path);
|
|
1153
|
+
if (array === undefined) {
|
|
1154
|
+
cannot(t, c, c.$, x, "an array or a string");
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
c.emit(c.$, t.b("ArrayReverse", [t.type(array).value], [array], t.type(array), c.path));
|
|
1158
|
+
});
|
|
1159
|
+
/**
|
|
1160
|
+
* Whether `a` holds `b`, as jq's `contains` decides it: a string its
|
|
1161
|
+
* substrings; an array an array whose every element some element of it
|
|
1162
|
+
* holds; a struct or a dict one whose every field or key it has, holding its
|
|
1163
|
+
* value; any other value an equal one.
|
|
1164
|
+
*/
|
|
1165
|
+
function contains(t, c, $, a, b) {
|
|
1166
|
+
// Sets, vectors and matrices are arrays to jq.
|
|
1167
|
+
const array = (v) => arrayLike(t.type(v)) && t.type(v).type !== "Array" ? t.asArray($, v, c.path) : v;
|
|
1168
|
+
const va = array(t.open(a));
|
|
1169
|
+
const vb = array(t.open(b));
|
|
1170
|
+
const ta = t.type(va);
|
|
1171
|
+
const tb = t.type(vb);
|
|
1172
|
+
if (ta.type === "String" && tb.type === "String")
|
|
1173
|
+
return t.b("StringContains", [], [va, vb], BooleanType, c.path);
|
|
1174
|
+
if (ta.type === "Array" && tb.type === "Array") {
|
|
1175
|
+
// `[]` is held by every array, and an empty array holds only `[]`.
|
|
1176
|
+
if (tb.value.type === "Never")
|
|
1177
|
+
return t.bool(true, c.path);
|
|
1178
|
+
if (ta.value.type === "Never")
|
|
1179
|
+
return t.eq(t.size(vb, c.path), t.int(0), c.path);
|
|
1180
|
+
const outer = t.bind($, va, "outer");
|
|
1181
|
+
const all = t.declare($, t.bool(true), "contains");
|
|
1182
|
+
t.forEach($, vb, ($2, wanted, _k, each) => {
|
|
1183
|
+
const found = t.declare($2, t.bool(false), "found");
|
|
1184
|
+
t.forEach($2, outer, ($3, item, _k2, inner) => {
|
|
1185
|
+
t.ifElse($3, contains(t, c, $3, item, wanted), $4 => { t.assign($4, found, t.bool(true)); t.brk($4, inner); }, undefined, c.path);
|
|
1186
|
+
}, c.path);
|
|
1187
|
+
t.ifElse($2, t.not(found, c.path), $3 => { t.assign($3, all, t.bool(false)); t.brk($3, each); }, undefined, c.path);
|
|
1188
|
+
}, c.path, "wanted");
|
|
1189
|
+
return all;
|
|
1190
|
+
}
|
|
1191
|
+
if (ta.type === "Struct" && tb.type === "Struct") {
|
|
1192
|
+
const fields = ta.fields;
|
|
1193
|
+
const outer = t.bind($, va, "outer");
|
|
1194
|
+
const inner = t.bind($, vb, "inner");
|
|
1195
|
+
let all;
|
|
1196
|
+
for (const name of Object.keys(tb.fields)) {
|
|
1197
|
+
// A field the outer struct lacks is not held.
|
|
1198
|
+
if (!(name in fields))
|
|
1199
|
+
return t.bool(false, c.path);
|
|
1200
|
+
const held = t.bind($, contains(t, c, $, t.field(outer, name), t.field(inner, name)), "held");
|
|
1201
|
+
all = all === undefined ? held : t.b("BooleanAnd", [], [all, held], BooleanType, c.path);
|
|
1202
|
+
}
|
|
1203
|
+
return all ?? t.bool(true, c.path);
|
|
1204
|
+
}
|
|
1205
|
+
// `{}` is held by every dict.
|
|
1206
|
+
if (ta.type === "Dict" && tb.type === "Struct")
|
|
1207
|
+
return t.bool(true, c.path);
|
|
1208
|
+
if (ta.type === "Dict" && tb.type === "Dict") {
|
|
1209
|
+
const K = ta.key;
|
|
1210
|
+
const V = ta.value;
|
|
1211
|
+
const outer = t.bind($, va, "outer");
|
|
1212
|
+
const all = t.declare($, t.bool(true), "contains");
|
|
1213
|
+
t.forEach($, vb, ($2, value, key, label) => {
|
|
1214
|
+
t.match($2, t.b("DictTryGet", [K, V], [outer, key], OptionType(V), c.path), {
|
|
1215
|
+
none: $3 => { t.assign($3, all, t.bool(false)); t.brk($3, label); },
|
|
1216
|
+
some: ($3, held) => t.ifElse($3, t.not(contains(t, c, $3, held, value), c.path), $4 => { t.assign($4, all, t.bool(false)); t.brk($4, label); }, undefined, c.path),
|
|
1217
|
+
}, c.path);
|
|
1218
|
+
}, c.path);
|
|
1219
|
+
return all;
|
|
1220
|
+
}
|
|
1221
|
+
// Values of two kinds (a lenient place lets them by) raise, as jq does.
|
|
1222
|
+
const [ka, kb] = [jqTypeNames(ta)[0], jqTypeNames(tb)[0]];
|
|
1223
|
+
if (ka !== kb || ["array", "object"].includes(ka ?? ""))
|
|
1224
|
+
return t.failure(`${ka ?? "a value"} and ${kb ?? "a value"} cannot have their containment checked`, c.path);
|
|
1225
|
+
return t.compare($, "==", va, vb, c.path);
|
|
1226
|
+
}
|
|
1227
|
+
rule("contains", (t, c) => values(t, c, [0], c.$, ($, [b]) => t.give($, contains(t, c, $, input(t, c), b), c.emit)));
|
|
1228
|
+
rule("inside", (t, c) => values(t, c, [0], c.$, ($, [b]) => t.give($, contains(t, c, $, b, input(t, c)), c.emit)));
|
|
1229
|
+
/** Every index where a string occurs in another, overlapping ones included, in code points. */
|
|
1230
|
+
function indices(t, c, $, s, sub) {
|
|
1231
|
+
const out = t.declare($, t.emptyArray(IntegerType), "indices");
|
|
1232
|
+
const length = t.declare($, t.b("StringLength", [], [s], IntegerType, c.path), "length", false);
|
|
1233
|
+
t.ifElse($, t.lt(t.int(0), t.b("StringLength", [], [sub], IntegerType, c.path), c.path), $2 => {
|
|
1234
|
+
const from = t.declare($2, t.int(0), "from");
|
|
1235
|
+
t.whileLoop($2, true, ($3, label) => {
|
|
1236
|
+
const rest = t.b("StringSubstring", [], [s, from, length], StringType, c.path);
|
|
1237
|
+
const at = t.declare($3, t.b("StringIndexOf", [], [rest, sub], IntegerType, c.path), "at", false);
|
|
1238
|
+
t.ifElse($3, t.lt(at, t.int(0), c.path), $4 => t.brk($4, label), undefined, c.path);
|
|
1239
|
+
t.push($3, out, t.add(from, at, c.path), c.path);
|
|
1240
|
+
t.assign($3, from, t.add(t.add(from, at, c.path), t.int(1), c.path));
|
|
1241
|
+
}, c.path);
|
|
1242
|
+
}, undefined, c.path);
|
|
1243
|
+
return out;
|
|
1244
|
+
}
|
|
1245
|
+
/**
|
|
1246
|
+
* Every position at which an array holds an element equal to a value, or,
|
|
1247
|
+
* for an array, a run of elements equal to its elements; an empty run is
|
|
1248
|
+
* found nowhere, as in jq.
|
|
1249
|
+
*/
|
|
1250
|
+
function indicesIn(t, c, $, array, x) {
|
|
1251
|
+
const out = t.declare($, t.emptyArray(IntegerType), "indices");
|
|
1252
|
+
const a = t.bind($, array, "array");
|
|
1253
|
+
const E = t.type(a).value;
|
|
1254
|
+
const xo = t.open(x);
|
|
1255
|
+
if (t.type(xo).type !== "Array") {
|
|
1256
|
+
t.forEach($, a, ($2, item, index) => t.ifElse($2, t.compare($2, "==", item, xo, c.path), $3 => t.push($3, out, index, c.path), undefined, c.path), c.path);
|
|
1257
|
+
return out;
|
|
1258
|
+
}
|
|
1259
|
+
const run = t.bind($, xo, "run");
|
|
1260
|
+
const n = t.declare($, t.size(run, c.path), "length", false);
|
|
1261
|
+
const last = t.declare($, t.b("IntegerSubtract", [], [t.size(a, c.path), n], IntegerType, c.path), "last", false);
|
|
1262
|
+
t.ifElse($, t.lt(t.int(0), n, c.path), $2 => {
|
|
1263
|
+
const i = t.declare($2, t.int(0), "start");
|
|
1264
|
+
t.whileLoop($2, t.b("LessEqual", [IntegerType], [i, last], BooleanType, c.path), $3 => {
|
|
1265
|
+
const same = t.declare($3, t.bool(true), "same");
|
|
1266
|
+
t.forEach($3, run, ($4, wanted, k, label) => {
|
|
1267
|
+
const item = t.b("ArrayGet", [E], [a, t.add(i, k, c.path)], E, c.path);
|
|
1268
|
+
t.ifElse($4, t.not(t.compare($4, "==", item, wanted, c.path), c.path), $5 => { t.assign($5, same, t.bool(false)); t.brk($5, label); }, undefined, c.path);
|
|
1269
|
+
}, c.path, "wanted");
|
|
1270
|
+
t.ifElse($3, same, $4 => t.push($4, out, i, c.path), undefined, c.path);
|
|
1271
|
+
t.assign($3, i, t.add(i, t.int(1), c.path));
|
|
1272
|
+
}, c.path);
|
|
1273
|
+
}, undefined, c.path);
|
|
1274
|
+
return out;
|
|
1275
|
+
}
|
|
1276
|
+
rule(["index", "rindex", "indices"], (t, c) => values(t, c, [0], c.$, ($, [sub]) => {
|
|
1277
|
+
const x = t.open(input(t, c));
|
|
1278
|
+
const all = t.type(x).type === "String" ? indices(t, c, $, x, sub) : indicesIn(t, c, $, t.asArray($, x, c.path), sub);
|
|
1279
|
+
if (c.name === "indices") {
|
|
1280
|
+
c.emit($, all);
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1283
|
+
const option = OptionType(IntegerType);
|
|
1284
|
+
const size = t.bind($, t.size(all, c.path), "count");
|
|
1285
|
+
c.emit($, t.ifValue(t.eq(size, t.int(0), c.path), () => t.none(option), () => t.some(t.b("ArrayGet", [IntegerType], [all, c.name === "index" ? t.int(0) : t.add(size, t.int(-1), c.path)], IntegerType, c.path), option), option, c.path));
|
|
1286
|
+
}));
|
|
1287
|
+
rule("bsearch", (t, c) => values(t, c, [0], c.$, ($, [target]) => {
|
|
1288
|
+
const array = elementsOf(t, c, $);
|
|
1289
|
+
if (array === undefined) {
|
|
1290
|
+
cannot(t, c, $, input(t, c), "a sorted array");
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
const element = t.type(array).value;
|
|
1294
|
+
const common = unifyOrFail(t, element, t.type(target));
|
|
1295
|
+
const a = t.bind($, isTypeEqual(common, element) ? array : t.widenTo($, array, ArrayType(common), c.path), "sorted");
|
|
1296
|
+
const x = t.bind($, t.widenTo($, target, common, c.path), "target");
|
|
1297
|
+
const identity = t.lambda([common], common, ["item"], (_$f, item) => item, c.path);
|
|
1298
|
+
// The first position whose element is not less than the target: where it is, or would be inserted.
|
|
1299
|
+
const at = t.declare($, t.b("ArrayFindSortedFirst", [common, common], [a, x, identity], IntegerType, c.path), "at", false);
|
|
1300
|
+
const found = t.declare($, t.bool(false), "found");
|
|
1301
|
+
t.ifElse($, t.lt(at, t.size(a, c.path), c.path), $2 => t.assign($2, found, t.eq(t.b("ArrayGet", [common], [a, at], common, c.path), x, c.path)), undefined, c.path);
|
|
1302
|
+
// jq's −1 − the insertion point when the value is not there.
|
|
1303
|
+
c.emit($, t.ifValue(found, () => at, () => t.b("IntegerSubtract", [], [t.int(-1), at], IntegerType, c.path), IntegerType, c.path));
|
|
1304
|
+
}));
|
|
1305
|
+
/**
|
|
1306
|
+
* A scalar as text for join and the text formats: a string as it is, an
|
|
1307
|
+
* Integer in decimal, a Float as jq writes a number, a boolean as `true` or
|
|
1308
|
+
* `false`, null as `nullText`, and NaN as `nanText`.
|
|
1309
|
+
*/
|
|
1310
|
+
function cellText(t, $, v, path, nullText, nanText) {
|
|
1311
|
+
const o = t.open(v);
|
|
1312
|
+
const ot = t.type(o);
|
|
1313
|
+
if (ot.type === "String")
|
|
1314
|
+
return o;
|
|
1315
|
+
if (ot.type === "Null")
|
|
1316
|
+
return t.str(nullText, path);
|
|
1317
|
+
if (nullablePayload(ot) !== undefined) {
|
|
1318
|
+
return t.matchValue(o, { none: () => t.str(nullText, path), some: ($2, p) => cellText(t, $2, p, path, nullText, nanText) }, StringType, path);
|
|
1319
|
+
}
|
|
1320
|
+
if (ot.type === "Float")
|
|
1321
|
+
return jqNumber(t, $, o, path, nanText);
|
|
1322
|
+
return t.b("Print", [ot], [o], StringType, path);
|
|
1323
|
+
}
|
|
1324
|
+
/**
|
|
1325
|
+
* A Float as jq writes a number: −0.0 as `-0`, ±Infinity as
|
|
1326
|
+
* `±1.7976931348623157e+308`, NaN as `nanText`, and any other as
|
|
1327
|
+
* {@link jqDigits} writes it.
|
|
1328
|
+
*/
|
|
1329
|
+
function jqNumber(t, $, f, path, nanText) {
|
|
1330
|
+
const x = t.bind($, f, "number");
|
|
1331
|
+
const text = (s) => () => t.str(s, path);
|
|
1332
|
+
return t.ifValue(t.isNan(x, path), text(nanText), () => t.ifValue(t.eq(x, t.float(Infinity), path), text("1.7976931348623157e+308"), () => t.ifValue(t.eq(x, t.float(-Infinity), path), text("-1.7976931348623157e+308"), () => t.ifValue(t.eq(x, t.float(0), path), text("0"), () => t.ifValue(t.eq(x, t.float(-0), path), text("-0"), $2 => jqDigits(t, $2, x, path), StringType, path), StringType, path), StringType, path), StringType, path), StringType, path);
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* A finite, non-zero Float as jq 1.8 writes one it computed (`jvp_dtoa_fmt`):
|
|
1336
|
+
* the shortest digits that read back as it, which East's JSON writes too;
|
|
1337
|
+
* placed fixed-point, or as `d.ddde±XX` (at least two exponent digits) where
|
|
1338
|
+
* the decimal point falls four or more places before the digits, or more
|
|
1339
|
+
* than fifteen past their end. So `0.0001`, `1e-05`, `1e+16`,
|
|
1340
|
+
* `123456789012345680000` and `1.5e+300`.
|
|
1341
|
+
*/
|
|
1342
|
+
function jqDigits(t, $, x, path) {
|
|
1343
|
+
const S = StringType;
|
|
1344
|
+
const I = IntegerType;
|
|
1345
|
+
const length = (s) => t.b("StringLength", [], [s], I, path);
|
|
1346
|
+
const slice = (s, from, to) => t.b("StringSubstring", [], [s, from, to], S, path);
|
|
1347
|
+
const strip = (s, pattern) => t.b("RegexReplace", [], [s, t.str(pattern), t.str(""), t.str("")], S, path);
|
|
1348
|
+
const minus = (a, b) => t.b("IntegerSubtract", [], [a, b], I, path);
|
|
1349
|
+
const atMost = (a, b) => t.b("LessEqual", [I], [a, b], BooleanType, path);
|
|
1350
|
+
const zeros = (n) => t.b("StringRepeat", [], [t.str("0"), n], S, path);
|
|
1351
|
+
// East's JSON of the number: an optional -, digits with an optional point, and an optional exponent.
|
|
1352
|
+
const json = t.bind($, t.b("StringPrintJSON", [FloatType], [x], S, path), "json");
|
|
1353
|
+
const negative = t.bind($, t.b("StringStartsWith", [], [json, t.str("-")], BooleanType, path), "negative");
|
|
1354
|
+
const body = t.bind($, t.ifValue(negative, () => slice(json, t.int(1), length(json)), () => json, S, path), "body");
|
|
1355
|
+
const e = t.bind($, t.b("StringIndexOf", [], [body, t.str("e")], I, path), "e");
|
|
1356
|
+
const mantissa = t.bind($, t.ifValue(t.lt(e, t.int(0), path), () => body, () => slice(body, t.int(0), e), S, path), "mantissa");
|
|
1357
|
+
const exponent = t.bind($, t.ifValue(t.lt(e, t.int(0), path), () => t.int(0), () => t.b("Parse", [I], [t.b("StringReplace", [], [slice(body, t.add(e, t.int(1), path), length(body)), t.str("+"), t.str("")], S, path)], I, path), I, path), "exponent");
|
|
1358
|
+
const dot = t.bind($, t.b("StringIndexOf", [], [mantissa, t.str(".")], I, path), "dot");
|
|
1359
|
+
const whole = t.bind($, t.ifValue(t.lt(dot, t.int(0), path), () => mantissa, () => slice(mantissa, t.int(0), dot), S, path), "whole");
|
|
1360
|
+
const fraction = t.bind($, t.ifValue(t.lt(dot, t.int(0), path), () => t.str(""), () => slice(mantissa, t.add(dot, t.int(1), path), length(mantissa)), S, path), "fraction");
|
|
1361
|
+
// The significant digits, and where the decimal point falls among them.
|
|
1362
|
+
const all = t.bind($, t.concat(whole, fraction, path), "all");
|
|
1363
|
+
const lead = t.bind($, strip(all, "^0+"), "lead");
|
|
1364
|
+
const digits = t.bind($, strip(lead, "0+$"), "digits");
|
|
1365
|
+
const count = t.bind($, length(digits), "count");
|
|
1366
|
+
const point = t.bind($, minus(t.add(length(whole), exponent, path), minus(length(all), length(lead))), "point");
|
|
1367
|
+
const exponential = t.b("BooleanOr", [], [atMost(point, t.int(-4)), t.lt(t.add(count, t.int(15), path), point, path)], BooleanType, path);
|
|
1368
|
+
const text = t.bind($, t.ifValue(exponential, $2 => {
|
|
1369
|
+
const power = t.bind($2, t.add(point, t.int(-1), path), "power");
|
|
1370
|
+
const magnitude = t.bind($2, t.b("Print", [I], [t.b("IntegerAbs", [], [power], I, path)], S, path), "magnitude");
|
|
1371
|
+
const first = t.ifValue(t.lt(t.int(1), count, path), () => t.concat(t.concat(slice(digits, t.int(0), t.int(1)), t.str("."), path), slice(digits, t.int(1), count), path), () => digits, S, path);
|
|
1372
|
+
const sign = t.ifValue(t.lt(power, t.int(0), path), () => t.str("e-"), () => t.str("e+"), S, path);
|
|
1373
|
+
const padded = t.ifValue(t.lt(length(magnitude), t.int(2), path), () => t.concat(t.str("0"), magnitude, path), () => magnitude, S, path);
|
|
1374
|
+
return t.concat(t.concat(first, sign, path), padded, path);
|
|
1375
|
+
}, () => t.ifValue(atMost(point, t.int(0)), () => t.concat(t.concat(t.str("0."), zeros(minus(t.int(0), point)), path), digits, path), () => t.ifValue(atMost(count, point), () => t.concat(digits, zeros(minus(point, count)), path), () => t.concat(t.concat(slice(digits, t.int(0), point), t.str("."), path), slice(digits, point, count), path), S, path), S, path), S, path), "text");
|
|
1376
|
+
return t.ifValue(negative, () => t.concat(t.str("-"), text, path), () => text, S, path);
|
|
1377
|
+
}
|
|
1378
|
+
rule("join", (t, c) => values(t, c, [0], c.$, ($, [sep]) => {
|
|
1379
|
+
const parts = t.declare($, t.emptyArray(StringType), "parts");
|
|
1380
|
+
// jq's join writes a number as tojson does: NaN as null.
|
|
1381
|
+
t.forEach($, input(t, c), ($2, item) => t.push($2, parts, cellText(t, $2, item, c.path, "", "null"), c.path), c.path);
|
|
1382
|
+
c.emit($, t.b("ArrayStringJoin", [], [parts, sep], StringType, c.path));
|
|
1383
|
+
}));
|
|
1384
|
+
// ─── Types and conversion ────────────────────────────────────────────────
|
|
1385
|
+
rule("type", (t, c) => {
|
|
1386
|
+
const x = t.open(input(t, c));
|
|
1387
|
+
const payload = nullablePayload(t.type(x));
|
|
1388
|
+
if (payload !== undefined) {
|
|
1389
|
+
const name = jqTypeNames(payload)[0] ?? "null";
|
|
1390
|
+
c.emit(c.$, t.matchValue(x, { none: () => t.str("null", c.path), some: () => t.str(name, c.path) }, StringType, c.path));
|
|
1391
|
+
return;
|
|
1392
|
+
}
|
|
1393
|
+
c.emit(c.$, t.str(jqTypeNames(t.type(x))[0] ?? "null", c.path));
|
|
1394
|
+
});
|
|
1395
|
+
/** A type selector (`numbers`, `values`, …): the input when its kind is kept; `maybe` tests the value itself. */
|
|
1396
|
+
function selector(keep, test) {
|
|
1397
|
+
return (t, c) => {
|
|
1398
|
+
const x = t.open(input(t, c));
|
|
1399
|
+
const xt = t.type(x);
|
|
1400
|
+
const emitKept = ($, v, verdict) => {
|
|
1401
|
+
if (verdict === "keep") {
|
|
1402
|
+
c.emit($, v);
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
if (verdict === "maybe" && test !== undefined)
|
|
1406
|
+
t.ifElse($, test(t, $, v, c.path), $2 => c.emit($2, v), undefined, c.path);
|
|
1407
|
+
};
|
|
1408
|
+
const payload = nullablePayload(xt);
|
|
1409
|
+
if (payload !== undefined) {
|
|
1410
|
+
const inner = keep(unwrap(payload));
|
|
1411
|
+
const nullKept = keep(NullType);
|
|
1412
|
+
if (inner === "drop" && nullKept === "drop")
|
|
1413
|
+
return;
|
|
1414
|
+
if (inner !== "drop" && nullKept !== "drop") {
|
|
1415
|
+
c.emit(c.$, x);
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
1418
|
+
t.match(c.$, x, {
|
|
1419
|
+
none: $ => { if (nullKept !== "drop")
|
|
1420
|
+
c.emit($, t.null(c.path)); },
|
|
1421
|
+
some: ($, p) => emitKept($, p, inner),
|
|
1422
|
+
}, c.path);
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
emitKept(c.$, x, keep(xt));
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
function isScalar(type) {
|
|
1429
|
+
const u = unwrap(type);
|
|
1430
|
+
const payload = nullablePayload(u);
|
|
1431
|
+
if (payload !== undefined)
|
|
1432
|
+
return isScalar(payload);
|
|
1433
|
+
return !["Array", "Set", "Dict", "Struct", "Vector", "Matrix", "Variant"].includes(u.type);
|
|
1434
|
+
}
|
|
1435
|
+
const arrayLike = (type) => ["Array", "Set", "Vector", "Matrix"].includes(type.type);
|
|
1436
|
+
rule("arrays", selector(ty => arrayLike(ty) ? "keep" : "drop"));
|
|
1437
|
+
rule("objects", selector(ty => ty.type === "Struct" || ty.type === "Dict" || ty.type === "Variant" ? "keep" : "drop"));
|
|
1438
|
+
rule("iterables", selector(ty => arrayLike(ty) || ty.type === "Struct" || ty.type === "Dict" ? "keep" : "drop"));
|
|
1439
|
+
rule("booleans", selector(ty => ty.type === "Boolean" ? "keep" : "drop"));
|
|
1440
|
+
rule("numbers", selector(ty => isNumber(ty) ? "keep" : "drop"));
|
|
1441
|
+
rule("strings", selector(ty => ty.type === "String" ? "keep" : "drop"));
|
|
1442
|
+
rule("nulls", selector(ty => ty.type === "Null" ? "keep" : "drop"));
|
|
1443
|
+
rule("values", selector(ty => ty.type === "Null" ? "drop" : "keep"));
|
|
1444
|
+
rule("scalars", selector(ty => isScalar(ty) ? "keep" : "drop"));
|
|
1445
|
+
rule("normals", selector(ty => isNumber(ty) ? "maybe" : "drop", (t, $, v, path) => isNormal(t, $, v, path)));
|
|
1446
|
+
rule("finites", selector(ty => ty.type === "Integer" ? "keep" : ty.type === "Float" ? "maybe" : "drop", (t, $, v, path) => t.not(isInfiniteOrNan(t, $, v, path), path)));
|
|
1447
|
+
/** Whether a number is NaN. */
|
|
1448
|
+
function isNan(t, $, v, path) {
|
|
1449
|
+
return t.isNan(float(t, $, v, path), path);
|
|
1450
|
+
}
|
|
1451
|
+
function isInfinite(t, $, v, path) {
|
|
1452
|
+
const f = t.bind($, float(t, $, v, path), "number");
|
|
1453
|
+
return t.b("BooleanOr", [], [t.eq(f, t.float(Infinity), path), t.eq(f, t.float(-Infinity), path)], BooleanType, path);
|
|
1454
|
+
}
|
|
1455
|
+
function isInfiniteOrNan(t, $, v, path) {
|
|
1456
|
+
return t.b("BooleanOr", [], [isInfinite(t, $, v, path), isNan(t, $, v, path)], BooleanType, path);
|
|
1457
|
+
}
|
|
1458
|
+
function isNormal(t, $, v, path) {
|
|
1459
|
+
const f = t.bind($, float(t, $, v, path), "number");
|
|
1460
|
+
const magnitude = t.b("FloatAbs", [], [f], FloatType, path);
|
|
1461
|
+
return t.b("BooleanAnd", [], [t.not(isInfiniteOrNan(t, $, f, path), path), t.b("GreaterEqual", [FloatType], [magnitude, t.float(2 ** -1022)], BooleanType, path)], BooleanType, path);
|
|
1462
|
+
}
|
|
1463
|
+
rule("tostring", (t, c) => c.emit(c.$, t.tostring(c.$, input(t, c), c.path)));
|
|
1464
|
+
/** East's JSON text of a value (§13.9). */
|
|
1465
|
+
function tojson(t, v, path) {
|
|
1466
|
+
const o = t.open(v);
|
|
1467
|
+
return t.b("StringPrintJSON", [t.type(o)], [o], StringType, path);
|
|
1468
|
+
}
|
|
1469
|
+
rule("tojson", (t, c) => c.emit(c.$, tojson(t, input(t, c), c.path)));
|
|
1470
|
+
rule("tonumber", (t, c) => {
|
|
1471
|
+
const x = t.open(input(t, c));
|
|
1472
|
+
if (isNumber(t.type(x))) {
|
|
1473
|
+
c.emit(c.$, x);
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
c.emit(c.$, t.b("Parse", [FloatType], [x], FloatType, c.path));
|
|
1477
|
+
});
|
|
1478
|
+
rule("toboolean", (t, c) => {
|
|
1479
|
+
const x = t.open(input(t, c));
|
|
1480
|
+
if (t.type(x).type === "Boolean") {
|
|
1481
|
+
c.emit(c.$, x);
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
c.emit(c.$, t.b("Parse", [BooleanType], [x], BooleanType, c.path));
|
|
1485
|
+
});
|
|
1486
|
+
rule("builtins", (t, c) => {
|
|
1487
|
+
const names = [];
|
|
1488
|
+
for (const [name, b] of BUILTINS) {
|
|
1489
|
+
if (name.startsWith("@") || !(b.status === "supported" || (b.status === "tooling" && t.options.tooling === true)))
|
|
1490
|
+
continue;
|
|
1491
|
+
for (const arity of b.arities)
|
|
1492
|
+
names.push(`${name}/${arity}`);
|
|
1493
|
+
}
|
|
1494
|
+
c.emit(c.$, t.value(names.sort(compareString), ArrayType(StringType), c.path));
|
|
1495
|
+
});
|
|
1496
|
+
rule("infinite", (t, c) => c.emit(c.$, t.float(Infinity, c.path)));
|
|
1497
|
+
rule("nan", (t, c) => c.emit(c.$, t.float(NaN, c.path)));
|
|
1498
|
+
rule("isinfinite", (t, c) => c.emit(c.$, isInfinite(t, c.$, input(t, c), c.path)));
|
|
1499
|
+
rule("isnan", (t, c) => c.emit(c.$, isNan(t, c.$, input(t, c), c.path)));
|
|
1500
|
+
rule("isnormal", (t, c) => c.emit(c.$, isNormal(t, c.$, input(t, c), c.path)));
|
|
1501
|
+
// jq's own definition: a number that is not infinite, so NaN is finite.
|
|
1502
|
+
rule("isfinite", (t, c) => c.emit(c.$, t.not(isInfinite(t, c.$, input(t, c), c.path), c.path)));
|
|
1503
|
+
// jq's `if . < 0 then -. else . end`: a value above every number is given back; null and a boolean raise.
|
|
1504
|
+
rule("abs", (t, c) => {
|
|
1505
|
+
const x = t.open(input(t, c));
|
|
1506
|
+
const xt = t.type(x);
|
|
1507
|
+
if (xt.type === "Integer")
|
|
1508
|
+
c.emit(c.$, t.b("IntegerAbs", [], [x], IntegerType, c.path));
|
|
1509
|
+
else if (xt.type === "Float")
|
|
1510
|
+
c.emit(c.$, t.b("FloatAbs", [], [x], FloatType, c.path));
|
|
1511
|
+
else if (xt.type === "Null" || xt.type === "Boolean")
|
|
1512
|
+
t.negate(c.$, x, c.path, c.emit);
|
|
1513
|
+
else
|
|
1514
|
+
c.emit(c.$, x);
|
|
1515
|
+
});
|
|
1516
|
+
// ─── Strings ─────────────────────────────────────────────────────────────
|
|
1517
|
+
/** A rule on a string input with value arguments. */
|
|
1518
|
+
function onString(indices, body) {
|
|
1519
|
+
return (t, c) => values(t, c, indices, c.$, ($, args) => {
|
|
1520
|
+
const s = t.open(input(t, c));
|
|
1521
|
+
if (t.type(s).type !== "String") {
|
|
1522
|
+
cannot(t, c, $, s, "a string");
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
body(t, c, $, s, args);
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
rule("startswith", onString([0], (t, c, $, s, [p]) => c.emit($, t.b("StringStartsWith", [], [s, p], BooleanType, c.path))));
|
|
1529
|
+
rule("endswith", onString([0], (t, c, $, s, [p]) => c.emit($, t.b("StringEndsWith", [], [s, p], BooleanType, c.path))));
|
|
1530
|
+
/** A string without a prefix, when it has it. */
|
|
1531
|
+
function trimStart(t, $, s, prefix, path) {
|
|
1532
|
+
return t.ifValue(t.b("StringStartsWith", [], [s, prefix], BooleanType, path), () => t.b("StringSubstring", [], [s, t.b("StringLength", [], [prefix], IntegerType, path), t.b("StringLength", [], [s], IntegerType, path)], StringType, path), () => s, StringType, path);
|
|
1533
|
+
}
|
|
1534
|
+
function trimEnd(t, $, s, suffix, path) {
|
|
1535
|
+
return t.ifValue(t.b("StringEndsWith", [], [s, suffix], BooleanType, path), () => t.b("StringSubstring", [], [s, t.int(0), t.b("IntegerSubtract", [], [t.b("StringLength", [], [s], IntegerType, path), t.b("StringLength", [], [suffix], IntegerType, path)], IntegerType, path)], StringType, path), () => s, StringType, path);
|
|
1536
|
+
}
|
|
1537
|
+
rule(["ltrimstr", "rtrimstr", "trimstr"], onString([0], (t, c, $, x, [affix]) => {
|
|
1538
|
+
const s = t.bind($, x, "string");
|
|
1539
|
+
if (c.name === "ltrimstr") {
|
|
1540
|
+
c.emit($, trimStart(t, $, s, affix, c.path));
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
if (c.name === "rtrimstr") {
|
|
1544
|
+
c.emit($, trimEnd(t, $, s, affix, c.path));
|
|
1545
|
+
return;
|
|
1546
|
+
}
|
|
1547
|
+
c.emit($, trimEnd(t, $, t.bind($, trimStart(t, $, s, affix, c.path), "trimmed"), affix, c.path));
|
|
1548
|
+
}));
|
|
1549
|
+
for (const [name, builtin] of [["trim", "StringTrim"], ["ltrim", "StringTrimStart"], ["rtrim", "StringTrimEnd"], ["ascii_downcase", "StringLowerCase"], ["ascii_upcase", "StringUpperCase"]]) {
|
|
1550
|
+
rule(name, onString([], (t, c, $, s) => c.emit($, t.b(builtin, [], [s], StringType, c.path))));
|
|
1551
|
+
}
|
|
1552
|
+
rule("split", onString([0], (t, c, $, s, [sep]) => c.emit($, t.split($, s, sep, c.path))));
|
|
1553
|
+
/** A regex's flags, as written in the query: East's `i` when jq's is there. */
|
|
1554
|
+
function regexFlags(t, c, i) {
|
|
1555
|
+
if (i === undefined)
|
|
1556
|
+
return { flags: "", global: false };
|
|
1557
|
+
const literal = t.literalOf(c.args[i]);
|
|
1558
|
+
const text = literal?.value ?? "";
|
|
1559
|
+
return { flags: text.includes("i") ? "i" : "", global: text.includes("g") };
|
|
1560
|
+
}
|
|
1561
|
+
rule("test", (t, c) => {
|
|
1562
|
+
const { flags } = regexFlags(t, c, c.args.length === 2 ? 1 : undefined);
|
|
1563
|
+
values(t, c, [0], c.$, ($, [pattern]) => {
|
|
1564
|
+
const s = t.open(input(t, c));
|
|
1565
|
+
if (t.type(s).type !== "String") {
|
|
1566
|
+
cannot(t, c, $, s, "a string");
|
|
1567
|
+
return;
|
|
1568
|
+
}
|
|
1569
|
+
c.emit($, t.b("RegexContains", [], [s, pattern, t.str(flags)], BooleanType, c.path));
|
|
1570
|
+
});
|
|
1571
|
+
});
|
|
1572
|
+
rule(["sub", "gsub"], (t, c) => {
|
|
1573
|
+
const { flags, global } = regexFlags(t, c, c.args.length === 3 ? 2 : undefined);
|
|
1574
|
+
const all = c.name === "gsub" || global;
|
|
1575
|
+
values(t, c, [0], c.$, ($, [pattern]) => {
|
|
1576
|
+
const s = t.open(input(t, c));
|
|
1577
|
+
if (t.type(s).type !== "String") {
|
|
1578
|
+
cannot(t, c, $, s, "a string");
|
|
1579
|
+
return;
|
|
1580
|
+
}
|
|
1581
|
+
// East's replace is global; `sub` anchors a lazy prefix so only the first match is replaced.
|
|
1582
|
+
const re = all ? pattern : t.concat(t.concat(t.str("^([\\s\\S]*?)(?:"), pattern, c.path), t.str(")"), c.path);
|
|
1583
|
+
const replacement = t.bind($, replacementOf(t, c, $), "replacement");
|
|
1584
|
+
const full = all ? replacement : t.concat(t.str("$1"), replacement, c.path);
|
|
1585
|
+
c.emit($, t.b("RegexReplace", [], [s, re, t.str(flags), full], StringType, c.path));
|
|
1586
|
+
});
|
|
1587
|
+
});
|
|
1588
|
+
/** A sub/gsub replacement as East's replacement text: its text with `$` doubled, each `\(.name)` as `$<name>`. */
|
|
1589
|
+
function replacementOf(t, c, $) {
|
|
1590
|
+
const node = c.args[1];
|
|
1591
|
+
const escape = (text) => text.replaceAll("$", "$$");
|
|
1592
|
+
const part = (n) => {
|
|
1593
|
+
if (n.type === "field")
|
|
1594
|
+
return t.str(`$<${n.value.name}>`);
|
|
1595
|
+
if (n.type === "variable") {
|
|
1596
|
+
const bound = c.env.vars.get(n.value);
|
|
1597
|
+
if (bound === undefined)
|
|
1598
|
+
throw t.gap(`$${n.value} is not bound`);
|
|
1599
|
+
return t.b("StringReplace", [], [t.tostring($, t.expr(bound, c.path), c.path), t.str("$"), t.str("$$")], StringType, c.path);
|
|
1600
|
+
}
|
|
1601
|
+
throw t.gap(`a replacement of ${n.type}`);
|
|
1602
|
+
};
|
|
1603
|
+
if (node.type === "literal")
|
|
1604
|
+
return t.str(escape(t.literal(node).value));
|
|
1605
|
+
if (node.type === "string") {
|
|
1606
|
+
let acc = t.str("");
|
|
1607
|
+
for (const p of node.value)
|
|
1608
|
+
acc = t.concat(acc, p.type === "text" ? t.str(escape(p.value)) : part(p.value), c.path);
|
|
1609
|
+
return acc;
|
|
1610
|
+
}
|
|
1611
|
+
return part(node);
|
|
1612
|
+
}
|
|
1613
|
+
rule("format", (t, c) => {
|
|
1614
|
+
const name = t.literalOf(c.args[0])?.value ?? "";
|
|
1615
|
+
const format = FORMATS[name];
|
|
1616
|
+
if (format === undefined)
|
|
1617
|
+
throw t.gap(`format("${name}")`);
|
|
1618
|
+
c.emit(c.$, format(t, c.$, input(t, c), c.path));
|
|
1619
|
+
});
|
|
1620
|
+
/** Replaces each of some characters in a string. */
|
|
1621
|
+
function replaceAll(t, s, pairs, path) {
|
|
1622
|
+
let out = s;
|
|
1623
|
+
for (const [from, to] of pairs)
|
|
1624
|
+
out = t.b("StringReplace", [], [out, t.str(from), t.str(to)], StringType, path);
|
|
1625
|
+
return out;
|
|
1626
|
+
}
|
|
1627
|
+
/** Each cell of an array of scalars as text, joined. */
|
|
1628
|
+
function cells(t, $, v, path, cell, separator) {
|
|
1629
|
+
const parts = t.declare($, t.emptyArray(StringType), "cells");
|
|
1630
|
+
t.forEach($, v, ($2, item) => t.push($2, parts, cell($2, item), path), path);
|
|
1631
|
+
return t.b("ArrayStringJoin", [], [parts, t.str(separator)], StringType, path);
|
|
1632
|
+
}
|
|
1633
|
+
/** Whether a scalar is a string, through its option. */
|
|
1634
|
+
function isStringCell(t, v) {
|
|
1635
|
+
const ot = t.type(t.open(v));
|
|
1636
|
+
return (nullablePayload(ot) ?? ot).type === "String";
|
|
1637
|
+
}
|
|
1638
|
+
/** A string cell's text: `f` of the string, or `nullText` for an absent one. */
|
|
1639
|
+
function stringCell(t, v, f, path, nullText) {
|
|
1640
|
+
const o = t.open(v);
|
|
1641
|
+
if (nullablePayload(t.type(o)) === undefined)
|
|
1642
|
+
return f(o);
|
|
1643
|
+
return t.matchValue(o, { none: () => t.str(nullText, path), some: (_$2, s) => f(s) }, StringType, path);
|
|
1644
|
+
}
|
|
1645
|
+
/** RFC 4648's alphabet. */
|
|
1646
|
+
const BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
1647
|
+
/** The bytes `@base64` encodes: a Blob's own (§2), or the UTF-8 of any other value's text. */
|
|
1648
|
+
function bytesOf(t, $, v, path) {
|
|
1649
|
+
const o = t.open(v);
|
|
1650
|
+
const ot = t.type(o);
|
|
1651
|
+
if (ot.type === "Blob")
|
|
1652
|
+
return o;
|
|
1653
|
+
const payload = nullablePayload(ot);
|
|
1654
|
+
if (payload !== undefined && unwrap(payload).type === "Blob") {
|
|
1655
|
+
return t.matchValue(o, { none: () => t.b("StringEncodeUtf8", [], [t.str("null", path)], BlobType, path), some: ($2, p) => bytesOf(t, $2, p, path) }, BlobType, path);
|
|
1656
|
+
}
|
|
1657
|
+
return t.b("StringEncodeUtf8", [], [t.tostring($, o, path)], BlobType, path);
|
|
1658
|
+
}
|
|
1659
|
+
/** Base-2ᵏ text of a value's bytes, RFC 4648 with padding: `bits` bits a character, in groups of `group` bytes. */
|
|
1660
|
+
function baseEncode(t, $, v, path, alphabet, bits, group) {
|
|
1661
|
+
const bytes = t.declare($, bytesOf(t, $, v, path), "bytes", false);
|
|
1662
|
+
const size = t.declare($, t.b("BlobSize", [], [bytes], IntegerType, path), "size", false);
|
|
1663
|
+
const letters = t.declare($, t.value([...alphabet], ArrayType(StringType), path), "alphabet", false);
|
|
1664
|
+
const out = t.declare($, t.emptyArray(StringType), "letters");
|
|
1665
|
+
const chars = (group * 8) / bits;
|
|
1666
|
+
const i = t.declare($, t.int(0), "i");
|
|
1667
|
+
t.whileLoop($, t.lt(i, size, path), $2 => {
|
|
1668
|
+
// The group's bytes as one integer, then its characters, the unused ones padding.
|
|
1669
|
+
const n = t.declare($2, t.int(0), "group");
|
|
1670
|
+
const used = t.declare($2, t.int(0), "used");
|
|
1671
|
+
for (let k = 0; k < group; k++) {
|
|
1672
|
+
const at = t.add(i, t.int(k), path);
|
|
1673
|
+
t.assign($2, n, t.b("IntegerMultiply", [], [n, t.int(256)], IntegerType, path));
|
|
1674
|
+
t.ifElse($2, t.lt(at, size, path), $3 => {
|
|
1675
|
+
t.assign($3, n, t.add(n, t.b("BlobGetUint8", [], [bytes, t.add(i, t.int(k), path)], IntegerType, path), path));
|
|
1676
|
+
t.assign($3, used, t.add(used, t.int(1), path));
|
|
1677
|
+
}, undefined, path);
|
|
1678
|
+
}
|
|
1679
|
+
// A group of `used` bytes gives ceil(used × 8 / bits) characters.
|
|
1680
|
+
const shown = t.declare($2, t.b("IntegerDivide", [], [t.add(t.b("IntegerMultiply", [], [used, t.int(8)], IntegerType, path), t.int(bits - 1), path), t.int(bits)], IntegerType, path), "shown", false);
|
|
1681
|
+
for (let k = 0; k < chars; k++) {
|
|
1682
|
+
const shift = BigInt(bits * (chars - 1 - k));
|
|
1683
|
+
const index = t.b("IntegerRemainder", [], [t.b("IntegerDivide", [], [n, t.int(2n ** shift)], IntegerType, path), t.int(2 ** bits)], IntegerType, path);
|
|
1684
|
+
t.ifElse($2, t.lt(t.int(k), shown, path), $3 => t.push($3, out, t.b("ArrayGet", [StringType], [letters, index], StringType, path), path), $3 => t.push($3, out, t.str("="), path), path);
|
|
1685
|
+
}
|
|
1686
|
+
t.assign($2, i, t.add(i, t.int(group), path));
|
|
1687
|
+
}, path);
|
|
1688
|
+
return t.b("ArrayStringJoin", [], [out, t.str("")], StringType, path);
|
|
1689
|
+
}
|
|
1690
|
+
/** The text each byte is percent-encoded as, or kept as: jq's unreserved characters. */
|
|
1691
|
+
function uriTable() {
|
|
1692
|
+
const table = [];
|
|
1693
|
+
for (let b = 0; b < 256; b++) {
|
|
1694
|
+
const c = String.fromCharCode(b);
|
|
1695
|
+
table.push(/[A-Za-z0-9\-_.~]/.test(c) ? c : `%${b.toString(16).toUpperCase().padStart(2, "0")}`);
|
|
1696
|
+
}
|
|
1697
|
+
return table;
|
|
1698
|
+
}
|
|
1699
|
+
/** Each format, by name. @internal */
|
|
1700
|
+
export const FORMATS = {
|
|
1701
|
+
text: (t, $, v, path) => t.tostring($, v, path),
|
|
1702
|
+
json: (t, _$, v, path) => tojson(t, v, path),
|
|
1703
|
+
html: (t, $, v, path) => replaceAll(t, t.tostring($, v, path), [["&", "&"], ["<", "<"], [">", ">"], ["'", "'"], ["\"", """]], path),
|
|
1704
|
+
uri: (t, $, v, path) => {
|
|
1705
|
+
const bytes = t.declare($, t.b("StringEncodeUtf8", [], [t.tostring($, v, path)], BlobType, path), "bytes", false);
|
|
1706
|
+
const table = t.declare($, t.value(uriTable(), ArrayType(StringType), path), "encoded", false);
|
|
1707
|
+
const out = t.declare($, t.emptyArray(StringType), "parts");
|
|
1708
|
+
const i = t.declare($, t.int(0), "i");
|
|
1709
|
+
t.whileLoop($, t.lt(i, t.b("BlobSize", [], [bytes], IntegerType, path), path), $2 => {
|
|
1710
|
+
t.push($2, out, t.b("ArrayGet", [StringType], [table, t.b("BlobGetUint8", [], [bytes, i], IntegerType, path)], StringType, path), path);
|
|
1711
|
+
t.assign($2, i, t.add(i, t.int(1), path));
|
|
1712
|
+
}, path);
|
|
1713
|
+
return t.b("ArrayStringJoin", [], [out, t.str("")], StringType, path);
|
|
1714
|
+
},
|
|
1715
|
+
base64: (t, $, v, path) => baseEncode(t, $, v, path, BASE64, 6, 3),
|
|
1716
|
+
// jq writes NaN in a CSV or TSV cell as nothing, and elsewhere as null.
|
|
1717
|
+
csv: (t, $, v, path) => cells(t, $, v, path, ($2, item) => isStringCell(t, item)
|
|
1718
|
+
? stringCell(t, item, s => t.concat(t.concat(t.str("\""), replaceAll(t, s, [["\"", "\"\""]], path), path), t.str("\""), path), path, "")
|
|
1719
|
+
: cellText(t, $2, item, path, "", ""), ","),
|
|
1720
|
+
tsv: (t, $, v, path) => cells(t, $, v, path, ($2, item) => isStringCell(t, item)
|
|
1721
|
+
? stringCell(t, item, s => replaceAll(t, s, [["\\", "\\\\"], ["\t", "\\t"], ["\n", "\\n"], ["\r", "\\r"]], path), path, "")
|
|
1722
|
+
: cellText(t, $2, item, path, "", ""), "\t"),
|
|
1723
|
+
sh: (t, $, v, path) => {
|
|
1724
|
+
const quote = (s) => t.concat(t.concat(t.str("'"), replaceAll(t, s, [["'", "'\\''"]], path), path), t.str("'"), path);
|
|
1725
|
+
const cell = ($2, item) => isStringCell(t, item) ? stringCell(t, item, quote, path, "null") : cellText(t, $2, item, path, "null", "null");
|
|
1726
|
+
const o = t.open(v);
|
|
1727
|
+
if (!arrayLike(t.type(o)))
|
|
1728
|
+
return cell($, o);
|
|
1729
|
+
return cells(t, $, o, path, cell, " ");
|
|
1730
|
+
},
|
|
1731
|
+
};
|
|
1732
|
+
// Formats called by name, `@base64`, and through `format("base64")`.
|
|
1733
|
+
for (const name of Object.keys(FORMATS))
|
|
1734
|
+
rule(`@${name}`, (t, c) => c.emit(c.$, FORMATS[name](t, c.$, input(t, c), c.path)));
|
|
1735
|
+
// ─── Math ────────────────────────────────────────────────────────────────
|
|
1736
|
+
for (const [name, fn] of [["floor", "roundFloor"], ["ceil", "roundCeil"], ["round", "roundHalf"], ["trunc", "roundTrunc"]]) {
|
|
1737
|
+
rule(name, (t, c) => {
|
|
1738
|
+
const x = t.open(input(t, c));
|
|
1739
|
+
c.emit(c.$, t.type(x).type === "Integer" ? x : t.round(fn, x, c.$, c.path));
|
|
1740
|
+
});
|
|
1741
|
+
}
|
|
1742
|
+
for (const [name, builtin] of [["sqrt", "FloatSqrt"], ["log", "FloatLog"], ["exp", "FloatExp"], ["sin", "FloatSin"], ["cos", "FloatCos"], ["tan", "FloatTan"], ["fabs", "FloatAbs"]]) {
|
|
1743
|
+
rule(name, (t, c) => c.emit(c.$, t.b(builtin, [], [float(t, c.$, input(t, c), c.path)], FloatType, c.path)));
|
|
1744
|
+
}
|
|
1745
|
+
rule(["log2", "log10"], (t, c) => {
|
|
1746
|
+
const base = c.name === "log2" ? 2 : 10;
|
|
1747
|
+
const x = float(t, c.$, input(t, c), c.path);
|
|
1748
|
+
c.emit(c.$, t.b("FloatDivide", [], [t.b("FloatLog", [], [x], FloatType, c.path), t.b("FloatLog", [], [t.float(base)], FloatType, c.path)], FloatType, c.path));
|
|
1749
|
+
});
|
|
1750
|
+
rule(["exp2", "exp10"], (t, c) => {
|
|
1751
|
+
c.emit(c.$, t.b("FloatPow", [], [t.float(c.name === "exp2" ? 2 : 10), float(t, c.$, input(t, c), c.path)], FloatType, c.path));
|
|
1752
|
+
});
|
|
1753
|
+
rule(["pow", "fmin", "fmax", "fmod"], (t, c) => values(t, c, [0, 1], c.$, ($, [a, b]) => {
|
|
1754
|
+
const x = t.bind($, float(t, $, a, c.path), "a");
|
|
1755
|
+
const y = t.bind($, float(t, $, b, c.path), "b");
|
|
1756
|
+
switch (c.name) {
|
|
1757
|
+
case "pow":
|
|
1758
|
+
c.emit($, t.b("FloatPow", [], [x, y], FloatType, c.path));
|
|
1759
|
+
return;
|
|
1760
|
+
case "fmod":
|
|
1761
|
+
c.emit($, t.b("FloatRemainder", [], [x, y], FloatType, c.path));
|
|
1762
|
+
return;
|
|
1763
|
+
// As C's: a NaN gives the other number. East orders NaN above every number,
|
|
1764
|
+
// so the lesser of the two is never a NaN unless both are.
|
|
1765
|
+
case "fmin":
|
|
1766
|
+
c.emit($, t.ifValue(t.lt(y, x, c.path), () => y, () => x, FloatType, c.path));
|
|
1767
|
+
return;
|
|
1768
|
+
default: {
|
|
1769
|
+
const other = t.b("BooleanOr", [], [isNan(t, $, y, c.path), t.lt(y, x, c.path)], BooleanType, c.path);
|
|
1770
|
+
c.emit($, t.ifValue(isNan(t, $, x, c.path), () => y, () => t.ifValue(other, () => x, () => y, FloatType, c.path), FloatType, c.path));
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
}));
|
|
1774
|
+
// ─── DateTime ────────────────────────────────────────────────────────────
|
|
1775
|
+
/** RFC 3339 as East's JSON writes a DateTime: `2026-09-07T00:00:00.000+00:00`. */
|
|
1776
|
+
function rfc3339Tokens() {
|
|
1777
|
+
return [
|
|
1778
|
+
variant("year4", null), variant("literal", "-"), variant("month2", null), variant("literal", "-"), variant("day2", null),
|
|
1779
|
+
variant("literal", "T"), variant("hour24_2", null), variant("literal", ":"), variant("minute2", null), variant("literal", ":"),
|
|
1780
|
+
variant("second2", null), variant("literal", "."), variant("millisecond3", null), variant("literal", "+00:00"),
|
|
1781
|
+
];
|
|
1782
|
+
}
|
|
1783
|
+
/** A DateTime, or epoch seconds as jq's date builtins take them. */
|
|
1784
|
+
function dateOf(t, $, v, path) {
|
|
1785
|
+
const o = t.open(v);
|
|
1786
|
+
const ot = t.type(o);
|
|
1787
|
+
if (ot.type === "DateTime")
|
|
1788
|
+
return o;
|
|
1789
|
+
const ms = ot.type === "Integer"
|
|
1790
|
+
? t.b("IntegerMultiply", [], [o, t.int(1000)], IntegerType, path)
|
|
1791
|
+
: t.round("roundFloor", t.b("FloatMultiply", [], [o, t.float(1000)], FloatType, path), $, path);
|
|
1792
|
+
return t.b("DateTimeFromEpochMilliseconds", [], [ms], DateTimeType, path);
|
|
1793
|
+
}
|
|
1794
|
+
rule(["todate", "todateiso8601"], (t, c) => {
|
|
1795
|
+
const tokens = t.value(rfc3339Tokens(), ArrayType(DateTimeFormatTokenType), c.path);
|
|
1796
|
+
c.emit(c.$, t.b("DateTimePrintFormat", [], [dateOf(t, c.$, input(t, c), c.path), tokens], StringType, c.path));
|
|
1797
|
+
});
|
|
1798
|
+
rule(["fromdate", "fromdateiso8601"], (t, c) => {
|
|
1799
|
+
const quoted = t.concat(t.concat(t.str("\""), t.open(input(t, c)), c.path), t.str("\""), c.path);
|
|
1800
|
+
c.emit(c.$, t.b("StringParseJSON", [DateTimeType], [quoted], DateTimeType, c.path));
|
|
1801
|
+
});
|
|
1802
|
+
/** A strftime/strptime format's tokens, made from the format the query writes, as the checker read it. */
|
|
1803
|
+
function tokensOf(t, c) {
|
|
1804
|
+
const literal = t.literalOf(c.args[0]);
|
|
1805
|
+
const format = literal?.type.type === "String" ? formatTokens(literal.value) : undefined;
|
|
1806
|
+
if (format === undefined || "code" in format)
|
|
1807
|
+
throw t.gap(`${c.name} without a format East has tokens for`);
|
|
1808
|
+
return t.value(format.tokens, ArrayType(DateTimeFormatTokenType), c.argPaths[0]);
|
|
1809
|
+
}
|
|
1810
|
+
rule("strftime", (t, c) => c.emit(c.$, t.b("DateTimePrintFormat", [], [dateOf(t, c.$, input(t, c), c.path), tokensOf(t, c)], StringType, c.path)));
|
|
1811
|
+
rule("strptime", (t, c) => c.emit(c.$, t.b("DateTimeParseFormat", [], [t.open(input(t, c)), tokensOf(t, c)], DateTimeType, c.path)));
|
|
1812
|
+
for (const [name, builtin] of [
|
|
1813
|
+
["year", "DateTimeGetYear"], ["month", "DateTimeGetMonth"], ["day", "DateTimeGetDayOfMonth"], ["hour", "DateTimeGetHour"],
|
|
1814
|
+
["minute", "DateTimeGetMinute"], ["second", "DateTimeGetSecond"], ["millisecond", "DateTimeGetMillisecond"],
|
|
1815
|
+
["weekday", "DateTimeGetDayOfWeek"], ["epoch_ms", "DateTimeToEpochMilliseconds"],
|
|
1816
|
+
]) {
|
|
1817
|
+
rule(name, (t, c) => c.emit(c.$, t.b(builtin, [], [t.open(input(t, c))], IntegerType, c.path)));
|
|
1818
|
+
}
|
|
1819
|
+
/** A unit's length in milliseconds. */
|
|
1820
|
+
const UNIT_MS = {
|
|
1821
|
+
millisecond: 1n, second: 1000n, minute: 60000n, hour: 3600000n, day: 86400000n, week: 604800000n,
|
|
1822
|
+
};
|
|
1823
|
+
function unitMs(t, c, i) {
|
|
1824
|
+
const unit = t.literalOf(c.args[i])?.value;
|
|
1825
|
+
const ms = unit === undefined ? undefined : UNIT_MS[unit];
|
|
1826
|
+
if (ms === undefined)
|
|
1827
|
+
throw t.gap(`the unit ${unit ?? "?"}`);
|
|
1828
|
+
return t.int(ms, c.argPaths[i]);
|
|
1829
|
+
}
|
|
1830
|
+
rule("datetime_add", (t, c) => values(t, c, [0], c.$, ($, [n]) => {
|
|
1831
|
+
const ms = t.b("IntegerMultiply", [], [n, unitMs(t, c, 1)], IntegerType, c.path);
|
|
1832
|
+
c.emit($, t.b("DateTimeAddMilliseconds", [], [t.open(input(t, c)), ms], DateTimeType, c.path));
|
|
1833
|
+
}));
|
|
1834
|
+
rule("datetime_diff", (t, c) => values(t, c, [0], c.$, ($, [other]) => {
|
|
1835
|
+
const ms = t.b("DateTimeDurationMilliseconds", [], [t.open(input(t, c)), t.open(other)], IntegerType, c.path);
|
|
1836
|
+
c.emit($, t.b("IntegerDivide", [], [ms, unitMs(t, c, 1)], IntegerType, c.path));
|
|
1837
|
+
}));
|
|
1838
|
+
// ─── Function values ─────────────────────────────────────────────────────
|
|
1839
|
+
rule("call", (t, c) => values(t, c, c.args.map((_, i) => i), c.$, ($, [fn, ...args]) => {
|
|
1840
|
+
const f = t.open(fn);
|
|
1841
|
+
const inputs = t.type(f).inputs;
|
|
1842
|
+
c.emit($, t.callFn(f, args.map((a, i) => callArgument(t, $, a, inputs[i], c.path)), c.path));
|
|
1843
|
+
}));
|
|
1844
|
+
/** An argument as a function's input: an Integer as a Float, a struct field by field. */
|
|
1845
|
+
function callArgument(t, $, v, want, path) {
|
|
1846
|
+
const o = t.open(v);
|
|
1847
|
+
const ot = t.type(o);
|
|
1848
|
+
const w = unwrap(want);
|
|
1849
|
+
if (isTypeEqual(ot, w))
|
|
1850
|
+
return t.widenTo($, o, want, path);
|
|
1851
|
+
if (ot.type === "Struct" && w.type === "Struct") {
|
|
1852
|
+
const s = t.bind($, o, "argument");
|
|
1853
|
+
const fields = {};
|
|
1854
|
+
for (const [name, f] of Object.entries(w.fields))
|
|
1855
|
+
fields[name] = callArgument(t, $, t.field(s, name), f, path);
|
|
1856
|
+
return t.struct(want, fields);
|
|
1857
|
+
}
|
|
1858
|
+
return t.widenTo($, o, want, path);
|
|
1859
|
+
}
|
|
1860
|
+
for (const [name, output] of [["signature", "String"], ["source", "String"], ["calls", "Array"], ["captures", "Array"]]) {
|
|
1861
|
+
rule(name, (t, c) => {
|
|
1862
|
+
const f = t.open(input(t, c));
|
|
1863
|
+
const type = output === "String" ? StringType : ArrayType(StringType);
|
|
1864
|
+
// A platform function the host gives (`devdocs/QUERY.md` §15.9).
|
|
1865
|
+
c.emit(c.$, t.mk({
|
|
1866
|
+
ast_type: "Platform", type, loc_id: t.loc(c.path), name: `jq_${name}`, type_parameters: [t.type(f)],
|
|
1867
|
+
arguments: [t.ast(f)], async: false, optional: false,
|
|
1868
|
+
}));
|
|
1869
|
+
});
|
|
1870
|
+
}
|
|
1871
|
+
/** Every builtin's rule, by name as called; formats as `@name`. @internal */
|
|
1872
|
+
export const BUILTIN_RULES = rules;
|
|
1873
|
+
//# sourceMappingURL=translate-builtins.js.map
|