@json-schema-engine/core 0.0.1
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/LICENSE +21 -0
- package/README.md +50 -0
- package/dist/coverage.d.ts +30 -0
- package/dist/coverage.d.ts.map +1 -0
- package/dist/coverage.js +81 -0
- package/dist/coverage.js.map +1 -0
- package/dist/cursor.d.ts +16 -0
- package/dist/cursor.d.ts.map +1 -0
- package/dist/cursor.js +28 -0
- package/dist/cursor.js.map +1 -0
- package/dist/dialect.d.ts +316 -0
- package/dist/dialect.d.ts.map +1 -0
- package/dist/dialect.js +135 -0
- package/dist/dialect.js.map +1 -0
- package/dist/engine.d.ts +203 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +493 -0
- package/dist/engine.js.map +1 -0
- package/dist/index.d.ts +182 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +370 -0
- package/dist/index.js.map +1 -0
- package/dist/json.d.ts +49 -0
- package/dist/json.d.ts.map +1 -0
- package/dist/json.js +128 -0
- package/dist/json.js.map +1 -0
- package/dist/keywords/applicator.d.ts +57 -0
- package/dist/keywords/applicator.d.ts.map +1 -0
- package/dist/keywords/applicator.js +789 -0
- package/dist/keywords/applicator.js.map +1 -0
- package/dist/keywords/core.d.ts +42 -0
- package/dist/keywords/core.d.ts.map +1 -0
- package/dist/keywords/core.js +139 -0
- package/dist/keywords/core.js.map +1 -0
- package/dist/keywords/format.d.ts +43 -0
- package/dist/keywords/format.d.ts.map +1 -0
- package/dist/keywords/format.js +101 -0
- package/dist/keywords/format.js.map +1 -0
- package/dist/keywords/metaschemas2019.d.ts +3 -0
- package/dist/keywords/metaschemas2019.d.ts.map +1 -0
- package/dist/keywords/metaschemas2019.js +406 -0
- package/dist/keywords/metaschemas2019.js.map +1 -0
- package/dist/keywords/metaschemas2020.d.ts +3 -0
- package/dist/keywords/metaschemas2020.d.ts.map +1 -0
- package/dist/keywords/metaschemas2020.js +441 -0
- package/dist/keywords/metaschemas2020.js.map +1 -0
- package/dist/keywords/metaschemas6.d.ts +3 -0
- package/dist/keywords/metaschemas6.d.ts.map +1 -0
- package/dist/keywords/metaschemas6.js +221 -0
- package/dist/keywords/metaschemas6.js.map +1 -0
- package/dist/keywords/metaschemas7.d.ts +3 -0
- package/dist/keywords/metaschemas7.d.ts.map +1 -0
- package/dist/keywords/metaschemas7.js +248 -0
- package/dist/keywords/metaschemas7.js.map +1 -0
- package/dist/keywords/unevaluated.d.ts +14 -0
- package/dist/keywords/unevaluated.d.ts.map +1 -0
- package/dist/keywords/unevaluated.js +296 -0
- package/dist/keywords/unevaluated.js.map +1 -0
- package/dist/keywords/validation.d.ts +21 -0
- package/dist/keywords/validation.d.ts.map +1 -0
- package/dist/keywords/validation.js +332 -0
- package/dist/keywords/validation.js.map +1 -0
- package/dist/keywords/vocab2019.d.ts +36 -0
- package/dist/keywords/vocab2019.d.ts.map +1 -0
- package/dist/keywords/vocab2019.js +585 -0
- package/dist/keywords/vocab2019.js.map +1 -0
- package/dist/keywords/vocab2020.d.ts +20 -0
- package/dist/keywords/vocab2020.d.ts.map +1 -0
- package/dist/keywords/vocab2020.js +55 -0
- package/dist/keywords/vocab2020.js.map +1 -0
- package/dist/keywords/vocab7.d.ts +46 -0
- package/dist/keywords/vocab7.d.ts.map +1 -0
- package/dist/keywords/vocab7.js +305 -0
- package/dist/keywords/vocab7.js.map +1 -0
- package/dist/loader.d.ts +41 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +13 -0
- package/dist/loader.js.map +1 -0
- package/dist/lowering.d.ts +371 -0
- package/dist/lowering.d.ts.map +1 -0
- package/dist/lowering.js +59 -0
- package/dist/lowering.js.map +1 -0
- package/dist/output.d.ts +251 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +357 -0
- package/dist/output.js.map +1 -0
- package/dist/records.d.ts +71 -0
- package/dist/records.d.ts.map +1 -0
- package/dist/records.js +150 -0
- package/dist/records.js.map +1 -0
- package/dist/ref.d.ts +10 -0
- package/dist/ref.d.ts.map +1 -0
- package/dist/ref.js +5 -0
- package/dist/ref.js.map +1 -0
- package/dist/regex.d.ts +45 -0
- package/dist/regex.d.ts.map +1 -0
- package/dist/regex.js +163 -0
- package/dist/regex.js.map +1 -0
- package/dist/registry.d.ts +141 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +407 -0
- package/dist/registry.js.map +1 -0
- package/dist/result.d.ts +109 -0
- package/dist/result.d.ts.map +1 -0
- package/dist/result.js +112 -0
- package/dist/result.js.map +1 -0
- package/dist/uri.d.ts +18 -0
- package/dist/uri.d.ts.map +1 -0
- package/dist/uri.js +27 -0
- package/dist/uri.js.map +1 -0
- package/dist/walk.d.ts +25 -0
- package/dist/walk.d.ts.map +1 -0
- package/dist/walk.js +65 -0
- package/dist/walk.js.map +1 -0
- package/package.json +32 -0
- package/src/coverage.ts +88 -0
- package/src/cursor.ts +42 -0
- package/src/dialect.ts +428 -0
- package/src/engine.ts +741 -0
- package/src/index.ts +699 -0
- package/src/json.ts +137 -0
- package/src/keywords/applicator.ts +895 -0
- package/src/keywords/core.ts +168 -0
- package/src/keywords/format.ts +143 -0
- package/src/keywords/metaschemas2019.ts +410 -0
- package/src/keywords/metaschemas2020.ts +443 -0
- package/src/keywords/metaschemas6.ts +226 -0
- package/src/keywords/metaschemas7.ts +253 -0
- package/src/keywords/unevaluated.ts +329 -0
- package/src/keywords/validation.ts +627 -0
- package/src/keywords/vocab2019.ts +676 -0
- package/src/keywords/vocab2020.ts +82 -0
- package/src/keywords/vocab7.ts +393 -0
- package/src/loader.ts +60 -0
- package/src/lowering.ts +414 -0
- package/src/output.ts +651 -0
- package/src/records.ts +226 -0
- package/src/ref.ts +14 -0
- package/src/regex.ts +171 -0
- package/src/registry.ts +481 -0
- package/src/result.ts +252 -0
- package/src/uri.ts +35 -0
- package/src/walk.ts +88 -0
package/src/lowering.ts
ADDED
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
// Compiler lowering IR (D1/D9, M6.1): the type vocabulary keyword behaviors
|
|
2
|
+
// use to describe their compiled form, and the LoweringContext service
|
|
3
|
+
// interface the compiler implements. Types only — core carries no compiler
|
|
4
|
+
// runtime. Keyword modules depend on this file, never on @json-schema-engine/compiler, so
|
|
5
|
+
// keyword knowledge stays in exactly one module per keyword.
|
|
6
|
+
//
|
|
7
|
+
// Two properties are load-bearing for security and semantics:
|
|
8
|
+
//
|
|
9
|
+
// 1. No IR node carries raw JavaScript text. Schema-derived data enters only
|
|
10
|
+
// as data nodes (constants, property keys, regex sources), which the
|
|
11
|
+
// compiler's gated serializer escapes. Injection is unrepresentable
|
|
12
|
+
// upstream of the serializer (D20/M6).
|
|
13
|
+
//
|
|
14
|
+
// 2. `combine` is EAGER: every part is evaluated, then the verdicts fold.
|
|
15
|
+
// This is channel rule 6 (DESIGN.md §4) in the IR contract — branches
|
|
16
|
+
// cannot short-circuit while a consumer or retained annotation could
|
|
17
|
+
// observe them. The serializer may emit short-circuits only where the
|
|
18
|
+
// compilation plan proves the region verdict-only.
|
|
19
|
+
|
|
20
|
+
import { JsonValue, JsonType } from "./json.js";
|
|
21
|
+
|
|
22
|
+
/** A value-producing IR expression. */
|
|
23
|
+
export type LowerExpr =
|
|
24
|
+
/** the instance value under evaluation at the lowering site */
|
|
25
|
+
| { readonly kind: "instance" }
|
|
26
|
+
/** a hoisted JSON constant (emitted via the gated formatter's json()) */
|
|
27
|
+
| { readonly kind: "const"; readonly value: JsonValue }
|
|
28
|
+
/** object member access by schema-derived key (bracket notation, escaped) */
|
|
29
|
+
| {
|
|
30
|
+
readonly kind: "member";
|
|
31
|
+
readonly target: LowerExpr;
|
|
32
|
+
readonly key: string;
|
|
33
|
+
}
|
|
34
|
+
/** array element access */
|
|
35
|
+
| {
|
|
36
|
+
readonly kind: "item";
|
|
37
|
+
readonly target: LowerExpr;
|
|
38
|
+
readonly index: LowerExpr;
|
|
39
|
+
}
|
|
40
|
+
/** a loop binding introduced by forEachKey/forEachIndex */
|
|
41
|
+
| { readonly kind: "binding"; readonly id: number }
|
|
42
|
+
/** JSON type test, including the "integer" refinement */
|
|
43
|
+
| {
|
|
44
|
+
readonly kind: "typeIs";
|
|
45
|
+
readonly target: LowerExpr;
|
|
46
|
+
readonly types: readonly (JsonType | "integer")[];
|
|
47
|
+
}
|
|
48
|
+
/** Object.hasOwn(target, key) — the only permitted membership test */
|
|
49
|
+
| {
|
|
50
|
+
readonly kind: "hasOwn";
|
|
51
|
+
readonly target: LowerExpr;
|
|
52
|
+
readonly key: LowerExpr | string;
|
|
53
|
+
}
|
|
54
|
+
/** numeric/string comparison of two expressions */
|
|
55
|
+
| {
|
|
56
|
+
readonly kind: "cmp";
|
|
57
|
+
readonly op: "<" | "<=" | ">" | ">=" | "===" | "!==";
|
|
58
|
+
readonly left: LowerExpr;
|
|
59
|
+
readonly right: LowerExpr;
|
|
60
|
+
}
|
|
61
|
+
/** call into the closed helper registry (never arbitrary code) */
|
|
62
|
+
| {
|
|
63
|
+
readonly kind: "helper";
|
|
64
|
+
readonly helper: LowerHelper;
|
|
65
|
+
readonly args: readonly LowerExpr[];
|
|
66
|
+
}
|
|
67
|
+
/** test a hoisted regex (compiled through the engine's RegexCache) */
|
|
68
|
+
| {
|
|
69
|
+
readonly kind: "regexTest";
|
|
70
|
+
readonly source: string;
|
|
71
|
+
readonly target: LowerExpr;
|
|
72
|
+
}
|
|
73
|
+
/** test a hoisted format definition (compiled through the engine's format table) */
|
|
74
|
+
| {
|
|
75
|
+
readonly kind: "formatTest";
|
|
76
|
+
readonly name: string;
|
|
77
|
+
readonly target: LowerExpr;
|
|
78
|
+
}
|
|
79
|
+
| { readonly kind: "not"; readonly expr: LowerExpr }
|
|
80
|
+
| {
|
|
81
|
+
readonly kind: "logic";
|
|
82
|
+
readonly op: "and" | "or";
|
|
83
|
+
readonly parts: readonly LowerExpr[];
|
|
84
|
+
}
|
|
85
|
+
/** the active combine-group tally (oneOf's match count) in a combineCheck message */
|
|
86
|
+
| { readonly kind: "tally" }
|
|
87
|
+
/**
|
|
88
|
+
* The passing branch INDEXES of the active combine group, as an array —
|
|
89
|
+
* combineCheck params only (oneOf reports which branches matched, not
|
|
90
|
+
* just how many; the ajv-compat mapping needs the indexes).
|
|
91
|
+
*/
|
|
92
|
+
| { readonly kind: "tallyList" }
|
|
93
|
+
/**
|
|
94
|
+
* A subschema application used as a boolean expression rather than a
|
|
95
|
+
* statement (M6.4): `if`'s condition, `not`'s single negated apply, and
|
|
96
|
+
* `contains`'/`oneOf`'s per-branch probes all need the verdict as a value,
|
|
97
|
+
* not a verdict-folding statement. The serializer renders it as the same
|
|
98
|
+
* call expression `apply` statements use; `apply.fold` still governs how a
|
|
99
|
+
* caller that wraps this in a statement folds the result (e.g. `negate`,
|
|
100
|
+
* `exactlyOne`), while a bare `applyExpr` used purely for its value (e.g.
|
|
101
|
+
* as a `forEachIndex` counter guard) carries `fold: "discard"`.
|
|
102
|
+
*/
|
|
103
|
+
| { readonly kind: "applyExpr"; readonly apply: LowerApply }
|
|
104
|
+
/**
|
|
105
|
+
* True when the folded coverage bound by a preceding `coverageFold`
|
|
106
|
+
* (identified by `fold`, its binding id) covers `target` — the swept
|
|
107
|
+
* property name (names half: set membership) or array index (indexes half:
|
|
108
|
+
* index below coveredPrefix, or index in coveredIdx). The consumer keyword's
|
|
109
|
+
* runtime sweep applies its subschema exactly where this is false, matching
|
|
110
|
+
* the interpreter's "skip already-evaluated" loop.
|
|
111
|
+
*/
|
|
112
|
+
| {
|
|
113
|
+
readonly kind: "coverageCovers";
|
|
114
|
+
readonly fold: number;
|
|
115
|
+
readonly target: LowerExpr;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The closed set of runtime helpers emitted code may call. All are imported
|
|
120
|
+
* from the compiler's runtime module (re-exports of core functions) — never
|
|
121
|
+
* re-emitted per artifact, so compiled and interpreted tiers share one
|
|
122
|
+
* implementation of each semantic.
|
|
123
|
+
*/
|
|
124
|
+
export type LowerHelper =
|
|
125
|
+
| "codePointLength"
|
|
126
|
+
| "jsonEqual"
|
|
127
|
+
| "canonicalKey"
|
|
128
|
+
| "escapeSegment"
|
|
129
|
+
| "keysOf" // Object.keys
|
|
130
|
+
| "lengthOf" // .length of a string or array (UTF-16 units / element count)
|
|
131
|
+
| "isMultipleOf"
|
|
132
|
+
| "hasDuplicateItems"
|
|
133
|
+
| "firstDuplicatePair";
|
|
134
|
+
|
|
135
|
+
/** A statement-level IR node. */
|
|
136
|
+
export type LowerStmt =
|
|
137
|
+
| {
|
|
138
|
+
readonly kind: "if";
|
|
139
|
+
readonly cond: LowerExpr;
|
|
140
|
+
readonly then: readonly LowerStmt[];
|
|
141
|
+
readonly else?: readonly LowerStmt[];
|
|
142
|
+
}
|
|
143
|
+
/** iterate own enumerable keys (Object.keys) binding each name */
|
|
144
|
+
| {
|
|
145
|
+
readonly kind: "forEachKey";
|
|
146
|
+
readonly target: LowerExpr;
|
|
147
|
+
readonly binding: number;
|
|
148
|
+
readonly body: readonly LowerStmt[];
|
|
149
|
+
}
|
|
150
|
+
/** iterate array indexes 0..length-1 (optionally from a constant start) */
|
|
151
|
+
| {
|
|
152
|
+
readonly kind: "forEachIndex";
|
|
153
|
+
readonly target: LowerExpr;
|
|
154
|
+
readonly binding: number;
|
|
155
|
+
readonly start?: number;
|
|
156
|
+
readonly body: readonly LowerStmt[];
|
|
157
|
+
}
|
|
158
|
+
/** report this keyword's assertion failure at the current cursor */
|
|
159
|
+
| {
|
|
160
|
+
readonly kind: "fail";
|
|
161
|
+
readonly message: LowerMessage;
|
|
162
|
+
readonly params?: LowerParams;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* record this keyword's value as an annotation (channel rule 2); the
|
|
166
|
+
* serializer emits the schema constant, so the node carries no payload
|
|
167
|
+
*/
|
|
168
|
+
| { readonly kind: "annotate" }
|
|
169
|
+
/** emit this keyword's dependency data (channel rule 2) */
|
|
170
|
+
| { readonly kind: "produce"; readonly value: LowerProduceValue }
|
|
171
|
+
/**
|
|
172
|
+
* Bind the folded coverage of the unit's runtime channel (consumer
|
|
173
|
+
* keywords only). The serializer supplies the channel — the flat array of
|
|
174
|
+
* raw dependency data from consumed producers, merged with mark/truncate
|
|
175
|
+
* at application boundaries — and folds it per `half`: "names" yields the
|
|
176
|
+
* evaluated-name set (foldNameCoverage), "indexes" yields the coveredPrefix/
|
|
177
|
+
* coveredIdx summary over the CURRENT instance array's length
|
|
178
|
+
* (foldIndexCoverage). The result is held in `binding` for a following
|
|
179
|
+
* `coverageCovers` to test. The compiled tier's alternative to
|
|
180
|
+
* `staticCoverage()`: emitted only when the planner licensed runtime
|
|
181
|
+
* evaluated-set tracking (LoweringContext.runtimeCoverage()).
|
|
182
|
+
*/
|
|
183
|
+
| {
|
|
184
|
+
readonly kind: "coverageFold";
|
|
185
|
+
readonly half: "names" | "indexes";
|
|
186
|
+
readonly binding: number;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Apply a subschema and fold its verdict into the keyword verdict per
|
|
190
|
+
* `fold`. EAGER combine semantics (header note 2). `apply.cursor`
|
|
191
|
+
* identifies the instance position; the compiler owns frames, locations,
|
|
192
|
+
* and scope threading, exactly as the engine does for the interpreter.
|
|
193
|
+
*/
|
|
194
|
+
| { readonly kind: "apply"; readonly apply: LowerApply }
|
|
195
|
+
/**
|
|
196
|
+
* Closes the immediately preceding run of anyMayPass/exactlyOne applies:
|
|
197
|
+
* the keyword fails (with `message`) when the run's combined verdict
|
|
198
|
+
* fails. Emitted by the keyword's lower() so failure text stays keyword
|
|
199
|
+
* knowledge (D1); the serializer folds it into the grouped check.
|
|
200
|
+
*/
|
|
201
|
+
| {
|
|
202
|
+
readonly kind: "combineCheck";
|
|
203
|
+
readonly message: LowerMessage;
|
|
204
|
+
readonly params?: LowerParams;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* `contains`'s shape: iterate array indexes 0..length-1 (like
|
|
208
|
+
* `forEachIndex`, binding each index), counting the iterations where
|
|
209
|
+
* `countWhen` holds true, then fail the keyword when the final count
|
|
210
|
+
* falls outside `[min, max]`. Every index is probed unconditionally (no
|
|
211
|
+
* short-circuit on reaching `max`), matching evaluate()'s full sweep.
|
|
212
|
+
* `countWhen` is typically an `applyExpr` with `fold: "discard"` (the
|
|
213
|
+
* per-item probe verdict feeds the count, never the keyword verdict
|
|
214
|
+
* directly — a failed probe is not itself a `contains` failure).
|
|
215
|
+
*/
|
|
216
|
+
| {
|
|
217
|
+
readonly kind: "countRange";
|
|
218
|
+
readonly target: LowerExpr;
|
|
219
|
+
readonly binding: number;
|
|
220
|
+
readonly countWhen: LowerExpr;
|
|
221
|
+
/** accumulate the counted indexes for a following produce (contains' dependency data) */
|
|
222
|
+
readonly collectIndexes?: boolean;
|
|
223
|
+
readonly min: number;
|
|
224
|
+
readonly max: number;
|
|
225
|
+
readonly outOfRangeMessage: LowerMessage;
|
|
226
|
+
readonly outOfRangeParams?: LowerParams;
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
/** How a keyword's lowered body applies one subschema. */
|
|
230
|
+
export interface LowerApply {
|
|
231
|
+
/** subschema position relative to the keyword's schema object (matches StaticFacts.applications[].path, with loop bindings for dynamic segments) */
|
|
232
|
+
readonly path: readonly (string | number | { binding: number })[];
|
|
233
|
+
/**
|
|
234
|
+
* Set when the applied subschema is a sibling keyword's value (`if` →
|
|
235
|
+
* `then`/`else`), mirroring {@link SubschemaApplication.sibling} — the
|
|
236
|
+
* planner already resolves the edge this way (plan.ts); the serializer
|
|
237
|
+
* matches an apply back to its planned edge by keyword + sibling + path
|
|
238
|
+
* identity, so a keyword emitting more than one apply at the same `path`
|
|
239
|
+
* (e.g. `if`'s condition vs. its `then` edge, both `path: []`) MUST set
|
|
240
|
+
* this to disambiguate.
|
|
241
|
+
*/
|
|
242
|
+
readonly sibling?: string;
|
|
243
|
+
/**
|
|
244
|
+
* For reference keywords: the reference value. The compiler resolves it
|
|
245
|
+
* at plan time against the unit's lexical base; `path` is ignored.
|
|
246
|
+
*/
|
|
247
|
+
readonly ref?: string;
|
|
248
|
+
/** instance cursor for the application */
|
|
249
|
+
readonly cursor: LowerCursor;
|
|
250
|
+
/** failure message for folds that assert with their own error (negate) */
|
|
251
|
+
readonly message?: LowerMessage;
|
|
252
|
+
/** structured params accompanying `message` (D13) */
|
|
253
|
+
readonly params?: LowerParams;
|
|
254
|
+
/** how the application verdict folds into the keyword verdict */
|
|
255
|
+
readonly fold:
|
|
256
|
+
"allMustPass" | "anyMayPass" | "exactlyOne" | "negate" | "discard";
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** Instance cursor IR: the current node or a child of one. */
|
|
260
|
+
export type LowerCursor =
|
|
261
|
+
| { readonly kind: "here" }
|
|
262
|
+
| {
|
|
263
|
+
readonly kind: "child";
|
|
264
|
+
readonly of: LowerCursor;
|
|
265
|
+
readonly segment: LowerExpr | string | number;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* `propertyNames`: the current loop binding's KEY STRING is the instance
|
|
269
|
+
* under evaluation, not a child of the object being iterated (there is no
|
|
270
|
+
* parent cursor to descend from — the property name itself is the value).
|
|
271
|
+
*/
|
|
272
|
+
| { readonly kind: "key"; readonly binding: number };
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* An error message: literal parts joined with expression parts, escaped by
|
|
276
|
+
* the serializer. Mirrors the interpreter's message-builder output so the
|
|
277
|
+
* differential gate can compare error text exactly.
|
|
278
|
+
*/
|
|
279
|
+
export type LowerMessage = readonly (string | LowerExpr)[];
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Structured failure params (D13): each value is an expression so runtime
|
|
283
|
+
* pieces (a swept key, a tally, a duplicate pair) sit next to compile-time
|
|
284
|
+
* constants (`lowerIR.constant`). Mirrors the interpreter's
|
|
285
|
+
* `ctx.error(message, params)` so the differential gate can compare params
|
|
286
|
+
* exactly.
|
|
287
|
+
*/
|
|
288
|
+
export type LowerParams = Readonly<Record<string, LowerExpr>>;
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* A dependency-data recipe: what a keyword's lowered body communicates to
|
|
292
|
+
* consumer keywords.
|
|
293
|
+
*
|
|
294
|
+
* Recipes read the accumulation the keyword's statements build against the
|
|
295
|
+
* instance. Within one keyword's lowered statement list, each executed
|
|
296
|
+
* child-of-here application (an `apply` or `applyExpr` whose cursor is
|
|
297
|
+
* `{kind:"child", of:{kind:"here"}, segment}`) contributes its segment —
|
|
298
|
+
* string segments to a name accumulation, numeric segments to an index
|
|
299
|
+
* accumulation — in execution order, deduplicated keeping the first
|
|
300
|
+
* occurrence. Applications count when ATTEMPTED, regardless of the
|
|
301
|
+
* subschema's verdict (the interpreter records the segment before applying).
|
|
302
|
+
*/
|
|
303
|
+
export type LowerProduceValue =
|
|
304
|
+
/**
|
|
305
|
+
* The accumulated (deduped) name array, possibly empty. Object-shaped
|
|
306
|
+
* producers emit an empty array for an object and nothing for a non-object,
|
|
307
|
+
* so lower() must gate this produce behind an instance object-type check.
|
|
308
|
+
*/
|
|
309
|
+
| { readonly kind: "collectedNames" }
|
|
310
|
+
/**
|
|
311
|
+
* The accumulated index array, rendered per `render`:
|
|
312
|
+
*
|
|
313
|
+
* - `"largestOrTrue"`: no accumulated indexes yields no production;
|
|
314
|
+
* otherwise `true` when the largest index + 1 equals the instance array
|
|
315
|
+
* length, else the largest accumulated index (a number).
|
|
316
|
+
* - `"appliedTrue"`: any accumulated index yields `true`; none yields no
|
|
317
|
+
* production.
|
|
318
|
+
* - `"matchedOrAllTrue"`: reads the keyword's countRange-counted indexes
|
|
319
|
+
* (see `collectIndexes`) — empty yields no production; a count equal to
|
|
320
|
+
* the instance array length yields `true`; otherwise the ascending index
|
|
321
|
+
* array.
|
|
322
|
+
*/
|
|
323
|
+
| {
|
|
324
|
+
readonly kind: "collectedIndexes";
|
|
325
|
+
readonly render: "largestOrTrue" | "appliedTrue" | "matchedOrAllTrue";
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Services available to one keyword's `lower()` (mirror of KeywordContext,
|
|
330
|
+
* D3): the only path to subschema application, hoisting, and the channel.
|
|
331
|
+
* Implemented by the compiler package; core defines the contract so keyword
|
|
332
|
+
* modules never import the compiler.
|
|
333
|
+
*/
|
|
334
|
+
export interface LoweringContext {
|
|
335
|
+
/** the instance expression at this lowering site */
|
|
336
|
+
readonly instance: LowerExpr;
|
|
337
|
+
/** the keyword's containing schema object (plan-time data) */
|
|
338
|
+
readonly schema: Readonly<Record<string, JsonValue>>;
|
|
339
|
+
/**
|
|
340
|
+
* The static evaluated-coverage result for this schema object (D9a),
|
|
341
|
+
* for `unevaluated*` lowerings: null when any contributor is dynamic —
|
|
342
|
+
* the lowering must then fall back to runtime evaluated-set tracking.
|
|
343
|
+
*/
|
|
344
|
+
staticCoverage(): {
|
|
345
|
+
names: readonly string[];
|
|
346
|
+
patterns: readonly string[];
|
|
347
|
+
coversAllNames: boolean;
|
|
348
|
+
prefixCount: number;
|
|
349
|
+
coversAllIndexes: boolean;
|
|
350
|
+
} | null;
|
|
351
|
+
/**
|
|
352
|
+
* True when the planner licensed RUNTIME evaluated-set tracking for this
|
|
353
|
+
* unit's consumer keywords: coverage flows through the unit's runtime
|
|
354
|
+
* channel and the keyword must emit coverageFold/coverageCovers instead of
|
|
355
|
+
* reading staticCoverage(). Exactly one of the two paths applies.
|
|
356
|
+
*/
|
|
357
|
+
runtimeCoverage(): boolean;
|
|
358
|
+
/** append statements to the keyword's lowered body */
|
|
359
|
+
emit(...stmts: LowerStmt[]): void;
|
|
360
|
+
/** allocate a loop binding id for forEachKey/forEachIndex */
|
|
361
|
+
binding(): number;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/** Constructor shorthands for the common IR shapes. */
|
|
365
|
+
export const lowerIR = {
|
|
366
|
+
instance: { kind: "instance" } as LowerExpr,
|
|
367
|
+
constant: (value: JsonValue): LowerExpr => ({ kind: "const", value }),
|
|
368
|
+
typeIs: (
|
|
369
|
+
target: LowerExpr,
|
|
370
|
+
...types: readonly (JsonType | "integer")[]
|
|
371
|
+
): LowerExpr => ({ kind: "typeIs", target, types }),
|
|
372
|
+
not: (expr: LowerExpr): LowerExpr => ({ kind: "not", expr }),
|
|
373
|
+
and: (...parts: readonly LowerExpr[]): LowerExpr => ({
|
|
374
|
+
kind: "logic",
|
|
375
|
+
op: "and",
|
|
376
|
+
parts,
|
|
377
|
+
}),
|
|
378
|
+
or: (...parts: readonly LowerExpr[]): LowerExpr => ({
|
|
379
|
+
kind: "logic",
|
|
380
|
+
op: "or",
|
|
381
|
+
parts,
|
|
382
|
+
}),
|
|
383
|
+
regexTest: (source: string, target: LowerExpr): LowerExpr => ({
|
|
384
|
+
kind: "regexTest",
|
|
385
|
+
source,
|
|
386
|
+
target,
|
|
387
|
+
}),
|
|
388
|
+
formatTest: (name: string, target: LowerExpr): LowerExpr => ({
|
|
389
|
+
kind: "formatTest",
|
|
390
|
+
name,
|
|
391
|
+
target,
|
|
392
|
+
}),
|
|
393
|
+
helper: (helper: LowerHelper, ...args: readonly LowerExpr[]): LowerExpr => ({
|
|
394
|
+
kind: "helper",
|
|
395
|
+
helper,
|
|
396
|
+
args,
|
|
397
|
+
}),
|
|
398
|
+
cmp: (
|
|
399
|
+
op: "<" | "<=" | ">" | ">=" | "===" | "!==",
|
|
400
|
+
left: LowerExpr,
|
|
401
|
+
right: LowerExpr,
|
|
402
|
+
): LowerExpr => ({ kind: "cmp", op, left, right }),
|
|
403
|
+
fail: (...message: LowerMessage): LowerStmt => ({ kind: "fail", message }),
|
|
404
|
+
failWith: (params: LowerParams, ...message: LowerMessage): LowerStmt => ({
|
|
405
|
+
kind: "fail",
|
|
406
|
+
message,
|
|
407
|
+
params,
|
|
408
|
+
}),
|
|
409
|
+
when: (
|
|
410
|
+
cond: LowerExpr,
|
|
411
|
+
then: readonly LowerStmt[],
|
|
412
|
+
elseStmts?: readonly LowerStmt[],
|
|
413
|
+
): LowerStmt => ({ kind: "if", cond, then, else: elseStmts }),
|
|
414
|
+
};
|