@json-schema-engine/compiler 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.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +47 -0
  3. package/dist/emit.d.ts +52 -0
  4. package/dist/emit.d.ts.map +1 -0
  5. package/dist/emit.js +166 -0
  6. package/dist/emit.js.map +1 -0
  7. package/dist/explain.d.ts +28 -0
  8. package/dist/explain.d.ts.map +1 -0
  9. package/dist/explain.js +42 -0
  10. package/dist/explain.js.map +1 -0
  11. package/dist/index.d.ts +132 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +169 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/plan.d.ts +83 -0
  16. package/dist/plan.d.ts.map +1 -0
  17. package/dist/plan.js +385 -0
  18. package/dist/plan.js.map +1 -0
  19. package/dist/runtime-compile.d.ts +30 -0
  20. package/dist/runtime-compile.d.ts.map +1 -0
  21. package/dist/runtime-compile.js +29 -0
  22. package/dist/runtime-compile.js.map +1 -0
  23. package/dist/runtime.d.ts +157 -0
  24. package/dist/runtime.d.ts.map +1 -0
  25. package/dist/runtime.js +336 -0
  26. package/dist/runtime.js.map +1 -0
  27. package/dist/serialize/apply.d.ts +28 -0
  28. package/dist/serialize/apply.d.ts.map +1 -0
  29. package/dist/serialize/apply.js +211 -0
  30. package/dist/serialize/apply.js.map +1 -0
  31. package/dist/serialize/context.d.ts +143 -0
  32. package/dist/serialize/context.d.ts.map +1 -0
  33. package/dist/serialize/context.js +93 -0
  34. package/dist/serialize/context.js.map +1 -0
  35. package/dist/serialize/expressions.d.ts +7 -0
  36. package/dist/serialize/expressions.d.ts.map +1 -0
  37. package/dist/serialize/expressions.js +177 -0
  38. package/dist/serialize/expressions.js.map +1 -0
  39. package/dist/serialize/guards.d.ts +7 -0
  40. package/dist/serialize/guards.d.ts.map +1 -0
  41. package/dist/serialize/guards.js +19 -0
  42. package/dist/serialize/guards.js.map +1 -0
  43. package/dist/serialize/index.d.ts +7 -0
  44. package/dist/serialize/index.d.ts.map +1 -0
  45. package/dist/serialize/index.js +266 -0
  46. package/dist/serialize/index.js.map +1 -0
  47. package/dist/serialize/keywords.d.ts +32 -0
  48. package/dist/serialize/keywords.d.ts.map +1 -0
  49. package/dist/serialize/keywords.js +131 -0
  50. package/dist/serialize/keywords.js.map +1 -0
  51. package/dist/serialize/messages.d.ts +36 -0
  52. package/dist/serialize/messages.d.ts.map +1 -0
  53. package/dist/serialize/messages.js +100 -0
  54. package/dist/serialize/messages.js.map +1 -0
  55. package/dist/serialize/names.d.ts +47 -0
  56. package/dist/serialize/names.d.ts.map +1 -0
  57. package/dist/serialize/names.js +115 -0
  58. package/dist/serialize/names.js.map +1 -0
  59. package/dist/serialize/spans.d.ts +34 -0
  60. package/dist/serialize/spans.d.ts.map +1 -0
  61. package/dist/serialize/spans.js +61 -0
  62. package/dist/serialize/spans.js.map +1 -0
  63. package/dist/serialize/statements.d.ts +16 -0
  64. package/dist/serialize/statements.d.ts.map +1 -0
  65. package/dist/serialize/statements.js +396 -0
  66. package/dist/serialize/statements.js.map +1 -0
  67. package/dist/serialize/unit.d.ts +8 -0
  68. package/dist/serialize/unit.d.ts.map +1 -0
  69. package/dist/serialize/unit.js +107 -0
  70. package/dist/serialize/unit.js.map +1 -0
  71. package/dist/standalone.d.ts +24 -0
  72. package/dist/standalone.d.ts.map +1 -0
  73. package/dist/standalone.js +166 -0
  74. package/dist/standalone.js.map +1 -0
  75. package/package.json +38 -0
  76. package/src/emit.ts +196 -0
  77. package/src/explain.ts +67 -0
  78. package/src/index.ts +366 -0
  79. package/src/plan.ts +496 -0
  80. package/src/runtime-compile.ts +90 -0
  81. package/src/runtime.ts +599 -0
  82. package/src/serialize/apply.ts +251 -0
  83. package/src/serialize/context.ts +161 -0
  84. package/src/serialize/expressions.ts +202 -0
  85. package/src/serialize/guards.ts +20 -0
  86. package/src/serialize/index.ts +432 -0
  87. package/src/serialize/keywords.ts +144 -0
  88. package/src/serialize/messages.ts +120 -0
  89. package/src/serialize/names.ts +136 -0
  90. package/src/serialize/spans.ts +88 -0
  91. package/src/serialize/statements.ts +531 -0
  92. package/src/serialize/unit.ts +118 -0
  93. package/src/standalone.ts +187 -0
package/src/runtime.ts ADDED
@@ -0,0 +1,599 @@
1
+ // Runtime surface shared by every compiled artifact. Emitted code never
2
+ // re-implements a semantic: it calls these, which are core's own functions,
3
+ // so the compiled and interpreted tiers agree by construction (D9, M6).
4
+
5
+ import {
6
+ MaxDepthExceededError,
7
+ renderAnnotation,
8
+ renderError,
9
+ traceToRenderNodes,
10
+ type AnnotationUnit,
11
+ type ErrorUnit,
12
+ type MutableRenderNode,
13
+ type PathNode,
14
+ type ResultUnits,
15
+ canonicalKey,
16
+ codePointLength,
17
+ escapeSegment,
18
+ evaluateFragment,
19
+ firstDuplicatePair,
20
+ foldIndexCoverage,
21
+ foldNameCoverage,
22
+ harvestCoverage,
23
+ hasDuplicateItems,
24
+ isMultipleOf,
25
+ jsonEqual,
26
+ makeRecordPredicate,
27
+ rootCursor,
28
+ type FormatTable,
29
+ type FragmentOptions,
30
+ type JsonValue,
31
+ type AnnotationSelection,
32
+ type SchemaRef,
33
+ type SchemaRegistry,
34
+ type RegexCache,
35
+ } from "@json-schema-engine/core";
36
+
37
+ /** True for JSON objects (not arrays, not null). */
38
+ export const isObject = (v: unknown): v is Record<string, JsonValue> =>
39
+ typeof v === "object" && v !== null && !Array.isArray(v);
40
+
41
+ /** `n` is an integer-valued JSON number. */
42
+ export const isInteger = (v: unknown): boolean =>
43
+ typeof v === "number" && Number.isInteger(v);
44
+
45
+ /**
46
+ * The application(s) that raised one recorded unit: one node, or the nodes
47
+ * sharing a path node inside a traced island (a segment-less custom apply
48
+ * shares its parent's, as core's own adapter attributes it).
49
+ */
50
+ export type NodeRef = MutableRenderNode | MutableRenderNode[];
51
+
52
+ /** A node's parent as emitted code sees it: the holder or another node. */
53
+ export interface NodeParent {
54
+ children: MutableRenderNode[];
55
+ }
56
+
57
+ /**
58
+ * Per-evaluation state of a trace artifact: the flat channels paired with
59
+ * the application that raised each unit, and the tree under its holder.
60
+ * Emitted code fills it; {@link finishTrace} turns it into renderer input.
61
+ */
62
+ export interface TraceState {
63
+ valid: boolean;
64
+ errs: ErrorUnit[];
65
+ errNodes: NodeRef[];
66
+ /**
67
+ * The errors an accepting keyword made irrelevant, in drop order (the
68
+ * interpreter's `dropErrorsFrom` order). Filled only by a retaining
69
+ * artifact; the relevant level discards at the cut.
70
+ */
71
+ droppedErrs: ErrorUnit[];
72
+ droppedErrNodes: NodeRef[];
73
+ anns: AnnotationUnit[];
74
+ annNodes: NodeRef[];
75
+ /**
76
+ * Indexes into `anns` of the annotations a failed application made
77
+ * irrelevant. A retaining artifact keeps every annotation in `anns` in
78
+ * recording order and its cuts flag here instead of truncating, so the
79
+ * dropped side renders in that order too; null on the relevant level.
80
+ */
81
+ annDropped: Set<number> | null;
82
+ /** the root's parent: `hold.children[0]` is the root application */
83
+ hold: NodeParent;
84
+ }
85
+
86
+ /**
87
+ * Attributes every unit to its application and applies the selection's
88
+ * `keep` predicate, yielding the flat surface and the tree. With `retain`
89
+ * the irrelevant records a retaining artifact kept aside are rendered as
90
+ * the dropped pair; without it they are left out, so a verbose artifact
91
+ * serves a relevant-level demand exactly. An invalid run has no relevant
92
+ * annotations (draft-03 §12.2): every one it recorded is a dropped one.
93
+ */
94
+ export function finishTrace(
95
+ st: TraceState,
96
+ keep: ((unit: AnnotationUnit) => boolean) | undefined,
97
+ retain: boolean,
98
+ ): { units: ResultUnits; root: MutableRenderNode } {
99
+ const attribute = (
100
+ ref: NodeRef,
101
+ list: "errors" | "droppedErrors" | "annotations" | "droppedAnnotations",
102
+ index: number,
103
+ ): void => {
104
+ if (Array.isArray(ref)) for (const node of ref) node[list].push(index);
105
+ else ref[list].push(index);
106
+ };
107
+ st.errs.forEach((_, i) => {
108
+ attribute(st.errNodes[i]!, "errors", i);
109
+ });
110
+ const droppedErrors = retain ? st.droppedErrs : [];
111
+ droppedErrors.forEach((_, i) => {
112
+ attribute(st.droppedErrNodes[i]!, "droppedErrors", i);
113
+ });
114
+ const annotations: AnnotationUnit[] = [];
115
+ const droppedAnnotations: AnnotationUnit[] = [];
116
+ const flagged = st.annDropped;
117
+ st.anns.forEach((unit, i) => {
118
+ const irrelevant = !st.valid || flagged?.has(i) === true;
119
+ if (irrelevant && !retain) return;
120
+ // `keep` sees relevant and dropped units alike, as the interpreter's
121
+ // selection does.
122
+ if (keep !== undefined && !keep(unit)) return;
123
+ if (irrelevant) {
124
+ attribute(
125
+ st.annNodes[i]!,
126
+ "droppedAnnotations",
127
+ droppedAnnotations.length,
128
+ );
129
+ droppedAnnotations.push(unit);
130
+ } else {
131
+ attribute(st.annNodes[i]!, "annotations", annotations.length);
132
+ annotations.push(unit);
133
+ }
134
+ });
135
+ return {
136
+ units: {
137
+ errors: st.errs,
138
+ droppedErrors,
139
+ annotations,
140
+ droppedAnnotations,
141
+ },
142
+ root: st.hold.children[0]!,
143
+ };
144
+ }
145
+
146
+ /**
147
+ * The closure passed to every compiled unit function: shared helpers, the
148
+ * regex table (pattern source → compiled RegExp, via the engine's cache),
149
+ * and the interpreter trampoline for islands and fallbacks.
150
+ */
151
+ export interface Runtime {
152
+ readonly isObject: typeof isObject;
153
+ readonly isInteger: typeof isInteger;
154
+ readonly jsonEqual: typeof jsonEqual;
155
+ readonly canonicalKey: typeof canonicalKey;
156
+ readonly codePointLength: typeof codePointLength;
157
+ readonly escapeSegment: typeof escapeSegment;
158
+ readonly isMultipleOf: typeof isMultipleOf;
159
+ readonly hasDuplicateItems: typeof hasDuplicateItems;
160
+ readonly firstDuplicatePair: typeof firstDuplicatePair;
161
+ /** compiled-consumer coverage folds (COMPILED-CONSUMERS.md phase B) */
162
+ readonly foldNameCoverage: typeof foldNameCoverage;
163
+ readonly foldIndexCoverage: typeof foldIndexCoverage;
164
+ /** compiled RegExp by pattern source */
165
+ readonly re: Record<string, { test(s: string): boolean }>;
166
+ /** asserting-format definitions by name (the engine's format table, filtered) */
167
+ readonly formats: Record<string, { test(v: JsonValue): boolean }>;
168
+ /** the artifact's depth bound (D20), shared with trampolined fragments */
169
+ readonly maxDepth: number;
170
+ /** throws the typed depth error a compiled unit's guard trips (D20) */
171
+ tooDeep(): never;
172
+ /**
173
+ * Trampoline into the interpreter for one island/fallback fragment,
174
+ * charging `depth` against the shared budget (D20). Flag-mode callers use
175
+ * only `.valid`.
176
+ */
177
+ frag(
178
+ target: SchemaRef,
179
+ value: JsonValue,
180
+ scope: readonly string[],
181
+ depth: number,
182
+ ): boolean;
183
+ /**
184
+ * Coverage-harvesting trampoline (COMPILED-CONSUMERS.md phase B, §5): like
185
+ * {@link frag} for an island applied IN-PLACE under a compiled consumer,
186
+ * but the island's surviving root-cursor consumed dependency records are
187
+ * folded into the parent's runtime coverage channel `ev`. Consumed ids are
188
+ * always recorded (the M5.5 invariant), so an island always reports the
189
+ * coverage a consumer would observe. A failed island's root records are
190
+ * empty; the caller's mark/truncate is the uniform backstop either way.
191
+ */
192
+ fragCov(
193
+ target: SchemaRef,
194
+ value: JsonValue,
195
+ scope: readonly string[],
196
+ depth: number,
197
+ ev: unknown[],
198
+ ): boolean;
199
+ /**
200
+ * List-mode trampoline: evaluates the fragment with the caller's
201
+ * evaluation-path prefix, maps its error records to interpreter-exact
202
+ * units (instance locations re-rooted under `ip`), and appends them.
203
+ * `ev` (an island applied IN-PLACE from a tracked consumer's region body)
204
+ * additionally receives the {@link Runtime.fragCov} coverage harvest — the
205
+ * error mapping is unconditional either way, because a failed in-place
206
+ * island's ERRORS survive in list output (only its coverage span, which is
207
+ * empty on failure anyway, is the caller's truncate concern).
208
+ */
209
+ fragList(
210
+ target: SchemaRef,
211
+ value: JsonValue,
212
+ scope: readonly string[],
213
+ depth: number,
214
+ ep: string,
215
+ ip: string,
216
+ errs: ErrorUnit[],
217
+ ev?: unknown[],
218
+ ): boolean;
219
+ /**
220
+ * Annotation-mode list trampoline: like {@link fragList} for errors, then
221
+ * harvests the island's surviving root annotation records as annotation
222
+ * units (retention lists applied, instance locations re-rooted under `ip`)
223
+ * onto `anns`. `ev` composes exactly as on {@link Runtime.fragList}.
224
+ * Present only on annotation-mode artifacts.
225
+ */
226
+ fragListAnn?(
227
+ target: SchemaRef,
228
+ value: JsonValue,
229
+ scope: readonly string[],
230
+ depth: number,
231
+ ep: string,
232
+ ip: string,
233
+ errs: ErrorUnit[],
234
+ anns: AnnotationUnit[],
235
+ ev?: unknown[],
236
+ ): boolean;
237
+ /** Trace emission: a fresh per-evaluation state. Present only on trace artifacts. */
238
+ traceState?(): TraceState;
239
+ /** Trace emission: the node of one application, linked under its parent. */
240
+ traceNode?(
241
+ tp: NodeParent,
242
+ ep: string,
243
+ sloc: string,
244
+ ip: string,
245
+ ): MutableRenderNode;
246
+ /** Trace emission: an error unit with the application that raised it. */
247
+ traceError?(st: TraceState, tn: MutableRenderNode, unit: ErrorUnit): void;
248
+ /** Trace emission: an annotation unit with the application that raised it. */
249
+ traceAnn?(st: TraceState, tn: MutableRenderNode, unit: AnnotationUnit): void;
250
+ /**
251
+ * Trace emission: an accepting keyword's cut of the errors since mark
252
+ * `m`. The emitted source is level-neutral; the artifact's level decides
253
+ * here whether the cut discards them or moves them aside as dropped.
254
+ */
255
+ cutErrors?(st: TraceState, m: number): void;
256
+ /**
257
+ * Trace emission: a failed application's cut of the annotations since
258
+ * mark `m` — discarded, or flagged as dropped, by the artifact's level.
259
+ */
260
+ cutAnns?(st: TraceState, m: number): void;
261
+ /**
262
+ * Trace emission's island trampoline: the fragment runs traced, its
263
+ * subtree is grafted under the calling application, and its records join
264
+ * the channels attributed to the fragment nodes that raised them — on a
265
+ * retaining artifact, the fragment's own dropped records included. `ev`
266
+ * composes as on {@link Runtime.fragList}.
267
+ */
268
+ fragTrace?(
269
+ target: SchemaRef,
270
+ value: JsonValue,
271
+ scope: readonly string[],
272
+ depth: number,
273
+ ep: string,
274
+ ip: string,
275
+ st: TraceState,
276
+ tp: NodeParent,
277
+ ev?: unknown[],
278
+ ): boolean;
279
+ }
280
+
281
+ /**
282
+ * Builds the {@link Runtime} closure for an artifact bound to one registry.
283
+ * `retain` selects the verbose level's trace helpers: the relevance cuts
284
+ * keep the irrelevant records aside instead of discarding them.
285
+ */
286
+ export function makeRuntime(
287
+ registry: SchemaRegistry,
288
+ regexCache: RegexCache,
289
+ patterns: readonly string[],
290
+ maxDepth: number,
291
+ listParams = false,
292
+ annotate?: { selection?: boolean | AnnotationSelection },
293
+ formatTable?: FormatTable,
294
+ usedFormats: readonly string[] = [],
295
+ trace = false,
296
+ retain = false,
297
+ ): Runtime {
298
+ const re = Object.create(null) as Record<
299
+ string,
300
+ { test(s: string): boolean }
301
+ >;
302
+ for (const source of patterns) re[source] = regexCache.compile(source);
303
+ // Filter the engine's format table down to the names the artifact tests,
304
+ // exactly as `re` narrows the pattern cache. A used name with no backing
305
+ // definition is unreachable via public paths (the constructor rejects
306
+ // assertFormats without a table, and lower() only emits formatTest for
307
+ // table-present names), so a miss here is a compiler bug — fail loudly.
308
+ const formats = Object.create(null) as Record<
309
+ string,
310
+ { test(v: JsonValue): boolean }
311
+ >;
312
+ for (const name of usedFormats) {
313
+ const definition = formatTable?.[name];
314
+ if (definition === undefined) {
315
+ throw new TypeError(
316
+ `compiled artifact tests format '${name}' with no table definition`,
317
+ );
318
+ }
319
+ formats[name] = definition;
320
+ }
321
+ // Flag-mode annotation elision: no annotation is ever rendered. Dependency
322
+ // records for consumed behavior ids still record (M5.5 invariant, enforced
323
+ // by the engine's produce()), so unevaluated* inside fragments sees its
324
+ // channel.
325
+ const shouldRecord = makeRecordPredicate(false);
326
+ // Captured once: the coverage producers a consumer would observe, for the
327
+ // coverage harvests (COMPILED-CONSUMERS.md §5).
328
+ const coverageIds = registry.coverageIds();
329
+ // Annotation harvest predicate (built once): an island records exactly the
330
+ // annotations the selection's lists keep. `keep` runs later in the wrapper.
331
+ const annRecord = annotate
332
+ ? makeRecordPredicate(annotate.selection ?? true)
333
+ : null;
334
+ return {
335
+ isObject,
336
+ isInteger,
337
+ jsonEqual,
338
+ canonicalKey,
339
+ codePointLength,
340
+ escapeSegment,
341
+ isMultipleOf,
342
+ hasDuplicateItems,
343
+ firstDuplicatePair,
344
+ foldNameCoverage,
345
+ foldIndexCoverage,
346
+ re,
347
+ formats,
348
+ maxDepth,
349
+ tooDeep: () => {
350
+ throw new MaxDepthExceededError(
351
+ `compiled evaluation exceeds maxDepth (${maxDepth})`,
352
+ );
353
+ },
354
+ frag: (target, value, scope, depth) => {
355
+ const options: FragmentOptions = {
356
+ dynamicScope: scope,
357
+ depth,
358
+ regexCache,
359
+ maxDepth,
360
+ shouldRecord,
361
+ };
362
+ return evaluateFragment(registry, target, rootCursor(value), options)
363
+ .valid;
364
+ },
365
+ fragCov: (target, value, scope, depth, ev) => {
366
+ // The SAME cursor object must reach harvestCoverage: it filters root
367
+ // records by cursor identity (coverage.ts), so a second rootCursor()
368
+ // would match nothing.
369
+ const cursor = rootCursor(value);
370
+ const options: FragmentOptions = {
371
+ dynamicScope: scope,
372
+ depth,
373
+ regexCache,
374
+ maxDepth,
375
+ shouldRecord,
376
+ };
377
+ const result = evaluateFragment(registry, target, cursor, options);
378
+ ev.push(...harvestCoverage(result.dependencies, cursor, coverageIds));
379
+ return result.valid;
380
+ },
381
+ fragList: (target, value, scope, depth, ep, ip, errs, ev) => {
382
+ // One synthetic pre-escaped PathNode segment reproduces the caller's
383
+ // whole evaluation-path prefix (materializePath joins on "/").
384
+ const pathNode: PathNode | null =
385
+ ep === "" ? null : { parent: null, segment: ep.slice(1) };
386
+ // The SAME cursor object must reach harvestCoverage (see fragCov).
387
+ const cursor = rootCursor(value);
388
+ const options: FragmentOptions = {
389
+ dynamicScope: scope,
390
+ depth,
391
+ regexCache,
392
+ maxDepth,
393
+ shouldRecord,
394
+ pathNode,
395
+ };
396
+ const result = evaluateFragment(registry, target, cursor, options);
397
+ for (const record of result.errors) {
398
+ const unit = renderError(record, listParams);
399
+ unit.inputLocation = ip + unit.inputLocation;
400
+ errs.push(unit);
401
+ }
402
+ if (ev !== undefined) {
403
+ ev.push(...harvestCoverage(result.dependencies, cursor, coverageIds));
404
+ }
405
+ return result.valid;
406
+ },
407
+ ...(annotate
408
+ ? {
409
+ fragListAnn: (
410
+ target,
411
+ value,
412
+ scope,
413
+ depth,
414
+ ep,
415
+ ip,
416
+ errs,
417
+ anns,
418
+ ev,
419
+ ) => {
420
+ const pathNode: PathNode | null =
421
+ ep === "" ? null : { parent: null, segment: ep.slice(1) };
422
+ // The SAME cursor object must reach harvestCoverage (see fragCov).
423
+ const cursor = rootCursor(value);
424
+ const options: FragmentOptions = {
425
+ dynamicScope: scope,
426
+ depth,
427
+ regexCache,
428
+ maxDepth,
429
+ shouldRecord: annRecord,
430
+ pathNode,
431
+ };
432
+ const result = evaluateFragment(registry, target, cursor, options);
433
+ for (const record of result.errors) {
434
+ const unit = renderError(record, listParams);
435
+ unit.inputLocation = ip + unit.inputLocation;
436
+ errs.push(unit);
437
+ }
438
+ for (const a of result.annotations) {
439
+ const unit = renderAnnotation(a);
440
+ unit.inputLocation = ip + unit.inputLocation;
441
+ anns.push(unit);
442
+ }
443
+ if (ev !== undefined) {
444
+ ev.push(
445
+ ...harvestCoverage(result.dependencies, cursor, coverageIds),
446
+ );
447
+ }
448
+ return result.valid;
449
+ },
450
+ }
451
+ : {}),
452
+ ...(trace ? traceHelpers(retain) : {}),
453
+ };
454
+
455
+ function traceHelpers(
456
+ retain: boolean,
457
+ ): Pick<
458
+ Runtime,
459
+ | "traceState"
460
+ | "traceNode"
461
+ | "traceError"
462
+ | "traceAnn"
463
+ | "cutErrors"
464
+ | "cutAnns"
465
+ | "fragTrace"
466
+ > {
467
+ return {
468
+ traceState: () => ({
469
+ valid: true,
470
+ errs: [],
471
+ errNodes: [],
472
+ droppedErrs: [],
473
+ droppedErrNodes: [],
474
+ anns: [],
475
+ annNodes: [],
476
+ annDropped: retain ? new Set() : null,
477
+ hold: { children: [] },
478
+ }),
479
+ traceNode: (tp, ep, sloc, ip) => {
480
+ const node: MutableRenderNode = {
481
+ evaluationPath: ep,
482
+ schemaLocation: sloc,
483
+ inputLocation: ip,
484
+ valid: true,
485
+ keywords: [],
486
+ errors: [],
487
+ droppedErrors: [],
488
+ annotations: [],
489
+ droppedAnnotations: [],
490
+ children: [],
491
+ };
492
+ tp.children.push(node);
493
+ return node;
494
+ },
495
+ traceError: (st, tn, unit) => {
496
+ st.errs.push(unit);
497
+ st.errNodes.push(tn);
498
+ },
499
+ traceAnn: (st, tn, unit) => {
500
+ st.anns.push(unit);
501
+ st.annNodes.push(tn);
502
+ },
503
+ // The relevant level discards what an accepting keyword or a failed
504
+ // application made irrelevant, exactly as list mode truncates. The
505
+ // verbose level keeps it: dropped errors move aside in drop order
506
+ // (the interpreter's `dropErrorsFrom`); dropped annotations stay in
507
+ // place, flagged, so the marks that later cuts take remain positions
508
+ // in the one recording-ordered channel.
509
+ cutErrors: retain
510
+ ? (st, m) => {
511
+ const { errs, errNodes } = st;
512
+ for (let i = m; i < errs.length; i++) {
513
+ st.droppedErrs.push(errs[i]!);
514
+ st.droppedErrNodes.push(errNodes[i]!);
515
+ }
516
+ errs.length = m;
517
+ errNodes.length = m;
518
+ }
519
+ : (st, m) => {
520
+ st.errs.length = m;
521
+ st.errNodes.length = m;
522
+ },
523
+ cutAnns: retain
524
+ ? (st, m) => {
525
+ const flagged = st.annDropped!;
526
+ for (let i = m; i < st.anns.length; i++) flagged.add(i);
527
+ }
528
+ : (st, m) => {
529
+ st.anns.length = m;
530
+ st.annNodes.length = m;
531
+ },
532
+ fragTrace: (target, value, scope, depth, ep, ip, st, tp, ev) => {
533
+ const pathNode: PathNode | null =
534
+ ep === "" ? null : { parent: null, segment: ep.slice(1) };
535
+ // The SAME cursor object must reach harvestCoverage (see fragCov).
536
+ const cursor = rootCursor(value);
537
+ const options: FragmentOptions = {
538
+ dynamicScope: scope,
539
+ depth,
540
+ regexCache,
541
+ maxDepth,
542
+ shouldRecord: annRecord ?? shouldRecord,
543
+ pathNode,
544
+ tracing: true,
545
+ };
546
+ const result = evaluateFragment(registry, target, cursor, options);
547
+ // The fragment's tree hangs under the calling application whether or
548
+ // not it passed: the interpreter keeps failed applications too.
549
+ const { root, at } = traceToRenderNodes(result.traceRoot!, ip);
550
+ tp.children.push(root);
551
+ const owner = (node: PathNode | null): NodeRef => {
552
+ const nodes = at.get(node)!;
553
+ return nodes.length === 1 ? nodes[0]! : nodes;
554
+ };
555
+ for (const record of result.errors) {
556
+ const unit = renderError(record, listParams);
557
+ unit.inputLocation = ip + unit.inputLocation;
558
+ st.errs.push(unit);
559
+ st.errNodes.push(owner(record.pathNode));
560
+ }
561
+ if (retain) {
562
+ // The fragment ran to completion between two compiled cuts, so
563
+ // appending its drops here keeps the whole run's drop order.
564
+ for (const record of result.droppedErrors) {
565
+ const unit = renderError(record, listParams);
566
+ unit.inputLocation = ip + unit.inputLocation;
567
+ st.droppedErrs.push(unit);
568
+ st.droppedErrNodes.push(owner(record.pathNode));
569
+ }
570
+ // Every annotation the fragment recorded, in recording order;
571
+ // those outside its root frame's survivors are dropped — all of
572
+ // them when the fragment failed, since a failed application's
573
+ // frame is discarded whole.
574
+ const relevant = result.valid ? new Set(result.annotations) : null;
575
+ for (const a of result.allAnnotations) {
576
+ const unit = renderAnnotation(a);
577
+ unit.inputLocation = ip + unit.inputLocation;
578
+ if (!relevant?.has(a)) {
579
+ st.annDropped!.add(st.anns.length);
580
+ }
581
+ st.anns.push(unit);
582
+ st.annNodes.push(owner(a.pathNode));
583
+ }
584
+ } else {
585
+ for (const a of result.annotations) {
586
+ const unit = renderAnnotation(a);
587
+ unit.inputLocation = ip + unit.inputLocation;
588
+ st.anns.push(unit);
589
+ st.annNodes.push(owner(a.pathNode));
590
+ }
591
+ }
592
+ if (ev !== undefined) {
593
+ ev.push(...harvestCoverage(result.dependencies, cursor, coverageIds));
594
+ }
595
+ return result.valid;
596
+ },
597
+ };
598
+ }
599
+ }