@elaraai/east 1.0.83 → 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 +12 -7
- package/dist/src/naming.d.ts.map +1 -1
- package/dist/src/naming.js +30 -13
- package/dist/src/naming.js.map +1 -1
- package/dist/src/naming.spec.js +49 -0
- package/dist/src/naming.spec.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 +60 -6
- package/dist/src/runner_protocol.d.ts.map +1 -1
- package/dist/src/runner_protocol.js +48 -6
- package/dist/src/runner_protocol.js.map +1 -1
- package/dist/src/serialization/beast2/index.d.ts +2 -2
- package/dist/src/serialization/beast2/index.d.ts.map +1 -1
- package/dist/src/serialization/beast2/index.js +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 +74 -7
- package/dist/src/serialization/beast2/v5/lazy.d.ts.map +1 -1
- package/dist/src/serialization/beast2/v5/lazy.js +129 -27
- package/dist/src/serialization/beast2/v5/lazy.js.map +1 -1
- package/dist/src/serialization/beast2/v5/lazy.spec.js +251 -16
- package/dist/src/serialization/beast2/v5/lazy.spec.js.map +1 -1
- package/dist/src/serialization/beast2/v5/stream.d.ts +139 -23
- package/dist/src/serialization/beast2/v5/stream.d.ts.map +1 -1
- package/dist/src/serialization/beast2/v5/stream.js +196 -44
- package/dist/src/serialization/beast2/v5/stream.js.map +1 -1
- package/dist/src/serialization/beast2/v5/type-section.d.ts.map +1 -1
- package/dist/src/serialization/beast2/v5/type-section.js +25 -12
- package/dist/src/serialization/beast2/v5/type-section.js.map +1 -1
- package/dist/src/serialization/beast2/v5/type-section.spec.d.ts +6 -0
- package/dist/src/serialization/beast2/v5/type-section.spec.d.ts.map +1 -0
- package/dist/src/serialization/beast2/v5/type-section.spec.js +41 -0
- package/dist/src/serialization/beast2/v5/type-section.spec.js.map +1 -0
- 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/serialization/index.js +1 -1
- package/dist/src/serialization/index.js.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 +7 -4
|
@@ -0,0 +1,3364 @@
|
|
|
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 { isVariant, none, some, variant } from "../../containers/variant.js";
|
|
6
|
+
import { valueOrExprToAstTyped } from "../../expr/ast.js";
|
|
7
|
+
import { BlockBuilder, fromAst, func } from "../../expr/block.js";
|
|
8
|
+
import { AstSymbol, Expr } from "../../expr/expr.js";
|
|
9
|
+
import FloatLib from "../../expr/libs/float.js";
|
|
10
|
+
import { get_current_source_map, get_location_id, UNKNOWN_LOC_ID } from "../../location.js";
|
|
11
|
+
import { ArrayType, BooleanType, DictType, FloatType, FunctionType, IntegerType, NeverType, NullType, OptionType, RefType, StringType, StructType, VariantType, isImmutableType, isSubtype, isTypeEqual, printType, } from "../../types.js";
|
|
12
|
+
import { QueryCallType } from "../types.js";
|
|
13
|
+
import { UPDATE_SELECTORS, walkSteps } from "./check.js";
|
|
14
|
+
import { isoDateTime, literalValue } from "./literals.js";
|
|
15
|
+
import { casesOf, membersOf, nullablePayload, unify, unifyShape, unwrap } from "./shapes.js";
|
|
16
|
+
import { childPath, jqChildren, toQuerySpan } from "./spans.js";
|
|
17
|
+
import { BUILTIN_RULES, FORMATS } from "./translate-builtins.js";
|
|
18
|
+
/** A type, its parts readable. @internal */
|
|
19
|
+
export function parts(type) {
|
|
20
|
+
return type;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Raised when a checked program holds something the translator cannot
|
|
24
|
+
* express. The checker refuses what a query may not do, so this marks a gap
|
|
25
|
+
* in the translation, never a mistake in the query.
|
|
26
|
+
*/
|
|
27
|
+
export class TranslationError extends Error {
|
|
28
|
+
constructor(message) {
|
|
29
|
+
super(`translateJq: ${message}`);
|
|
30
|
+
this.name = "TranslationError";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Whether a value is the root. @internal */
|
|
34
|
+
export function isRoot(value) {
|
|
35
|
+
return !(value instanceof Expr);
|
|
36
|
+
}
|
|
37
|
+
/** The translator for one checked program. @internal */
|
|
38
|
+
export class Translator {
|
|
39
|
+
checked;
|
|
40
|
+
options;
|
|
41
|
+
locs = new Map();
|
|
42
|
+
retyped = new Map();
|
|
43
|
+
constructor(checked, options) {
|
|
44
|
+
this.checked = checked;
|
|
45
|
+
this.options = options;
|
|
46
|
+
}
|
|
47
|
+
/** The error for a gap in the translation. */
|
|
48
|
+
gap(message) {
|
|
49
|
+
return new TranslationError(message);
|
|
50
|
+
}
|
|
51
|
+
// ─── Building IR ────────────────────────────────────────────────────────
|
|
52
|
+
/** The location of a node in the jq text, as the IR's source map holds it: file `jq`, its line and column. */
|
|
53
|
+
loc(path) {
|
|
54
|
+
const known = this.locs.get(path);
|
|
55
|
+
if (known !== undefined)
|
|
56
|
+
return known;
|
|
57
|
+
const map = get_current_source_map();
|
|
58
|
+
const range = this.checked.source.spans.get(path);
|
|
59
|
+
let id = UNKNOWN_LOC_ID;
|
|
60
|
+
if (map !== null && range !== undefined) {
|
|
61
|
+
const span = toQuerySpan(this.checked.source.text, range.from, range.to);
|
|
62
|
+
id = map.intern_stack([{ filename: "jq", line: span.line, column: span.column }]);
|
|
63
|
+
}
|
|
64
|
+
this.locs.set(path, id);
|
|
65
|
+
return id;
|
|
66
|
+
}
|
|
67
|
+
mk(ast) {
|
|
68
|
+
return fromAst(ast);
|
|
69
|
+
}
|
|
70
|
+
ast(e) {
|
|
71
|
+
return e[AstSymbol];
|
|
72
|
+
}
|
|
73
|
+
type(e) {
|
|
74
|
+
return Expr.type(e);
|
|
75
|
+
}
|
|
76
|
+
block() {
|
|
77
|
+
return BlockBuilder(NeverType);
|
|
78
|
+
}
|
|
79
|
+
/** Whether a block has ended: its last statement breaks, raises or returns. */
|
|
80
|
+
ended($) {
|
|
81
|
+
const s = $.statements;
|
|
82
|
+
return s.length > 0 && s[s.length - 1].type.type === "Never";
|
|
83
|
+
}
|
|
84
|
+
/** A call of an East builtin. */
|
|
85
|
+
b(name, typeParameters, args, output, path) {
|
|
86
|
+
return this.mk({ ast_type: "Builtin", type: output, loc_id: this.loc(path), builtin: name, type_parameters: typeParameters, arguments: args.map(a => this.ast(a)) });
|
|
87
|
+
}
|
|
88
|
+
/** A constant of a type. */
|
|
89
|
+
value(v, type, path = "") {
|
|
90
|
+
return this.mk(valueOrExprToAstTyped(v, type, undefined, this.loc(path)));
|
|
91
|
+
}
|
|
92
|
+
int(n, path = "") {
|
|
93
|
+
return this.value(BigInt(n), IntegerType, path);
|
|
94
|
+
}
|
|
95
|
+
float(n, path = "") {
|
|
96
|
+
return this.value(n, FloatType, path);
|
|
97
|
+
}
|
|
98
|
+
str(s, path = "") {
|
|
99
|
+
return this.value(s, StringType, path);
|
|
100
|
+
}
|
|
101
|
+
bool(v, path = "") {
|
|
102
|
+
return this.value(v, BooleanType, path);
|
|
103
|
+
}
|
|
104
|
+
null(path = "") {
|
|
105
|
+
return this.value(null, NullType, path);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* A value as a wider type it is a subtype of. A recursive type equals its
|
|
109
|
+
* node to East's type checks, but a value passes between the two through
|
|
110
|
+
* its wrapper, which this adds or takes away.
|
|
111
|
+
*/
|
|
112
|
+
as(e, type) {
|
|
113
|
+
const from = this.type(e);
|
|
114
|
+
// A value of Never, which never arrives, is one of every type: East casts none.
|
|
115
|
+
if (from.type === "Never")
|
|
116
|
+
return e;
|
|
117
|
+
if (type.type === "Recursive" && from.type !== "Recursive") {
|
|
118
|
+
return this.mk({ ast_type: "WrapRecursive", type, loc_id: UNKNOWN_LOC_ID, value: this.ast(this.as(e, type.node)) });
|
|
119
|
+
}
|
|
120
|
+
if (from.type === "Recursive" && type.type !== "Recursive")
|
|
121
|
+
return this.as(this.open(e), type);
|
|
122
|
+
if (isTypeEqual(from, type))
|
|
123
|
+
return e;
|
|
124
|
+
if (!isSubtype(from, type))
|
|
125
|
+
throw this.gap(`${printType(from)} is not a ${printType(type)}`);
|
|
126
|
+
return this.mk({ ast_type: "As", type, loc_id: UNKNOWN_LOC_ID, value: this.ast(e) });
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* An option holding a value. An option that can only be none
|
|
130
|
+
* (`Option<Never>`, over an empty collection's elements) is never made
|
|
131
|
+
* some: the payload, a value that never arrives, stands for it, as East's
|
|
132
|
+
* builders cannot spell a case of Never.
|
|
133
|
+
*/
|
|
134
|
+
some(payload, optionType) {
|
|
135
|
+
const p = parts(optionType).cases["some"];
|
|
136
|
+
if (p.type === "Never")
|
|
137
|
+
return payload;
|
|
138
|
+
return this.mk({ ast_type: "Variant", type: optionType, loc_id: UNKNOWN_LOC_ID, case: "some", value: this.ast(this.as(payload, p)) });
|
|
139
|
+
}
|
|
140
|
+
none(optionType) {
|
|
141
|
+
return this.mk({ ast_type: "Variant", type: optionType, loc_id: UNKNOWN_LOC_ID, case: "none", value: { ast_type: "Value", type: NullType, loc_id: UNKNOWN_LOC_ID, value: null } });
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* A case of a variant type, wrapped when the type is recursive. A case of
|
|
145
|
+
* Never is never made: the payload, which never arrives, stands for it.
|
|
146
|
+
*/
|
|
147
|
+
variantOf(type, name, payload) {
|
|
148
|
+
const node = type.type === "Recursive" ? type.node : type;
|
|
149
|
+
const p = parts(node).cases[name];
|
|
150
|
+
if (p.type === "Never")
|
|
151
|
+
return payload;
|
|
152
|
+
const built = { ast_type: "Variant", type: node, loc_id: UNKNOWN_LOC_ID, case: name, value: this.ast(this.as(payload, p)) };
|
|
153
|
+
return this.mk(type.type === "Recursive" ? { ast_type: "WrapRecursive", type, loc_id: UNKNOWN_LOC_ID, value: built } : built);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* A struct of a type from its fields' values, wrapped when the type is
|
|
157
|
+
* recursive. A struct with a field of Never is never made: that field's
|
|
158
|
+
* value, which never arrives, stands for it.
|
|
159
|
+
*/
|
|
160
|
+
struct(type, fields) {
|
|
161
|
+
const node = type.type === "Recursive" ? type.node : type;
|
|
162
|
+
const asts = {};
|
|
163
|
+
for (const [name, fieldType] of Object.entries(parts(node).fields)) {
|
|
164
|
+
const v = fields[name];
|
|
165
|
+
if (v === undefined)
|
|
166
|
+
throw this.gap(`no value for the field ${name}`);
|
|
167
|
+
if (fieldType.type === "Never")
|
|
168
|
+
return v;
|
|
169
|
+
asts[name] = this.ast(this.as(v, fieldType));
|
|
170
|
+
}
|
|
171
|
+
const built = { ast_type: "Struct", type: node, loc_id: UNKNOWN_LOC_ID, fields: asts };
|
|
172
|
+
return this.mk(type.type === "Recursive" ? { ast_type: "WrapRecursive", type, loc_id: UNKNOWN_LOC_ID, value: built } : built);
|
|
173
|
+
}
|
|
174
|
+
/** A field of a struct value. */
|
|
175
|
+
field(struct, name) {
|
|
176
|
+
const s = this.open(struct);
|
|
177
|
+
const fieldType = this.type(s).fields[name];
|
|
178
|
+
if (fieldType === undefined)
|
|
179
|
+
throw this.gap(`.${name} is not a field of ${printType(this.type(s))}`);
|
|
180
|
+
return this.mk({ ast_type: "GetField", type: fieldType, loc_id: UNKNOWN_LOC_ID, field: name, struct: this.ast(s) });
|
|
181
|
+
}
|
|
182
|
+
/** A value read through its recursive wrapper and references: what jq sees. */
|
|
183
|
+
open(e) {
|
|
184
|
+
let v = e;
|
|
185
|
+
for (;;) {
|
|
186
|
+
const t = this.type(v);
|
|
187
|
+
if (t.type === "Recursive")
|
|
188
|
+
v = this.mk({ ast_type: "UnwrapRecursive", type: t.node, loc_id: UNKNOWN_LOC_ID, value: this.ast(v) });
|
|
189
|
+
else if (t.type === "Ref")
|
|
190
|
+
v = this.b("RefGet", [t.value], [v], t.value, "");
|
|
191
|
+
else
|
|
192
|
+
return v;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/** A call of a function value. */
|
|
196
|
+
callFn(fn, args, path) {
|
|
197
|
+
const t = this.type(fn);
|
|
198
|
+
const inputs = t.inputs;
|
|
199
|
+
return this.mk({
|
|
200
|
+
ast_type: "Call", type: t.output, loc_id: this.loc(path), function: this.ast(fn),
|
|
201
|
+
arguments: args.map((a, i) => this.ast(this.as(a, inputs[i]))),
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
/** An East function value, built from a body that gives its result. */
|
|
205
|
+
lambda(inputs, output, names, body, path) {
|
|
206
|
+
const parameters = inputs.map((type, i) => ({ ast_type: "Variable", type, loc_id: UNKNOWN_LOC_ID, mutable: false, name: names[i] }));
|
|
207
|
+
const $ = BlockBuilder(output);
|
|
208
|
+
const result = body($, ...parameters.map(p => fromAst(p)));
|
|
209
|
+
if (result !== undefined && !this.ended($))
|
|
210
|
+
$.statements.push(this.ast(this.as(result, output)));
|
|
211
|
+
const s = $.statements;
|
|
212
|
+
const bodyAst = s.length === 0 ? { ast_type: "Value", type: NullType, loc_id: UNKNOWN_LOC_ID, value: null }
|
|
213
|
+
: s.length === 1 ? s[0]
|
|
214
|
+
: { ast_type: "Block", type: s[s.length - 1].type, loc_id: UNKNOWN_LOC_ID, statements: s };
|
|
215
|
+
return this.mk({ ast_type: "Function", type: FunctionType(inputs, output), loc_id: this.loc(path), parameters, body: bodyAst });
|
|
216
|
+
}
|
|
217
|
+
/** A function of East's float library, called on a number. */
|
|
218
|
+
round(name, v, $, path) {
|
|
219
|
+
return this.callFn(libraryFunction(name), [this.widenTo($, v, FloatType, path)], path);
|
|
220
|
+
}
|
|
221
|
+
// ─── Statements ─────────────────────────────────────────────────────────
|
|
222
|
+
/** Declares a variable holding a value: `let` when `mutable`, else `const`. */
|
|
223
|
+
declare($, init, name, mutable = true, type) {
|
|
224
|
+
const t = type ?? this.type(init);
|
|
225
|
+
const variable = { ast_type: "Variable", type: t, loc_id: UNKNOWN_LOC_ID, mutable, name };
|
|
226
|
+
$.statements.push({ ast_type: "Let", type: NullType, loc_id: UNKNOWN_LOC_ID, variable, value: this.ast(this.as(init, t)) });
|
|
227
|
+
return fromAst(variable);
|
|
228
|
+
}
|
|
229
|
+
/** A value bound to a variable of its own, unless it is one already or a constant: so it is computed once. */
|
|
230
|
+
bind($, e, name) {
|
|
231
|
+
const kind = this.ast(e).ast_type;
|
|
232
|
+
if (kind === "Variable" || kind === "Value")
|
|
233
|
+
return e;
|
|
234
|
+
return this.declare($, e, name, false);
|
|
235
|
+
}
|
|
236
|
+
assign($, variable, value) {
|
|
237
|
+
if (this.ended($))
|
|
238
|
+
return;
|
|
239
|
+
let v = this.ast(variable);
|
|
240
|
+
if (v.ast_type === "UnwrapRecursive")
|
|
241
|
+
v = v.value;
|
|
242
|
+
if (v.ast_type !== "Variable")
|
|
243
|
+
throw this.gap("an assignment to something not a variable");
|
|
244
|
+
$.statements.push({ ast_type: "Assign", type: NullType, loc_id: UNKNOWN_LOC_ID, variable: v, value: this.ast(this.as(value, v.type)) });
|
|
245
|
+
}
|
|
246
|
+
/** An expression evaluated for its effect. */
|
|
247
|
+
stmt($, e) {
|
|
248
|
+
if (this.ended($))
|
|
249
|
+
return;
|
|
250
|
+
$.statements.push(this.ast(e));
|
|
251
|
+
}
|
|
252
|
+
/** A runtime error, located at a node. */
|
|
253
|
+
raise($, message, path) {
|
|
254
|
+
if (this.ended($))
|
|
255
|
+
return;
|
|
256
|
+
const m = typeof message === "string" ? this.str(message, path) : message;
|
|
257
|
+
$.statements.push({ ast_type: "Error", type: NeverType, loc_id: this.loc(path), message: this.ast(m) });
|
|
258
|
+
}
|
|
259
|
+
brk($, label) {
|
|
260
|
+
if (this.ended($))
|
|
261
|
+
return;
|
|
262
|
+
$.statements.push({ ast_type: "Break", type: NeverType, loc_id: UNKNOWN_LOC_ID, label });
|
|
263
|
+
}
|
|
264
|
+
/** A block's statements as one body, closed as the builder closes a block: null-terminated. */
|
|
265
|
+
body(inner) {
|
|
266
|
+
const s = inner.statements;
|
|
267
|
+
if (s.length === 0)
|
|
268
|
+
return { ast_type: "Value", type: NullType, loc_id: UNKNOWN_LOC_ID, value: null };
|
|
269
|
+
if (s.length === 1 && isSubtype(s[0].type, NullType))
|
|
270
|
+
return s[0];
|
|
271
|
+
if (!isSubtype(s[s.length - 1].type, NullType))
|
|
272
|
+
s.push({ ast_type: "Value", type: NullType, loc_id: UNKNOWN_LOC_ID, value: null });
|
|
273
|
+
return { ast_type: "Block", type: s[s.length - 1].type, loc_id: UNKNOWN_LOC_ID, statements: s };
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Statements the last of which diverges, as one body, assembled as the
|
|
277
|
+
* builders assemble one: a single statement is that statement.
|
|
278
|
+
*/
|
|
279
|
+
diverging(statements) {
|
|
280
|
+
return statements.length === 1 ? statements[0] : { ast_type: "Block", type: NeverType, loc_id: UNKNOWN_LOC_ID, statements };
|
|
281
|
+
}
|
|
282
|
+
/** A block that gives a value: its statements, then the value. */
|
|
283
|
+
blockValue(build, type) {
|
|
284
|
+
const inner = this.block();
|
|
285
|
+
const v = build(inner);
|
|
286
|
+
if (this.ended(inner))
|
|
287
|
+
return this.diverging(inner.statements);
|
|
288
|
+
const valueAst = this.ast(this.as(v, type));
|
|
289
|
+
if (inner.statements.length === 0)
|
|
290
|
+
return valueAst;
|
|
291
|
+
return { ast_type: "Block", type, loc_id: UNKNOWN_LOC_ID, statements: [...inner.statements, valueAst] };
|
|
292
|
+
}
|
|
293
|
+
/** Loops over an array, set, dict, vector or matrix: each element (a set's key; a dict's value, and its key). */
|
|
294
|
+
forEach($, collection, each, path, name = "item") {
|
|
295
|
+
if (this.ended($))
|
|
296
|
+
return;
|
|
297
|
+
const c = this.open(collection);
|
|
298
|
+
const t = this.type(c);
|
|
299
|
+
const label = { loc_id: this.loc(path) };
|
|
300
|
+
const inner = this.block();
|
|
301
|
+
const variable = (type, n) => ({ ast_type: "Variable", type, loc_id: UNKNOWN_LOC_ID, mutable: false, name: n });
|
|
302
|
+
switch (t.type) {
|
|
303
|
+
case "Array": {
|
|
304
|
+
const value = variable(t.value, name);
|
|
305
|
+
const key = variable(IntegerType, "index");
|
|
306
|
+
each(inner, fromAst(value), fromAst(key), label);
|
|
307
|
+
$.statements.push({ ast_type: "ForArray", type: NullType, loc_id: label.loc_id, label, array: this.ast(c), key, value, body: this.body(inner) });
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
case "Set": {
|
|
311
|
+
const key = variable(t.key, name);
|
|
312
|
+
each(inner, fromAst(key), undefined, label);
|
|
313
|
+
$.statements.push({ ast_type: "ForSet", type: NullType, loc_id: label.loc_id, label, set: this.ast(c), key, body: this.body(inner) });
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
case "Dict": {
|
|
317
|
+
const value = variable(t.value, name);
|
|
318
|
+
const key = variable(t.key, "key");
|
|
319
|
+
each(inner, fromAst(value), fromAst(key), label);
|
|
320
|
+
$.statements.push({ ast_type: "ForDict", type: NullType, loc_id: label.loc_id, label, dict: this.ast(c), key, value, body: this.body(inner) });
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
case "Vector":
|
|
324
|
+
this.forEach($, this.b("VectorToArray", [t.element], [c], ArrayType(t.element), path), each, path, name);
|
|
325
|
+
return;
|
|
326
|
+
case "Matrix":
|
|
327
|
+
this.forEach($, this.b("MatrixToArray", [t.element], [c], ArrayType(ArrayType(t.element)), path), each, path, name);
|
|
328
|
+
return;
|
|
329
|
+
default:
|
|
330
|
+
throw this.gap(`a loop over ${printType(t)}`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
/** A loop while a condition holds; `true` for one left only by `break`. */
|
|
334
|
+
whileLoop($, condition, body, path) {
|
|
335
|
+
if (this.ended($))
|
|
336
|
+
return;
|
|
337
|
+
const label = { loc_id: this.loc(path) };
|
|
338
|
+
const inner = this.block();
|
|
339
|
+
body(inner, label);
|
|
340
|
+
const predicate = condition === true ? { ast_type: "Value", type: BooleanType, loc_id: UNKNOWN_LOC_ID, value: true } : this.ast(condition);
|
|
341
|
+
$.statements.push({ ast_type: "While", type: NullType, loc_id: label.loc_id, predicate, label, body: this.body(inner) });
|
|
342
|
+
}
|
|
343
|
+
/** A block that runs once, with a label that leaves it early: how a stream stops. */
|
|
344
|
+
once($, body, path) {
|
|
345
|
+
this.whileLoop($, true, (inner, label) => {
|
|
346
|
+
body(inner, label);
|
|
347
|
+
this.brk(inner, label);
|
|
348
|
+
}, path);
|
|
349
|
+
}
|
|
350
|
+
/** `if`, with an optional `else`. */
|
|
351
|
+
ifElse($, condition, then, otherwise, path) {
|
|
352
|
+
if (this.ended($))
|
|
353
|
+
return;
|
|
354
|
+
const t = this.block();
|
|
355
|
+
then(t);
|
|
356
|
+
const e = this.block();
|
|
357
|
+
otherwise?.(e);
|
|
358
|
+
const thenAst = this.body(t);
|
|
359
|
+
const elseAst = this.body(e);
|
|
360
|
+
const type = thenAst.type.type === "Never" && elseAst.type.type === "Never" ? NeverType : NullType;
|
|
361
|
+
$.statements.push({ ast_type: "IfElse", type, loc_id: this.loc(path), ifs: [{ predicate: this.ast(condition), body: thenAst }], else_body: elseAst });
|
|
362
|
+
}
|
|
363
|
+
/** Runs `then` where a condition holds and `otherwise` where it does not; a known condition runs one of them only. */
|
|
364
|
+
branch($, condition, then, otherwise, path) {
|
|
365
|
+
if (this.ended($))
|
|
366
|
+
return;
|
|
367
|
+
const known = typeof condition === "boolean" ? condition : this.constant(condition)?.value;
|
|
368
|
+
if (known === true) {
|
|
369
|
+
then($);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (known === false) {
|
|
373
|
+
otherwise($);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
this.ifElse($, condition, then, otherwise, path);
|
|
377
|
+
}
|
|
378
|
+
/** A statement that runs a handler for the case a variant holds; a case with no handler does nothing. */
|
|
379
|
+
match($, v, handlers, path) {
|
|
380
|
+
if (this.ended($))
|
|
381
|
+
return;
|
|
382
|
+
const opened = this.open(v);
|
|
383
|
+
const t = this.type(opened);
|
|
384
|
+
const cases = {};
|
|
385
|
+
let never = true;
|
|
386
|
+
for (const [name, payload] of Object.entries(t.cases)) {
|
|
387
|
+
const variable = { ast_type: "Variable", type: payload, loc_id: UNKNOWN_LOC_ID, mutable: false, name: name === "some" ? "value" : "payload" };
|
|
388
|
+
const inner = this.block();
|
|
389
|
+
handlers[name]?.(inner, fromAst(variable));
|
|
390
|
+
const body = this.body(inner);
|
|
391
|
+
if (body.type.type !== "Never")
|
|
392
|
+
never = false;
|
|
393
|
+
cases[name] = { variable, body };
|
|
394
|
+
}
|
|
395
|
+
$.statements.push({ ast_type: "Match", type: never ? NeverType : NullType, loc_id: this.loc(path), variant: this.ast(opened), cases });
|
|
396
|
+
}
|
|
397
|
+
/** A value chosen by the case a variant holds: each case's block gives it. */
|
|
398
|
+
matchValue(v, cases, type, path) {
|
|
399
|
+
const opened = this.open(v);
|
|
400
|
+
const t = this.type(opened);
|
|
401
|
+
const out = {};
|
|
402
|
+
for (const [name, payload] of Object.entries(t.cases)) {
|
|
403
|
+
const variable = { ast_type: "Variable", type: payload, loc_id: UNKNOWN_LOC_ID, mutable: false, name: name === "some" ? "value" : "payload" };
|
|
404
|
+
const build = cases[name];
|
|
405
|
+
if (build === undefined)
|
|
406
|
+
throw this.gap(`no value for the case ${name}`);
|
|
407
|
+
out[name] = { variable, body: this.blockValue($ => build($, fromAst(variable)), type) };
|
|
408
|
+
}
|
|
409
|
+
return this.mk({ ast_type: "Match", type, loc_id: this.loc(path), variant: this.ast(opened), cases: out });
|
|
410
|
+
}
|
|
411
|
+
/** A variant with each case's payload given by `f` of it, of the variant type the new payloads make. */
|
|
412
|
+
mapPayloads(v, f, path) {
|
|
413
|
+
const opened = this.open(v);
|
|
414
|
+
const built = Object.entries(this.type(opened).cases).map(([name, type]) => {
|
|
415
|
+
const variable = { ast_type: "Variable", type, loc_id: UNKNOWN_LOC_ID, mutable: false, name: "payload" };
|
|
416
|
+
const block = this.block();
|
|
417
|
+
const value = f(block, name, fromAst(variable));
|
|
418
|
+
return { name, type, variable, block, value };
|
|
419
|
+
});
|
|
420
|
+
const cases = {};
|
|
421
|
+
for (const b of built)
|
|
422
|
+
cases[b.name] = this.ended(b.block) ? b.type : this.type(b.value);
|
|
423
|
+
const V = VariantType(cases);
|
|
424
|
+
const out = {};
|
|
425
|
+
for (const b of built) {
|
|
426
|
+
if (this.ended(b.block)) {
|
|
427
|
+
out[b.name] = { variable: b.variable, body: this.diverging(b.block.statements) };
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
const wrapped = this.ast(this.variantOf(V, b.name, b.value));
|
|
431
|
+
out[b.name] = { variable: b.variable, body: b.block.statements.length === 0 ? wrapped : { ast_type: "Block", type: V, loc_id: UNKNOWN_LOC_ID, statements: [...b.block.statements, wrapped] } };
|
|
432
|
+
}
|
|
433
|
+
return this.mk({ ast_type: "Match", type: V, loc_id: this.loc(path), variant: this.ast(opened), cases: out });
|
|
434
|
+
}
|
|
435
|
+
/** A value chosen by a condition. */
|
|
436
|
+
ifValue(condition, then, otherwise, type, path) {
|
|
437
|
+
const known = this.constant(condition)?.value;
|
|
438
|
+
if (known === true || known === false) {
|
|
439
|
+
return this.mk(this.blockValue(known ? then : otherwise, type));
|
|
440
|
+
}
|
|
441
|
+
return this.mk({
|
|
442
|
+
ast_type: "IfElse", type, loc_id: this.loc(path),
|
|
443
|
+
ifs: [{ predicate: this.ast(condition), body: this.blockValue(then, type) }],
|
|
444
|
+
else_body: this.blockValue(otherwise, type),
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
/** `try`/`catch`: the handler receives the error's message. */
|
|
448
|
+
tryCatch($, body, handler, path) {
|
|
449
|
+
if (this.ended($))
|
|
450
|
+
return;
|
|
451
|
+
const t = this.block();
|
|
452
|
+
body(t);
|
|
453
|
+
const message = { ast_type: "Variable", type: StringType, loc_id: UNKNOWN_LOC_ID, mutable: false, name: "message" };
|
|
454
|
+
const stack = {
|
|
455
|
+
ast_type: "Variable", type: ArrayType(StructType({ filename: StringType, line: IntegerType, column: IntegerType })),
|
|
456
|
+
loc_id: UNKNOWN_LOC_ID, mutable: false, name: "stack",
|
|
457
|
+
};
|
|
458
|
+
const c = this.block();
|
|
459
|
+
handler(c, fromAst(message));
|
|
460
|
+
const tryAst = this.body(t);
|
|
461
|
+
const catchAst = this.body(c);
|
|
462
|
+
const type = tryAst.type.type === "Never" && catchAst.type.type === "Never" ? NeverType : NullType;
|
|
463
|
+
$.statements.push({ ast_type: "TryCatch", type, loc_id: this.loc(path), try_body: tryAst, catch_body: catchAst, message, stack });
|
|
464
|
+
}
|
|
465
|
+
// ─── Collections ────────────────────────────────────────────────────────
|
|
466
|
+
emptyArray(element) {
|
|
467
|
+
return this.value([], ArrayType(element));
|
|
468
|
+
}
|
|
469
|
+
/** Appends a value to an array variable. */
|
|
470
|
+
push($, array, v, path) {
|
|
471
|
+
if (this.ended($))
|
|
472
|
+
return;
|
|
473
|
+
const element = this.type(array).value;
|
|
474
|
+
this.stmt($, this.b("ArrayPushLast", [element], [array, this.widenTo($, v, element, path)], NullType, path));
|
|
475
|
+
}
|
|
476
|
+
/** Sets a key of a dict variable, inserting it or replacing its value. */
|
|
477
|
+
put($, dict, key, v, path) {
|
|
478
|
+
if (this.ended($))
|
|
479
|
+
return;
|
|
480
|
+
const t = this.type(dict);
|
|
481
|
+
const K = t.key;
|
|
482
|
+
const V = t.value;
|
|
483
|
+
const k = this.bind($, this.widenTo($, key, K, path), "key");
|
|
484
|
+
const value = this.bind($, this.widenTo($, v, V, path), "value");
|
|
485
|
+
this.ifElse($, this.b("DictHas", [K, V], [dict, k], BooleanType, path), $2 => this.stmt($2, this.b("DictUpdate", [K, V], [dict, k, value], NullType, path)), $2 => this.stmt($2, this.b("DictInsert", [K, V], [dict, k, value], NullType, path)), path);
|
|
486
|
+
}
|
|
487
|
+
/** An array-like value as an array: an array itself, a set's keys, a vector's elements, a matrix's rows. */
|
|
488
|
+
asArray($, v, path) {
|
|
489
|
+
const e = this.open(v);
|
|
490
|
+
const t = this.type(e);
|
|
491
|
+
switch (t.type) {
|
|
492
|
+
case "Array": return e;
|
|
493
|
+
case "Set": {
|
|
494
|
+
const out = this.declare($, this.emptyArray(t.key), "keys");
|
|
495
|
+
this.forEach($, e, ($2, key) => this.push($2, out, key, path), path, "key");
|
|
496
|
+
return out;
|
|
497
|
+
}
|
|
498
|
+
case "Vector": return this.b("VectorToArray", [t.element], [e], ArrayType(t.element), path);
|
|
499
|
+
case "Matrix": return this.b("MatrixToArray", [t.element], [e], ArrayType(ArrayType(t.element)), path);
|
|
500
|
+
default: return undefined;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
/** The number of elements of an array. */
|
|
504
|
+
size(array, path) {
|
|
505
|
+
return this.b("ArraySize", [this.type(array).value], [array], IntegerType, path);
|
|
506
|
+
}
|
|
507
|
+
lt(a, b, path) {
|
|
508
|
+
return this.b("Less", [this.type(a)], [a, b], BooleanType, path);
|
|
509
|
+
}
|
|
510
|
+
eq(a, b, path) {
|
|
511
|
+
return this.b("Equal", [this.type(a)], [a, b], BooleanType, path);
|
|
512
|
+
}
|
|
513
|
+
add(a, b, path) {
|
|
514
|
+
return this.b("IntegerAdd", [], [a, b], IntegerType, path);
|
|
515
|
+
}
|
|
516
|
+
not(a, path) {
|
|
517
|
+
return this.b("BooleanNot", [], [a], BooleanType, path);
|
|
518
|
+
}
|
|
519
|
+
// ─── Literals and the checker's types ───────────────────────────────────
|
|
520
|
+
/** A literal node's value and type, as the program writes it. */
|
|
521
|
+
literal(node) {
|
|
522
|
+
return literalValue(node.value);
|
|
523
|
+
}
|
|
524
|
+
/** A node's value when it is a literal, as the program writes it. */
|
|
525
|
+
literalOf(node) {
|
|
526
|
+
return node?.type === "literal" ? this.literal(node) : undefined;
|
|
527
|
+
}
|
|
528
|
+
/** An expression's value when it is a constant. */
|
|
529
|
+
constant(e) {
|
|
530
|
+
const ast = this.ast(e);
|
|
531
|
+
return ast.ast_type === "Value" ? { value: ast.value } : undefined;
|
|
532
|
+
}
|
|
533
|
+
/** What checking a node gave in an instance. */
|
|
534
|
+
result(path, env) {
|
|
535
|
+
return this.checked.resultAt(path, env.instance);
|
|
536
|
+
}
|
|
537
|
+
/** The one type a node's outputs share, as the checker collects them. */
|
|
538
|
+
typeAt(path, env) {
|
|
539
|
+
const r = this.result(path, env);
|
|
540
|
+
const t = r === null ? undefined : unifyShape(r.shape);
|
|
541
|
+
if (t === undefined)
|
|
542
|
+
throw this.gap(`no one type for ${this.text(path) || "the program"}`);
|
|
543
|
+
return parts(t);
|
|
544
|
+
}
|
|
545
|
+
/** A node's text. */
|
|
546
|
+
text(path) {
|
|
547
|
+
const range = this.checked.source.spans.get(path);
|
|
548
|
+
return range === undefined ? "" : this.checked.source.text.slice(range.from, range.to);
|
|
549
|
+
}
|
|
550
|
+
/** What narrowing proved about a value of a type in a shape. */
|
|
551
|
+
factsIn(shape, type) {
|
|
552
|
+
if (shape === null || shape === undefined)
|
|
553
|
+
return undefined;
|
|
554
|
+
return membersOf(shape).find(m => isTypeEqual(m.shape.type, type))?.shape.facts;
|
|
555
|
+
}
|
|
556
|
+
/** The instance a node's records are in for an input: its own, or one checked again for this input's type. */
|
|
557
|
+
envFor(path, env, x) {
|
|
558
|
+
if (isRoot(x))
|
|
559
|
+
return env;
|
|
560
|
+
const input = this.checked.inputAt(path, env.instance);
|
|
561
|
+
if (input === null || fits(this.type(x), input))
|
|
562
|
+
return env;
|
|
563
|
+
const key = `${env.instance}|${path}|${printType(this.type(x))}`;
|
|
564
|
+
let instance = this.retyped.get(key);
|
|
565
|
+
if (instance === undefined) {
|
|
566
|
+
instance = this.checked.retype(path, env.instance, this.type(x));
|
|
567
|
+
this.retyped.set(key, instance);
|
|
568
|
+
}
|
|
569
|
+
if (instance === null)
|
|
570
|
+
throw this.gap(`${this.text(path) || "the program"} on ${printType(this.type(x))}`);
|
|
571
|
+
return { ...env, instance };
|
|
572
|
+
}
|
|
573
|
+
// ─── Converting to the checker's types ──────────────────────────────────
|
|
574
|
+
/**
|
|
575
|
+
* A value as a type the checker widened it to: Integer as Float, `T` or
|
|
576
|
+
* `null` as `Option<T>`, and arrays, dicts, structs and variants element by
|
|
577
|
+
* element; and a string literal the checker read as a DateTime, as the
|
|
578
|
+
* DateTime it writes.
|
|
579
|
+
*
|
|
580
|
+
* @throws {TranslationError} When the type is not wider.
|
|
581
|
+
*/
|
|
582
|
+
widenTo($, e, to, path) {
|
|
583
|
+
const from = this.type(e);
|
|
584
|
+
if (isTypeEqual(from, to))
|
|
585
|
+
return this.as(e, to);
|
|
586
|
+
if (from.type === "Never")
|
|
587
|
+
return this.as(e, to);
|
|
588
|
+
// A reference is read through, as jq sees its value.
|
|
589
|
+
if (from.type === "Ref" && to.type !== "Ref")
|
|
590
|
+
return this.widenTo($, this.open(e), to, path);
|
|
591
|
+
// An option of Never is always none: as Null, it is null.
|
|
592
|
+
if (to.type === "Null" && nullablePayload(from)?.type === "Never") {
|
|
593
|
+
this.bind($, e, "none");
|
|
594
|
+
return this.null(path);
|
|
595
|
+
}
|
|
596
|
+
const toPayload = nullablePayload(to);
|
|
597
|
+
if (toPayload !== undefined) {
|
|
598
|
+
if (from.type === "Null")
|
|
599
|
+
return this.none(to);
|
|
600
|
+
const fromPayload = nullablePayload(from);
|
|
601
|
+
if (fromPayload !== undefined) {
|
|
602
|
+
// An option of Never is always none; East's casts do not take a Never inside a type.
|
|
603
|
+
if (fromPayload.type === "Never") {
|
|
604
|
+
this.bind($, e, "none");
|
|
605
|
+
return this.none(to);
|
|
606
|
+
}
|
|
607
|
+
if (isSubtype(from, to) && !holdsNever(from))
|
|
608
|
+
return this.as(e, to);
|
|
609
|
+
return this.matchValue(e, {
|
|
610
|
+
none: () => this.none(to),
|
|
611
|
+
some: ($2, p) => this.some(this.widenTo($2, p, toPayload, path), to),
|
|
612
|
+
}, to, path);
|
|
613
|
+
}
|
|
614
|
+
return this.some(this.widenTo($, e, toPayload, path), to);
|
|
615
|
+
}
|
|
616
|
+
if (from.type === "Integer" && to.type === "Float") {
|
|
617
|
+
const c = this.constant(e);
|
|
618
|
+
if (c !== undefined)
|
|
619
|
+
return this.float(Number(c.value), path);
|
|
620
|
+
return this.b("IntegerToFloat", [], [e], FloatType, path);
|
|
621
|
+
}
|
|
622
|
+
// An ISO-8601 string the checker read as a DateTime: the date it writes, through East's RFC 3339 reader, as the checker read it.
|
|
623
|
+
if (from.type === "String" && to.type === "DateTime") {
|
|
624
|
+
const c = this.constant(e);
|
|
625
|
+
const date = c === undefined ? undefined : isoDateTime(c.value);
|
|
626
|
+
if (date !== undefined)
|
|
627
|
+
return this.value(date, to, path);
|
|
628
|
+
}
|
|
629
|
+
if (isSubtype(from, to) && !holdsNever(from))
|
|
630
|
+
return this.as(e, to);
|
|
631
|
+
const f = this.type(this.open(e));
|
|
632
|
+
const t = to.type === "Recursive" ? to.node : to;
|
|
633
|
+
// A collection of Never is always empty: it is an empty one of the wider type.
|
|
634
|
+
const empty = (value) => { this.bind($, e, "empty"); return this.as(this.value(value, t), to); };
|
|
635
|
+
if (f.type === "Array" && t.type === "Array" && f.value.type === "Never")
|
|
636
|
+
return empty([]);
|
|
637
|
+
if (f.type === "Dict" && t.type === "Dict" && f.value.type === "Never")
|
|
638
|
+
return empty(new Map());
|
|
639
|
+
if (f.type === "Set" && t.type === "Set" && f.key.type === "Never")
|
|
640
|
+
return empty(new Set());
|
|
641
|
+
if (f.type === "Array" && t.type === "Array") {
|
|
642
|
+
const out = this.declare($, this.emptyArray(t.value), "array");
|
|
643
|
+
this.forEach($, e, ($2, item) => this.push($2, out, item, path), path);
|
|
644
|
+
return out;
|
|
645
|
+
}
|
|
646
|
+
if (f.type === "Dict" && t.type === "Dict" && isTypeEqual(f.key, t.key)) {
|
|
647
|
+
const out = this.declare($, this.value(new Map(), t), "dict");
|
|
648
|
+
this.forEach($, e, ($2, item, key) => {
|
|
649
|
+
this.stmt($2, this.b("DictInsert", [t.key, t.value], [out, key, this.widenTo($2, item, t.value, path)], NullType, path));
|
|
650
|
+
}, path);
|
|
651
|
+
return out;
|
|
652
|
+
}
|
|
653
|
+
if (f.type === "Struct" && t.type === "Dict" && Object.keys(f.fields).length === 0)
|
|
654
|
+
return this.value(new Map(), t);
|
|
655
|
+
if (f.type === "Struct" && t.type === "Struct") {
|
|
656
|
+
const bound = this.bind($, this.open(e), "struct");
|
|
657
|
+
const fields = {};
|
|
658
|
+
for (const [name, fieldType] of Object.entries(t.fields)) {
|
|
659
|
+
fields[name] = this.widenTo($, this.field(bound, name), fieldType, path);
|
|
660
|
+
}
|
|
661
|
+
return this.struct(to, fields);
|
|
662
|
+
}
|
|
663
|
+
if (f.type === "Variant" && t.type === "Variant") {
|
|
664
|
+
const targetCases = t.cases;
|
|
665
|
+
const cases = {};
|
|
666
|
+
for (const name of Object.keys(f.cases)) {
|
|
667
|
+
if (!(name in targetCases))
|
|
668
|
+
throw this.gap(`the case ${name} as ${printType(to)}`);
|
|
669
|
+
cases[name] = ($2, p) => this.variantOf(to, name, this.widenTo($2, p, targetCases[name], path));
|
|
670
|
+
}
|
|
671
|
+
return this.matchValue(e, cases, to, path);
|
|
672
|
+
}
|
|
673
|
+
throw this.gap(`${printType(from)} as ${printType(to)}`);
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Gives a value to `emit` as the checker's type for it: widened, or
|
|
677
|
+
* narrowed as narrowing proved. A `null` where the checker proved a value
|
|
678
|
+
* never reaches `emit`, and an option it proved present gives its payload.
|
|
679
|
+
*/
|
|
680
|
+
emitAs($, e, to, path, emit) {
|
|
681
|
+
if (this.ended($))
|
|
682
|
+
return;
|
|
683
|
+
const from = this.type(e);
|
|
684
|
+
if (isTypeEqual(from, to)) {
|
|
685
|
+
emit($, this.as(e, to));
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
// An option of Never is always none: null, where null can go.
|
|
689
|
+
if (nullablePayload(from)?.type === "Never") {
|
|
690
|
+
this.bind($, e, "none");
|
|
691
|
+
if (to.type === "Null" || nullablePayload(to) !== undefined)
|
|
692
|
+
emit($, this.bind($, this.widenTo($, this.null(path), to, path), "value"));
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
if (nullablePayload(to) === undefined) {
|
|
696
|
+
if (from.type === "Null")
|
|
697
|
+
return;
|
|
698
|
+
if (nullablePayload(from) !== undefined) {
|
|
699
|
+
// Proved null, an option gives its none; proved present, its value.
|
|
700
|
+
if (to.type === "Null")
|
|
701
|
+
this.match($, e, { none: $2 => emit($2, this.null(path)) }, path);
|
|
702
|
+
else
|
|
703
|
+
this.match($, e, { some: ($2, p) => this.emitAs($2, p, to, path, emit) }, path);
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
emit($, this.bind($, this.widenTo($, e, to, path), "value"));
|
|
708
|
+
}
|
|
709
|
+
// ─── Truthiness ─────────────────────────────────────────────────────────
|
|
710
|
+
/** Whether a value is true to jq, not `false` or `null`: a constant when its type decides it. */
|
|
711
|
+
truthy(e, path) {
|
|
712
|
+
const v = this.open(e);
|
|
713
|
+
const t = this.type(v);
|
|
714
|
+
if (t.type === "Boolean") {
|
|
715
|
+
const c = this.constant(v);
|
|
716
|
+
return c === undefined ? v : c.value;
|
|
717
|
+
}
|
|
718
|
+
if (t.type === "Null" || t.type === "Never")
|
|
719
|
+
return false;
|
|
720
|
+
if (nullablePayload(t) !== undefined) {
|
|
721
|
+
return this.matchValue(v, {
|
|
722
|
+
none: () => this.bool(false),
|
|
723
|
+
some: (_$2, p) => this.asExpr(this.truthy(p, path)),
|
|
724
|
+
}, BooleanType, path);
|
|
725
|
+
}
|
|
726
|
+
return true;
|
|
727
|
+
}
|
|
728
|
+
asExpr(v) {
|
|
729
|
+
return typeof v === "boolean" ? this.bool(v) : v;
|
|
730
|
+
}
|
|
731
|
+
// ─── The walk ───────────────────────────────────────────────────────────
|
|
732
|
+
/**
|
|
733
|
+
* Generates a node: code in `$` that gives each of its outputs to `emit`,
|
|
734
|
+
* as the checker's type for them.
|
|
735
|
+
*/
|
|
736
|
+
gen(node, path, $, x, env, emit) {
|
|
737
|
+
if (this.ended($))
|
|
738
|
+
return;
|
|
739
|
+
// A literal is its own value, as the checker read it; a builtin that reads an argument as a literal never checked it.
|
|
740
|
+
if (node.type === "literal") {
|
|
741
|
+
this.genNode(node, path, $, x, env, emit);
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
const input = this.checked.inputAt(path, env.instance);
|
|
745
|
+
// A node the checker never reached, or reached with no input, never runs.
|
|
746
|
+
if (input === null || (input.kind === "type" && input.type.type === "Never"))
|
|
747
|
+
return;
|
|
748
|
+
const e = this.envFor(path, env, x);
|
|
749
|
+
const r = this.result(path, e);
|
|
750
|
+
if (r === null || (r.mult.lo === 1 && r.mult.hi === 0))
|
|
751
|
+
return;
|
|
752
|
+
const target = r.shape.kind === "type" && r.shape.type.type !== "Never" ? r.shape.type : undefined;
|
|
753
|
+
const out = target === undefined ? emit : ($2, v) => this.emitAs($2, v, target, path, emit);
|
|
754
|
+
this.genNode(node, path, $, x, e, out);
|
|
755
|
+
}
|
|
756
|
+
/** Generates a node, each output as the one type its outputs share. */
|
|
757
|
+
collected(node, path, $, x, env, emit) {
|
|
758
|
+
// A literal is its own value, as the checker read it (an ISO date as a DateTime), never collected as another type.
|
|
759
|
+
if (node.type === "literal") {
|
|
760
|
+
this.genNode(node, path, $, x, env, emit);
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
let type;
|
|
764
|
+
this.gen(node, path, $, x, env, ($2, v) => {
|
|
765
|
+
type ??= this.typeAt(path, this.envFor(path, env, x));
|
|
766
|
+
emit($2, this.bind($2, this.widenTo($2, v, type, path), "value"));
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* The one output of a node that gives exactly one, as an expression of a
|
|
771
|
+
* type: the value itself when the node gives it once in `$`, else a
|
|
772
|
+
* variable each place that gives it assigns.
|
|
773
|
+
*/
|
|
774
|
+
one(node, path, $, x, env, type) {
|
|
775
|
+
const mark = $.statements.length;
|
|
776
|
+
let direct;
|
|
777
|
+
let cell;
|
|
778
|
+
const declareCell = () => {
|
|
779
|
+
if (cell !== undefined)
|
|
780
|
+
return cell;
|
|
781
|
+
cell = { ast_type: "Variable", type, loc_id: UNKNOWN_LOC_ID, mutable: true, name: "one" };
|
|
782
|
+
$.statements.splice(mark, 0, { ast_type: "Let", type: NullType, loc_id: UNKNOWN_LOC_ID, variable: cell, value: this.ast(this.placeholder(type)) });
|
|
783
|
+
if (direct !== undefined) {
|
|
784
|
+
// The first output was taken as it was given; it assigns the variable there instead.
|
|
785
|
+
$.statements.splice(direct.at + 1, 0, { ast_type: "Assign", type: NullType, loc_id: UNKNOWN_LOC_ID, variable: cell, value: this.ast(direct.value) });
|
|
786
|
+
direct = undefined;
|
|
787
|
+
}
|
|
788
|
+
return cell;
|
|
789
|
+
};
|
|
790
|
+
this.gen(node, path, $, x, env, ($2, v) => {
|
|
791
|
+
if (this.ended($2))
|
|
792
|
+
return;
|
|
793
|
+
const w = this.widenTo($2, v, type, path);
|
|
794
|
+
if ($2 === $ && cell === undefined && direct === undefined) {
|
|
795
|
+
const bound = this.bind($, w, "one");
|
|
796
|
+
direct = { value: bound, at: $.statements.length };
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
const variable = declareCell();
|
|
800
|
+
$2.statements.push({ ast_type: "Assign", type: NullType, loc_id: UNKNOWN_LOC_ID, variable, value: this.ast(w) });
|
|
801
|
+
});
|
|
802
|
+
if (direct !== undefined)
|
|
803
|
+
return direct.value;
|
|
804
|
+
if (cell !== undefined)
|
|
805
|
+
return fromAst(cell);
|
|
806
|
+
return this.placeholder(type);
|
|
807
|
+
}
|
|
808
|
+
/** A value of a type to start a variable with, assigned before it is read. */
|
|
809
|
+
placeholder(type) {
|
|
810
|
+
const zero = this.zeroOf(type, new Set());
|
|
811
|
+
if (zero !== undefined)
|
|
812
|
+
return zero;
|
|
813
|
+
// A type with no plain value: an expression that raises, never evaluated.
|
|
814
|
+
return this.mk({
|
|
815
|
+
ast_type: "As", type, loc_id: UNKNOWN_LOC_ID,
|
|
816
|
+
value: { ast_type: "Error", type: NeverType, loc_id: UNKNOWN_LOC_ID, message: { ast_type: "Value", type: StringType, loc_id: UNKNOWN_LOC_ID, value: "unreachable: a query value read before it was given" } },
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
/** A plain value of a type, when there is one. */
|
|
820
|
+
zeroOf(type, seen) {
|
|
821
|
+
if (seen.has(type))
|
|
822
|
+
return undefined;
|
|
823
|
+
const t = type.type === "Recursive" ? type.node : type;
|
|
824
|
+
switch (t.type) {
|
|
825
|
+
case "Null": return this.null();
|
|
826
|
+
case "Boolean": return this.bool(false);
|
|
827
|
+
case "Integer": return this.int(0);
|
|
828
|
+
case "Float": return this.float(0);
|
|
829
|
+
case "String": return this.str("");
|
|
830
|
+
case "DateTime": return this.value(new Date(0), t);
|
|
831
|
+
case "Blob": return this.value(new Uint8Array(0), t);
|
|
832
|
+
case "Array": return this.value([], t);
|
|
833
|
+
case "Set": return this.value(new Set(), t);
|
|
834
|
+
case "Dict": return this.value(new Map(), t);
|
|
835
|
+
case "Struct": {
|
|
836
|
+
const inner = new Set(seen).add(type);
|
|
837
|
+
const fields = {};
|
|
838
|
+
for (const [name, f] of Object.entries(t.fields)) {
|
|
839
|
+
const z = this.zeroOf(f, inner);
|
|
840
|
+
if (z === undefined)
|
|
841
|
+
return undefined;
|
|
842
|
+
fields[name] = z;
|
|
843
|
+
}
|
|
844
|
+
return this.struct(type, fields);
|
|
845
|
+
}
|
|
846
|
+
case "Variant": {
|
|
847
|
+
const inner = new Set(seen).add(type);
|
|
848
|
+
for (const [name, payload] of Object.entries(t.cases)) {
|
|
849
|
+
const z = this.zeroOf(payload, inner);
|
|
850
|
+
if (z !== undefined)
|
|
851
|
+
return this.variantOf(type, name, z);
|
|
852
|
+
}
|
|
853
|
+
return undefined;
|
|
854
|
+
}
|
|
855
|
+
default: return undefined;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
genNode(node, path, $, x, env, emit) {
|
|
859
|
+
const at = (step) => childPath(path, step);
|
|
860
|
+
switch (node.type) {
|
|
861
|
+
case "identity":
|
|
862
|
+
emit($, this.expr(x, path));
|
|
863
|
+
return;
|
|
864
|
+
case "literal": {
|
|
865
|
+
// As written, or as the type the checker read it as here: an ISO-8601 string as the DateTime it writes.
|
|
866
|
+
const { type, value } = this.literal(node);
|
|
867
|
+
const written = this.value(value, type, path);
|
|
868
|
+
const read = this.result(path, env);
|
|
869
|
+
const as = read === null ? undefined : unifyShape(read.shape);
|
|
870
|
+
emit($, as === undefined || as.type === "Never" || isTypeEqual(as, type) ? written : this.widenTo($, written, as, path));
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
case "variable": {
|
|
874
|
+
if (node.value === "__loc__") {
|
|
875
|
+
emit($, this.location(path));
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
const bound = env.vars.get(node.value);
|
|
879
|
+
if (bound === undefined)
|
|
880
|
+
throw this.gap(`$${node.value} is not bound`);
|
|
881
|
+
emit($, this.expr(bound, path));
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
case "pipe":
|
|
885
|
+
// `. | f` is f on the input itself, the root included.
|
|
886
|
+
if (node.value.left.type === "identity") {
|
|
887
|
+
this.gen(node.value.right, at("pipe.right"), $, x, env, emit);
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
this.gen(node.value.left, at("pipe.left"), $, x, env, ($2, v) => this.gen(node.value.right, at("pipe.right"), $2, v, env, emit));
|
|
891
|
+
return;
|
|
892
|
+
case "comma":
|
|
893
|
+
this.gen(node.value.left, at("comma.left"), $, x, env, emit);
|
|
894
|
+
this.gen(node.value.right, at("comma.right"), $, x, env, emit);
|
|
895
|
+
return;
|
|
896
|
+
case "field": {
|
|
897
|
+
const { name, optional, target } = node.value;
|
|
898
|
+
const targetPath = at("field.target");
|
|
899
|
+
// `.name` reads the input itself: a root's field is the input of that name.
|
|
900
|
+
if (target.type === "identity" && isRoot(x)) {
|
|
901
|
+
this.readField($, x, name, optional, path, undefined, emit);
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
this.gen(target, targetPath, $, x, env, ($2, v) => {
|
|
905
|
+
const facts = isRoot(v) ? undefined : this.factsIn(this.result(targetPath, this.envFor(targetPath, env, x))?.shape, this.type(v));
|
|
906
|
+
this.readField($2, v, name, optional, path, facts, emit);
|
|
907
|
+
});
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
case "index":
|
|
911
|
+
this.genIndex(node, path, $, x, env, emit);
|
|
912
|
+
return;
|
|
913
|
+
case "slice":
|
|
914
|
+
this.genSlice(node, path, $, x, env, emit);
|
|
915
|
+
return;
|
|
916
|
+
case "iterate":
|
|
917
|
+
this.gen(node.value.target, at("iterate.target"), $, x, env, ($2, v) => this.iterate($2, this.expr(v, path), node.value.optional, path, emit));
|
|
918
|
+
return;
|
|
919
|
+
case "descend":
|
|
920
|
+
this.descend($, this.expr(x, path), path, emit);
|
|
921
|
+
return;
|
|
922
|
+
case "array": {
|
|
923
|
+
const element = this.typeAt(path, env).value;
|
|
924
|
+
if (node.value.type === "none") {
|
|
925
|
+
emit($, this.emptyArray(element));
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
const out = this.declare($, this.emptyArray(element), "array");
|
|
929
|
+
this.gen(node.value.value, at("array.some"), $, x, env, ($2, v) => this.push($2, out, v, path));
|
|
930
|
+
emit($, out);
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
case "object":
|
|
934
|
+
this.genObject(node, path, $, x, env, emit);
|
|
935
|
+
return;
|
|
936
|
+
case "string":
|
|
937
|
+
this.genString(node.value, path, $, x, env, emit, ($2, v) => this.tostring($2, v, path), path);
|
|
938
|
+
return;
|
|
939
|
+
case "format": {
|
|
940
|
+
const format = FORMATS[node.value.name];
|
|
941
|
+
if (format === undefined)
|
|
942
|
+
throw this.gap(`no translation for the format @${node.value.name}`);
|
|
943
|
+
const inner = node.value.string;
|
|
944
|
+
if (inner.type === "some" && inner.value.type === "string") {
|
|
945
|
+
// `@base64 "…\(f)…"`: each interpolated value through the format, the text as it is.
|
|
946
|
+
const stringPath = at("format.string.some");
|
|
947
|
+
this.genString(inner.value.value, path, $, x, env, emit, ($2, v) => format(this, $2, v, path), stringPath);
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
emit($, format(this, $, this.expr(x, path), path));
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
case "negate":
|
|
954
|
+
this.gen(node.value, at("negate"), $, x, env, ($2, v) => this.negate($2, v, path, emit));
|
|
955
|
+
return;
|
|
956
|
+
case "binary":
|
|
957
|
+
this.genBinary(node, path, $, x, env, emit);
|
|
958
|
+
return;
|
|
959
|
+
case "alternative":
|
|
960
|
+
this.genAlternative(node, path, $, x, env, emit);
|
|
961
|
+
return;
|
|
962
|
+
case "if":
|
|
963
|
+
this.genIf(node, path, $, x, env, emit, 0);
|
|
964
|
+
return;
|
|
965
|
+
case "try":
|
|
966
|
+
this.genTry(node, path, $, x, env, emit);
|
|
967
|
+
return;
|
|
968
|
+
case "reduce":
|
|
969
|
+
case "foreach":
|
|
970
|
+
this.genFold(node, path, $, x, env, emit);
|
|
971
|
+
return;
|
|
972
|
+
case "bind":
|
|
973
|
+
this.genBind(node, path, $, x, env, emit);
|
|
974
|
+
return;
|
|
975
|
+
case "label":
|
|
976
|
+
this.once($, ($2, label) => {
|
|
977
|
+
const labels = new Map(env.labels);
|
|
978
|
+
labels.set(node.value.name, label);
|
|
979
|
+
this.gen(node.value.body, at("label.body"), $2, x, { ...env, labels }, emit);
|
|
980
|
+
}, path);
|
|
981
|
+
return;
|
|
982
|
+
case "break": {
|
|
983
|
+
const label = env.labels.get(node.value);
|
|
984
|
+
if (label === undefined)
|
|
985
|
+
throw this.gap(`break $${node.value} outside its label, or across a function`);
|
|
986
|
+
this.brk($, label);
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
case "def": {
|
|
990
|
+
const defs = new Map(env.defs);
|
|
991
|
+
let closure;
|
|
992
|
+
defs.set(`${node.value.name}/${node.value.params.length}`, { kind: "def", node, path, env: () => closure });
|
|
993
|
+
closure = { ...env, defs };
|
|
994
|
+
this.gen(node.value.rest, at("def.rest"), $, x, closure, emit);
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
case "call":
|
|
998
|
+
this.genCall(node, path, $, x, env, emit);
|
|
999
|
+
return;
|
|
1000
|
+
case "update":
|
|
1001
|
+
this.genUpdate(node, path, $, x, env, emit);
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* `-v`: a number negated. Any other value raises jq's error, naming its
|
|
1007
|
+
* kind, as a lenient place (`try`, `?`) lets it by; an option is null, or
|
|
1008
|
+
* its value.
|
|
1009
|
+
*/
|
|
1010
|
+
negate($, v, path, emit) {
|
|
1011
|
+
const o = this.open(v);
|
|
1012
|
+
const t = this.type(o);
|
|
1013
|
+
if (t.type === "Integer")
|
|
1014
|
+
emit($, this.b("IntegerNegate", [], [o], IntegerType, path));
|
|
1015
|
+
else if (t.type === "Float")
|
|
1016
|
+
emit($, this.b("FloatNegate", [], [o], FloatType, path));
|
|
1017
|
+
else if (nullablePayload(t) !== undefined) {
|
|
1018
|
+
this.match($, o, { none: $2 => this.raise($2, "null cannot be negated", path), some: ($2, p) => this.negate($2, p, path, emit) }, path);
|
|
1019
|
+
}
|
|
1020
|
+
else
|
|
1021
|
+
this.raise($, `${jqKind(t)} cannot be negated`, path);
|
|
1022
|
+
}
|
|
1023
|
+
/** `$__loc__`: where a node is, as jq gives it. */
|
|
1024
|
+
location(path) {
|
|
1025
|
+
const range = this.checked.source.spans.get(path);
|
|
1026
|
+
const line = range === undefined ? 1n : toQuerySpan(this.checked.source.text, range.from, range.to).line;
|
|
1027
|
+
return this.value({ file: "<top-level>", line }, StructType({ file: StringType, line: IntegerType }), path);
|
|
1028
|
+
}
|
|
1029
|
+
/** A value that must be an expression: the root is only ever read a field at a time. */
|
|
1030
|
+
expr(v, path) {
|
|
1031
|
+
if (isRoot(v))
|
|
1032
|
+
throw this.gap(`the whole root as a value at ${this.text(path) || "the program"}`);
|
|
1033
|
+
return v;
|
|
1034
|
+
}
|
|
1035
|
+
// ─── Paths ──────────────────────────────────────────────────────────────
|
|
1036
|
+
/** `.name` on a value, as jq reads it: `facts` say which cases a variant can hold. */
|
|
1037
|
+
readField($, v, name, optional, path, facts, emit) {
|
|
1038
|
+
if (isRoot(v)) {
|
|
1039
|
+
const input = v.fields.get(name);
|
|
1040
|
+
if (input === undefined)
|
|
1041
|
+
throw this.gap(`.${name} is not an input`);
|
|
1042
|
+
emit($, input);
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
const e = this.open(v);
|
|
1046
|
+
const t = this.type(e);
|
|
1047
|
+
if (t.type === "Null") {
|
|
1048
|
+
emit($, this.null(path));
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
if (nullablePayload(t) !== undefined) {
|
|
1052
|
+
const inner = facts?.kind === "present" ? facts.payload : undefined;
|
|
1053
|
+
this.match($, e, {
|
|
1054
|
+
none: $2 => emit($2, this.null(path)),
|
|
1055
|
+
some: ($2, p) => this.readField($2, p, name, optional, path, inner, emit),
|
|
1056
|
+
}, path);
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
switch (t.type) {
|
|
1060
|
+
case "Struct":
|
|
1061
|
+
emit($, name in t.fields ? this.field(e, name) : this.null(path));
|
|
1062
|
+
return;
|
|
1063
|
+
case "Dict":
|
|
1064
|
+
if (unwrap(t.key).type !== "String") {
|
|
1065
|
+
if (!optional)
|
|
1066
|
+
this.raise($, `Cannot index object with "${name}"`, path);
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
this.dictGet($, e, this.str(name, path), path, emit);
|
|
1070
|
+
return;
|
|
1071
|
+
case "Variant": {
|
|
1072
|
+
const cases = casesOf(t, facts);
|
|
1073
|
+
if (name === "type") {
|
|
1074
|
+
if (cases.length === 1) {
|
|
1075
|
+
emit($, this.str(cases[0], path));
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
const handlers = {};
|
|
1079
|
+
for (const c of cases)
|
|
1080
|
+
handlers[c] = $2 => emit($2, this.str(c, path));
|
|
1081
|
+
this.match($, e, handlers, path);
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
if (name === "value") {
|
|
1085
|
+
const handlers = {};
|
|
1086
|
+
for (const c of cases)
|
|
1087
|
+
handlers[c] = ($2, payload) => emit($2, payload);
|
|
1088
|
+
this.match($, e, handlers, path);
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
if (optional) {
|
|
1092
|
+
emit($, this.null(path));
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
this.raise($, `Cannot index object with "${name}"`, path);
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
default:
|
|
1099
|
+
if (!optional)
|
|
1100
|
+
this.raise($, `Cannot index ${jqKind(t)} with "${name}"`, path);
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
/** A dict's value at a key: `null` when it lacks the key. */
|
|
1105
|
+
dictGet($, dict, key, path, emit) {
|
|
1106
|
+
const d = this.open(dict);
|
|
1107
|
+
const t = this.type(d);
|
|
1108
|
+
const K = t.key;
|
|
1109
|
+
const V = t.value;
|
|
1110
|
+
const found = this.b("DictTryGet", [K, V], [d, this.widenTo($, key, K, path)], OptionType(V), path);
|
|
1111
|
+
// A value that can be null already is the lookup's value, or null.
|
|
1112
|
+
if (nullablePayload(OptionType(V)) === undefined) {
|
|
1113
|
+
this.match($, found, { none: $2 => emit($2, this.null(path)), some: ($2, value) => emit($2, value) }, path);
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
emit($, found);
|
|
1117
|
+
}
|
|
1118
|
+
genIndex(node, path, $, x, env, emit) {
|
|
1119
|
+
const { index, optional, target } = node.value;
|
|
1120
|
+
const targetPath = childPath(path, "index.target");
|
|
1121
|
+
const keyPath = childPath(path, "index.index");
|
|
1122
|
+
const literal = this.literalOf(index);
|
|
1123
|
+
if (literal?.type.type === "String") {
|
|
1124
|
+
if (target.type === "identity" && isRoot(x)) {
|
|
1125
|
+
this.readField($, x, literal.value, optional, path, undefined, emit);
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
1128
|
+
// A literal string reads a field, unless the value is a dict.
|
|
1129
|
+
this.gen(target, targetPath, $, x, env, ($2, v) => {
|
|
1130
|
+
if (!isRoot(v) && this.type(this.open(v)).type === "Dict") {
|
|
1131
|
+
this.dictGet($2, v, this.str(literal.value, path), path, emit);
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
const facts = isRoot(v) ? undefined : this.factsIn(this.result(targetPath, this.envFor(targetPath, env, x))?.shape, this.type(v));
|
|
1135
|
+
this.readField($2, v, literal.value, optional, path, facts, emit);
|
|
1136
|
+
});
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
// jq takes each key, then each value it indexes: `(a, b)[0, 1]` is a[0], b[0], a[1], b[1].
|
|
1140
|
+
this.collected(index, keyPath, $, x, env, ($2, k) => {
|
|
1141
|
+
this.gen(target, targetPath, $2, x, env, ($3, v) => this.indexOf($3, this.expr(v, path), k, optional, path, emit));
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
/** `.[k]` on a value. */
|
|
1145
|
+
indexOf($, v, k, optional, path, emit) {
|
|
1146
|
+
const e = this.open(v);
|
|
1147
|
+
const t = this.type(e);
|
|
1148
|
+
if (t.type === "Null") {
|
|
1149
|
+
emit($, this.null(path));
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
if (nullablePayload(t) !== undefined) {
|
|
1153
|
+
this.match($, e, {
|
|
1154
|
+
none: $2 => emit($2, this.null(path)),
|
|
1155
|
+
some: ($2, p) => this.indexOf($2, p, k, optional, path, emit),
|
|
1156
|
+
}, path);
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
if (t.type === "Dict") {
|
|
1160
|
+
this.dictGet($, e, k, path, emit);
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
const array = this.asArray($, e, path);
|
|
1164
|
+
if (array === undefined || this.type(k).type !== "Integer") {
|
|
1165
|
+
if (!optional)
|
|
1166
|
+
this.raise($, `Cannot index ${jqKind(t)} with ${jqKind(this.type(k))}`, path);
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
const element = this.type(array).value;
|
|
1170
|
+
const bound = this.bind($, array, "array");
|
|
1171
|
+
const i = this.declare($, k, "index");
|
|
1172
|
+
this.ifElse($, this.lt(i, this.int(0), path), $2 => this.assign($2, i, this.add(i, this.size(bound, path), path)), undefined, path);
|
|
1173
|
+
const found = this.b("ArrayTryGet", [element], [bound, i], OptionType(element), path);
|
|
1174
|
+
if (nullablePayload(OptionType(element)) === undefined) {
|
|
1175
|
+
this.match($, found, { none: $2 => emit($2, this.null(path)), some: ($2, value) => emit($2, value) }, path);
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
emit($, found);
|
|
1179
|
+
}
|
|
1180
|
+
genSlice(node, path, $, x, env, emit) {
|
|
1181
|
+
const { from, optional, target, to } = node.value;
|
|
1182
|
+
const bound = ($2, option, step, then) => {
|
|
1183
|
+
if (option.type === "none") {
|
|
1184
|
+
then($2, undefined);
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
this.collected(option.value, childPath(path, step), $2, x, env, then);
|
|
1188
|
+
};
|
|
1189
|
+
bound($, from, "slice.from.some", ($2, a) => bound($2, to, "slice.to.some", ($3, b) => {
|
|
1190
|
+
this.gen(target, childPath(path, "slice.target"), $3, x, env, ($4, v) => this.sliceOf($4, this.expr(v, path), a, b, optional, path, emit));
|
|
1191
|
+
}));
|
|
1192
|
+
}
|
|
1193
|
+
/** `.[a:b]` on a value: half-open, negative bounds counted from the end, clamped. */
|
|
1194
|
+
sliceOf($, v, a, b, optional, path, emit) {
|
|
1195
|
+
const e = this.open(v);
|
|
1196
|
+
const t = this.type(e);
|
|
1197
|
+
if (t.type === "Null") {
|
|
1198
|
+
emit($, this.null(path));
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
if (nullablePayload(t) !== undefined) {
|
|
1202
|
+
this.match($, e, { none: $2 => emit($2, this.null(path)), some: ($2, p) => this.sliceOf($2, p, a, b, optional, path, emit) }, path);
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
if (t.type === "Vector" || t.type === "String" || t.type === "Array" || t.type === "Set") {
|
|
1206
|
+
const s = t.type === "Set" ? this.asArray($, e, path) : this.bind($, e, "sliced");
|
|
1207
|
+
const st = this.type(s);
|
|
1208
|
+
const length = this.declare($, st.type === "String" ? this.b("StringLength", [], [s], IntegerType, path)
|
|
1209
|
+
: st.type === "Vector" ? this.b("VectorLength", [st.element], [s], IntegerType, path)
|
|
1210
|
+
: this.size(s, path), "length", false);
|
|
1211
|
+
const start = this.boundOf($, a, this.int(0), length, path);
|
|
1212
|
+
const end = this.boundOf($, b, length, length, path);
|
|
1213
|
+
this.ifElse($, this.lt(end, start, path), $2 => this.assign($2, end, start), undefined, path);
|
|
1214
|
+
if (st.type === "String")
|
|
1215
|
+
emit($, this.b("StringSubstring", [], [s, start, end], StringType, path));
|
|
1216
|
+
else if (st.type === "Vector")
|
|
1217
|
+
emit($, this.b("VectorSlice", [st.element], [s, start, end], st, path));
|
|
1218
|
+
else
|
|
1219
|
+
emit($, this.b("ArraySlice", [st.value], [s, start, end], st, path));
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1222
|
+
if (!optional)
|
|
1223
|
+
this.raise($, `Cannot index ${jqKind(t)} with object`, path);
|
|
1224
|
+
}
|
|
1225
|
+
/** A slice bound as an index: absent or null for its default, negative from the end, clamped to the length. */
|
|
1226
|
+
boundOf($, b, fallback, length, path) {
|
|
1227
|
+
const i = this.declare($, fallback, "bound", true, IntegerType);
|
|
1228
|
+
if (b !== undefined) {
|
|
1229
|
+
const t = this.type(b);
|
|
1230
|
+
if (t.type === "Integer")
|
|
1231
|
+
this.assign($, i, b);
|
|
1232
|
+
else if (nullablePayload(t) !== undefined)
|
|
1233
|
+
this.match($, b, { some: ($2, p) => this.assign($2, i, this.widenTo($2, p, IntegerType, path)) }, path);
|
|
1234
|
+
else if (t.type !== "Null")
|
|
1235
|
+
throw this.gap(`a slice bound of ${printType(t)}`);
|
|
1236
|
+
}
|
|
1237
|
+
this.ifElse($, this.lt(i, this.int(0), path), $2 => this.assign($2, i, this.add(i, length, path)), undefined, path);
|
|
1238
|
+
this.ifElse($, this.lt(i, this.int(0), path), $2 => this.assign($2, i, this.int(0)), undefined, path);
|
|
1239
|
+
this.ifElse($, this.lt(length, i, path), $2 => this.assign($2, i, length), undefined, path);
|
|
1240
|
+
return i;
|
|
1241
|
+
}
|
|
1242
|
+
/** `.[]` on a value: an array's elements, a set's, a dict's values in key order, a struct's field values in order. */
|
|
1243
|
+
iterate($, v, optional, path, emit) {
|
|
1244
|
+
const e = this.open(v);
|
|
1245
|
+
const t = this.type(e);
|
|
1246
|
+
if (nullablePayload(t) !== undefined) {
|
|
1247
|
+
this.match($, e, {
|
|
1248
|
+
none: $2 => { if (!optional)
|
|
1249
|
+
this.raise($2, "Cannot iterate over null", path); },
|
|
1250
|
+
some: ($2, p) => this.iterate($2, p, optional, path, emit),
|
|
1251
|
+
}, path);
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
switch (t.type) {
|
|
1255
|
+
case "Array":
|
|
1256
|
+
case "Set":
|
|
1257
|
+
case "Dict":
|
|
1258
|
+
case "Vector":
|
|
1259
|
+
case "Matrix":
|
|
1260
|
+
this.forEach($, e, ($2, value) => emit($2, value), path);
|
|
1261
|
+
return;
|
|
1262
|
+
case "Struct": {
|
|
1263
|
+
const s = this.bind($, e, "struct");
|
|
1264
|
+
for (const name of Object.keys(t.fields))
|
|
1265
|
+
emit($, this.field(s, name));
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
default:
|
|
1269
|
+
if (!optional)
|
|
1270
|
+
this.raise($, `Cannot iterate over ${jqKind(t)}`, path);
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
/** The kinds of value `..` finds directly inside a value of a type, as the checker walks them. */
|
|
1275
|
+
childKinds(type) {
|
|
1276
|
+
const t = unwrap(type);
|
|
1277
|
+
const payload = nullablePayload(t);
|
|
1278
|
+
if (payload !== undefined)
|
|
1279
|
+
return this.childKinds(payload);
|
|
1280
|
+
switch (t.type) {
|
|
1281
|
+
case "Array": return [t.value];
|
|
1282
|
+
case "Set": return [t.key];
|
|
1283
|
+
case "Vector": return [t.element];
|
|
1284
|
+
case "Matrix": return [ArrayType(t.element)];
|
|
1285
|
+
case "Dict": return [t.value];
|
|
1286
|
+
case "Struct": return Object.values(t.fields);
|
|
1287
|
+
case "Variant": return [StringType, ...Object.values(t.cases)];
|
|
1288
|
+
default: return [];
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
/** Gives each value `..` finds directly inside a value to `emit`, in order. */
|
|
1292
|
+
children($, v, path, emit) {
|
|
1293
|
+
const e = this.open(v);
|
|
1294
|
+
const t = this.type(e);
|
|
1295
|
+
if (nullablePayload(t) !== undefined) {
|
|
1296
|
+
this.match($, e, { some: ($2, p) => this.children($2, p, path, emit) }, path);
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
switch (t.type) {
|
|
1300
|
+
case "Array":
|
|
1301
|
+
case "Set":
|
|
1302
|
+
case "Dict":
|
|
1303
|
+
case "Vector":
|
|
1304
|
+
case "Matrix":
|
|
1305
|
+
// A collection of Never is empty: nothing is inside it.
|
|
1306
|
+
if (this.childKinds(t)[0].type === "Never")
|
|
1307
|
+
return;
|
|
1308
|
+
this.forEach($, e, ($2, value) => emit($2, value), path);
|
|
1309
|
+
return;
|
|
1310
|
+
case "Struct": {
|
|
1311
|
+
const s = this.bind($, e, "struct");
|
|
1312
|
+
for (const name of Object.keys(t.fields))
|
|
1313
|
+
emit($, this.field(s, name));
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
case "Variant": {
|
|
1317
|
+
// A variant is jq's {type, value}: the case's name, then its payload.
|
|
1318
|
+
const handlers = {};
|
|
1319
|
+
for (const c of Object.keys(t.cases))
|
|
1320
|
+
handlers[c] = ($2, payload) => { emit($2, this.str(c, path)); emit($2, payload); };
|
|
1321
|
+
this.match($, e, handlers, path);
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
default:
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
/**
|
|
1329
|
+
* `..`: a value and every value inside it, in pre-order. A recursive type
|
|
1330
|
+
* is walked with a stack of the kinds of value it holds, so the IR needs no
|
|
1331
|
+
* recursion.
|
|
1332
|
+
*/
|
|
1333
|
+
descend($, v, path, emit) {
|
|
1334
|
+
const kinds = [];
|
|
1335
|
+
let recursive = false;
|
|
1336
|
+
const visit = (t) => {
|
|
1337
|
+
// A recursive type equals its node, so a value typed as the node meets
|
|
1338
|
+
// the recursive type as a kind already seen: it recurses all the same.
|
|
1339
|
+
if (t.type === "Recursive")
|
|
1340
|
+
recursive = true;
|
|
1341
|
+
if (kinds.some(k => isTypeEqual(k, t)))
|
|
1342
|
+
return;
|
|
1343
|
+
kinds.push(t);
|
|
1344
|
+
this.childKinds(t).forEach(visit);
|
|
1345
|
+
};
|
|
1346
|
+
visit(this.type(v));
|
|
1347
|
+
if (!recursive) {
|
|
1348
|
+
const walk = ($2, value) => {
|
|
1349
|
+
emit($2, value);
|
|
1350
|
+
this.children($2, value, path, walk);
|
|
1351
|
+
};
|
|
1352
|
+
walk($, v);
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
this.walkStack($, v, kinds, path, ($2, value, push) => {
|
|
1356
|
+
emit($2, value);
|
|
1357
|
+
this.children($2, value, path, push);
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* A depth-first walk with an explicit stack of values of several kinds:
|
|
1362
|
+
* `visit` gets each value in pre-order, and a `push` for the values to walk
|
|
1363
|
+
* after it, first to last.
|
|
1364
|
+
*/
|
|
1365
|
+
walkStack($, start, kinds, path, visit) {
|
|
1366
|
+
const cases = {};
|
|
1367
|
+
kinds.forEach((k, i) => { cases[`k${String(i).padStart(3, "0")}`] = k; });
|
|
1368
|
+
const Item = VariantType(cases);
|
|
1369
|
+
const caseOf = (t) => {
|
|
1370
|
+
const i = kinds.findIndex(k => isTypeEqual(k, t));
|
|
1371
|
+
if (i < 0)
|
|
1372
|
+
throw this.gap(`${printType(t)} in a walk of ${kinds.map(k => printType(k)).join(", ")}`);
|
|
1373
|
+
return `k${String(i).padStart(3, "0")}`;
|
|
1374
|
+
};
|
|
1375
|
+
const stack = this.declare($, this.emptyArray(Item), "stack");
|
|
1376
|
+
this.push($, stack, this.variantOf(Item, caseOf(this.type(start)), start), path);
|
|
1377
|
+
this.whileLoop($, this.lt(this.int(0), this.size(stack, path), path), $2 => {
|
|
1378
|
+
const item = this.declare($2, this.b("ArrayPopLast", [Item], [stack], Item, path), "item", false);
|
|
1379
|
+
const handlers = {};
|
|
1380
|
+
for (const name of Object.keys(cases)) {
|
|
1381
|
+
handlers[name] = ($3, value) => {
|
|
1382
|
+
// The values found are pushed last first, so the first is walked next.
|
|
1383
|
+
const found = this.declare($3, this.emptyArray(Item), "found");
|
|
1384
|
+
visit($3, value, ($4, child) => this.push($4, found, this.variantOf(Item, caseOf(this.type(child)), child), path));
|
|
1385
|
+
this.forEach($3, this.b("ArrayReverse", [Item], [found], ArrayType(Item), path), ($4, child) => this.push($4, stack, child, path), path);
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
this.match($2, item, handlers, path);
|
|
1389
|
+
}, path);
|
|
1390
|
+
}
|
|
1391
|
+
// ─── Construction ───────────────────────────────────────────────────────
|
|
1392
|
+
genObject(node, path, $, x, env, emit) {
|
|
1393
|
+
const type = this.typeAt(path, env);
|
|
1394
|
+
const t = unwrap(type);
|
|
1395
|
+
const entries = node.value;
|
|
1396
|
+
if (t.type === "Struct") {
|
|
1397
|
+
const inputFacts = isRoot(x) ? undefined : this.factsIn(this.checked.inputAt(path, env.instance), this.type(x));
|
|
1398
|
+
// Each combination of the values' outputs, the first entry's slowest.
|
|
1399
|
+
const step = ($2, i, values) => {
|
|
1400
|
+
if (i === entries.length) {
|
|
1401
|
+
const fields = {};
|
|
1402
|
+
for (const [name, fieldType] of Object.entries(t.fields))
|
|
1403
|
+
fields[name] = this.widenTo($2, values[name], fieldType, path);
|
|
1404
|
+
emit($2, this.struct(type, fields));
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
const entry = entries[i];
|
|
1408
|
+
const name = entry.key.value;
|
|
1409
|
+
const next = ($3, v) => step($3, i + 1, { ...values, [name]: this.bind($3, v, "field") });
|
|
1410
|
+
if (entry.value.type === "some") {
|
|
1411
|
+
this.gen(entry.value.value, childPath(path, `object[${i}].value.some`), $2, x, env, next);
|
|
1412
|
+
}
|
|
1413
|
+
else if (entry.key.type === "variable") {
|
|
1414
|
+
if (name === "__loc__") {
|
|
1415
|
+
next($2, this.location(path));
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
1418
|
+
const bound = env.vars.get(name);
|
|
1419
|
+
if (bound === undefined)
|
|
1420
|
+
throw this.gap(`$${name} is not bound`);
|
|
1421
|
+
next($2, this.expr(bound, path));
|
|
1422
|
+
}
|
|
1423
|
+
else {
|
|
1424
|
+
this.readField($2, x, name, false, path, inputFacts, next);
|
|
1425
|
+
}
|
|
1426
|
+
};
|
|
1427
|
+
step($, 0, {});
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
if (t.type === "Dict") {
|
|
1431
|
+
const step = ($2, i, pairs) => {
|
|
1432
|
+
if (i === entries.length) {
|
|
1433
|
+
const dict = this.declare($2, this.value(new Map(), t), "object");
|
|
1434
|
+
for (const [k, v] of pairs)
|
|
1435
|
+
this.put($2, dict, k, v, path);
|
|
1436
|
+
emit($2, dict);
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
const entry = entries[i];
|
|
1440
|
+
if (entry.key.type !== "computed")
|
|
1441
|
+
throw this.gap("a dict entry without a computed key");
|
|
1442
|
+
const value = entry.value;
|
|
1443
|
+
this.gen(entry.key.value, childPath(path, `object[${i}].key.computed`), $2, x, env, ($3, k) => {
|
|
1444
|
+
const key = this.bind($3, k, "key");
|
|
1445
|
+
const next = ($4, v) => step($4, i + 1, [...pairs, [key, this.bind($4, v, "value")]]);
|
|
1446
|
+
// `{"\(f)"}` holds the input's value at the key, `.[key]`.
|
|
1447
|
+
if (value.type === "none")
|
|
1448
|
+
this.indexOf($3, this.expr(x, path), key, false, path, next);
|
|
1449
|
+
else
|
|
1450
|
+
this.gen(value.value, childPath(path, `object[${i}].value.some`), $3, x, env, next);
|
|
1451
|
+
});
|
|
1452
|
+
};
|
|
1453
|
+
step($, 0, []);
|
|
1454
|
+
return;
|
|
1455
|
+
}
|
|
1456
|
+
throw this.gap(`an object of ${printType(type)}`);
|
|
1457
|
+
}
|
|
1458
|
+
/**
|
|
1459
|
+
* A string with interpolations: each combination of the interpolated
|
|
1460
|
+
* values, each written by `text` (`tostring`, or a format).
|
|
1461
|
+
*/
|
|
1462
|
+
genString(parts, path, $, x, env, emit, text, partsPath) {
|
|
1463
|
+
const step = ($2, i, acc) => {
|
|
1464
|
+
if (i === parts.length) {
|
|
1465
|
+
emit($2, acc);
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
const part = parts[i];
|
|
1469
|
+
if (part.type === "text") {
|
|
1470
|
+
step($2, i + 1, this.concat(acc, this.str(part.value, path), path));
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
this.gen(part.value, childPath(partsPath, `string[${i}].interpolate`), $2, x, env, ($3, v) => {
|
|
1474
|
+
step($3, i + 1, this.bind($3, this.concat(acc, text($3, v), path), "text"));
|
|
1475
|
+
});
|
|
1476
|
+
};
|
|
1477
|
+
step($, 0, this.str("", path));
|
|
1478
|
+
}
|
|
1479
|
+
/** Two strings joined. */
|
|
1480
|
+
concat(a, b, path) {
|
|
1481
|
+
if (this.constant(a)?.value === "")
|
|
1482
|
+
return b;
|
|
1483
|
+
if (this.constant(b)?.value === "")
|
|
1484
|
+
return a;
|
|
1485
|
+
return this.b("StringConcat", [], [a, b], StringType, path);
|
|
1486
|
+
}
|
|
1487
|
+
/** jq's `tostring`: a string as it is, `null` as `null`, anything else as its East text (§13.9). */
|
|
1488
|
+
tostring($, v, path) {
|
|
1489
|
+
const e = this.open(v);
|
|
1490
|
+
const t = this.type(e);
|
|
1491
|
+
if (t.type === "String")
|
|
1492
|
+
return e;
|
|
1493
|
+
if (t.type === "Null")
|
|
1494
|
+
return this.str("null", path);
|
|
1495
|
+
if (nullablePayload(t) !== undefined) {
|
|
1496
|
+
return this.matchValue(e, { none: () => this.str("null", path), some: ($2, p) => this.tostring($2, p, path) }, StringType, path);
|
|
1497
|
+
}
|
|
1498
|
+
return this.b("Print", [t], [e], StringType, path);
|
|
1499
|
+
}
|
|
1500
|
+
// ─── Operators ──────────────────────────────────────────────────────────
|
|
1501
|
+
genBinary(node, path, $, x, env, emit) {
|
|
1502
|
+
const { left, op, right } = node.value;
|
|
1503
|
+
const leftPath = childPath(path, "binary.left");
|
|
1504
|
+
const rightPath = childPath(path, "binary.right");
|
|
1505
|
+
if (op === "and" || op === "or") {
|
|
1506
|
+
// The right side runs only when the left does not decide.
|
|
1507
|
+
this.gen(left, leftPath, $, x, env, ($2, a) => {
|
|
1508
|
+
const decided = ($3) => emit($3, this.bool(op === "or", path));
|
|
1509
|
+
const undecided = ($3) => this.gen(right, rightPath, $3, x, env, ($4, b) => emit($4, this.asExpr(this.truthy(b, path))));
|
|
1510
|
+
const truth = this.truthy(a, path);
|
|
1511
|
+
if (op === "and")
|
|
1512
|
+
this.branch($2, truth, undecided, decided, path);
|
|
1513
|
+
else
|
|
1514
|
+
this.branch($2, truth, decided, undecided, path);
|
|
1515
|
+
});
|
|
1516
|
+
return;
|
|
1517
|
+
}
|
|
1518
|
+
// jq takes the right side's outputs first, and the left side's for each.
|
|
1519
|
+
// An Integer with a Float is widened by the operator, as the checker typed it.
|
|
1520
|
+
this.collected(right, rightPath, $, x, env, ($2, b) => {
|
|
1521
|
+
this.collected(left, leftPath, $2, x, env, ($3, a) => {
|
|
1522
|
+
if (COMPARISONS.has(op))
|
|
1523
|
+
emit($3, this.compare($3, op, a, b, path));
|
|
1524
|
+
else
|
|
1525
|
+
this.give($3, this.arith($3, op, a, b, path), emit);
|
|
1526
|
+
});
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1529
|
+
/** A comparison: East's order within a kind, jq's order across kinds (null, booleans, numbers, strings, arrays, objects). */
|
|
1530
|
+
compare($, op, a, b, path) {
|
|
1531
|
+
const va = this.open(a);
|
|
1532
|
+
const vb = this.open(b);
|
|
1533
|
+
const ta = this.type(va);
|
|
1534
|
+
const tb = this.type(vb);
|
|
1535
|
+
const oa = nullablePayload(ta) !== undefined;
|
|
1536
|
+
const ob = nullablePayload(tb) !== undefined;
|
|
1537
|
+
if (oa || ob) {
|
|
1538
|
+
// An option compares as null, or as its payload.
|
|
1539
|
+
const side = (v, open, then) => open
|
|
1540
|
+
? this.matchValue(v, { none: $2 => then($2, this.null(path)), some: ($2, p) => then($2, p) }, BooleanType, path)
|
|
1541
|
+
: then($, v);
|
|
1542
|
+
return side(va, oa, ($2, a2) => side(vb, ob, ($3, b2) => this.compare($3, op, a2, b2, path)));
|
|
1543
|
+
}
|
|
1544
|
+
const ra = jqRank(ta);
|
|
1545
|
+
const rb = jqRank(tb);
|
|
1546
|
+
if (ra !== rb || ta.type === "Null")
|
|
1547
|
+
return this.bool(ORDERINGS[op](ra < rb ? -1 : ra > rb ? 1 : 0), path);
|
|
1548
|
+
let l = va;
|
|
1549
|
+
let r = vb;
|
|
1550
|
+
if (!isTypeEqual(ta, tb)) {
|
|
1551
|
+
const common = unify(ta, tb);
|
|
1552
|
+
if (common === undefined)
|
|
1553
|
+
throw this.gap(`${printType(ta)} ${op} ${printType(tb)}`);
|
|
1554
|
+
l = this.widenTo($, va, common, path);
|
|
1555
|
+
r = this.widenTo($, vb, common, path);
|
|
1556
|
+
}
|
|
1557
|
+
const cl = this.constant(l);
|
|
1558
|
+
const cr = this.constant(r);
|
|
1559
|
+
if (cl !== undefined && cr !== undefined && typeof cl.value !== "object") {
|
|
1560
|
+
const order = cl.value === cr.value ? 0 : cl.value < cr.value ? -1 : 1;
|
|
1561
|
+
return this.bool(ORDERINGS[op](order), path);
|
|
1562
|
+
}
|
|
1563
|
+
return this.b(COMPARISON_BUILTINS[op], [this.type(l)], [l, r], BooleanType, path);
|
|
1564
|
+
}
|
|
1565
|
+
/**
|
|
1566
|
+
* An arithmetic operator on two values, as jq defines it for their kinds:
|
|
1567
|
+
* its value, or an error expression (of type Never) for kinds it does not
|
|
1568
|
+
* combine, which only a lenient place (`try`, `?`) lets through.
|
|
1569
|
+
*/
|
|
1570
|
+
arith($, op, a, b, path) {
|
|
1571
|
+
const va = this.open(a);
|
|
1572
|
+
const vb = this.open(b);
|
|
1573
|
+
const ta = this.type(va);
|
|
1574
|
+
const tb = this.type(vb);
|
|
1575
|
+
if (op === "+") {
|
|
1576
|
+
// `null` is the identity of `+`.
|
|
1577
|
+
if (ta.type === "Null")
|
|
1578
|
+
return vb;
|
|
1579
|
+
if (tb.type === "Null")
|
|
1580
|
+
return va;
|
|
1581
|
+
if (nullablePayload(ta) !== undefined) {
|
|
1582
|
+
return this.matchAuto(va, { none: () => vb, some: ($2, p) => this.arith($2, op, p, vb, path) }, path);
|
|
1583
|
+
}
|
|
1584
|
+
if (nullablePayload(tb) !== undefined) {
|
|
1585
|
+
return this.matchAuto(vb, { none: () => va, some: ($2, p) => this.arith($2, op, va, p, path) }, path);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
else if (nullablePayload(ta) !== undefined || nullablePayload(tb) !== undefined) {
|
|
1589
|
+
// An option is null or its value: null raises, as jq raises where a lenient place lets it by, and a value is combined.
|
|
1590
|
+
if (nullablePayload(ta) !== undefined) {
|
|
1591
|
+
return this.matchAuto(va, { none: $2 => this.arith($2, op, this.null(path), vb, path), some: ($2, p) => this.arith($2, op, p, vb, path) }, path);
|
|
1592
|
+
}
|
|
1593
|
+
return this.matchAuto(vb, { none: $2 => this.arith($2, op, va, this.null(path), path), some: ($2, p) => this.arith($2, op, va, p, path) }, path);
|
|
1594
|
+
}
|
|
1595
|
+
const number = (t) => t.type === "Integer" || t.type === "Float";
|
|
1596
|
+
if (number(ta) && number(tb)) {
|
|
1597
|
+
if (op === "/") {
|
|
1598
|
+
const divisor = this.bind($, this.widenTo($, vb, FloatType, path), "divisor");
|
|
1599
|
+
this.ifElse($, this.b("Equal", [FloatType], [divisor, this.float(0)], BooleanType, path), $2 => this.raise($2, "Division by zero", path), undefined, path);
|
|
1600
|
+
return this.b("FloatDivide", [], [this.widenTo($, va, FloatType, path), divisor], FloatType, path);
|
|
1601
|
+
}
|
|
1602
|
+
if (op === "%")
|
|
1603
|
+
return this.remainder($, va, vb, path);
|
|
1604
|
+
const name = ARITHMETIC_BUILTINS[op];
|
|
1605
|
+
if (ta.type === "Integer" && tb.type === "Integer")
|
|
1606
|
+
return this.b(`Integer${name}`, [], [va, vb], IntegerType, path);
|
|
1607
|
+
return this.b(`Float${name}`, [], [this.widenTo($, va, FloatType, path), this.widenTo($, vb, FloatType, path)], FloatType, path);
|
|
1608
|
+
}
|
|
1609
|
+
if (op === "+" && ta.type === "String" && tb.type === "String")
|
|
1610
|
+
return this.concat(va, vb, path);
|
|
1611
|
+
if (op === "+" && ta.type === "Array" && tb.type === "Array") {
|
|
1612
|
+
const element = unify(ta.value, tb.value);
|
|
1613
|
+
if (element === undefined)
|
|
1614
|
+
throw this.gap(`${printType(ta)} + ${printType(tb)}`);
|
|
1615
|
+
return this.b("ArrayConcat", [element], [this.widenTo($, va, ArrayType(element), path), this.widenTo($, vb, ArrayType(element), path)], ArrayType(element), path);
|
|
1616
|
+
}
|
|
1617
|
+
if (op === "-" && ta.type === "Array" && tb.type === "Array") {
|
|
1618
|
+
const others = this.bind($, vb, "others");
|
|
1619
|
+
const out = this.declare($, this.emptyArray(ta.value), "difference");
|
|
1620
|
+
this.forEach($, va, ($2, item) => {
|
|
1621
|
+
const found = this.declare($2, this.bool(false), "found");
|
|
1622
|
+
this.forEach($2, others, ($3, other, _key, label) => {
|
|
1623
|
+
this.ifElse($3, this.compare($3, "==", item, other, path), $4 => { this.assign($4, found, this.bool(true)); this.brk($4, label); }, undefined, path);
|
|
1624
|
+
}, path, "other");
|
|
1625
|
+
this.ifElse($2, this.not(found, path), $3 => this.push($3, out, item, path), undefined, path);
|
|
1626
|
+
}, path);
|
|
1627
|
+
return out;
|
|
1628
|
+
}
|
|
1629
|
+
if ((op === "+" || op === "*") && ta.type === "Struct" && tb.type === "Struct")
|
|
1630
|
+
return this.mergeStructs($, va, vb, op === "*", path);
|
|
1631
|
+
if ((op === "+" || op === "*") && ta.type === "Dict" && tb.type === "Dict")
|
|
1632
|
+
return this.mergeDicts($, va, vb, op === "*", path);
|
|
1633
|
+
if ((op === "+" || op === "*") && (ta.type === "Dict" || tb.type === "Dict") && (ta.type === "Struct" || tb.type === "Struct"))
|
|
1634
|
+
return ta.type === "Dict" ? va : vb;
|
|
1635
|
+
// A string repeated, the count a number on either side.
|
|
1636
|
+
if (op === "*" && ta.type === "String" && number(tb))
|
|
1637
|
+
return this.repeat($, va, vb, path);
|
|
1638
|
+
if (op === "*" && number(ta) && tb.type === "String")
|
|
1639
|
+
return this.repeat($, vb, va, path);
|
|
1640
|
+
if (op === "/" && ta.type === "String" && tb.type === "String")
|
|
1641
|
+
return this.split($, va, vb, path);
|
|
1642
|
+
return this.failure(`${jqKind(ta)} and ${jqKind(tb)} cannot be combined with ${op}`, path);
|
|
1643
|
+
}
|
|
1644
|
+
/**
|
|
1645
|
+
* jq's `%`: the remainder of the numbers truncated to 64-bit integers, one
|
|
1646
|
+
* beyond the range taken as its end (±Infinity too). Integers give an
|
|
1647
|
+
* Integer; with a Float, a NaN gives NaN, and the remainder is a Float. A
|
|
1648
|
+
* divisor of 0 raises, as IntegerRemainder does.
|
|
1649
|
+
*/
|
|
1650
|
+
remainder($, a, b, path) {
|
|
1651
|
+
if (this.type(a).type === "Integer" && this.type(b).type === "Integer")
|
|
1652
|
+
return this.b("IntegerRemainder", [], [a, b], IntegerType, path);
|
|
1653
|
+
const x = this.bind($, this.widenTo($, a, FloatType, path), "dividend");
|
|
1654
|
+
const y = this.bind($, this.widenTo($, b, FloatType, path), "divisor");
|
|
1655
|
+
const nan = this.b("BooleanOr", [], [this.isNan(x, path), this.isNan(y, path)], BooleanType, path);
|
|
1656
|
+
return this.ifValue(nan, () => this.float(NaN, path), $2 => {
|
|
1657
|
+
const r = this.b("IntegerRemainder", [], [this.truncated($2, x, path), this.truncated($2, y, path)], IntegerType, path);
|
|
1658
|
+
return this.b("IntegerToFloat", [], [r], FloatType, path);
|
|
1659
|
+
}, FloatType, path);
|
|
1660
|
+
}
|
|
1661
|
+
/** A Float truncated to an Integer as jq's `dtoi` takes it: below the range its least, above it its greatest. */
|
|
1662
|
+
truncated($, f, path) {
|
|
1663
|
+
return this.bind($, this.ifValue(this.lt(f, this.float(-(2 ** 63)), path), () => this.int(-(2n ** 63n)), () => this.ifValue(this.b("GreaterEqual", [FloatType], [f, this.float(2 ** 63)], BooleanType, path), () => this.int(2n ** 63n - 1n), $2 => this.round("roundTrunc", f, $2, path), IntegerType, path), IntegerType, path), "whole");
|
|
1664
|
+
}
|
|
1665
|
+
/**
|
|
1666
|
+
* jq's string repetition: `n` copies of a string, a Float count truncated,
|
|
1667
|
+
* at most 2³¹ − 1 as jq takes it. A negative count, or NaN, gives null.
|
|
1668
|
+
*/
|
|
1669
|
+
repeat($, s, n, path) {
|
|
1670
|
+
const option = OptionType(StringType);
|
|
1671
|
+
const count = this.bind($, n, "count");
|
|
1672
|
+
if (this.type(count).type === "Integer") {
|
|
1673
|
+
return this.ifValue(this.lt(count, this.int(0), path), () => this.none(option), () => this.some(this.b("StringRepeat", [], [s, count], StringType, path), option), option, path);
|
|
1674
|
+
}
|
|
1675
|
+
const invalid = this.b("BooleanOr", [], [this.lt(count, this.float(0), path), this.isNan(count, path)], BooleanType, path);
|
|
1676
|
+
return this.ifValue(invalid, () => this.none(option), $2 => {
|
|
1677
|
+
const capped = this.ifValue(this.lt(count, this.float(2 ** 31 - 1), path), () => count, () => this.float(2 ** 31 - 1), FloatType, path);
|
|
1678
|
+
return this.some(this.b("StringRepeat", [], [s, this.round("roundTrunc", capped, $2, path)], StringType, path), option);
|
|
1679
|
+
}, option, path);
|
|
1680
|
+
}
|
|
1681
|
+
/** jq's split of a string by a separator: an empty string has no parts. */
|
|
1682
|
+
split($, s, separator, path) {
|
|
1683
|
+
const text = this.bind($, s, "text");
|
|
1684
|
+
return this.ifValue(this.eq(this.b("StringLength", [], [text], IntegerType, path), this.int(0), path), () => this.emptyArray(StringType), () => this.b("StringSplit", [], [text, separator], ArrayType(StringType), path), ArrayType(StringType), path);
|
|
1685
|
+
}
|
|
1686
|
+
/** Whether a Float is NaN: East's order puts it above +Infinity. */
|
|
1687
|
+
isNan(f, path) {
|
|
1688
|
+
return this.lt(this.float(Infinity), f, path);
|
|
1689
|
+
}
|
|
1690
|
+
/** An expression that raises an error: of type Never, so it stands for a value of any type. */
|
|
1691
|
+
failure(message, path) {
|
|
1692
|
+
return this.mk({ ast_type: "Error", type: NeverType, loc_id: this.loc(path), message: this.ast(this.str(message, path)) });
|
|
1693
|
+
}
|
|
1694
|
+
/** Gives a value to `emit`, unless it is an error expression, which runs as a statement instead. */
|
|
1695
|
+
give($, v, emit) {
|
|
1696
|
+
if (this.ended($))
|
|
1697
|
+
return;
|
|
1698
|
+
if (this.type(v).type === "Never") {
|
|
1699
|
+
this.stmt($, v);
|
|
1700
|
+
return;
|
|
1701
|
+
}
|
|
1702
|
+
emit($, v);
|
|
1703
|
+
}
|
|
1704
|
+
/** A value chosen by the case a variant holds, of the one type the cases' values share. */
|
|
1705
|
+
matchAuto(v, cases, path) {
|
|
1706
|
+
const opened = this.open(v);
|
|
1707
|
+
const built = [];
|
|
1708
|
+
let type = NeverType;
|
|
1709
|
+
for (const [name, payload] of Object.entries(this.type(opened).cases)) {
|
|
1710
|
+
const variable = { ast_type: "Variable", type: payload, loc_id: UNKNOWN_LOC_ID, mutable: false, name: name === "some" ? "value" : "payload" };
|
|
1711
|
+
const block = this.block();
|
|
1712
|
+
const build = cases[name];
|
|
1713
|
+
if (build === undefined)
|
|
1714
|
+
throw this.gap(`no value for the case ${name}`);
|
|
1715
|
+
const value = build(block, fromAst(variable));
|
|
1716
|
+
built.push({ name, variable, block, value });
|
|
1717
|
+
if (this.ended(block) || this.type(value).type === "Never")
|
|
1718
|
+
continue;
|
|
1719
|
+
const next = unify(type, this.type(value));
|
|
1720
|
+
if (next === undefined)
|
|
1721
|
+
throw this.gap(`${printType(type)} and ${printType(this.type(value))} as one value`);
|
|
1722
|
+
type = next;
|
|
1723
|
+
}
|
|
1724
|
+
const out = {};
|
|
1725
|
+
for (const { name, variable, block, value } of built) {
|
|
1726
|
+
// A case that raises: its statements, the error last.
|
|
1727
|
+
if (this.ended(block) || this.type(value).type === "Never") {
|
|
1728
|
+
out[name] = { variable, body: this.diverging(this.ended(block) ? block.statements : [...block.statements, this.ast(value)]) };
|
|
1729
|
+
continue;
|
|
1730
|
+
}
|
|
1731
|
+
const w = this.widenTo(block, value, type, path);
|
|
1732
|
+
out[name] = { variable, body: block.statements.length === 0 ? this.ast(w) : { ast_type: "Block", type, loc_id: UNKNOWN_LOC_ID, statements: [...block.statements, this.ast(w)] } };
|
|
1733
|
+
}
|
|
1734
|
+
return this.mk({ ast_type: "Match", type, loc_id: this.loc(path), variant: this.ast(opened), cases: out });
|
|
1735
|
+
}
|
|
1736
|
+
/** `a + b` on structs: `b`'s fields win, and new ones follow; `a * b` merges fields that are objects in both deeply. */
|
|
1737
|
+
mergeStructs($, a, b, deep, path) {
|
|
1738
|
+
const left = this.bind($, this.open(a), "left");
|
|
1739
|
+
const right = this.bind($, this.open(b), "right");
|
|
1740
|
+
const af = this.type(left).fields;
|
|
1741
|
+
const bf = this.type(right).fields;
|
|
1742
|
+
const values = {};
|
|
1743
|
+
const types = {};
|
|
1744
|
+
for (const name of Object.keys(af)) {
|
|
1745
|
+
values[name] = this.field(left, name);
|
|
1746
|
+
types[name] = af[name];
|
|
1747
|
+
}
|
|
1748
|
+
for (const [name, t] of Object.entries(bf)) {
|
|
1749
|
+
values[name] = deep && name in af && mergesDeeply(af[name], t)
|
|
1750
|
+
? this.bind($, this.deepMerge($, this.field(left, name), this.field(right, name), path), name)
|
|
1751
|
+
: this.field(right, name);
|
|
1752
|
+
types[name] = this.type(values[name]);
|
|
1753
|
+
}
|
|
1754
|
+
return this.struct(StructType(types), values);
|
|
1755
|
+
}
|
|
1756
|
+
/**
|
|
1757
|
+
* `a + b` on dicts: every key of both, `b`'s value where both hold one;
|
|
1758
|
+
* `a * b` merges those values deeply where both are objects.
|
|
1759
|
+
*/
|
|
1760
|
+
mergeDicts($, a, b, deep, path) {
|
|
1761
|
+
const merged = unify(this.type(a), this.type(b));
|
|
1762
|
+
if (merged === undefined)
|
|
1763
|
+
throw this.gap(`${printType(this.type(a))} ${deep ? "*" : "+"} ${printType(this.type(b))}`);
|
|
1764
|
+
const K = parts(merged).key;
|
|
1765
|
+
const V = parts(merged).value;
|
|
1766
|
+
const out = this.declare($, this.b("DictCopy", [K, V], [this.widenTo($, a, merged, path)], merged, path), "merged");
|
|
1767
|
+
this.forEach($, this.widenTo($, b, merged, path), ($2, value, key) => {
|
|
1768
|
+
if (!deep || !mergesDeeply(V, V)) {
|
|
1769
|
+
this.put($2, out, key, value, path);
|
|
1770
|
+
return;
|
|
1771
|
+
}
|
|
1772
|
+
this.match($2, this.b("DictTryGet", [K, V], [out, key], OptionType(V), path), {
|
|
1773
|
+
none: $3 => this.stmt($3, this.b("DictInsert", [K, V], [out, key, value], NullType, path)),
|
|
1774
|
+
some: ($3, held) => this.stmt($3, this.b("DictUpdate", [K, V], [out, key, this.widenTo($3, this.deepMerge($3, held, value, path), V, path)], NullType, path)),
|
|
1775
|
+
}, path);
|
|
1776
|
+
}, path);
|
|
1777
|
+
return out;
|
|
1778
|
+
}
|
|
1779
|
+
/**
|
|
1780
|
+
* Two values merged as jq's `*` merges them: structs and dicts deeply, an
|
|
1781
|
+
* option of one where both hold a value; anything else is `b`.
|
|
1782
|
+
*/
|
|
1783
|
+
deepMerge($, a, b, path) {
|
|
1784
|
+
const va = this.open(a);
|
|
1785
|
+
const vb = this.open(b);
|
|
1786
|
+
const ta = this.type(va);
|
|
1787
|
+
const tb = this.type(vb);
|
|
1788
|
+
if (ta.type === "Struct" && tb.type === "Struct")
|
|
1789
|
+
return this.mergeStructs($, va, vb, true, path);
|
|
1790
|
+
if (ta.type === "Dict" && tb.type === "Dict")
|
|
1791
|
+
return this.mergeDicts($, va, vb, true, path);
|
|
1792
|
+
if (!mergesDeeply(ta, tb))
|
|
1793
|
+
return vb;
|
|
1794
|
+
// Options of one object type: merged where both hold one, else b's.
|
|
1795
|
+
const left = this.bind($, va, "left");
|
|
1796
|
+
const right = this.bind($, vb, "right");
|
|
1797
|
+
return this.matchValue(right, {
|
|
1798
|
+
none: () => right,
|
|
1799
|
+
some: ($2, q) => this.matchValue(left, {
|
|
1800
|
+
none: () => right,
|
|
1801
|
+
some: ($3, p) => this.some(this.deepMerge($3, p, q, path), tb),
|
|
1802
|
+
}, tb, path),
|
|
1803
|
+
}, tb, path);
|
|
1804
|
+
}
|
|
1805
|
+
genAlternative(node, path, $, x, env, emit) {
|
|
1806
|
+
// `a // b`: a's outputs that are neither false nor null, its errors
|
|
1807
|
+
// suppressed; or else b's. They are kept, then given on after the try, so
|
|
1808
|
+
// an error downstream is not suppressed.
|
|
1809
|
+
const leftPath = childPath(path, "alternative.left");
|
|
1810
|
+
const kept = this.declare($, this.emptyArray(this.typeAt(path, env)), "kept");
|
|
1811
|
+
this.tryCatch($, $2 => {
|
|
1812
|
+
this.gen(node.value.left, leftPath, $2, x, env, ($3, v) => this.present($3, v, path, ($4, w) => this.push($4, kept, w, path)));
|
|
1813
|
+
}, () => { }, path);
|
|
1814
|
+
this.ifElse($, this.lt(this.int(0), this.size(kept, path), path), $2 => this.forEach($2, kept, ($3, v) => emit($3, v), path), $2 => this.gen(node.value.right, childPath(path, "alternative.right"), $2, x, env, emit), path);
|
|
1815
|
+
}
|
|
1816
|
+
/** Gives a value to `emit` when it is neither false nor null, out of its option. */
|
|
1817
|
+
present($, v, path, emit) {
|
|
1818
|
+
const e = this.open(v);
|
|
1819
|
+
const t = this.type(e);
|
|
1820
|
+
if (t.type === "Null")
|
|
1821
|
+
return;
|
|
1822
|
+
if (nullablePayload(t) !== undefined) {
|
|
1823
|
+
this.match($, e, { some: ($2, p) => this.present($2, p, path, emit) }, path);
|
|
1824
|
+
return;
|
|
1825
|
+
}
|
|
1826
|
+
if (t.type === "Boolean") {
|
|
1827
|
+
const c = this.constant(e);
|
|
1828
|
+
if (c?.value === false)
|
|
1829
|
+
return;
|
|
1830
|
+
if (c?.value === true) {
|
|
1831
|
+
emit($, e);
|
|
1832
|
+
return;
|
|
1833
|
+
}
|
|
1834
|
+
this.ifElse($, e, $2 => emit($2, e), undefined, path);
|
|
1835
|
+
return;
|
|
1836
|
+
}
|
|
1837
|
+
emit($, e);
|
|
1838
|
+
}
|
|
1839
|
+
// ─── Control ────────────────────────────────────────────────────────────
|
|
1840
|
+
genIf(node, path, $, x, env, emit, index) {
|
|
1841
|
+
const { branches, otherwise } = node.value;
|
|
1842
|
+
if (index === branches.length) {
|
|
1843
|
+
if (otherwise.type === "some")
|
|
1844
|
+
this.gen(otherwise.value, childPath(path, "if.otherwise.some"), $, x, env, emit);
|
|
1845
|
+
else
|
|
1846
|
+
emit($, this.expr(x, path));
|
|
1847
|
+
return;
|
|
1848
|
+
}
|
|
1849
|
+
const branch = branches[index];
|
|
1850
|
+
const conditionPath = childPath(path, `if.branches[${index}].condition`);
|
|
1851
|
+
this.narrowed($, x, conditionPath, env, ($2, value) => {
|
|
1852
|
+
this.gen(branch.condition, conditionPath, $2, value, env, ($3, c) => {
|
|
1853
|
+
this.branch($3, this.truthy(c, path), $4 => this.gen(branch.then, childPath(path, `if.branches[${index}].then`), $4, value, env, emit), $4 => this.genIf(node, path, $4, value, env, emit, index + 1), path);
|
|
1854
|
+
});
|
|
1855
|
+
});
|
|
1856
|
+
}
|
|
1857
|
+
/**
|
|
1858
|
+
* Runs a condition's continuation with its input opened when the condition
|
|
1859
|
+
* tests its type: `type == "number"` on an option then knows which it holds,
|
|
1860
|
+
* and the branch it rules out is never generated.
|
|
1861
|
+
*/
|
|
1862
|
+
narrowed($, x, conditionPath, env, then) {
|
|
1863
|
+
const proves = this.result(conditionPath, this.envFor(conditionPath, env, x))?.proves;
|
|
1864
|
+
const typeTest = proves?.some(p => p.kind === "type" && p.path.length === 0) === true;
|
|
1865
|
+
if (!typeTest || isRoot(x) || nullablePayload(this.type(this.open(x))) === undefined) {
|
|
1866
|
+
then($, x);
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1869
|
+
this.match($, this.open(x), {
|
|
1870
|
+
none: $2 => then($2, this.null(conditionPath)),
|
|
1871
|
+
some: ($2, p) => then($2, p),
|
|
1872
|
+
}, conditionPath);
|
|
1873
|
+
}
|
|
1874
|
+
genTry(node, path, $, x, env, emit) {
|
|
1875
|
+
// The body's outputs are kept, then given on after the try, so an error
|
|
1876
|
+
// their consumer raises is not the body's to catch; outputs before an
|
|
1877
|
+
// error stand, as in jq.
|
|
1878
|
+
const bodyPath = childPath(path, "try.body");
|
|
1879
|
+
const body = this.result(bodyPath, this.envFor(bodyPath, env, x));
|
|
1880
|
+
const type = body === null ? undefined : unifyShape(body.shape);
|
|
1881
|
+
const kept = type === undefined || type.type === "Never" ? undefined : this.declare($, this.emptyArray(type), "kept");
|
|
1882
|
+
const handler = node.value.catch.type === "some" ? node.value.catch.value : undefined;
|
|
1883
|
+
const caught = handler === undefined ? undefined : this.declare($, this.none(OptionType(StringType)), "caught");
|
|
1884
|
+
this.tryCatch($, $2 => {
|
|
1885
|
+
this.gen(node.value.body, bodyPath, $2, x, env, ($3, v) => {
|
|
1886
|
+
// An output after an error the body always raises never comes.
|
|
1887
|
+
if (this.ended($3))
|
|
1888
|
+
return;
|
|
1889
|
+
if (kept === undefined)
|
|
1890
|
+
throw this.gap("a try whose outputs have no one type");
|
|
1891
|
+
this.push($3, kept, v, path);
|
|
1892
|
+
});
|
|
1893
|
+
}, ($2, message) => {
|
|
1894
|
+
if (caught !== undefined)
|
|
1895
|
+
this.assign($2, caught, this.some(message, OptionType(StringType)));
|
|
1896
|
+
}, path);
|
|
1897
|
+
if (kept !== undefined)
|
|
1898
|
+
this.forEach($, kept, ($2, v) => emit($2, v), path);
|
|
1899
|
+
if (handler !== undefined && caught !== undefined) {
|
|
1900
|
+
this.match($, caught, { some: ($2, message) => this.gen(handler, childPath(path, "try.catch.some"), $2, message, env, emit) }, path);
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
genFold(node, path, $, x, env, emit) {
|
|
1904
|
+
const kind = node.type;
|
|
1905
|
+
const accType = this.checked.typeAt(`${path}#acc`, env.instance)?.type;
|
|
1906
|
+
if (accType === undefined)
|
|
1907
|
+
throw this.gap(`no accumulator type for ${kind}`);
|
|
1908
|
+
const updatePath = childPath(path, `${kind}.update`);
|
|
1909
|
+
const inPlace = this.updatesInPlace(node, path, env, accType);
|
|
1910
|
+
// For each value `init` gives, a fold over the source's values.
|
|
1911
|
+
this.gen(node.value.init, childPath(path, `${kind}.init`), $, x, env, ($2, init) => {
|
|
1912
|
+
const start = this.widenTo($2, init, accType, path);
|
|
1913
|
+
// A state the update sets keys of in place starts as a copy, so what `init` gave never changes.
|
|
1914
|
+
const acc = this.declare($2, inPlace ? this.b("DictCopy", [parts(accType).key, parts(accType).value], [start], accType, path) : start, "acc");
|
|
1915
|
+
this.collected(node.value.source, childPath(path, `${kind}.source`), $2, x, env, ($3, item) => {
|
|
1916
|
+
this.destructure($3, node.value.pattern, childPath(path, `${kind}.pattern`), item, env, updatePath, ($4, vars) => {
|
|
1917
|
+
const inner = { ...env, vars };
|
|
1918
|
+
// The update runs on the state as it was; each output becomes the state, the last staying.
|
|
1919
|
+
const state = this.declare($4, acc, "state", false);
|
|
1920
|
+
this.gen(node.value.update, updatePath, $4, state, inPlace ? { ...inner, inPlace: updatePath } : inner, ($5, next) => {
|
|
1921
|
+
const value = this.bind($5, this.widenTo($5, next, accType, path), "next");
|
|
1922
|
+
this.assign($5, acc, value);
|
|
1923
|
+
if (kind === "foreach") {
|
|
1924
|
+
const extract = node.value.extract;
|
|
1925
|
+
if (extract.type === "none")
|
|
1926
|
+
emit($5, value);
|
|
1927
|
+
else
|
|
1928
|
+
this.gen(extract.value, childPath(path, "foreach.extract.some"), $5, value, inner, emit);
|
|
1929
|
+
}
|
|
1930
|
+
});
|
|
1931
|
+
});
|
|
1932
|
+
});
|
|
1933
|
+
if (kind === "reduce")
|
|
1934
|
+
emit($2, acc);
|
|
1935
|
+
});
|
|
1936
|
+
}
|
|
1937
|
+
/**
|
|
1938
|
+
* Whether a fold's update may set a key of its state in place, as jq
|
|
1939
|
+
* changes a value nothing else holds (#1093). The state is a dict; the
|
|
1940
|
+
* update sets a key of it, `.[k]` or `.name` with `=`, `|=` or an
|
|
1941
|
+
* arithmetic update, perhaps further down (`.[k].n += 1`), where only the
|
|
1942
|
+
* state's own key is set in place; the value of `=` and of an arithmetic
|
|
1943
|
+
* update is one value, which holds nothing of the state; and a `foreach`
|
|
1944
|
+
* gives what its `extract` reads of the state, never the state. A key is
|
|
1945
|
+
* of an immutable type, so it never holds the state, and `|=`'s update runs
|
|
1946
|
+
* on the old value at the key, which cannot reach the state.
|
|
1947
|
+
*/
|
|
1948
|
+
updatesInPlace(node, path, env, accType) {
|
|
1949
|
+
if (accType.type !== "Dict")
|
|
1950
|
+
return false;
|
|
1951
|
+
const update = node.value.update;
|
|
1952
|
+
if (update.type !== "update" || !setsKeyOfInput(update.value.path))
|
|
1953
|
+
return false;
|
|
1954
|
+
// A value of the state's type, for the records of the nodes that run on it.
|
|
1955
|
+
const state = this.placeholder(accType);
|
|
1956
|
+
if (node.type === "foreach") {
|
|
1957
|
+
const extract = node.value.extract;
|
|
1958
|
+
if (extract.type === "none" || !this.holdsNoState(extract.value, childPath(path, "foreach.extract.some"), env, state))
|
|
1959
|
+
return false;
|
|
1960
|
+
}
|
|
1961
|
+
if (update.value.op === "|=")
|
|
1962
|
+
return true;
|
|
1963
|
+
const updatePath = childPath(path, `${node.type}.update`);
|
|
1964
|
+
const valuePath = childPath(updatePath, "update.value");
|
|
1965
|
+
const updateEnv = this.envFor(updatePath, env, state);
|
|
1966
|
+
const value = this.result(valuePath, this.envFor(valuePath, updateEnv, state));
|
|
1967
|
+
if (value === null || value.mult.lo !== 1 || value.mult.hi !== 1)
|
|
1968
|
+
return false;
|
|
1969
|
+
return this.holdsNoState(update.value.value, valuePath, updateEnv, state);
|
|
1970
|
+
}
|
|
1971
|
+
/**
|
|
1972
|
+
* Whether a filter's outputs on a fold's state hold nothing of it: they
|
|
1973
|
+
* are of an immutable type, which holds no dict, or {@link stateHeldBy}
|
|
1974
|
+
* finds them read from the state or made without it.
|
|
1975
|
+
*/
|
|
1976
|
+
holdsNoState(node, path, env, state) {
|
|
1977
|
+
const r = this.result(path, this.envFor(path, env, state));
|
|
1978
|
+
if (r === null)
|
|
1979
|
+
return false;
|
|
1980
|
+
const type = unifyShape(r.shape);
|
|
1981
|
+
if (type !== undefined && isImmutableType(type))
|
|
1982
|
+
return true;
|
|
1983
|
+
return stateHeldBy(node, "itself", new Map(), env.defs) === "nothing";
|
|
1984
|
+
}
|
|
1985
|
+
genBind(node, path, $, x, env, emit) {
|
|
1986
|
+
const { body, patterns, source } = node.value;
|
|
1987
|
+
const bodyPath = childPath(path, "bind.body");
|
|
1988
|
+
this.gen(source, childPath(path, "bind.source"), $, x, env, ($2, s) => {
|
|
1989
|
+
if (patterns.length === 1) {
|
|
1990
|
+
this.destructure($2, patterns[0], childPath(path, "bind.patterns[0]"), s, env, bodyPath, ($3, vars) => {
|
|
1991
|
+
this.gen(body, bodyPath, $3, x, { ...env, vars }, emit);
|
|
1992
|
+
});
|
|
1993
|
+
return;
|
|
1994
|
+
}
|
|
1995
|
+
// `?//`: the first pattern that binds and whose body raises no error;
|
|
1996
|
+
// the body's outputs are kept, then given on. A variable only another
|
|
1997
|
+
// pattern binds is null, as jq binds it.
|
|
1998
|
+
const type = this.typeAt(bodyPath, env);
|
|
1999
|
+
const kept = this.declare($2, this.emptyArray(type), "kept");
|
|
2000
|
+
const names = [...new Set(patterns.flatMap(patternNames))];
|
|
2001
|
+
const scope = this.checked.scopeAt(bodyPath, env.instance);
|
|
2002
|
+
const attempt = ($3, i) => {
|
|
2003
|
+
const run = ($4) => {
|
|
2004
|
+
this.destructure($4, patterns[i], childPath(path, `bind.patterns[${i}]`), s, env, bodyPath, ($5, vars) => {
|
|
2005
|
+
const own = new Set(patternNames(patterns[i]));
|
|
2006
|
+
const all = new Map(vars);
|
|
2007
|
+
for (const name of names) {
|
|
2008
|
+
if (own.has(name))
|
|
2009
|
+
continue;
|
|
2010
|
+
const checked = scope?.vars.get(name);
|
|
2011
|
+
const nullType = checked === undefined ? undefined : unifyShape(checked.shape);
|
|
2012
|
+
all.set(name, this.bind($5, nullType === undefined ? this.null(path) : this.widenTo($5, this.null(path), nullType, path), name));
|
|
2013
|
+
}
|
|
2014
|
+
this.gen(body, bodyPath, $5, x, { ...env, vars: all }, ($6, v) => this.push($6, kept, v, path));
|
|
2015
|
+
});
|
|
2016
|
+
};
|
|
2017
|
+
if (i === patterns.length - 1) {
|
|
2018
|
+
run($3);
|
|
2019
|
+
return;
|
|
2020
|
+
}
|
|
2021
|
+
this.tryCatch($3, run, $4 => {
|
|
2022
|
+
this.stmt($4, this.b("ArrayClear", [type], [kept], NullType, path));
|
|
2023
|
+
attempt($4, i + 1);
|
|
2024
|
+
}, path);
|
|
2025
|
+
};
|
|
2026
|
+
attempt($2, 0);
|
|
2027
|
+
this.forEach($2, kept, ($3, v) => emit($3, v), path);
|
|
2028
|
+
});
|
|
2029
|
+
}
|
|
2030
|
+
/**
|
|
2031
|
+
* Binds a pattern's variables to the parts of a value, each as the type the
|
|
2032
|
+
* checker gave the variable where the body runs.
|
|
2033
|
+
*/
|
|
2034
|
+
destructure($, pattern, path, value, env, bodyPath, then) {
|
|
2035
|
+
const scope = this.checked.scopeAt(bodyPath, env.instance);
|
|
2036
|
+
const bind = ($2, name, v) => {
|
|
2037
|
+
if (isRoot(v))
|
|
2038
|
+
return v;
|
|
2039
|
+
const checked = scope?.vars.get(name);
|
|
2040
|
+
const type = checked === undefined ? undefined : unifyShape(checked.shape);
|
|
2041
|
+
return this.bind($2, type === undefined ? v : this.widenTo($2, v, type, path), name);
|
|
2042
|
+
};
|
|
2043
|
+
const visit = ($2, p, v, vars, done) => {
|
|
2044
|
+
switch (p.type) {
|
|
2045
|
+
case "variable": {
|
|
2046
|
+
const next = new Map(vars);
|
|
2047
|
+
next.set(p.value, bind($2, p.value, v));
|
|
2048
|
+
done($2, next);
|
|
2049
|
+
return;
|
|
2050
|
+
}
|
|
2051
|
+
case "array": {
|
|
2052
|
+
const items = p.value;
|
|
2053
|
+
const e = this.expr(v, path);
|
|
2054
|
+
const opened = this.type(this.open(e));
|
|
2055
|
+
if (opened.type !== "Null" && opened.type !== "Array" && opened.type !== "Vector") {
|
|
2056
|
+
this.raise($2, `Cannot index ${jqKind(opened)} with number`, path);
|
|
2057
|
+
return;
|
|
2058
|
+
}
|
|
2059
|
+
const step = ($3, i, acc) => {
|
|
2060
|
+
if (i === items.length) {
|
|
2061
|
+
done($3, acc);
|
|
2062
|
+
return;
|
|
2063
|
+
}
|
|
2064
|
+
this.indexOf($3, e, this.int(i), false, path, ($4, element) => visit($4, items[i], element, acc, ($5, next) => step($5, i + 1, next)));
|
|
2065
|
+
};
|
|
2066
|
+
step($2, 0, vars);
|
|
2067
|
+
return;
|
|
2068
|
+
}
|
|
2069
|
+
case "object": {
|
|
2070
|
+
const entries = p.value;
|
|
2071
|
+
const step = ($3, i, acc) => {
|
|
2072
|
+
if (i === entries.length) {
|
|
2073
|
+
done($3, acc);
|
|
2074
|
+
return;
|
|
2075
|
+
}
|
|
2076
|
+
const entry = entries[i];
|
|
2077
|
+
this.readField($3, v, entry.key, false, path, undefined, ($4, part) => {
|
|
2078
|
+
if (entry.value.type === "none") {
|
|
2079
|
+
const next = new Map(acc);
|
|
2080
|
+
next.set(entry.key, bind($4, entry.key, part));
|
|
2081
|
+
step($4, i + 1, next);
|
|
2082
|
+
}
|
|
2083
|
+
else {
|
|
2084
|
+
visit($4, entry.value.value, part, acc, ($5, next) => step($5, i + 1, next));
|
|
2085
|
+
}
|
|
2086
|
+
});
|
|
2087
|
+
};
|
|
2088
|
+
step($2, 0, vars);
|
|
2089
|
+
return;
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
};
|
|
2093
|
+
visit($, pattern, value, new Map(env.vars), then);
|
|
2094
|
+
}
|
|
2095
|
+
// ─── Calls ──────────────────────────────────────────────────────────────
|
|
2096
|
+
genCall(node, path, $, x, env, emit) {
|
|
2097
|
+
const { args, name } = node.value;
|
|
2098
|
+
const binding = env.defs.get(`${name}/${args.length}`);
|
|
2099
|
+
const argPaths = args.map((_, i) => childPath(path, `call.args[${i}]`));
|
|
2100
|
+
if (binding === undefined) {
|
|
2101
|
+
this.genBuiltin(name, args, argPaths, path, $, x, env, emit);
|
|
2102
|
+
return;
|
|
2103
|
+
}
|
|
2104
|
+
const instance = `${env.instance}>${path}`;
|
|
2105
|
+
if (binding.kind === "param") {
|
|
2106
|
+
this.gen(binding.arg, binding.path, $, x, { ...binding.env, instance }, emit);
|
|
2107
|
+
return;
|
|
2108
|
+
}
|
|
2109
|
+
const def = binding.node.value;
|
|
2110
|
+
const closure = binding.env();
|
|
2111
|
+
const bodyPath = childPath(binding.path, "def.body");
|
|
2112
|
+
// Each combination of the value parameters' outputs, the first slowest.
|
|
2113
|
+
const bindParams = ($2, i, vars, defs, types) => {
|
|
2114
|
+
if (i === def.params.length) {
|
|
2115
|
+
const signature = [binding.path, isRoot(x) ? "root" : printType(this.type(x)), ...types].join("|");
|
|
2116
|
+
const values = def.params.filter(p => p.startsWith("$")).map(p => this.expr(vars.get(p.slice(1)), path));
|
|
2117
|
+
const recursion = env.recursion.get(signature);
|
|
2118
|
+
if (recursion !== undefined) {
|
|
2119
|
+
this.callRecursive($2, recursion, x, values, path, emit);
|
|
2120
|
+
return;
|
|
2121
|
+
}
|
|
2122
|
+
const bodyEnv = { vars, defs, labels: closure.labels, instance, recursion: env.recursion };
|
|
2123
|
+
if (callsItself(def.body, `${def.name}/${def.params.length}`))
|
|
2124
|
+
this.genRecursive($2, def, bodyPath, signature, x, values, bodyEnv, path, env, emit);
|
|
2125
|
+
else
|
|
2126
|
+
this.gen(def.body, bodyPath, $2, x, bodyEnv, emit);
|
|
2127
|
+
return;
|
|
2128
|
+
}
|
|
2129
|
+
const param = def.params[i];
|
|
2130
|
+
const argPath = childPath(path, `call.args[${i}]`);
|
|
2131
|
+
if (!param.startsWith("$")) {
|
|
2132
|
+
const nextDefs = new Map(defs);
|
|
2133
|
+
nextDefs.set(`${param}/0`, { kind: "param", arg: args[i], path: argPath, env });
|
|
2134
|
+
bindParams($2, i + 1, vars, nextDefs, types);
|
|
2135
|
+
return;
|
|
2136
|
+
}
|
|
2137
|
+
this.collected(args[i], argPath, $2, x, env, ($3, v) => {
|
|
2138
|
+
const name2 = param.slice(1);
|
|
2139
|
+
const bound = this.bind($3, v, name2);
|
|
2140
|
+
const nextVars = new Map(vars);
|
|
2141
|
+
nextVars.set(name2, bound);
|
|
2142
|
+
const nextDefs = new Map(defs);
|
|
2143
|
+
nextDefs.set(`${name2}/0`, { kind: "param", arg: variant("variable", name2), path: argPath, env: { ...closure, vars: nextVars } });
|
|
2144
|
+
bindParams($3, i + 1, nextVars, nextDefs, [...types, printType(this.type(bound))]);
|
|
2145
|
+
});
|
|
2146
|
+
};
|
|
2147
|
+
bindParams($, 0, new Map(closure.vars), new Map(closure.defs), []);
|
|
2148
|
+
}
|
|
2149
|
+
/**
|
|
2150
|
+
* A recursive def with value parameters: an East function held in a
|
|
2151
|
+
* reference, which its own body calls through the reference. It returns
|
|
2152
|
+
* its whole stream as an array, so early exit does not reach into it.
|
|
2153
|
+
*/
|
|
2154
|
+
genRecursive($, def, bodyPath, signature, x, values, bodyEnv, callPath, callerEnv, emit) {
|
|
2155
|
+
const output = this.typeAt(callPath, callerEnv);
|
|
2156
|
+
const input = this.type(this.expr(x, callPath));
|
|
2157
|
+
const params = def.params.filter(p => p.startsWith("$")).map(p => p.slice(1));
|
|
2158
|
+
const inputs = [input, ...values.map(v => this.type(v))];
|
|
2159
|
+
const fnType = FunctionType(inputs, ArrayType(output));
|
|
2160
|
+
const placeholder = this.lambda(inputs, ArrayType(output), [], () => this.emptyArray(output), callPath);
|
|
2161
|
+
const cell = this.declare($, this.mk({ ast_type: "NewRef", type: RefType(fnType), loc_id: UNKNOWN_LOC_ID, value: this.ast(placeholder) }), def.name, false);
|
|
2162
|
+
const recursion = new Map(bodyEnv.recursion);
|
|
2163
|
+
recursion.set(signature, cell);
|
|
2164
|
+
const fn = this.lambda(inputs, ArrayType(output), ["input", ...params], ($f, self, ...args) => {
|
|
2165
|
+
const vars = new Map(bodyEnv.vars);
|
|
2166
|
+
params.forEach((p, i) => vars.set(p, args[i]));
|
|
2167
|
+
const out = this.declare($f, this.emptyArray(output), "outputs");
|
|
2168
|
+
// A function body cannot break to a label outside it.
|
|
2169
|
+
this.gen(def.body, bodyPath, $f, self, { ...bodyEnv, vars, labels: new Map(), recursion }, ($g, v) => this.push($g, out, v, callPath));
|
|
2170
|
+
return out;
|
|
2171
|
+
}, callPath);
|
|
2172
|
+
this.stmt($, this.b("RefUpdate", [fnType], [cell, fn], NullType, callPath));
|
|
2173
|
+
this.callRecursive($, cell, x, values, callPath, emit);
|
|
2174
|
+
}
|
|
2175
|
+
/** A call of a recursive def through its reference: its stream, in order. */
|
|
2176
|
+
callRecursive($, cell, x, values, path, emit) {
|
|
2177
|
+
const fnType = this.type(cell).value;
|
|
2178
|
+
const fn = this.b("RefGet", [fnType], [cell], fnType, path);
|
|
2179
|
+
const results = this.bind($, this.callFn(fn, [this.expr(x, path), ...values], path), "outputs");
|
|
2180
|
+
this.forEach($, results, ($2, v) => emit($2, v), path);
|
|
2181
|
+
}
|
|
2182
|
+
/** A builtin, by its rule. */
|
|
2183
|
+
genBuiltin(name, args, argPaths, path, $, x, env, emit) {
|
|
2184
|
+
const rule = BUILTIN_RULES[name];
|
|
2185
|
+
if (rule === undefined)
|
|
2186
|
+
throw this.gap(`no translation for the builtin ${name}/${args.length}`);
|
|
2187
|
+
// Checked in a lenient place with its input's options opened: the builtin is given the value, and null,
|
|
2188
|
+
// or where it cannot take null, jq's error for it.
|
|
2189
|
+
const opened = isRoot(x) ? null : this.checked.opened(path, env.instance);
|
|
2190
|
+
if (opened !== null) {
|
|
2191
|
+
const o = this.open(x);
|
|
2192
|
+
if (nullablePayload(this.type(o)) !== undefined) {
|
|
2193
|
+
this.match($, o, {
|
|
2194
|
+
none: $2 => {
|
|
2195
|
+
if (opened.nullRaises)
|
|
2196
|
+
this.raise($2, `null cannot be used with ${name}`, path);
|
|
2197
|
+
else
|
|
2198
|
+
rule(this, { name, path, args, argPaths, $: $2, x: this.null(path), env, emit });
|
|
2199
|
+
},
|
|
2200
|
+
some: ($2, p) => rule(this, { name, path, args, argPaths, $: $2, x: p, env, emit }),
|
|
2201
|
+
}, path);
|
|
2202
|
+
return;
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
rule(this, { name, path, args, argPaths, $, x, env, emit });
|
|
2206
|
+
}
|
|
2207
|
+
// ─── Assignment ─────────────────────────────────────────────────────────
|
|
2208
|
+
genUpdate(node, path, $, x, env, emit) {
|
|
2209
|
+
const { op, path: target, value } = node.value;
|
|
2210
|
+
const targetPath = childPath(path, "update.path");
|
|
2211
|
+
const valuePath = childPath(path, "update.value");
|
|
2212
|
+
const input = this.expr(x, path);
|
|
2213
|
+
// A fold's update of a state nothing else holds sets the state's key in place (#1093).
|
|
2214
|
+
const inPlace = env.inPlace === path;
|
|
2215
|
+
if (op === "|=") {
|
|
2216
|
+
// Each position's new value is the update's first output on the old one; none deletes it.
|
|
2217
|
+
emit($, this.whole(this.modify($, input, target, targetPath, env, ($2, old) => this.firstOf(value, valuePath, $2, old, env), inPlace), path));
|
|
2218
|
+
return;
|
|
2219
|
+
}
|
|
2220
|
+
// `=` and the arithmetic updates take their value on `.`, once for each of its outputs.
|
|
2221
|
+
const update = ($2, v) => {
|
|
2222
|
+
const bound = this.bind($2, v, "update");
|
|
2223
|
+
emit($2, this.whole(this.modify($2, input, target, targetPath, env, ($3, old) => {
|
|
2224
|
+
if (op === "=")
|
|
2225
|
+
return { value: bound, optional: false };
|
|
2226
|
+
const operator = op.slice(0, -1);
|
|
2227
|
+
if (operator === "//") {
|
|
2228
|
+
// The old value where it is neither false nor null, else the update.
|
|
2229
|
+
const kept = nullablePayload(this.type(this.open(old))) ?? this.type(this.open(old));
|
|
2230
|
+
const type = kept.type === "Null" ? this.type(bound) : unify(kept, this.type(bound)) ?? this.type(bound);
|
|
2231
|
+
const cell = this.declare($3, this.widenTo($3, bound, type, path), "value");
|
|
2232
|
+
this.present($3, old, path, ($4, w) => this.assign($4, cell, this.widenTo($4, w, type, path)));
|
|
2233
|
+
return { value: cell, optional: false };
|
|
2234
|
+
}
|
|
2235
|
+
return { value: this.arith($3, operator, old, bound, path), optional: false };
|
|
2236
|
+
}, inPlace), path));
|
|
2237
|
+
};
|
|
2238
|
+
// In place, the one value is taken first, so no loop it runs over the state is open while the state changes.
|
|
2239
|
+
if (inPlace)
|
|
2240
|
+
update($, this.one(value, valuePath, $, input, env, this.typeAt(valuePath, this.envFor(valuePath, env, input))));
|
|
2241
|
+
else
|
|
2242
|
+
this.collected(value, valuePath, $, x, env, update);
|
|
2243
|
+
}
|
|
2244
|
+
/** What an update gives: the value, `null` where it deleted `.` itself (an optional value's `none`), as jq gives it. */
|
|
2245
|
+
whole(update, path) {
|
|
2246
|
+
return update === undefined ? this.null(path) : update.value;
|
|
2247
|
+
}
|
|
2248
|
+
/** The type of an update's new value: an optional one's payload. */
|
|
2249
|
+
updateType(update) {
|
|
2250
|
+
const t = this.type(update.value);
|
|
2251
|
+
return update.optional ? parts(t).cases["some"] : t;
|
|
2252
|
+
}
|
|
2253
|
+
/** The first output of a filter: exactly it when there is always one, else an option whose `none` is no output. */
|
|
2254
|
+
firstOf(node, path, $, x, env) {
|
|
2255
|
+
const e = this.envFor(path, env, x);
|
|
2256
|
+
const r = this.result(path, e);
|
|
2257
|
+
const type = r === null ? undefined : unifyShape(r.shape);
|
|
2258
|
+
if (r === null || type === undefined || type.type === "Never")
|
|
2259
|
+
return undefined;
|
|
2260
|
+
if (r.mult.lo === 1 && r.mult.hi === 1)
|
|
2261
|
+
return { value: this.one(node, path, $, x, env, type), optional: false };
|
|
2262
|
+
const option = OptionType(type);
|
|
2263
|
+
const found = this.declare($, this.none(option), "first");
|
|
2264
|
+
this.once($, ($2, label) => this.gen(node, path, $2, x, env, ($3, v) => {
|
|
2265
|
+
this.assign($3, found, this.some(this.widenTo($3, v, type, path), option));
|
|
2266
|
+
this.brk($3, label);
|
|
2267
|
+
}), path);
|
|
2268
|
+
return { value: found, optional: true };
|
|
2269
|
+
}
|
|
2270
|
+
/**
|
|
2271
|
+
* A value with the positions a path names updated: `f` gives each
|
|
2272
|
+
* position's new value from its old one, or none, which deletes it. With
|
|
2273
|
+
* `inPlace`, `v` is a fold's state nothing else holds, and the key of it
|
|
2274
|
+
* the path sets is set in place (#1093); a position further down is set in
|
|
2275
|
+
* a copy, as any other is.
|
|
2276
|
+
*
|
|
2277
|
+
* @returns the value updated; an option of it, none where `.` itself was
|
|
2278
|
+
* deleted; or `undefined` where it always is
|
|
2279
|
+
*/
|
|
2280
|
+
modify($, v, target, path, env, f, inPlace = false) {
|
|
2281
|
+
const at = (step) => childPath(path, step);
|
|
2282
|
+
const present = (value) => ({ value, optional: false });
|
|
2283
|
+
switch (target.type) {
|
|
2284
|
+
case "identity":
|
|
2285
|
+
return f($, v);
|
|
2286
|
+
case "pipe":
|
|
2287
|
+
return this.modify($, v, target.value.left, at("pipe.left"), env, ($2, inner) => this.modify($2, inner, target.value.right, at("pipe.right"), env, f), inPlace);
|
|
2288
|
+
case "field": {
|
|
2289
|
+
const { name, optional } = target.value;
|
|
2290
|
+
// Only a key of `v` itself is set in place.
|
|
2291
|
+
const own = inPlace && target.value.target.type === "identity";
|
|
2292
|
+
return this.modify($, v, target.value.target, at("field.target"), env, ($2, inner) => present(this.setField($2, inner, name, optional, path, env, f, own)), inPlace);
|
|
2293
|
+
}
|
|
2294
|
+
case "index": {
|
|
2295
|
+
const { index, optional } = target.value;
|
|
2296
|
+
const literal = this.literalOf(index);
|
|
2297
|
+
const keyPath = at("index.index");
|
|
2298
|
+
// The key is taken on the index's own input, as jq takes it; a literal is its own value, which the key's type widens.
|
|
2299
|
+
const key = literal?.type.type === "String" ? undefined
|
|
2300
|
+
: literal !== undefined ? this.value(literal.value, literal.type, keyPath)
|
|
2301
|
+
: this.one(index, keyPath, $, v, env, this.typeAt(keyPath, this.envFor(keyPath, env, v)));
|
|
2302
|
+
const own = inPlace && target.value.target.type === "identity";
|
|
2303
|
+
return this.modify($, v, target.value.target, at("index.target"), env, ($2, inner) => {
|
|
2304
|
+
if (key !== undefined)
|
|
2305
|
+
return present(this.setIndex($2, inner, key, optional, path, f, own));
|
|
2306
|
+
const name = literal.value;
|
|
2307
|
+
return present(this.type(this.open(inner)).type === "Dict"
|
|
2308
|
+
? this.setIndex($2, inner, this.str(name, path), optional, path, f, own)
|
|
2309
|
+
: this.setField($2, inner, name, optional, path, env, f, own));
|
|
2310
|
+
}, inPlace);
|
|
2311
|
+
}
|
|
2312
|
+
case "slice": {
|
|
2313
|
+
const { from, optional, to } = target.value;
|
|
2314
|
+
// The bounds are taken on the slice's own input, as jq takes them.
|
|
2315
|
+
const bound = (option, step) => option.type === "none" ? undefined
|
|
2316
|
+
: this.one(option.value, at(step), $, v, env, this.typeAt(at(step), this.envFor(at(step), env, v)));
|
|
2317
|
+
const a = bound(from, "slice.from.some");
|
|
2318
|
+
const b = bound(to, "slice.to.some");
|
|
2319
|
+
return this.modify($, v, target.value.target, at("slice.target"), env, ($2, inner) => present(this.setSlice($2, inner, a, b, optional, path, f)), inPlace);
|
|
2320
|
+
}
|
|
2321
|
+
case "iterate":
|
|
2322
|
+
return this.modify($, v, target.value.target, at("iterate.target"), env, ($2, inner) => present(this.setEach($2, inner, target.value.optional, path, f)), inPlace);
|
|
2323
|
+
case "descend":
|
|
2324
|
+
return this.walkUpdate($, v, undefined, path, f);
|
|
2325
|
+
case "call": {
|
|
2326
|
+
const { args, name } = target.value;
|
|
2327
|
+
if (name === "select" && args.length === 1) {
|
|
2328
|
+
const conditionPath = at("call.args[0]");
|
|
2329
|
+
return this.selected($, v, path, f, ($2, value, then) => this.narrowed($2, value, conditionPath, env, ($3, opened) => {
|
|
2330
|
+
this.gen(args[0], conditionPath, $3, opened, env, ($4, c) => this.branch($4, this.truthy(c, path), $5 => then($5, this.expr(opened, path)), () => { }, path));
|
|
2331
|
+
}));
|
|
2332
|
+
}
|
|
2333
|
+
if (UPDATE_SELECTORS.has(name) && args.length === 0) {
|
|
2334
|
+
return this.selected($, v, path, f, ($2, value, then) => this.genBuiltin(name, [], [], path, $2, value, env, then));
|
|
2335
|
+
}
|
|
2336
|
+
if (name === "empty" && args.length === 0)
|
|
2337
|
+
return present(v);
|
|
2338
|
+
if (name === "recurse" && args.length <= 1)
|
|
2339
|
+
return this.walkUpdate($, v, args[0], path, f);
|
|
2340
|
+
throw this.gap(`assigning to ${name}(…)`);
|
|
2341
|
+
}
|
|
2342
|
+
default:
|
|
2343
|
+
throw this.gap(`assigning to ${this.text(path)}`);
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
/**
|
|
2347
|
+
* An update where a filter keeps the value (`select(f)`, `numbers`):
|
|
2348
|
+
* `each` calls `then` with the value, narrowed, each time the filter keeps
|
|
2349
|
+
* it, and `f` gives the new value there; where it is not kept, it stays.
|
|
2350
|
+
*/
|
|
2351
|
+
selected($, v, path, f, each) {
|
|
2352
|
+
const old = this.bind($, v, "selected");
|
|
2353
|
+
const T = this.type(old);
|
|
2354
|
+
const mark = $.statements.length;
|
|
2355
|
+
let cell;
|
|
2356
|
+
each($, old, ($2, kept) => {
|
|
2357
|
+
const next = f($2, kept);
|
|
2358
|
+
if (cell === undefined) {
|
|
2359
|
+
// The updated value's type, known from the update's, declared before the filter runs.
|
|
2360
|
+
const given = next === undefined ? NeverType : this.updateType(next);
|
|
2361
|
+
const value = sameType(T, given) ? T : unify(T, given) ?? T;
|
|
2362
|
+
const optional = next === undefined || next.optional;
|
|
2363
|
+
const type = optional ? OptionType(value) : value;
|
|
2364
|
+
const init = this.block();
|
|
2365
|
+
const start = this.widenTo(init, old, value, path);
|
|
2366
|
+
const variable = { ast_type: "Variable", type, loc_id: UNKNOWN_LOC_ID, mutable: true, name: "selected" };
|
|
2367
|
+
$.statements.splice(mark, 0, ...init.statements, { ast_type: "Let", type: NullType, loc_id: UNKNOWN_LOC_ID, variable, value: this.ast(optional ? this.some(start, type) : start) });
|
|
2368
|
+
cell = { variable: fromAst(variable), type, value, optional };
|
|
2369
|
+
}
|
|
2370
|
+
const c = cell;
|
|
2371
|
+
const set = ($3, w) => {
|
|
2372
|
+
const widened = this.widenTo($3, w, c.value, path);
|
|
2373
|
+
this.assign($3, c.variable, c.optional ? this.some(widened, c.type) : widened);
|
|
2374
|
+
};
|
|
2375
|
+
if (next === undefined) {
|
|
2376
|
+
if (c.optional)
|
|
2377
|
+
this.assign($2, c.variable, this.none(c.type));
|
|
2378
|
+
else
|
|
2379
|
+
this.raise($2, UNDELETABLE, path);
|
|
2380
|
+
return;
|
|
2381
|
+
}
|
|
2382
|
+
if (!next.optional || !c.optional) {
|
|
2383
|
+
set($2, this.required($2, next, path));
|
|
2384
|
+
return;
|
|
2385
|
+
}
|
|
2386
|
+
this.match($2, next.value, { some: set, none: $3 => this.assign($3, c.variable, this.none(c.type)) }, path);
|
|
2387
|
+
});
|
|
2388
|
+
return cell === undefined ? { value: old, optional: false } : { value: cell.variable, optional: cell.optional };
|
|
2389
|
+
}
|
|
2390
|
+
/** An update's value where the position cannot be deleted (a struct field, a variant's payload): an error where it has none. */
|
|
2391
|
+
required($, update, path) {
|
|
2392
|
+
if (!update.optional)
|
|
2393
|
+
return update.value;
|
|
2394
|
+
const payload = this.updateType(update);
|
|
2395
|
+
return this.matchValue(update.value, {
|
|
2396
|
+
none: $2 => { this.raise($2, UNDELETABLE, path); return this.placeholder(payload); },
|
|
2397
|
+
some: (_$2, p) => p,
|
|
2398
|
+
}, payload, path);
|
|
2399
|
+
}
|
|
2400
|
+
/**
|
|
2401
|
+
* A position that cannot be deleted: its new value, or where the update
|
|
2402
|
+
* never gives one, an error raised there (and a value of its type for the
|
|
2403
|
+
* code after, which never runs).
|
|
2404
|
+
*/
|
|
2405
|
+
kept($, update, type, path) {
|
|
2406
|
+
if (update !== undefined)
|
|
2407
|
+
return this.required($, update, path);
|
|
2408
|
+
this.raise($, UNDELETABLE, path);
|
|
2409
|
+
return this.placeholder(type);
|
|
2410
|
+
}
|
|
2411
|
+
/**
|
|
2412
|
+
* A struct of some fields' values: of the original's type, recursive
|
|
2413
|
+
* wrapper and all, when its fields' types are the original's.
|
|
2414
|
+
*/
|
|
2415
|
+
rebuilt(original, types, values) {
|
|
2416
|
+
const type = StructType(types);
|
|
2417
|
+
const t = this.type(original);
|
|
2418
|
+
return this.struct(t.type === "Recursive" && isTypeEqual(t.node, type) ? t : type, values);
|
|
2419
|
+
}
|
|
2420
|
+
/**
|
|
2421
|
+
* A struct with one field replaced or added, or deleted by an update that
|
|
2422
|
+
* gives none; a dict's string key set, in place with `inPlace`, as
|
|
2423
|
+
* {@link Translator.setIndex} sets it; a variant's payload; `null` made a
|
|
2424
|
+
* struct. With `?`, a value of another kind is unchanged.
|
|
2425
|
+
*/
|
|
2426
|
+
setField($, v, name, optional, path, env, f, inPlace = false) {
|
|
2427
|
+
const e = this.open(v);
|
|
2428
|
+
const t = this.type(e);
|
|
2429
|
+
if (t.type === "Variant" && nullablePayload(t) === undefined && name === "value")
|
|
2430
|
+
return this.setPayload(e, path, env, f);
|
|
2431
|
+
if (nullablePayload(t) !== undefined) {
|
|
2432
|
+
// An option: `null` is updated as null is, and a value as it is; the checker gave the two one type.
|
|
2433
|
+
return this.matchAuto(e, {
|
|
2434
|
+
none: $2 => this.setField($2, this.null(path), name, optional, path, env, f),
|
|
2435
|
+
some: ($2, p) => this.setField($2, p, name, optional, path, env, f),
|
|
2436
|
+
}, path);
|
|
2437
|
+
}
|
|
2438
|
+
if (t.type === "Null") {
|
|
2439
|
+
const next = f($, this.null(path));
|
|
2440
|
+
if (next === undefined)
|
|
2441
|
+
return e;
|
|
2442
|
+
const value = this.required($, next, path);
|
|
2443
|
+
return this.struct(StructType({ [name]: this.type(value) }), { [name]: value });
|
|
2444
|
+
}
|
|
2445
|
+
if (t.type === "Struct") {
|
|
2446
|
+
const fields = t.fields;
|
|
2447
|
+
const s = this.bind($, e, "struct");
|
|
2448
|
+
const next = f($, name in fields ? this.field(s, name) : this.null(path));
|
|
2449
|
+
const values = {};
|
|
2450
|
+
const types = {};
|
|
2451
|
+
const put = (field, value) => { values[field] = value; types[field] = this.type(value); };
|
|
2452
|
+
for (const field of Object.keys(fields)) {
|
|
2453
|
+
if (field !== name)
|
|
2454
|
+
put(field, this.field(s, field));
|
|
2455
|
+
else if (next !== undefined)
|
|
2456
|
+
put(field, this.required($, next, path));
|
|
2457
|
+
}
|
|
2458
|
+
if (!(name in fields) && next !== undefined)
|
|
2459
|
+
put(name, this.required($, next, path));
|
|
2460
|
+
return this.rebuilt(v, types, values);
|
|
2461
|
+
}
|
|
2462
|
+
if (t.type === "Dict")
|
|
2463
|
+
return this.setIndex($, e, this.str(name, path), optional, path, f, inPlace);
|
|
2464
|
+
if (optional)
|
|
2465
|
+
return v;
|
|
2466
|
+
throw this.gap(`.${name} = … on ${printType(t)}`);
|
|
2467
|
+
}
|
|
2468
|
+
/**
|
|
2469
|
+
* A variant with its payload updated, in each case the checker found it can
|
|
2470
|
+
* hold there; in the others it is as it was. A payload cannot be deleted.
|
|
2471
|
+
*/
|
|
2472
|
+
setPayload(v, path, env, f) {
|
|
2473
|
+
const t = this.type(v);
|
|
2474
|
+
const payloads = t.cases;
|
|
2475
|
+
const updated = this.checked.updatedCases(path, env.instance, t) ?? Object.keys(payloads);
|
|
2476
|
+
const built = Object.entries(payloads).map(([name, type]) => {
|
|
2477
|
+
const variable = { ast_type: "Variable", type, loc_id: UNKNOWN_LOC_ID, mutable: false, name: "payload" };
|
|
2478
|
+
const block = this.block();
|
|
2479
|
+
const payload = fromAst(variable);
|
|
2480
|
+
const value = updated.includes(name) ? this.kept(block, f(block, payload), type, path) : payload;
|
|
2481
|
+
return { name, type, variable, block, value };
|
|
2482
|
+
});
|
|
2483
|
+
// The variant's new type, from its payloads' new types.
|
|
2484
|
+
const cases = {};
|
|
2485
|
+
for (const b of built)
|
|
2486
|
+
cases[b.name] = this.ended(b.block) ? b.type : this.type(b.value);
|
|
2487
|
+
const V = VariantType(cases);
|
|
2488
|
+
const out = {};
|
|
2489
|
+
for (const b of built) {
|
|
2490
|
+
if (this.ended(b.block)) {
|
|
2491
|
+
out[b.name] = { variable: b.variable, body: this.diverging(b.block.statements) };
|
|
2492
|
+
continue;
|
|
2493
|
+
}
|
|
2494
|
+
const wrapped = this.ast(this.variantOf(V, b.name, b.value));
|
|
2495
|
+
out[b.name] = { variable: b.variable, body: b.block.statements.length === 0 ? wrapped : { ast_type: "Block", type: V, loc_id: UNKNOWN_LOC_ID, statements: [...b.block.statements, wrapped] } };
|
|
2496
|
+
}
|
|
2497
|
+
return this.mk({ ast_type: "Match", type: V, loc_id: this.loc(path), variant: this.ast(v), cases: out });
|
|
2498
|
+
}
|
|
2499
|
+
/** An array without the element at an index. */
|
|
2500
|
+
without($, array, i, path) {
|
|
2501
|
+
const kept = this.declare($, this.emptyArray(this.type(array).value), "array");
|
|
2502
|
+
this.forEach($, array, ($2, item, index) => this.ifElse($2, this.not(this.eq(index, i, path), path), $3 => this.push($3, kept, item, path), undefined, path), path);
|
|
2503
|
+
return kept;
|
|
2504
|
+
}
|
|
2505
|
+
/**
|
|
2506
|
+
* An array or dict with one element replaced, or deleted where the update
|
|
2507
|
+
* gives none; an empty struct or `null` made a dict. With `?`, a value of
|
|
2508
|
+
* another kind is unchanged. With `inPlace`, a dict nothing else holds is
|
|
2509
|
+
* changed itself, not a copy of it, while its values' type stays (#1093).
|
|
2510
|
+
*/
|
|
2511
|
+
setIndex($, v, key, optional, path, f, inPlace = false) {
|
|
2512
|
+
const e = this.open(v);
|
|
2513
|
+
const t = this.type(e);
|
|
2514
|
+
if (t.type === "Array") {
|
|
2515
|
+
// A key that is not an Integer (in a `try`, or after `?`, where the checker lets it by) raises, as jq does.
|
|
2516
|
+
if (this.type(key).type !== "Integer") {
|
|
2517
|
+
this.raise($, `Cannot update an array at a ${jqKind(this.type(key))} index`, path);
|
|
2518
|
+
return v;
|
|
2519
|
+
}
|
|
2520
|
+
const element = t.value;
|
|
2521
|
+
const source = this.bind($, e, "array");
|
|
2522
|
+
const i = this.declare($, key, "index");
|
|
2523
|
+
this.ifElse($, this.lt(i, this.int(0), path), $2 => this.assign($2, i, this.add(i, this.size(source, path), path)), undefined, path);
|
|
2524
|
+
const next = f($, this.b("ArrayGet", [element], [source, i], element, path));
|
|
2525
|
+
if (next === undefined)
|
|
2526
|
+
return this.without($, source, i, path);
|
|
2527
|
+
const E = unify(element, this.updateType(next)) ?? element;
|
|
2528
|
+
const out = this.declare($, isTypeEqual(E, element) ? this.b("ArrayCopy", [element], [source], t, path) : this.widenTo($, source, ArrayType(E), path), "array");
|
|
2529
|
+
const set = ($2, w) => this.stmt($2, this.b("ArrayUpdate", [E], [out, i, this.widenTo($2, w, E, path)], NullType, path));
|
|
2530
|
+
if (!next.optional) {
|
|
2531
|
+
set($, next.value);
|
|
2532
|
+
return out;
|
|
2533
|
+
}
|
|
2534
|
+
const result = this.declare($, out, "array");
|
|
2535
|
+
this.match($, next.value, { some: set, none: $2 => this.assign($2, result, this.without($2, out, i, path)) }, path);
|
|
2536
|
+
return result;
|
|
2537
|
+
}
|
|
2538
|
+
if (t.type === "Dict" || t.type === "Null" || (t.type === "Struct" && Object.keys(t.fields).length === 0)) {
|
|
2539
|
+
const K = t.type === "Dict" ? t.key : this.type(key);
|
|
2540
|
+
const V0 = t.type === "Dict" ? t.value : NeverType;
|
|
2541
|
+
const k = this.bind($, this.widenTo($, key, K, path), "key");
|
|
2542
|
+
// A missing key is null: an option of the value, or the value's own null where it can be null.
|
|
2543
|
+
const old = t.type === "Dict"
|
|
2544
|
+
? (nullablePayload(OptionType(V0)) === undefined
|
|
2545
|
+
? this.matchValue(this.b("DictTryGet", [K, V0], [e, k], OptionType(V0), path), { none: $2 => this.widenTo($2, this.null(path), V0, path), some: (_$2, p) => p }, V0, path)
|
|
2546
|
+
: this.b("DictTryGet", [K, V0], [e, k], OptionType(V0), path))
|
|
2547
|
+
: this.null(path);
|
|
2548
|
+
const next = f($, old);
|
|
2549
|
+
if (next === undefined) {
|
|
2550
|
+
if (t.type !== "Dict")
|
|
2551
|
+
return this.value(new Map(), DictType(K, NeverType));
|
|
2552
|
+
const out = inPlace ? e : this.declare($, this.b("DictCopy", [K, V0], [e], t, path), "dict");
|
|
2553
|
+
this.stmt($, this.b("DictTryDelete", [K, V0], [out, k], BooleanType, path));
|
|
2554
|
+
return out;
|
|
2555
|
+
}
|
|
2556
|
+
const nextType = this.updateType(next);
|
|
2557
|
+
const V = V0.type === "Never" ? nextType : unify(V0, nextType) ?? V0;
|
|
2558
|
+
const D = DictType(K, V);
|
|
2559
|
+
const out = inPlace && t.type === "Dict" && isTypeEqual(D, t) ? e
|
|
2560
|
+
: this.declare($, t.type === "Dict" ? this.b("DictCopy", [K, V], [this.widenTo($, e, D, path)], D, path) : this.value(new Map(), D), "dict");
|
|
2561
|
+
if (next.optional) {
|
|
2562
|
+
this.match($, next.value, {
|
|
2563
|
+
some: ($2, w) => this.put($2, out, k, w, path),
|
|
2564
|
+
none: $2 => this.stmt($2, this.b("DictTryDelete", [K, V], [out, k], BooleanType, path)),
|
|
2565
|
+
}, path);
|
|
2566
|
+
}
|
|
2567
|
+
else {
|
|
2568
|
+
this.put($, out, k, next.value, path);
|
|
2569
|
+
}
|
|
2570
|
+
return out;
|
|
2571
|
+
}
|
|
2572
|
+
if (optional)
|
|
2573
|
+
return v;
|
|
2574
|
+
throw this.gap(`.[k] = … on ${printType(t)}`);
|
|
2575
|
+
}
|
|
2576
|
+
/**
|
|
2577
|
+
* An array with a slice replaced by the array the update gives, or deleted
|
|
2578
|
+
* where it gives none; `null`'s slice is null, so the update's array is
|
|
2579
|
+
* the value. With `?`, a value of another kind is unchanged.
|
|
2580
|
+
*/
|
|
2581
|
+
setSlice($, v, a, b, optional, path, f) {
|
|
2582
|
+
const e = this.open(v);
|
|
2583
|
+
const t = this.type(e);
|
|
2584
|
+
if (t.type === "Null") {
|
|
2585
|
+
const next = f($, this.null(path));
|
|
2586
|
+
return next === undefined ? e : next.value;
|
|
2587
|
+
}
|
|
2588
|
+
if (t.type === "Array") {
|
|
2589
|
+
const element = t.value;
|
|
2590
|
+
const source = this.bind($, e, "array");
|
|
2591
|
+
const length = this.declare($, this.size(source, path), "length", false);
|
|
2592
|
+
const start = this.boundOf($, a, this.int(0), length, path);
|
|
2593
|
+
const end = this.boundOf($, b, length, length, path);
|
|
2594
|
+
this.ifElse($, this.lt(end, start, path), $2 => this.assign($2, end, start), undefined, path);
|
|
2595
|
+
const before = this.bind($, this.b("ArraySlice", [element], [source, this.int(0), start], t, path), "before");
|
|
2596
|
+
const after = this.bind($, this.b("ArraySlice", [element], [source, end, length], t, path), "after");
|
|
2597
|
+
const next = f($, this.b("ArraySlice", [element], [source, start, end], t, path));
|
|
2598
|
+
const removed = () => this.b("ArrayConcat", [element], [before, after], t, path);
|
|
2599
|
+
if (next === undefined)
|
|
2600
|
+
return removed();
|
|
2601
|
+
const E = unify(element, parts(this.updateType(next)).value) ?? element;
|
|
2602
|
+
const A = ArrayType(E);
|
|
2603
|
+
const spliced = ($2, middle) => this.b("ArrayConcat", [E], [
|
|
2604
|
+
this.b("ArrayConcat", [E], [this.widenTo($2, before, A, path), this.widenTo($2, middle, A, path)], A, path),
|
|
2605
|
+
this.widenTo($2, after, A, path),
|
|
2606
|
+
], A, path);
|
|
2607
|
+
if (!next.optional)
|
|
2608
|
+
return this.bind($, spliced($, next.value), "array");
|
|
2609
|
+
return this.bind($, this.matchValue(next.value, { some: ($2, middle) => spliced($2, middle), none: $2 => this.widenTo($2, removed(), A, path) }, A, path), "array");
|
|
2610
|
+
}
|
|
2611
|
+
if (optional)
|
|
2612
|
+
return v;
|
|
2613
|
+
throw this.gap(`.[a:b] = … on ${printType(t)}`);
|
|
2614
|
+
}
|
|
2615
|
+
/**
|
|
2616
|
+
* Every element, dict value or struct field replaced; an element or value
|
|
2617
|
+
* with no new value is deleted, and a field whose update never gives one.
|
|
2618
|
+
* With `?`, a value of another kind is unchanged.
|
|
2619
|
+
*/
|
|
2620
|
+
setEach($, v, optional, path, f) {
|
|
2621
|
+
const e = this.open(v);
|
|
2622
|
+
const t = this.type(e);
|
|
2623
|
+
if (t.type === "Array" || t.type === "Dict") {
|
|
2624
|
+
let element;
|
|
2625
|
+
const each = ($2, item, insert) => {
|
|
2626
|
+
const next = f($2, item);
|
|
2627
|
+
if (next === undefined)
|
|
2628
|
+
return;
|
|
2629
|
+
if (next.optional)
|
|
2630
|
+
this.match($2, next.value, { some: insert }, path);
|
|
2631
|
+
else
|
|
2632
|
+
insert($2, next.value);
|
|
2633
|
+
};
|
|
2634
|
+
if (t.type === "Array") {
|
|
2635
|
+
let out;
|
|
2636
|
+
const outDecl = this.block();
|
|
2637
|
+
this.forEach($, e, ($2, item) => each($2, item, ($3, w) => {
|
|
2638
|
+
element ??= this.type(w);
|
|
2639
|
+
out ??= this.declare(outDecl, this.emptyArray(element), "array");
|
|
2640
|
+
this.push($3, out, w, path);
|
|
2641
|
+
}), path);
|
|
2642
|
+
// The array is declared before the loop, once the element type is known from the update.
|
|
2643
|
+
$.statements.splice($.statements.length - 1, 0, ...outDecl.statements);
|
|
2644
|
+
// An update that never gives a value deletes every element.
|
|
2645
|
+
return out ?? this.value([], ArrayType(NeverType));
|
|
2646
|
+
}
|
|
2647
|
+
let out;
|
|
2648
|
+
const outDecl = this.block();
|
|
2649
|
+
this.forEach($, e, ($2, item, key) => each($2, item, ($3, w) => {
|
|
2650
|
+
element ??= this.type(w);
|
|
2651
|
+
const D = DictType(t.key, element);
|
|
2652
|
+
out ??= this.declare(outDecl, this.value(new Map(), D), "dict");
|
|
2653
|
+
this.stmt($3, this.b("DictInsert", [t.key, element], [out, key, this.widenTo($3, w, element, path)], NullType, path));
|
|
2654
|
+
}), path);
|
|
2655
|
+
$.statements.splice($.statements.length - 1, 0, ...outDecl.statements);
|
|
2656
|
+
return out ?? this.value(new Map(), DictType(t.key, NeverType));
|
|
2657
|
+
}
|
|
2658
|
+
if (t.type === "Struct") {
|
|
2659
|
+
const s = this.bind($, e, "struct");
|
|
2660
|
+
const values = {};
|
|
2661
|
+
const types = {};
|
|
2662
|
+
for (const name of Object.keys(t.fields)) {
|
|
2663
|
+
const next = f($, this.field(s, name));
|
|
2664
|
+
if (next === undefined)
|
|
2665
|
+
continue;
|
|
2666
|
+
values[name] = this.required($, next, path);
|
|
2667
|
+
types[name] = this.type(values[name]);
|
|
2668
|
+
}
|
|
2669
|
+
return this.rebuilt(v, types, values);
|
|
2670
|
+
}
|
|
2671
|
+
if (optional)
|
|
2672
|
+
return v;
|
|
2673
|
+
throw this.gap(`.[] |= … on ${printType(t)}`);
|
|
2674
|
+
}
|
|
2675
|
+
/**
|
|
2676
|
+
* `..`, `recurse` or `recurse(.a[])` in an update's path: in pre-order,
|
|
2677
|
+
* each position the walk visits is updated by `f`, then the positions
|
|
2678
|
+
* inside it that it had before its update, in turn. A value of a recursive
|
|
2679
|
+
* type is walked by a function its own positions call, through a
|
|
2680
|
+
* reference, each declared before the walk.
|
|
2681
|
+
*/
|
|
2682
|
+
walkUpdate($, v, child, path, f) {
|
|
2683
|
+
const steps = child === undefined ? undefined : walkSteps(child);
|
|
2684
|
+
if (child !== undefined && steps === undefined)
|
|
2685
|
+
throw this.gap(`assigning through ${this.text(path)}`);
|
|
2686
|
+
const mark = $.statements.length;
|
|
2687
|
+
const declarations = this.block();
|
|
2688
|
+
const definitions = this.block();
|
|
2689
|
+
const cells = [];
|
|
2690
|
+
// A position: updated, then the positions inside it.
|
|
2691
|
+
const visit = ($2, current, old) => {
|
|
2692
|
+
const K = this.type(current);
|
|
2693
|
+
const next = f($2, current);
|
|
2694
|
+
if (next === undefined)
|
|
2695
|
+
return undefined;
|
|
2696
|
+
const inside = ($3, w) => steps === undefined
|
|
2697
|
+
? this.walkInside($3, this.widenTo($3, w, K, path), old, path, node)
|
|
2698
|
+
: this.walkAlong($3, this.widenTo($3, w, K, path), old, steps.fields, path, node);
|
|
2699
|
+
if (!next.optional)
|
|
2700
|
+
return { value: this.bind($2, inside($2, next.value), "walked"), optional: false };
|
|
2701
|
+
const option = OptionType(K);
|
|
2702
|
+
const value = this.matchValue(next.value, { none: () => this.none(option), some: ($3, w) => this.some(inside($3, w), option) }, option, path);
|
|
2703
|
+
return { value: this.bind($2, value, "walked"), optional: true };
|
|
2704
|
+
};
|
|
2705
|
+
// A recursive position, walked by its kind's function.
|
|
2706
|
+
const call = ($2, current, old) => {
|
|
2707
|
+
const R = this.type(current);
|
|
2708
|
+
const option = OptionType(R);
|
|
2709
|
+
let entry = cells.find(c => isTypeEqual(c.type, R));
|
|
2710
|
+
if (entry === undefined) {
|
|
2711
|
+
const fnType = FunctionType([R, R], option);
|
|
2712
|
+
const placeholder = this.lambda([R, R], option, [], () => this.none(option), path);
|
|
2713
|
+
const cell = this.declare(declarations, this.mk({ ast_type: "NewRef", type: RefType(fnType), loc_id: UNKNOWN_LOC_ID, value: this.ast(placeholder) }), "walk", false);
|
|
2714
|
+
entry = { type: R, cell, fnType };
|
|
2715
|
+
cells.push(entry);
|
|
2716
|
+
const fn = this.lambda([R, R], option, ["value", "old"], ($f, value, previous) => {
|
|
2717
|
+
const u = visit($f, value, previous);
|
|
2718
|
+
if (u === undefined)
|
|
2719
|
+
return this.none(option);
|
|
2720
|
+
return u.optional ? u.value : this.some(u.value, option);
|
|
2721
|
+
}, path);
|
|
2722
|
+
this.stmt(definitions, this.b("RefUpdate", [fnType], [cell, fn], NullType, path));
|
|
2723
|
+
}
|
|
2724
|
+
const fn = this.b("RefGet", [entry.fnType], [entry.cell], entry.fnType, path);
|
|
2725
|
+
return { value: this.bind($2, this.callFn(fn, [current, old], path), "walked"), optional: true };
|
|
2726
|
+
};
|
|
2727
|
+
const node = ($2, current, old) => this.type(current).type === "Recursive" ? call($2, current, old) : visit($2, current, old);
|
|
2728
|
+
const result = node($, v, v);
|
|
2729
|
+
// The functions, declared before the walk that calls them, and each given its body once every one is declared.
|
|
2730
|
+
$.statements.splice(mark, 0, ...declarations.statements, ...definitions.statements);
|
|
2731
|
+
return result;
|
|
2732
|
+
}
|
|
2733
|
+
/**
|
|
2734
|
+
* A value with the positions `..` finds inside it walked: an option's
|
|
2735
|
+
* value's positions, a variant's payload (its case's name is not a
|
|
2736
|
+
* position), and the elements, dict values and struct fields of
|
|
2737
|
+
* {@link Translator.walkElements}; only those `old`, the value before its
|
|
2738
|
+
* own update, had.
|
|
2739
|
+
*/
|
|
2740
|
+
walkInside($, w, old, path, node) {
|
|
2741
|
+
const e = this.open(w);
|
|
2742
|
+
const t = this.type(e);
|
|
2743
|
+
if (nullablePayload(t) !== undefined) {
|
|
2744
|
+
return this.matchValue(e, {
|
|
2745
|
+
none: () => e,
|
|
2746
|
+
some: ($2, p) => this.matchValue(this.open(old), {
|
|
2747
|
+
none: () => this.some(p, t),
|
|
2748
|
+
some: ($3, q) => this.some(this.walkInside($3, p, q, path, node), t),
|
|
2749
|
+
}, t, path),
|
|
2750
|
+
}, t, path);
|
|
2751
|
+
}
|
|
2752
|
+
if (t.type === "Variant") {
|
|
2753
|
+
const whole = this.type(w);
|
|
2754
|
+
const cases = {};
|
|
2755
|
+
for (const name of Object.keys(t.cases)) {
|
|
2756
|
+
cases[name] = ($2, p) => {
|
|
2757
|
+
// The payload is walked where the value had this case before.
|
|
2758
|
+
const before = {};
|
|
2759
|
+
for (const other of Object.keys(t.cases)) {
|
|
2760
|
+
before[other] = other === name ? ($3, q) => this.kept($3, node($3, p, q), this.type(p), path) : () => p;
|
|
2761
|
+
}
|
|
2762
|
+
return this.variantOf(whole, name, this.matchValue(this.open(old), before, this.type(p), path));
|
|
2763
|
+
};
|
|
2764
|
+
}
|
|
2765
|
+
return this.matchValue(e, cases, whole, path);
|
|
2766
|
+
}
|
|
2767
|
+
return this.walkElements($, w, old, path, node);
|
|
2768
|
+
}
|
|
2769
|
+
/**
|
|
2770
|
+
* `recurse(.a.b[])`'s positions inside a value, walked: along the fields,
|
|
2771
|
+
* then each element there that `old` had.
|
|
2772
|
+
*/
|
|
2773
|
+
walkAlong($, w, old, fields, path, node) {
|
|
2774
|
+
if (fields.length === 0)
|
|
2775
|
+
return this.walkElements($, w, old, path, node);
|
|
2776
|
+
const e = this.open(w);
|
|
2777
|
+
const t = this.type(e);
|
|
2778
|
+
if (t.type !== "Struct")
|
|
2779
|
+
throw this.gap(`recurse(.${fields.join(".")}[]) on ${printType(t)}`);
|
|
2780
|
+
const s = this.bind($, e, "struct");
|
|
2781
|
+
const o = this.bind($, this.open(old), "old");
|
|
2782
|
+
const values = {};
|
|
2783
|
+
const types = {};
|
|
2784
|
+
for (const name of Object.keys(t.fields)) {
|
|
2785
|
+
values[name] = name === fields[0] ? this.bind($, this.walkAlong($, this.field(s, name), this.field(o, name), fields.slice(1), path, node), name) : this.field(s, name);
|
|
2786
|
+
types[name] = this.type(values[name]);
|
|
2787
|
+
}
|
|
2788
|
+
return this.rebuilt(w, types, values);
|
|
2789
|
+
}
|
|
2790
|
+
/**
|
|
2791
|
+
* The positions `.[]` names inside a value, each walked by `node`: an
|
|
2792
|
+
* array's elements, a set's, a dict's values, a struct's fields, and those
|
|
2793
|
+
* of an option's value; only those `old` had. An element or dict value the
|
|
2794
|
+
* walk gives no value is deleted.
|
|
2795
|
+
*/
|
|
2796
|
+
walkElements($, w, old, path, node) {
|
|
2797
|
+
const e = this.open(w);
|
|
2798
|
+
const o = this.open(old);
|
|
2799
|
+
const t = this.type(e);
|
|
2800
|
+
// An element the walk updated, or none to drop it.
|
|
2801
|
+
const each = ($2, update, insert) => {
|
|
2802
|
+
if (update === undefined)
|
|
2803
|
+
return;
|
|
2804
|
+
if (update.optional)
|
|
2805
|
+
this.match($2, update.value, { some: insert }, path);
|
|
2806
|
+
else
|
|
2807
|
+
insert($2, update.value);
|
|
2808
|
+
};
|
|
2809
|
+
if (nullablePayload(t) !== undefined) {
|
|
2810
|
+
return this.matchValue(e, {
|
|
2811
|
+
none: () => e,
|
|
2812
|
+
some: ($2, p) => this.matchValue(o, {
|
|
2813
|
+
none: () => this.some(p, t),
|
|
2814
|
+
some: ($3, q) => this.some(this.walkElements($3, p, q, path, node), t),
|
|
2815
|
+
}, t, path),
|
|
2816
|
+
}, t, path);
|
|
2817
|
+
}
|
|
2818
|
+
switch (t.type) {
|
|
2819
|
+
case "Array": {
|
|
2820
|
+
const E = t.value;
|
|
2821
|
+
const out = this.declare($, this.emptyArray(E), "array");
|
|
2822
|
+
const count = this.declare($, this.size(o, path), "count", false);
|
|
2823
|
+
this.forEach($, e, ($2, item, index) => this.ifElse($2, this.lt(index, count, path), $3 => each($3, node($3, item, this.b("ArrayGet", [E], [o, index], E, path)), ($4, value) => this.push($4, out, value, path)), $3 => this.push($3, out, item, path), path), path);
|
|
2824
|
+
return out;
|
|
2825
|
+
}
|
|
2826
|
+
case "Set": {
|
|
2827
|
+
const K = t.key;
|
|
2828
|
+
const out = this.declare($, this.value(new Set(), t), "set");
|
|
2829
|
+
this.forEach($, e, ($2, item) => this.ifElse($2, this.b("SetHas", [K], [o, item], BooleanType, path), $3 => each($3, node($3, item, item), ($4, value) => this.stmt($4, this.b("SetTryInsert", [K], [out, value], BooleanType, path))), $3 => this.stmt($3, this.b("SetTryInsert", [K], [out, item], BooleanType, path)), path), path, "key");
|
|
2830
|
+
return out;
|
|
2831
|
+
}
|
|
2832
|
+
case "Dict": {
|
|
2833
|
+
const K = t.key;
|
|
2834
|
+
const V = t.value;
|
|
2835
|
+
const out = this.declare($, this.value(new Map(), t), "dict");
|
|
2836
|
+
const insert = ($2, key, value) => this.stmt($2, this.b("DictInsert", [K, V], [out, key, this.widenTo($2, value, V, path)], NullType, path));
|
|
2837
|
+
this.forEach($, e, ($2, item, key) => this.match($2, this.b("DictTryGet", [K, V], [o, key], OptionType(V), path), {
|
|
2838
|
+
none: $3 => insert($3, key, item),
|
|
2839
|
+
some: ($3, previous) => each($3, node($3, item, previous), ($4, value) => insert($4, key, value)),
|
|
2840
|
+
}, path), path);
|
|
2841
|
+
return out;
|
|
2842
|
+
}
|
|
2843
|
+
case "Struct": {
|
|
2844
|
+
const s = this.bind($, e, "struct");
|
|
2845
|
+
const before = this.bind($, o, "old");
|
|
2846
|
+
const values = {};
|
|
2847
|
+
const types = {};
|
|
2848
|
+
for (const [name, fieldType] of Object.entries(t.fields)) {
|
|
2849
|
+
values[name] = this.bind($, this.kept($, node($, this.field(s, name), this.field(before, name)), fieldType, path), name);
|
|
2850
|
+
types[name] = this.type(values[name]);
|
|
2851
|
+
}
|
|
2852
|
+
return this.rebuilt(w, types, values);
|
|
2853
|
+
}
|
|
2854
|
+
default:
|
|
2855
|
+
return w;
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
/** East's float library functions, as the translation embeds them. */
|
|
2860
|
+
const libraryFunctions = new Map();
|
|
2861
|
+
/**
|
|
2862
|
+
* A float library function, its locations dropped: they are ids in the
|
|
2863
|
+
* library's own source map, made when it loaded, and would name nothing in a
|
|
2864
|
+
* translation's, nor encode to the same bytes in every process.
|
|
2865
|
+
*/
|
|
2866
|
+
function libraryFunction(name) {
|
|
2867
|
+
let fn = libraryFunctions.get(name);
|
|
2868
|
+
if (fn === undefined) {
|
|
2869
|
+
fn = fromAst(withoutLocations(FloatLib[name][AstSymbol]));
|
|
2870
|
+
libraryFunctions.set(name, fn);
|
|
2871
|
+
}
|
|
2872
|
+
return fn;
|
|
2873
|
+
}
|
|
2874
|
+
/** A copy of an AST with every location unknown; a node shared in the original is shared in the copy. */
|
|
2875
|
+
function withoutLocations(ast) {
|
|
2876
|
+
const copies = new Map();
|
|
2877
|
+
const copy = (node) => {
|
|
2878
|
+
if (Array.isArray(node)) {
|
|
2879
|
+
const known = copies.get(node);
|
|
2880
|
+
if (known !== undefined)
|
|
2881
|
+
return known;
|
|
2882
|
+
const out = [];
|
|
2883
|
+
copies.set(node, out);
|
|
2884
|
+
for (const item of node)
|
|
2885
|
+
out.push(copy(item));
|
|
2886
|
+
return out;
|
|
2887
|
+
}
|
|
2888
|
+
if (node === null || typeof node !== "object" || isVariant(node))
|
|
2889
|
+
return node;
|
|
2890
|
+
const known = copies.get(node);
|
|
2891
|
+
if (known !== undefined)
|
|
2892
|
+
return known;
|
|
2893
|
+
const out = {};
|
|
2894
|
+
copies.set(node, out);
|
|
2895
|
+
const value = node.ast_type === "Value";
|
|
2896
|
+
for (const [key, item] of Object.entries(node)) {
|
|
2897
|
+
// Types, and a Value node's value, are data, not tree.
|
|
2898
|
+
if (key === "type" || key === "type_parameters" || (value && key === "value"))
|
|
2899
|
+
out[key] = item;
|
|
2900
|
+
else if (key === "loc_id" && typeof item === "bigint")
|
|
2901
|
+
out[key] = UNKNOWN_LOC_ID;
|
|
2902
|
+
else
|
|
2903
|
+
out[key] = copy(item);
|
|
2904
|
+
}
|
|
2905
|
+
return out;
|
|
2906
|
+
};
|
|
2907
|
+
return copy(ast);
|
|
2908
|
+
}
|
|
2909
|
+
/**
|
|
2910
|
+
* Whether a type holds Never below its top: an empty collection's element
|
|
2911
|
+
* type, an option that is always none. East's casts do not take such a type.
|
|
2912
|
+
*/
|
|
2913
|
+
function holdsNever(type) {
|
|
2914
|
+
const seen = new Set();
|
|
2915
|
+
const visit = (t, top) => {
|
|
2916
|
+
if (t.type === "Never")
|
|
2917
|
+
return !top;
|
|
2918
|
+
if (seen.has(t))
|
|
2919
|
+
return false;
|
|
2920
|
+
seen.add(t);
|
|
2921
|
+
switch (t.type) {
|
|
2922
|
+
case "Array":
|
|
2923
|
+
case "Ref": return visit(t.value, false);
|
|
2924
|
+
case "Set": return visit(t.key, false);
|
|
2925
|
+
case "Dict": return visit(t.key, false) || visit(t.value, false);
|
|
2926
|
+
case "Vector":
|
|
2927
|
+
case "Matrix": return visit(t.element, false);
|
|
2928
|
+
case "Struct": return Object.values(t.fields).some(f => visit(f, false));
|
|
2929
|
+
case "Variant": return Object.values(t.cases).some(c => visit(c, false));
|
|
2930
|
+
case "Recursive": return visit(t.node, false);
|
|
2931
|
+
default: return false;
|
|
2932
|
+
}
|
|
2933
|
+
};
|
|
2934
|
+
return visit(type, true);
|
|
2935
|
+
}
|
|
2936
|
+
/** The error an update raises where it gives no value for a position that cannot be deleted. */
|
|
2937
|
+
const UNDELETABLE = "an update gave no value for a struct field, which cannot be deleted";
|
|
2938
|
+
/**
|
|
2939
|
+
* Whether `*` merges values of two types deeply, as jq merges objects: two
|
|
2940
|
+
* structs, two dicts, or options of one struct or dict type (merged where
|
|
2941
|
+
* both hold one).
|
|
2942
|
+
*/
|
|
2943
|
+
function mergesDeeply(a, b) {
|
|
2944
|
+
const ua = unwrap(a);
|
|
2945
|
+
const ub = unwrap(b);
|
|
2946
|
+
if (ua.type === "Struct" && ub.type === "Struct")
|
|
2947
|
+
return true;
|
|
2948
|
+
if (ua.type === "Dict" && ub.type === "Dict")
|
|
2949
|
+
return true;
|
|
2950
|
+
const payload = nullablePayload(ua);
|
|
2951
|
+
return payload !== undefined && isTypeEqual(ua, ub) && ["Struct", "Dict"].includes(unwrap(payload).type);
|
|
2952
|
+
}
|
|
2953
|
+
/** Whether values of type `b` are values of type `a`: equal types, or `a` recursive with `b` its node. */
|
|
2954
|
+
function sameType(a, b) {
|
|
2955
|
+
return isTypeEqual(a, b) || (a.type === "Recursive" && isTypeEqual(a.node, b));
|
|
2956
|
+
}
|
|
2957
|
+
/** Whether a value of a type is one a node's records were made for. */
|
|
2958
|
+
function fits(type, input) {
|
|
2959
|
+
if (input.kind === "type")
|
|
2960
|
+
return isTypeEqual(type, input.type);
|
|
2961
|
+
if (input.kind === "union")
|
|
2962
|
+
return input.members.some(m => isTypeEqual(type, m.shape.type));
|
|
2963
|
+
return false;
|
|
2964
|
+
}
|
|
2965
|
+
/** How jq names a value's kind in its error messages. @internal */
|
|
2966
|
+
export function jqKind(type) {
|
|
2967
|
+
const t = unwrap(type);
|
|
2968
|
+
if (nullablePayload(t) !== undefined)
|
|
2969
|
+
return "null";
|
|
2970
|
+
switch (t.type) {
|
|
2971
|
+
case "Null": return "null";
|
|
2972
|
+
case "Boolean": return "boolean";
|
|
2973
|
+
case "Integer":
|
|
2974
|
+
case "Float": return "number";
|
|
2975
|
+
case "String": return "string";
|
|
2976
|
+
case "Array":
|
|
2977
|
+
case "Set":
|
|
2978
|
+
case "Vector":
|
|
2979
|
+
case "Matrix": return "array";
|
|
2980
|
+
case "DateTime": return "datetime";
|
|
2981
|
+
case "Blob": return "blob";
|
|
2982
|
+
case "Function":
|
|
2983
|
+
case "AsyncFunction": return "function";
|
|
2984
|
+
default: return "object";
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
/** A kind's place in jq's order across kinds. */
|
|
2988
|
+
function jqRank(t) {
|
|
2989
|
+
switch (t.type) {
|
|
2990
|
+
case "Null": return 0;
|
|
2991
|
+
case "Boolean": return 1;
|
|
2992
|
+
case "Integer":
|
|
2993
|
+
case "Float": return 2;
|
|
2994
|
+
case "String": return 3;
|
|
2995
|
+
case "Array":
|
|
2996
|
+
case "Set":
|
|
2997
|
+
case "Vector":
|
|
2998
|
+
case "Matrix": return 4;
|
|
2999
|
+
case "DateTime": return 6;
|
|
3000
|
+
case "Blob": return 7;
|
|
3001
|
+
default: return 5;
|
|
3002
|
+
}
|
|
3003
|
+
}
|
|
3004
|
+
/** The comparison operators. */
|
|
3005
|
+
const COMPARISONS = new Set(["==", "!=", "<", "<=", ">", ">="]);
|
|
3006
|
+
const COMPARISON_BUILTINS = {
|
|
3007
|
+
"==": "Equal", "!=": "NotEqual", "<": "Less", "<=": "LessEqual", ">": "Greater", ">=": "GreaterEqual",
|
|
3008
|
+
};
|
|
3009
|
+
/** Each comparison's truth for an order of its operands: -1, 0 or 1. */
|
|
3010
|
+
const ORDERINGS = {
|
|
3011
|
+
"==": o => o === 0, "!=": o => o !== 0, "<": o => o < 0, "<=": o => o <= 0, ">": o => o > 0, ">=": o => o >= 0,
|
|
3012
|
+
};
|
|
3013
|
+
const ARITHMETIC_BUILTINS = { "+": "Add", "-": "Subtract", "*": "Multiply" };
|
|
3014
|
+
/** The variables a pattern binds. */
|
|
3015
|
+
function patternNames(pattern) {
|
|
3016
|
+
switch (pattern.type) {
|
|
3017
|
+
case "variable": return [pattern.value];
|
|
3018
|
+
case "array": return pattern.value.flatMap(patternNames);
|
|
3019
|
+
case "object":
|
|
3020
|
+
return pattern.value
|
|
3021
|
+
.flatMap(entry => entry.value.type === "none" ? [entry.key] : patternNames(entry.value.value));
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
/** Whether a node calls a def by `name/arity`, outside any def of the same name inside it. */
|
|
3025
|
+
function callsItself(node, key) {
|
|
3026
|
+
if (node.type === "call" && `${node.value.name}/${node.value.args.length}` === key)
|
|
3027
|
+
return true;
|
|
3028
|
+
if (node.type === "def" && `${node.value.name}/${node.value.params.length}` === key)
|
|
3029
|
+
return callsItself(node.value.rest, key);
|
|
3030
|
+
return jqChildren(node).some(child => child.node !== undefined && callsItself(child.node, key));
|
|
3031
|
+
}
|
|
3032
|
+
// ─── Updates in place (#1093) ─────────────────────────────────────────────
|
|
3033
|
+
/**
|
|
3034
|
+
* Whether an update's path sets a key of its input: its first step on `.` is
|
|
3035
|
+
* a field read or an index, whatever steps follow (`.[k]`, `.name`,
|
|
3036
|
+
* `.[k].lines[]`).
|
|
3037
|
+
*/
|
|
3038
|
+
function setsKeyOfInput(path) {
|
|
3039
|
+
switch (path.type) {
|
|
3040
|
+
case "field":
|
|
3041
|
+
case "index": return path.value.target.type === "identity" || setsKeyOfInput(path.value.target);
|
|
3042
|
+
case "slice":
|
|
3043
|
+
case "iterate": return setsKeyOfInput(path.value.target);
|
|
3044
|
+
case "pipe": return setsKeyOfInput(path.value.left);
|
|
3045
|
+
default: return false;
|
|
3046
|
+
}
|
|
3047
|
+
}
|
|
3048
|
+
const HELD_ORDER = { nothing: 0, itself: 1, anything: 2 };
|
|
3049
|
+
/** The more of the state either of two values may hold. */
|
|
3050
|
+
function most(a, b) {
|
|
3051
|
+
return HELD_ORDER[a] >= HELD_ORDER[b] ? a : b;
|
|
3052
|
+
}
|
|
3053
|
+
/** What a value read from another may hold: nothing, unless the other may hold the state inside it. */
|
|
3054
|
+
function readFrom(from) {
|
|
3055
|
+
return from === "anything" ? "anything" : "nothing";
|
|
3056
|
+
}
|
|
3057
|
+
/** What a new value holding values that may hold `held` may hold: the state inside it, where they hold any of it. */
|
|
3058
|
+
function holding(held) {
|
|
3059
|
+
return held === "nothing" ? "nothing" : "anything";
|
|
3060
|
+
}
|
|
3061
|
+
/** Builtins whose outputs hold nothing of what they are given: numbers, strings, booleans, a dict's keys, or no output. */
|
|
3062
|
+
const SCALAR_BUILTINS = new Set([
|
|
3063
|
+
"empty", "error", "not", "isempty", "range", "IN", "length", "utf8bytelength", "keys", "keys_unsorted", "has", "in",
|
|
3064
|
+
"any", "all", "contains", "inside", "index", "rindex", "indices", "bsearch", "join", "type", "tostring", "tojson",
|
|
3065
|
+
"tonumber", "toboolean", "builtins", "infinite", "nan", "isfinite", "isinfinite", "isnan", "isnormal", "startswith",
|
|
3066
|
+
"endswith", "ltrimstr", "rtrimstr", "trimstr", "trim", "ltrim", "rtrim", "ascii_downcase", "ascii_upcase", "split",
|
|
3067
|
+
"test", "sub", "gsub", "format", "@text", "@json", "@html", "@uri", "@base64", "@csv", "@tsv", "@sh", "floor", "ceil",
|
|
3068
|
+
"round", "trunc", "sqrt", "log", "log2", "log10", "exp", "exp2", "exp10", "sin", "cos", "tan", "fabs", "pow", "fmin",
|
|
3069
|
+
"fmax", "fmod", "todate", "todateiso8601", "fromdate", "fromdateiso8601", "strftime", "strptime", "year", "month",
|
|
3070
|
+
"day", "hour", "minute", "second", "millisecond", "weekday", "epoch_ms", "datetime_add", "datetime_diff",
|
|
3071
|
+
"signature", "source", "calls", "captures",
|
|
3072
|
+
]);
|
|
3073
|
+
/** Builtins whose outputs are their input's parts, or new values made of them, whatever their arguments give. */
|
|
3074
|
+
const PART_BUILTINS = new Set([
|
|
3075
|
+
"to_entries", "from_entries", "pick", "transpose", "flatten", "combinations", "sort", "sort_by", "group_by", "unique",
|
|
3076
|
+
"unique_by", "min", "max", "min_by", "max_by", "reverse",
|
|
3077
|
+
]);
|
|
3078
|
+
/** Builtins whose outputs are their input itself, or nothing of it. */
|
|
3079
|
+
const SELECTING_BUILTINS = new Set([
|
|
3080
|
+
"select", "arrays", "objects", "iterables", "booleans", "numbers", "strings", "nulls", "values", "scalars", "normals",
|
|
3081
|
+
"finites", "abs",
|
|
3082
|
+
]);
|
|
3083
|
+
/**
|
|
3084
|
+
* Builtins whose outputs hold what they are given otherwise, by
|
|
3085
|
+
* `name/arity`: `"parts"`, as {@link PART_BUILTINS}'; `"input"`, as
|
|
3086
|
+
* {@link SELECTING_BUILTINS}'; `"last"`, their last argument's outputs;
|
|
3087
|
+
* `"each"`, each of their input's parts through their argument, in a new
|
|
3088
|
+
* value; `"first"`, their first argument's outputs, in a new value.
|
|
3089
|
+
*/
|
|
3090
|
+
const BUILTINS_BY_ARITY = {
|
|
3091
|
+
"first/0": "parts", "last/0": "parts", "nth/1": "parts", "add/0": "parts", "INDEX/1": "parts",
|
|
3092
|
+
"recurse/0": "input",
|
|
3093
|
+
"first/1": "last", "last/1": "last", "nth/2": "last", "limit/2": "last", "skip/2": "last",
|
|
3094
|
+
"map/1": "each", "map_values/1": "each", "with_entries/1": "each",
|
|
3095
|
+
"add/1": "first", "INDEX/2": "first",
|
|
3096
|
+
};
|
|
3097
|
+
/**
|
|
3098
|
+
* How a builtin's outputs hold what it is given, by its name and arity:
|
|
3099
|
+
* `"scalar"`, nothing of it; `"parts"`, its input's parts, or new values made
|
|
3100
|
+
* of them (`sort`, `to_entries`); `"input"`, its input itself, or nothing of
|
|
3101
|
+
* it (`select`, `numbers`); `"last"`, its last argument's outputs
|
|
3102
|
+
* (`first(f)`, `limit(n; f)`); `"each"`, each of its input's parts through
|
|
3103
|
+
* its argument, in a new value (`map(f)`); `"first"`, its first argument's
|
|
3104
|
+
* outputs, in a new value (`add(f)`, `INDEX(src; f)`); or `undefined`, any of
|
|
3105
|
+
* it.
|
|
3106
|
+
*/
|
|
3107
|
+
function builtinHolds(name, arity) {
|
|
3108
|
+
const byArity = BUILTINS_BY_ARITY[`${name}/${arity}`];
|
|
3109
|
+
if (byArity !== undefined)
|
|
3110
|
+
return byArity;
|
|
3111
|
+
if (SCALAR_BUILTINS.has(name))
|
|
3112
|
+
return "scalar";
|
|
3113
|
+
if (PART_BUILTINS.has(name))
|
|
3114
|
+
return "parts";
|
|
3115
|
+
if (SELECTING_BUILTINS.has(name))
|
|
3116
|
+
return "input";
|
|
3117
|
+
return undefined;
|
|
3118
|
+
}
|
|
3119
|
+
/** What each variable a pattern binds holds of the state, the pattern taking a value that holds `source`: the value itself, or its parts. */
|
|
3120
|
+
function patternHolds(pattern, source, into) {
|
|
3121
|
+
switch (pattern.type) {
|
|
3122
|
+
case "variable":
|
|
3123
|
+
into.set(pattern.value, source);
|
|
3124
|
+
return;
|
|
3125
|
+
case "array":
|
|
3126
|
+
for (const item of pattern.value)
|
|
3127
|
+
patternHolds(item, readFrom(source), into);
|
|
3128
|
+
return;
|
|
3129
|
+
case "object":
|
|
3130
|
+
for (const entry of pattern.value) {
|
|
3131
|
+
if (entry.value.type === "none")
|
|
3132
|
+
into.set(entry.key, readFrom(source));
|
|
3133
|
+
else
|
|
3134
|
+
patternHolds(entry.value.value, readFrom(source), into);
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
3138
|
+
/**
|
|
3139
|
+
* What of a fold's state a filter's outputs may hold, given what its input
|
|
3140
|
+
* may hold: how an update that sets a key of the state in place knows its
|
|
3141
|
+
* value never holds the state. It reads the filter as written, and is
|
|
3142
|
+
* conservative: what it cannot follow, a `def` written inside the filter, or
|
|
3143
|
+
* a def or builtin it does not know given a value that may hold the state,
|
|
3144
|
+
* holds anything.
|
|
3145
|
+
*
|
|
3146
|
+
* @param node - the filter
|
|
3147
|
+
* @param input - what its input may hold of the state
|
|
3148
|
+
* @param vars - what the variables bound inside the filter hold of the
|
|
3149
|
+
* state; any other was bound before the state was made, and holds nothing
|
|
3150
|
+
* of it
|
|
3151
|
+
* @param defs - the defs and filter parameters in scope, by `name/arity`,
|
|
3152
|
+
* which a call names before a builtin of the same name
|
|
3153
|
+
* @returns what its outputs may hold of the state
|
|
3154
|
+
*/
|
|
3155
|
+
function stateHeldBy(node, input, vars, defs) {
|
|
3156
|
+
const held = (n, from = input, scope = vars) => stateHeldBy(n, from, scope, defs);
|
|
3157
|
+
switch (node.type) {
|
|
3158
|
+
case "identity":
|
|
3159
|
+
case "descend":
|
|
3160
|
+
return input;
|
|
3161
|
+
case "literal":
|
|
3162
|
+
case "string":
|
|
3163
|
+
case "format":
|
|
3164
|
+
case "negate":
|
|
3165
|
+
case "break":
|
|
3166
|
+
return "nothing";
|
|
3167
|
+
case "variable":
|
|
3168
|
+
return vars.get(node.value) ?? "nothing";
|
|
3169
|
+
case "field":
|
|
3170
|
+
case "index":
|
|
3171
|
+
case "slice":
|
|
3172
|
+
case "iterate":
|
|
3173
|
+
// An index's key and a slice's bounds are only read: what is read is part of the target.
|
|
3174
|
+
return readFrom(held(node.value.target));
|
|
3175
|
+
case "pipe":
|
|
3176
|
+
return held(node.value.right, held(node.value.left));
|
|
3177
|
+
case "comma":
|
|
3178
|
+
case "alternative":
|
|
3179
|
+
return most(held(node.value.left), held(node.value.right));
|
|
3180
|
+
case "binary": {
|
|
3181
|
+
// A comparison or a logical operator gives a boolean; arithmetic, an operand as it is or a new value of their parts.
|
|
3182
|
+
const op = node.value.op;
|
|
3183
|
+
return op === "and" || op === "or" || COMPARISONS.has(op) ? "nothing" : most(held(node.value.left), held(node.value.right));
|
|
3184
|
+
}
|
|
3185
|
+
case "array":
|
|
3186
|
+
return node.value.type === "none" ? "nothing" : holding(held(node.value.value));
|
|
3187
|
+
case "object": {
|
|
3188
|
+
let out = "nothing";
|
|
3189
|
+
for (const entry of node.value) {
|
|
3190
|
+
// `{$v}` holds the variable; `{name}` and `{"\(k)"}`, the input's value at the key.
|
|
3191
|
+
const value = entry.value.type === "some" ? held(entry.value.value)
|
|
3192
|
+
: entry.key.type === "variable" ? vars.get(entry.key.value) ?? "nothing"
|
|
3193
|
+
: readFrom(input);
|
|
3194
|
+
out = most(out, holding(value));
|
|
3195
|
+
}
|
|
3196
|
+
return out;
|
|
3197
|
+
}
|
|
3198
|
+
case "if": {
|
|
3199
|
+
let out = node.value.otherwise.type === "some" ? held(node.value.otherwise.value) : input;
|
|
3200
|
+
for (const branch of node.value.branches)
|
|
3201
|
+
out = most(out, held(branch.then));
|
|
3202
|
+
return out;
|
|
3203
|
+
}
|
|
3204
|
+
case "try":
|
|
3205
|
+
// The handler runs on the error's message, a string.
|
|
3206
|
+
return node.value.catch.type === "some" ? most(held(node.value.body), held(node.value.catch.value, "nothing")) : held(node.value.body);
|
|
3207
|
+
case "label":
|
|
3208
|
+
return held(node.value.body);
|
|
3209
|
+
case "bind": {
|
|
3210
|
+
// A name holds what the pattern that binds it gives it; another pattern binds it null.
|
|
3211
|
+
const source = held(node.value.source);
|
|
3212
|
+
const bound = new Map();
|
|
3213
|
+
for (const pattern of node.value.patterns) {
|
|
3214
|
+
const one = new Map();
|
|
3215
|
+
patternHolds(pattern, source, one);
|
|
3216
|
+
for (const [name, h] of one)
|
|
3217
|
+
bound.set(name, most(bound.get(name) ?? "nothing", h));
|
|
3218
|
+
}
|
|
3219
|
+
return held(node.value.body, input, new Map([...vars, ...bound]));
|
|
3220
|
+
}
|
|
3221
|
+
case "reduce":
|
|
3222
|
+
case "foreach": {
|
|
3223
|
+
const scope = new Map(vars);
|
|
3224
|
+
patternHolds(node.value.pattern, held(node.value.source), scope);
|
|
3225
|
+
// The fold's own state: what `init` gives, then what its update gives on it, until that settles.
|
|
3226
|
+
let state = held(node.value.init);
|
|
3227
|
+
for (;;) {
|
|
3228
|
+
const next = most(state, held(node.value.update, state, scope));
|
|
3229
|
+
if (next === state)
|
|
3230
|
+
break;
|
|
3231
|
+
state = next;
|
|
3232
|
+
}
|
|
3233
|
+
if (node.type === "reduce")
|
|
3234
|
+
return state;
|
|
3235
|
+
const extract = node.value.extract;
|
|
3236
|
+
return extract.type === "none" ? state : held(extract.value, state, scope);
|
|
3237
|
+
}
|
|
3238
|
+
case "call": {
|
|
3239
|
+
const { args, name } = node.value;
|
|
3240
|
+
switch (defs.has(`${name}/${args.length}`) ? undefined : builtinHolds(name, args.length)) {
|
|
3241
|
+
case "scalar": return "nothing";
|
|
3242
|
+
case "parts": return readFrom(input);
|
|
3243
|
+
case "input": return input;
|
|
3244
|
+
case "last": return held(args[args.length - 1]);
|
|
3245
|
+
case "each": return input === "anything" ? "anything" : holding(held(args[0], "nothing"));
|
|
3246
|
+
case "first": return holding(held(args[0]));
|
|
3247
|
+
default:
|
|
3248
|
+
// A def, a filter parameter or another builtin, given nothing of the state, gives nothing of it.
|
|
3249
|
+
return input === "nothing" && args.every(arg => held(arg, "nothing") === "nothing") ? "nothing" : "anything";
|
|
3250
|
+
}
|
|
3251
|
+
}
|
|
3252
|
+
case "update":
|
|
3253
|
+
// An update gives its input rebuilt, or as it is.
|
|
3254
|
+
return input === "nothing" && jqChildren(node).every(child => child.node === undefined || held(child.node, "nothing") === "nothing") ? "nothing" : "anything";
|
|
3255
|
+
case "def":
|
|
3256
|
+
return "anything";
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
/**
|
|
3260
|
+
* Translates a checked jq program to East IR.
|
|
3261
|
+
*
|
|
3262
|
+
* @param checked - what {@link checkJq} made of the program; it must have no
|
|
3263
|
+
* error
|
|
3264
|
+
* @param options - `maxOutputs` for a limit, `tooling` for the tooling-only
|
|
3265
|
+
* builtins
|
|
3266
|
+
* @returns the translation: its inputs, its result type, and builders for the
|
|
3267
|
+
* IR
|
|
3268
|
+
* @throws {TranslationError} When the program does not check, or holds
|
|
3269
|
+
* something the translator cannot express.
|
|
3270
|
+
*
|
|
3271
|
+
* @remarks
|
|
3272
|
+
* Streams become loops that give each output to what consumes it, and the
|
|
3273
|
+
* program's result is its sink: the value for `one`, an `Option` for `maybe`,
|
|
3274
|
+
* an `Array` for `many`. Checked as an e3 root, each root field the program
|
|
3275
|
+
* reads is an input of its own, so a lazy dataset is read only where the
|
|
3276
|
+
* query reads it (`devdocs/QUERY.md` §15).
|
|
3277
|
+
*
|
|
3278
|
+
* @example
|
|
3279
|
+
* ```ts
|
|
3280
|
+
* const Order = StructType({ id: IntegerType, total: FloatType });
|
|
3281
|
+
* const checked = checkJq(".orders | map(.total) | add", StructType({ orders: ArrayType(Order) }), { root: true });
|
|
3282
|
+
* const translation = translateJq(checked);
|
|
3283
|
+
* translation.inputs; // [{ name: "orders", type: ArrayType(Order) }]
|
|
3284
|
+
* translation.resultType; // FloatType
|
|
3285
|
+
* const total = East.compile(translation.fn(), []);
|
|
3286
|
+
* total([{ id: 1n, total: 2.5 }, { id: 2n, total: 4.0 }]); // 6.5
|
|
3287
|
+
* ```
|
|
3288
|
+
*/
|
|
3289
|
+
export function translateJq(checked, options = {}) {
|
|
3290
|
+
if (checked.program === null || checked.elementType === null || checked.multiplicity === null) {
|
|
3291
|
+
const errors = checked.diagnostics.filter(d => d.severity.type === "error").map(d => d.message);
|
|
3292
|
+
throw new TranslationError(`the program does not check: ${errors.join(" ")}`);
|
|
3293
|
+
}
|
|
3294
|
+
// The program as written: each value it holds is given as the type the checker read it as.
|
|
3295
|
+
const program = checked.program;
|
|
3296
|
+
const element = checked.elementType;
|
|
3297
|
+
const multiplicity = checked.multiplicity;
|
|
3298
|
+
const inputType = checked.inputType;
|
|
3299
|
+
const root = checked.source.root;
|
|
3300
|
+
const inputs = root
|
|
3301
|
+
? checked.reads.map(name => ({ name, type: parts(unwrap(inputType)).fields[name] }))
|
|
3302
|
+
: [{ name: null, type: inputType }];
|
|
3303
|
+
const resultType = multiplicity === "one" ? element : multiplicity === "maybe" ? OptionType(element) : ArrayType(element);
|
|
3304
|
+
// What the `Query` builtin's function takes: every field of a root, in order, or the one input.
|
|
3305
|
+
const fields = root ? Object.entries(parts(unwrap(inputType)).fields) : [];
|
|
3306
|
+
const params = root ? fields.map(([, type]) => type) : [inputType];
|
|
3307
|
+
const into = ($, values) => {
|
|
3308
|
+
const t = new Translator(checked, options);
|
|
3309
|
+
const x = root ? { root: true, fields: new Map(inputs.map((input, i) => [input.name, values[i]])) } : values[0];
|
|
3310
|
+
const env = { vars: new Map(), defs: new Map(), labels: new Map(), instance: "", recursion: new Map() };
|
|
3311
|
+
if (multiplicity === "one")
|
|
3312
|
+
return t.one(program, "", $, x, env, element);
|
|
3313
|
+
if (multiplicity === "maybe") {
|
|
3314
|
+
const out = t.declare($, t.none(resultType), "result");
|
|
3315
|
+
t.once($, ($2, label) => t.gen(program, "", $2, x, env, ($3, v) => {
|
|
3316
|
+
t.assign($3, out, t.some(t.widenTo($3, v, element, ""), resultType));
|
|
3317
|
+
t.brk($3, label);
|
|
3318
|
+
}), "");
|
|
3319
|
+
return out;
|
|
3320
|
+
}
|
|
3321
|
+
const out = t.declare($, t.emptyArray(element), "results");
|
|
3322
|
+
const limit = options.maxOutputs;
|
|
3323
|
+
if (limit === undefined) {
|
|
3324
|
+
t.gen(program, "", $, x, env, ($2, v) => t.push($2, out, v, ""));
|
|
3325
|
+
return out;
|
|
3326
|
+
}
|
|
3327
|
+
// One output past the limit is kept, so the caller can tell the result was cut short.
|
|
3328
|
+
t.once($, ($2, label) => t.gen(program, "", $2, x, env, ($3, v) => {
|
|
3329
|
+
t.push($3, out, v, "");
|
|
3330
|
+
t.ifElse($3, t.b("GreaterEqual", [IntegerType], [t.size(out, ""), t.int(limit + 1)], BooleanType, ""), $4 => t.brk($4, label), undefined, "");
|
|
3331
|
+
}), "");
|
|
3332
|
+
return out;
|
|
3333
|
+
};
|
|
3334
|
+
return {
|
|
3335
|
+
inputs,
|
|
3336
|
+
resultType,
|
|
3337
|
+
build: (...values) => {
|
|
3338
|
+
const $ = BlockBuilder(NeverType);
|
|
3339
|
+
const result = into($, values);
|
|
3340
|
+
// A block of one expression is the expression, as the builders give it.
|
|
3341
|
+
if ($.statements.length === 0)
|
|
3342
|
+
return result;
|
|
3343
|
+
const statements = isTypeEqual($.type(), NeverType) ? $.statements : [...$.statements, result[AstSymbol]];
|
|
3344
|
+
return fromAst({ ast_type: "Block", type: isTypeEqual($.type(), NeverType) ? NeverType : resultType, loc_id: UNKNOWN_LOC_ID, statements });
|
|
3345
|
+
},
|
|
3346
|
+
fn: () => func(inputs.map(i => i.type), resultType, ($, ...values) => into($, values)),
|
|
3347
|
+
call: (...values) => {
|
|
3348
|
+
if (values.length !== params.length) {
|
|
3349
|
+
throw new TranslationError(`the query takes ${params.length} input${params.length === 1 ? "" : "s"}, not ${values.length}`);
|
|
3350
|
+
}
|
|
3351
|
+
const loc = get_location_id();
|
|
3352
|
+
const F = FunctionType(params, resultType);
|
|
3353
|
+
// Each field of the root is a parameter; the translation reads those the query reads.
|
|
3354
|
+
const translation = func(params, resultType, ($, ...args) => into($, root ? inputs.map(i => args[fields.findIndex(([name]) => name === i.name)]) : args));
|
|
3355
|
+
const constant = variant("v1", { inputs: root ? some(fields.map(([name]) => name)) : none, program });
|
|
3356
|
+
const builtin = {
|
|
3357
|
+
ast_type: "Builtin", type: F, loc_id: loc, builtin: "Query", type_parameters: [F],
|
|
3358
|
+
arguments: [valueOrExprToAstTyped(constant, QueryCallType, undefined, loc), translation[AstSymbol]],
|
|
3359
|
+
};
|
|
3360
|
+
return fromAst({ ast_type: "Call", type: resultType, loc_id: loc, function: builtin, arguments: values.map(v => v[AstSymbol]) });
|
|
3361
|
+
},
|
|
3362
|
+
};
|
|
3363
|
+
}
|
|
3364
|
+
//# sourceMappingURL=translate.js.map
|