@oh-my-pi/omptype 17.2.6 → 17.2.8

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.
@@ -0,0 +1,942 @@
1
+ /**
2
+ * JIT compiler: lowers schema IR into a specialized validator via
3
+ * `new Function`. Invoked by `type.ts` after a schema's third call; the
4
+ * interpreter (`interp.ts`) covers earlier calls so rarely-used schemas never
5
+ * pay codegen cost.
6
+ *
7
+ * Generated code philosophy:
8
+ * - success path is straight-line monomorphic JS with zero allocation; when
9
+ * the schema has no morphs the input value itself is returned
10
+ * - failure allocates a single `OmpErrors` (`E(path, expected, data)`); path
11
+ * arrays and messages are inline literals, so cost is one small allocation
12
+ * - morphing nodes (defaults, `"+": "delete"`, embedded stepped schemas)
13
+ * produce a fresh output object; pure subtrees below them stay check-only
14
+ * - morphing union members use separately compiled or hoisted runners; pure
15
+ * members compile to inline predicates
16
+ */
17
+ import { MISSING, OmpErrors } from "./errors.js";
18
+ import { canRefineUnionFailure, materializeDefault, unionFail, walk } from "./interp.js";
19
+ import { expectedOf, hasMorph } from "./ir.js";
20
+ const own = Object.prototype.hasOwnProperty;
21
+ const IDENT = /^[A-Za-z_$][\w$]*$/;
22
+ /** Inline-able literal, else undefined (caller hoists into the refs pool). */
23
+ function litSource(v) {
24
+ if (v === null)
25
+ return "null";
26
+ if (v === undefined)
27
+ return "undefined";
28
+ switch (typeof v) {
29
+ case "string":
30
+ case "boolean":
31
+ return JSON.stringify(v);
32
+ case "number":
33
+ return Number.isFinite(v) ? String(v) : undefined;
34
+ default:
35
+ return undefined;
36
+ }
37
+ }
38
+ function isPrimitiveLiteral(node) {
39
+ return node.k === "lit" && (node.v === null || (typeof node.v !== "object" && typeof node.v !== "function"));
40
+ }
41
+ /** Whether `undefined` necessarily fails, allowing property-presence checks to be elided. */
42
+ function rejectsUndefined(node) {
43
+ switch (node.k) {
44
+ case "unknown":
45
+ case "undefined":
46
+ case "alias":
47
+ case "sub":
48
+ return false;
49
+ case "lit":
50
+ return node.v !== undefined;
51
+ case "union":
52
+ return node.members.every(rejectsUndefined);
53
+ case "intersection":
54
+ return node.members.some(rejectsUndefined);
55
+ case "refine":
56
+ return rejectsUndefined(node.base);
57
+ case "morph":
58
+ return rejectsUndefined(node.input);
59
+ default:
60
+ return true;
61
+ }
62
+ }
63
+ class CompiledMorphContext {
64
+ #path;
65
+ #data;
66
+ constructor(path, data) {
67
+ this.#path = path;
68
+ this.#data = data;
69
+ }
70
+ error(expectation) {
71
+ return new OmpErrors(this.#path, expectation, this.#data);
72
+ }
73
+ reject(expectation) {
74
+ return this.error(expectation);
75
+ }
76
+ }
77
+ class Builder {
78
+ #lines = [];
79
+ #refs = [];
80
+ #activeAliases;
81
+ #id = 0;
82
+ next(prefix) {
83
+ return `${prefix}${this.#id++}`;
84
+ }
85
+ push(line) {
86
+ this.#lines.push(line);
87
+ }
88
+ ref(value) {
89
+ const idx = this.#refs.indexOf(value);
90
+ if (idx >= 0)
91
+ return `R[${idx}]`;
92
+ this.#refs.push(value);
93
+ return `R[${this.#refs.length - 1}]`;
94
+ }
95
+ lit(v) {
96
+ return litSource(v) ?? this.ref(v);
97
+ }
98
+ access(base, key) {
99
+ return typeof key === "string" && IDENT.test(key) ? `${base}.${key}` : `${base}[${this.lit(key)}]`;
100
+ }
101
+ pathExpr(segs) {
102
+ const parts = segs.map(seg => ("s" in seg ? this.lit(seg.s) : seg.d));
103
+ return `[${parts.join(",")}]`;
104
+ }
105
+ storedPathExpr(segs) {
106
+ if (segs.length === 0)
107
+ return "undefined";
108
+ if (segs.length === 1) {
109
+ const seg = segs[0];
110
+ return "s" in seg ? this.lit(seg.s) : seg.d;
111
+ }
112
+ const staticParts = [];
113
+ for (const seg of segs) {
114
+ if ("d" in seg)
115
+ return this.pathExpr(segs);
116
+ staticParts.push(seg.s);
117
+ }
118
+ return this.ref(staticParts);
119
+ }
120
+ error(segs, expected, dataExpr) {
121
+ return `new AE(${this.storedPathExpr(segs)},${JSON.stringify(expected)},${dataExpr})`;
122
+ }
123
+ fail(segs, expected, dataExpr) {
124
+ return `return ${this.error(segs, expected, dataExpr)}`;
125
+ }
126
+ appendError(errors, error) {
127
+ return `if(${errors}===undefined)${errors}=${error};else ${errors}.append(${error});`;
128
+ }
129
+ /** Pure boolean predicate for a morph-free subtree. */
130
+ predicate(node, v) {
131
+ switch (node.k) {
132
+ case "unknown":
133
+ return "true";
134
+ case "null":
135
+ return `${v}===null`;
136
+ case "undefined":
137
+ return `${v}===undefined`;
138
+ case "boolean":
139
+ return `typeof ${v}==="boolean"`;
140
+ case "bigint":
141
+ return `typeof ${v}==="bigint"`;
142
+ case "symbol":
143
+ return `typeof ${v}==="symbol"`;
144
+ case "never":
145
+ return "false";
146
+ case "anyobject":
147
+ return `(typeof ${v}==="object"&&${v}!==null)`;
148
+ case "lit":
149
+ return node.v instanceof Date
150
+ ? `(${v} instanceof Date&&${v}.valueOf()===${node.v.valueOf()})`
151
+ : `${v}===${this.lit(node.v)}`;
152
+ case "instance":
153
+ return `${v} instanceof ${this.ref(node.ctor)}`;
154
+ case "string": {
155
+ let out = `typeof ${v}==="string"`;
156
+ if (node.min !== undefined)
157
+ out += `&&${v}.length>=${node.min}`;
158
+ if (node.max !== undefined)
159
+ out += `&&${v}.length<=${node.max}`;
160
+ if (node.url)
161
+ out += `&&URL.canParse(${v})`;
162
+ return out;
163
+ }
164
+ case "number": {
165
+ let out = node.int ? `Number.isInteger(${v})` : `Number.isFinite(${v})`;
166
+ if (node.divisor !== undefined)
167
+ out += `&&${v}%${node.divisor}===0`;
168
+ if (node.min !== undefined)
169
+ out += `&&${v}${node.xmin ? ">" : ">="}${node.min}`;
170
+ if (node.max !== undefined)
171
+ out += `&&${v}${node.xmax ? "<" : "<="}${node.max}`;
172
+ return out;
173
+ }
174
+ case "union": {
175
+ const lits = node.members.filter(isPrimitiveLiteral);
176
+ if (lits.length > 8) {
177
+ const values = this.ref(new Set(lits.map(member => member.v)));
178
+ const literalNodes = new Set(lits);
179
+ const rest = node.members.filter(member => !literalNodes.has(member));
180
+ let out = `${values}.has(${v})`;
181
+ for (const m of rest)
182
+ out += `||(${this.predicate(m, v)})`;
183
+ return `(${out})`;
184
+ }
185
+ return `(${node.members.map(m => `(${this.predicate(m, v)})`).join("||")})`;
186
+ }
187
+ case "intersection":
188
+ return `(${node.members.map(member => `(${this.predicate(member, v)})`).join("&&")})`;
189
+ case "array": {
190
+ const array = this.next("a");
191
+ const index = this.next("i");
192
+ let out = `Array.isArray(${v})`;
193
+ if (node.min !== undefined)
194
+ out += `&&${v}.length>=${node.min}`;
195
+ if (node.max !== undefined)
196
+ out += `&&${v}.length<=${node.max}`;
197
+ const item = `${array}[${index}]`;
198
+ out += `&&((${array})=>{for(let ${index}=0;${index}<${array}.length;${index}++)if(!(${this.predicate(node.el, item)}))return false;return true})(${v})`;
199
+ return out;
200
+ }
201
+ case "object": {
202
+ const checks = [`typeof ${v}==="object"`, `${v}!==null`];
203
+ for (const p of node.props) {
204
+ const av = this.access(v, p.key);
205
+ const present = `${this.lit(p.key)} in ${v}`;
206
+ const predicate = this.predicate(p.val, av);
207
+ checks.push(p.opt || p.hasDefault
208
+ ? rejectsUndefined(p.val)
209
+ ? `((${av}!==undefined&&(${predicate}))||!(${present}))`
210
+ : `(!(${present})||(${predicate}))`
211
+ : rejectsUndefined(p.val)
212
+ ? predicate
213
+ : `((${present})&&(${predicate}))`);
214
+ }
215
+ const stringKey = this.next("k");
216
+ if (node.index !== undefined) {
217
+ checks.push(`(()=>{for(const ${stringKey} in ${v})if(own.call(${v},${stringKey})&&!(${this.predicate(node.index, `${v}[${stringKey}]`)}))return false;return true})()`);
218
+ }
219
+ if (node.patternIndexes !== undefined) {
220
+ for (const pattern of node.patternIndexes) {
221
+ checks.push(`(()=>{for(const ${stringKey} in ${v})if(own.call(${v},${stringKey})&&(${this.predicate(pattern.key, stringKey)})&&!(${this.predicate(pattern.val, `${v}[${stringKey}]`)}))return false;return true})()`);
222
+ }
223
+ }
224
+ if (node.symbolIndex !== undefined) {
225
+ const symbol = this.next("s");
226
+ checks.push(`(()=>{for(const ${symbol} of Object.getOwnPropertySymbols(${v}))if(Object.prototype.propertyIsEnumerable.call(${v},${symbol})&&!(${this.predicate(node.symbolIndex, `${v}[${symbol}]`)}))return false;return true})()`);
227
+ }
228
+ if (node.extras === "reject") {
229
+ const patternMatch = node.patternIndexes?.map(pattern => `(${this.predicate(pattern.key, stringKey)})`).join("||") ??
230
+ "false";
231
+ if (node.index === undefined) {
232
+ checks.push(`(()=>{for(const ${stringKey} in ${v})if(own.call(${v},${stringKey})&&!(${this.declaredCheck(node.props, stringKey)})&&!(${patternMatch}))return false;return true})()`);
233
+ }
234
+ if (node.symbolIndex === undefined) {
235
+ const symbol = this.next("s");
236
+ checks.push(`(()=>{for(const ${symbol} of Object.getOwnPropertySymbols(${v}))if(Object.prototype.propertyIsEnumerable.call(${v},${symbol})&&!(${this.declaredCheck(node.props, symbol)}))return false;return true})()`);
237
+ }
238
+ }
239
+ return `(${checks.join("&&")})`;
240
+ }
241
+ case "sub":
242
+ return `!(${this.ref(node.schema.run)}(${v}) instanceof AE)`;
243
+ default:
244
+ return `!(${this.ref(boundWalk(node))}(${v}) instanceof AE)`;
245
+ }
246
+ }
247
+ declaredCheck(props, keyVar) {
248
+ if (props.length === 0)
249
+ return "false";
250
+ if (props.length > 6) {
251
+ const set = this.ref(new Set(props.map(p => p.key)));
252
+ return `${set}.has(${keyVar})`;
253
+ }
254
+ return `(${props.map(p => `${keyVar}===${this.lit(p.key)}`).join("||")})`;
255
+ }
256
+ /**
257
+ * Run a node through its interpreter/sub-schema runner, appending any
258
+ * failure to `errors`. `brk` (when given) exits the enclosing block on
259
+ * failure so dependent statements (output assignment, morph fns) are
260
+ * skipped. Both runner kinds receive the absolute path so nested step
261
+ * callbacks observe ctx.path; walk-produced errors are already absolute,
262
+ * while sub runners return schema-relative errors that need prefixing.
263
+ */
264
+ emitCollectDelegate(node, v, segs, errors, brk, out) {
265
+ const sub = node.k === "sub";
266
+ const runner = sub ? node.schema.run : boundWalk(node);
267
+ const result = this.next("r");
268
+ const args = segs.length > 0 ? `${v},${this.pathExpr(segs)}` : v;
269
+ this.push(`const ${result}=${this.ref(runner)}(${args});`);
270
+ const failure = sub && segs.length > 0 ? `PF(${result},${this.pathExpr(segs)})` : result;
271
+ this.push(`if(${result} instanceof AE){${this.appendError(errors, failure)}${brk === undefined ? "" : `break ${brk};`}}`);
272
+ if (out !== undefined)
273
+ this.push(`${out}=${result};`);
274
+ }
275
+ /** Snapshot the error count so sequencing sites can detect soft failures. */
276
+ markErrors(errors) {
277
+ const mark = this.next("n");
278
+ this.push(`const ${mark}=${errors}===void 0?0:${errors}.length;`);
279
+ return mark;
280
+ }
281
+ /** Exit `brk` when errors were appended since `mark` (interp's return-on-error). */
282
+ guardGrowth(errors, mark, brk) {
283
+ this.push(`if((${errors}===void 0?0:${errors}.length)!==${mark})break ${brk};`);
284
+ }
285
+ /** Aggregate every independent failure in a morph-free subtree. */
286
+ emitCollectCheck(node, v, segs, errors, failureData = v) {
287
+ if (node.cfg !== undefined || node.k === "refine") {
288
+ this.emitCollectDelegate(node, v, segs, errors);
289
+ return;
290
+ }
291
+ switch (node.k) {
292
+ case "unknown":
293
+ return;
294
+ case "array": {
295
+ this.push(`if(!Array.isArray(${v})){${this.appendError(errors, this.error(segs, "an array", failureData))}}`);
296
+ if (node.min !== undefined) {
297
+ this.push(`else if(${v}.length<${node.min}){${this.appendError(errors, this.error(segs, `at least length ${node.min}`, `${v}.length`))}}`);
298
+ }
299
+ if (node.max !== undefined) {
300
+ this.push(`else if(${v}.length>${node.max}){${this.appendError(errors, this.error(segs, `at most length ${node.max}`, `${v}.length`))}}`);
301
+ }
302
+ this.push("else{");
303
+ const index = this.next("i");
304
+ this.push(`for(let ${index}=0;${index}<${v}.length;${index}++){`);
305
+ this.emitCollectCheck(node.el, `${v}[${index}]`, [...segs, { d: index }], errors);
306
+ this.push("}}");
307
+ return;
308
+ }
309
+ case "tuple": {
310
+ this.push(`if(!Array.isArray(${v})){${this.appendError(errors, this.error(segs, "an array", failureData))}}else{`);
311
+ const requiredPrefix = node.prefix.filter(item => !item.opt && !item.hasDefault).length;
312
+ const minimum = requiredPrefix + node.postfix.length;
313
+ const maximum = node.prefix.length + node.postfix.length;
314
+ if (minimum > 0) {
315
+ this.push(`if(${v}.length<${minimum}){${this.appendError(errors, this.error(segs, `an array of at least length ${minimum}`, failureData))}}else{`);
316
+ }
317
+ if (node.variadic === undefined) {
318
+ this.push(`if(${v}.length>${maximum}){${this.appendError(errors, this.error(segs, `an array of at most length ${maximum}`, failureData))}}else{`);
319
+ }
320
+ let postfixStart = `${v}.length`;
321
+ if (node.postfix.length > 0) {
322
+ postfixStart = this.next("p");
323
+ this.push(`const ${postfixStart}=${v}.length-${node.postfix.length};`);
324
+ }
325
+ let prefixCount = String(node.prefix.length);
326
+ if (requiredPrefix !== node.prefix.length) {
327
+ prefixCount = this.next("n");
328
+ this.push(`const ${prefixCount}=Math.min(${node.prefix.length},${postfixStart});`);
329
+ }
330
+ for (let index = 0; index < node.prefix.length; index++) {
331
+ if (index >= requiredPrefix)
332
+ this.push(`if(${index}<${prefixCount}){`);
333
+ this.emitCollectCheck(node.prefix[index].val, `${v}[${index}]`, [...segs, { d: String(index) }], errors);
334
+ if (index >= requiredPrefix)
335
+ this.push("}");
336
+ }
337
+ if (node.variadic !== undefined) {
338
+ const index = this.next("i");
339
+ this.push(`for(let ${index}=${prefixCount};${index}<${postfixStart};${index}++){`);
340
+ this.emitCollectCheck(node.variadic, `${v}[${index}]`, [...segs, { d: index }], errors);
341
+ this.push("}");
342
+ }
343
+ for (let index = 0; index < node.postfix.length; index++) {
344
+ const inputIndex = index === 0 ? postfixStart : `${postfixStart}+${index}`;
345
+ this.emitCollectCheck(node.postfix[index], `${v}[${inputIndex}]`, [...segs, { d: inputIndex }], errors);
346
+ }
347
+ if (node.variadic === undefined)
348
+ this.push("}");
349
+ if (minimum > 0)
350
+ this.push("}");
351
+ this.push("}");
352
+ return;
353
+ }
354
+ case "object": {
355
+ if (node.patternIndexes !== undefined ||
356
+ node.symbolIndex !== undefined ||
357
+ node.props.some(prop => typeof prop.key === "symbol")) {
358
+ this.emitCollectDelegate(node, v, segs, errors);
359
+ return;
360
+ }
361
+ this.push(`if(typeof ${v}!=="object"||${v}===null){${this.appendError(errors, this.error(segs, "an object", failureData))}}else{`);
362
+ for (const prop of node.props) {
363
+ const present = `${this.lit(prop.key)} in ${v}`;
364
+ const propSegs = [...segs, { s: prop.key }];
365
+ if (prop.opt || prop.hasDefault) {
366
+ this.push(`if(${present}){`);
367
+ this.emitCollectCheck(prop.val, this.access(v, prop.key), propSegs, errors);
368
+ this.push("}");
369
+ }
370
+ else {
371
+ this.push(`if(!(${present})){${this.appendError(errors, this.error(propSegs, expectedOf(prop.val), "M"))}}else{`);
372
+ this.emitCollectCheck(prop.val, this.access(v, prop.key), propSegs, errors);
373
+ this.push("}");
374
+ }
375
+ }
376
+ if (node.index !== undefined) {
377
+ const key = this.next("k");
378
+ this.push(`for(const ${key} in ${v})if(own.call(${v},${key})){`);
379
+ this.emitCollectCheck(node.index, `${v}[${key}]`, [...segs, { d: key }], errors);
380
+ this.push("}");
381
+ }
382
+ else if (node.extras === "reject") {
383
+ const key = this.next("k");
384
+ this.push(`for(const ${key} in ${v})if(own.call(${v},${key})&&!(${this.declaredCheck(node.props, key)})){`);
385
+ this.push(this.appendError(errors, this.error([...segs, { d: key }], "removed", `${v}[${key}]`)));
386
+ this.push("}");
387
+ }
388
+ if (node.extras === "reject") {
389
+ const symbol = this.next("s");
390
+ this.push(`for(const ${symbol} of Object.getOwnPropertySymbols(${v}))if(Object.prototype.propertyIsEnumerable.call(${v},${symbol})&&!(${this.declaredCheck(node.props, symbol)})){${this.appendError(errors, this.error([...segs, { d: symbol }], "removed", `${v}[${symbol}]`))}}`);
391
+ }
392
+ this.push("}");
393
+ return;
394
+ }
395
+ case "union": {
396
+ const failure = node.members.some(canRefineUnionFailure)
397
+ ? `UF(${this.ref(node)},${failureData},${this.pathExpr(segs)},${JSON.stringify(expectedOf(node))})`
398
+ : this.error(segs, expectedOf(node), failureData);
399
+ this.push(`if(!(${this.predicate(node, v)})){${this.appendError(errors, failure)}}`);
400
+ return;
401
+ }
402
+ case "string": {
403
+ this.push(`if(typeof ${v}!=="string"){${this.appendError(errors, this.error(segs, "a string", failureData))}}`);
404
+ if (node.min !== undefined) {
405
+ this.push(`else if(${v}.length<${node.min}){${this.appendError(errors, this.error(segs, `at least length ${node.min}`, `${v}.length`))}}`);
406
+ }
407
+ if (node.max !== undefined) {
408
+ this.push(`else if(${v}.length>${node.max}){${this.appendError(errors, this.error(segs, `at most length ${node.max}`, `${v}.length`))}}`);
409
+ }
410
+ if (node.url) {
411
+ this.push(`else if(!URL.canParse(${v})){${this.appendError(errors, this.error(segs, "a URL string", failureData))}}`);
412
+ }
413
+ return;
414
+ }
415
+ case "number": {
416
+ this.push(`if(typeof ${v}!=="number"||!Number.isFinite(${v})){${this.appendError(errors, this.error(segs, node.int ? "an integer" : "a number", failureData))}}else{`);
417
+ if (node.int) {
418
+ this.push(`if(!Number.isInteger(${v})){${this.appendError(errors, this.error(segs, "an integer", failureData))}}`);
419
+ }
420
+ if (node.divisor !== undefined) {
421
+ this.push(`if(${v}%${node.divisor}!==0){${this.appendError(errors, this.error(segs, `a number divisible by ${node.divisor}`, failureData))}}`);
422
+ }
423
+ if (node.min !== undefined) {
424
+ const expected = node.min === 0
425
+ ? node.xmin
426
+ ? "positive"
427
+ : "non-negative"
428
+ : `a number ${node.xmin ? "more than" : "at least"} ${node.min}`;
429
+ this.push(`if(${v}${node.xmin ? "<=" : "<"}${node.min}){${this.appendError(errors, this.error(segs, expected, failureData))}}`);
430
+ }
431
+ if (node.max !== undefined) {
432
+ const expected = node.max === 0
433
+ ? node.xmax
434
+ ? "negative"
435
+ : "non-positive"
436
+ : `a number ${node.xmax ? "less than" : "at most"} ${node.max}`;
437
+ this.push(`if(${v}${node.xmax ? ">=" : ">"}${node.max}){${this.appendError(errors, this.error(segs, expected, failureData))}}`);
438
+ }
439
+ this.push("}");
440
+ return;
441
+ }
442
+ case "lit":
443
+ if ((node.v !== null && typeof node.v === "object" && !(node.v instanceof Date)) ||
444
+ typeof node.v === "function") {
445
+ this.emitCollectDelegate(node, v, segs, errors);
446
+ }
447
+ else {
448
+ this.push(`if(!(${this.predicate(node, v)})){${this.appendError(errors, this.error(segs, expectedOf(node), failureData))}}`);
449
+ }
450
+ return;
451
+ case "intersection":
452
+ case "sub":
453
+ this.emitCollectDelegate(node, v, segs, errors);
454
+ return;
455
+ case "null":
456
+ case "undefined":
457
+ case "boolean":
458
+ case "bigint":
459
+ case "symbol":
460
+ case "never":
461
+ case "anyobject":
462
+ case "instance":
463
+ this.push(`if(!(${this.predicate(node, v)})){${this.appendError(errors, this.error(segs, expectedOf(node), failureData))}}`);
464
+ return;
465
+ default:
466
+ this.emitCollectDelegate(node, v, segs, errors);
467
+ }
468
+ }
469
+ emitTupleShape(node, v, segs, errors, brk, failureData) {
470
+ this.push(`if(!Array.isArray(${v})){${this.appendError(errors, this.error(segs, "an array", failureData))}break ${brk};}`);
471
+ const requiredPrefix = node.prefix.filter(item => !item.opt && !item.hasDefault).length;
472
+ const minimum = requiredPrefix + node.postfix.length;
473
+ if (minimum > 0) {
474
+ this.push(`if(${v}.length<${minimum}){${this.appendError(errors, this.error(segs, `an array of at least length ${minimum}`, failureData))}break ${brk};}`);
475
+ }
476
+ if (node.variadic === undefined) {
477
+ const maximum = node.prefix.length + node.postfix.length;
478
+ this.push(`if(${v}.length>${maximum}){${this.appendError(errors, this.error(segs, `an array of at most length ${maximum}`, failureData))}break ${brk};}`);
479
+ }
480
+ let postfixStart = `${v}.length`;
481
+ if (node.postfix.length > 0) {
482
+ postfixStart = this.next("p");
483
+ this.push(`const ${postfixStart}=${v}.length-${node.postfix.length};`);
484
+ }
485
+ let prefixCount = String(node.prefix.length);
486
+ if (requiredPrefix !== node.prefix.length) {
487
+ prefixCount = this.next("n");
488
+ this.push(`const ${prefixCount}=Math.min(${node.prefix.length},${postfixStart});`);
489
+ }
490
+ return { postfixStart, prefixCount, requiredPrefix };
491
+ }
492
+ /** Fill `target` with a validated default (factory output revalidated per call). */
493
+ emitDefaultFill(val, def, isFactory, target, segs, errors) {
494
+ if (isFactory && typeof def === "function") {
495
+ const candidate = this.next("d");
496
+ const resolved = this.next("t");
497
+ const label = this.next("L");
498
+ this.push(`const ${candidate}=${this.ref(def)}();let ${resolved};${label}:{`);
499
+ this.emitCollectProduce(val, candidate, segs, resolved, errors, label);
500
+ this.push(`${target}=${resolved};}`);
501
+ }
502
+ else {
503
+ // Static defaults were prevalidated at construction; MD clones
504
+ // mutable payloads so callers cannot alias the schema's copy.
505
+ this.push(`${target}=${litSource(def) ?? `MD(${this.ref(def)})`};`);
506
+ }
507
+ }
508
+ /**
509
+ * Validate `v` against a morphing subtree and assign the produced output
510
+ * to `out` (an already-declared `let`). Failures append to `errors` and
511
+ * `break ${brk}` (skipping the output assignment), mirroring interp: an
512
+ * error in one child never suppresses sibling validation or morphs.
513
+ */
514
+ emitCollectProduce(node, v, segs, out, errors, brk, failureData = v) {
515
+ if (node.cfg !== undefined || node.k === "refine") {
516
+ this.emitCollectDelegate(node, v, segs, errors, brk, out);
517
+ return;
518
+ }
519
+ if (!hasMorph(node)) {
520
+ this.emitCollectCheck(node, v, segs, errors, failureData);
521
+ this.push(`${out}=${v};`);
522
+ return;
523
+ }
524
+ switch (node.k) {
525
+ case "sub":
526
+ this.emitCollectDelegate(node, v, segs, errors, brk, out);
527
+ return;
528
+ case "morph": {
529
+ const input = this.next("t");
530
+ this.push(`let ${input};`);
531
+ const mark = this.markErrors(errors);
532
+ this.emitCollectProduce(node.input, v, segs, input, errors, brk, failureData);
533
+ this.guardGrowth(errors, mark, brk);
534
+ const context = this.next("c");
535
+ const result = this.next("r");
536
+ this.push(`const ${context}=new MC(${this.storedPathExpr(segs)},${input});`);
537
+ this.push(`const ${result}=${this.ref(node.fn)}(${input},${context});`);
538
+ this.push(`if(${result} instanceof AE){${this.appendError(errors, result)}break ${brk};}`);
539
+ if (node.out === undefined)
540
+ this.push(`${out}=${result};`);
541
+ else
542
+ this.emitCollectProduce(node.out, result, segs, out, errors, brk);
543
+ return;
544
+ }
545
+ case "alias": {
546
+ let active = this.#activeAliases;
547
+ if (active === undefined) {
548
+ active = new Set();
549
+ this.#activeAliases = active;
550
+ }
551
+ if (active.has(node)) {
552
+ this.emitCollectDelegate(node, v, segs, errors, brk, out);
553
+ return;
554
+ }
555
+ active.add(node);
556
+ try {
557
+ this.emitCollectProduce(node.resolve(), v, segs, out, errors, brk, failureData);
558
+ }
559
+ finally {
560
+ active.delete(node);
561
+ }
562
+ return;
563
+ }
564
+ case "union": {
565
+ const ok = this.next("u");
566
+ this.push(`let ${ok}=false;`);
567
+ const label = this.next("b");
568
+ this.push(`${label}:{`);
569
+ for (const m of node.members) {
570
+ if (m.k !== "sub" && !hasMorph(m)) {
571
+ this.push(`if(${this.predicate(m, v)}){${out}=${v};${ok}=true;break ${label};}`);
572
+ }
573
+ }
574
+ for (const m of node.members) {
575
+ if (m.k === "sub" || hasMorph(m)) {
576
+ const runner = m.k === "sub" ? m.schema.run : m.k === "alias" ? boundWalk(m) : compile(m);
577
+ const r = this.next("r");
578
+ this.push(`const ${r}=${this.ref(runner)}(${v});`);
579
+ this.push(`if(!(${r} instanceof AE)){${out}=${r};${ok}=true;break ${label};}`);
580
+ }
581
+ }
582
+ this.push("}");
583
+ const failure = `UF(${this.ref(node)},${failureData},${this.pathExpr(segs)},${JSON.stringify(expectedOf(node))})`;
584
+ this.push(`if(!${ok}){${this.appendError(errors, failure)}break ${brk};}`);
585
+ return;
586
+ }
587
+ case "array": {
588
+ this.push(`if(!Array.isArray(${v})){${this.appendError(errors, this.error(segs, "an array", failureData))}break ${brk};}`);
589
+ if (node.min !== undefined) {
590
+ this.push(`if(${v}.length<${node.min}){${this.appendError(errors, this.error(segs, `at least length ${node.min}`, `${v}.length`))}break ${brk};}`);
591
+ }
592
+ if (node.max !== undefined) {
593
+ this.push(`if(${v}.length>${node.max}){${this.appendError(errors, this.error(segs, `at most length ${node.max}`, `${v}.length`))}break ${brk};}`);
594
+ }
595
+ const array = this.next("a");
596
+ const index = this.next("i");
597
+ const input = this.next("x");
598
+ const element = this.next("t");
599
+ const label = this.next("L");
600
+ this.push(`const ${array}=new Array(${v}.length);`);
601
+ this.push(`for(let ${index}=0;${index}<${v}.length;${index}++){const ${input}=${v}[${index}];let ${element};${label}:{`);
602
+ this.emitCollectProduce(node.el, input, [...segs, { d: index }], element, errors, label);
603
+ this.push(`${array}[${index}]=${element};}}`);
604
+ this.push(`${out}=${array};`);
605
+ return;
606
+ }
607
+ case "tuple": {
608
+ const { postfixStart, prefixCount, requiredPrefix } = this.emitTupleShape(node, v, segs, errors, brk, failureData);
609
+ const tuple = this.next("a");
610
+ this.push(`const ${tuple}=[...${v}];`);
611
+ for (let index = 0; index < node.prefix.length; index++) {
612
+ const item = node.prefix[index];
613
+ const itemSegs = [...segs, { s: index }];
614
+ const input = `${v}[${index}]`;
615
+ const output = `${tuple}[${index}]`;
616
+ const label = this.next("L");
617
+ if (index >= requiredPrefix)
618
+ this.push(`if(${index}<${prefixCount}){`);
619
+ this.push(`${label}:{`);
620
+ if (hasMorph(item.val)) {
621
+ const temporary = this.next("t");
622
+ this.push(`let ${temporary};`);
623
+ this.emitCollectProduce(item.val, input, itemSegs, temporary, errors, label);
624
+ this.push(`${output}=${temporary};`);
625
+ }
626
+ else {
627
+ this.emitCollectCheck(item.val, input, itemSegs, errors);
628
+ }
629
+ this.push("}");
630
+ if (index >= requiredPrefix) {
631
+ if (item.hasDefault) {
632
+ this.push("}else{");
633
+ this.emitDefaultFill(item.val, item.def, item.defFactory === true, output, itemSegs, errors);
634
+ this.push("}");
635
+ }
636
+ else {
637
+ this.push("}");
638
+ }
639
+ }
640
+ }
641
+ if (node.variadic !== undefined) {
642
+ const index = this.next("i");
643
+ const input = this.next("x");
644
+ const label = this.next("L");
645
+ this.push(`for(let ${index}=${prefixCount};${index}<${postfixStart};${index}++){const ${input}=${v}[${index}];${label}:{`);
646
+ if (hasMorph(node.variadic)) {
647
+ const temporary = this.next("t");
648
+ this.push(`let ${temporary};`);
649
+ this.emitCollectProduce(node.variadic, input, [...segs, { d: index }], temporary, errors, label);
650
+ this.push(`${tuple}[${index}]=${temporary};`);
651
+ }
652
+ else {
653
+ this.emitCollectCheck(node.variadic, input, [...segs, { d: index }], errors);
654
+ }
655
+ this.push("}}");
656
+ }
657
+ for (let index = 0; index < node.postfix.length; index++) {
658
+ const inputIndex = index === 0 ? postfixStart : `${postfixStart}+${index}`;
659
+ const input = `${v}[${inputIndex}]`;
660
+ const item = node.postfix[index];
661
+ const label = this.next("L");
662
+ this.push(`${label}:{`);
663
+ if (hasMorph(item)) {
664
+ const temporary = this.next("t");
665
+ this.push(`let ${temporary};`);
666
+ this.emitCollectProduce(item, input, [...segs, { d: inputIndex }], temporary, errors, label);
667
+ this.push(`${tuple}[${inputIndex}]=${temporary};`);
668
+ }
669
+ else {
670
+ this.emitCollectCheck(item, input, [...segs, { d: inputIndex }], errors);
671
+ }
672
+ this.push("}");
673
+ }
674
+ this.push(`${out}=${tuple};`);
675
+ return;
676
+ }
677
+ case "object": {
678
+ if (node.patternIndexes !== undefined ||
679
+ node.symbolIndex !== undefined ||
680
+ node.props.some(prop => typeof prop.key === "symbol")) {
681
+ this.emitCollectDelegate(node, v, segs, errors, brk, out);
682
+ return;
683
+ }
684
+ this.push(`if(typeof ${v}!=="object"||${v}===null){${this.appendError(errors, this.error(segs, "an object", failureData))}break ${brk};}`);
685
+ const object = this.next("o");
686
+ const fresh = node.extras === "delete" && node.index === undefined;
687
+ this.push(fresh ? `const ${object}={};` : `const ${object}={...${v}};`);
688
+ for (const prop of node.props) {
689
+ const present = `${this.lit(prop.key)} in ${v}`;
690
+ const propSegs = [...segs, { s: prop.key }];
691
+ const input = this.access(v, prop.key);
692
+ const output = this.access(object, prop.key);
693
+ const label = this.next("L");
694
+ this.push(`if(!(${present})){`);
695
+ if (prop.hasDefault) {
696
+ this.emitDefaultFill(prop.val, prop.def, prop.defFactory === true, output, propSegs, errors);
697
+ }
698
+ else if (!prop.opt) {
699
+ this.push(this.appendError(errors, this.error(propSegs, expectedOf(prop.val), "M")));
700
+ }
701
+ this.push(`}else{${label}:{`);
702
+ if (hasMorph(prop.val)) {
703
+ const temporary = this.next("t");
704
+ this.push(`let ${temporary};`);
705
+ this.emitCollectProduce(prop.val, input, propSegs, temporary, errors, label);
706
+ this.push(`${output}=${temporary};`);
707
+ }
708
+ else {
709
+ this.emitCollectCheck(prop.val, input, propSegs, errors);
710
+ if (fresh)
711
+ this.push(`${output}=${input};`);
712
+ }
713
+ this.push("}}");
714
+ }
715
+ if (node.index !== undefined) {
716
+ const key = this.next("k");
717
+ const label = this.next("L");
718
+ this.push(`for(const ${key} in ${v})if(own.call(${v},${key})){${label}:{`);
719
+ if (hasMorph(node.index)) {
720
+ const temporary = this.next("t");
721
+ this.push(`let ${temporary};`);
722
+ this.emitCollectProduce(node.index, `${v}[${key}]`, [...segs, { d: key }], temporary, errors, label);
723
+ this.push(`${object}[${key}]=${temporary};`);
724
+ }
725
+ else {
726
+ this.emitCollectCheck(node.index, `${v}[${key}]`, [...segs, { d: key }], errors);
727
+ }
728
+ this.push("}}");
729
+ }
730
+ else if (node.extras === "reject") {
731
+ const key = this.next("k");
732
+ this.push(`for(const ${key} in ${v})if(own.call(${v},${key})&&!(${this.declaredCheck(node.props, key)})){${this.appendError(errors, this.error([...segs, { d: key }], "removed", `${v}[${key}]`))}}`);
733
+ }
734
+ if (node.extras === "reject") {
735
+ const symbol = this.next("s");
736
+ this.push(`for(const ${symbol} of Object.getOwnPropertySymbols(${v}))if(Object.prototype.propertyIsEnumerable.call(${v},${symbol})&&!(${this.declaredCheck(node.props, symbol)})){${this.appendError(errors, this.error([...segs, { d: symbol }], "removed", `${v}[${symbol}]`))}}`);
737
+ }
738
+ this.push(`${out}=${object};`);
739
+ return;
740
+ }
741
+ case "intersection": {
742
+ const current = this.next("t");
743
+ this.push(`let ${current}=${v};`);
744
+ const mark = this.markErrors(errors);
745
+ for (let index = 0; index < node.members.length; index++) {
746
+ if (index > 0)
747
+ this.guardGrowth(errors, mark, brk);
748
+ const member = node.members[index];
749
+ if (hasMorph(member))
750
+ this.emitCollectProduce(member, current, segs, current, errors, brk);
751
+ else
752
+ this.emitCollectCheck(member, current, segs, errors);
753
+ }
754
+ this.push(`${out}=${current};`);
755
+ return;
756
+ }
757
+ default:
758
+ this.emitCollectDelegate(node, v, segs, errors, brk, out);
759
+ }
760
+ }
761
+ build(ir) {
762
+ const errors = this.next("e");
763
+ let ret;
764
+ if (hasMorph(ir)) {
765
+ const label = this.next("L");
766
+ this.push(`let ${errors};let o;${label}:{`);
767
+ this.emitCollectProduce(ir, "v", [], "o", errors, label);
768
+ this.push("}");
769
+ ret = "o";
770
+ }
771
+ else {
772
+ this.push(`let ${errors};`);
773
+ this.emitCollectCheck(ir, "v", [], errors);
774
+ ret = "v";
775
+ }
776
+ this.push(`if(${errors}!==undefined)return ${errors};`);
777
+ const src = `return function(v){${this.#lines.join("")}return ${ret}}`;
778
+ const make = new Function("R", "AE", "M", "PF", "UF", "MC", "own", "MD", src);
779
+ return make(this.#refs, OmpErrors, MISSING, prefixErrors, unionFail, CompiledMorphContext, own, materializeDefault);
780
+ }
781
+ emitAllows(node, v) {
782
+ switch (node.k) {
783
+ case "array": {
784
+ const array = this.next("a");
785
+ const index = this.next("i");
786
+ this.push(`const ${array}=${v};if(!Array.isArray(${array}))return false;`);
787
+ if (node.min !== undefined)
788
+ this.push(`if(${array}.length<${node.min})return false;`);
789
+ if (node.max !== undefined)
790
+ this.push(`if(${array}.length>${node.max})return false;`);
791
+ this.push(`for(let ${index}=0;${index}<${array}.length;${index}++){`);
792
+ this.emitAllows(node.el, `${array}[${index}]`);
793
+ this.push("}");
794
+ return;
795
+ }
796
+ case "object": {
797
+ const object = this.next("o");
798
+ this.push(`const ${object}=${v};if(typeof ${object}!=="object"||${object}===null)return false;`);
799
+ for (const prop of node.props) {
800
+ const value = this.next("p");
801
+ const present = `${this.lit(prop.key)} in ${object}`;
802
+ this.push(`const ${value}=${this.access(object, prop.key)};`);
803
+ if (prop.opt || prop.hasDefault) {
804
+ if (rejectsUndefined(prop.val)) {
805
+ this.push(`if(${value}!==undefined){`);
806
+ this.emitAllows(prop.val, value);
807
+ this.push(`}else if(${present})return false;`);
808
+ }
809
+ else {
810
+ this.push(`if(${present}){`);
811
+ this.emitAllows(prop.val, value);
812
+ this.push("}");
813
+ }
814
+ }
815
+ else {
816
+ if (!rejectsUndefined(prop.val))
817
+ this.push(`if(!(${present}))return false;`);
818
+ this.emitAllows(prop.val, value);
819
+ }
820
+ }
821
+ const stringKey = this.next("k");
822
+ if (node.index !== undefined) {
823
+ this.push(`for(const ${stringKey} in ${object}){if(!own.call(${object},${stringKey}))continue;`);
824
+ this.emitAllows(node.index, `${object}[${stringKey}]`);
825
+ this.push("}");
826
+ }
827
+ if (node.patternIndexes !== undefined) {
828
+ for (const pattern of node.patternIndexes) {
829
+ this.push(`for(const ${stringKey} in ${object})if(own.call(${object},${stringKey})&&(${this.predicate(pattern.key, stringKey)})&&!(${this.predicate(pattern.val, `${object}[${stringKey}]`)}))return false;`);
830
+ }
831
+ }
832
+ if (node.symbolIndex !== undefined) {
833
+ const symbol = this.next("s");
834
+ this.push(`for(const ${symbol} of Object.getOwnPropertySymbols(${object})){if(!Object.prototype.propertyIsEnumerable.call(${object},${symbol}))continue;`);
835
+ this.emitAllows(node.symbolIndex, `${object}[${symbol}]`);
836
+ this.push("}");
837
+ }
838
+ if (node.extras === "reject") {
839
+ const patternMatch = node.patternIndexes?.map(pattern => `(${this.predicate(pattern.key, stringKey)})`).join("||") ??
840
+ "false";
841
+ if (node.index === undefined) {
842
+ this.push(`for(const ${stringKey} in ${object})if(own.call(${object},${stringKey})&&!(${this.declaredCheck(node.props, stringKey)})&&!(${patternMatch}))return false;`);
843
+ }
844
+ if (node.symbolIndex === undefined) {
845
+ const symbol = this.next("s");
846
+ this.push(`for(const ${symbol} of Object.getOwnPropertySymbols(${object}))if(Object.prototype.propertyIsEnumerable.call(${object},${symbol})&&!(${this.declaredCheck(node.props, symbol)}))return false;`);
847
+ }
848
+ }
849
+ return;
850
+ }
851
+ case "union": {
852
+ const sources = [];
853
+ for (const member of node.members) {
854
+ if (!isPrimitiveLiteral(member))
855
+ break;
856
+ const source = litSource(member.v);
857
+ if (source === undefined)
858
+ break;
859
+ sources.push(source);
860
+ }
861
+ if (sources.length === node.members.length && sources.length >= 4) {
862
+ this.push(`switch(${v}){${sources.map(source => `case ${source}:`).join("")}break;default:return false;}`);
863
+ return;
864
+ }
865
+ this.push(`if(!(${this.predicate(node, v)}))return false;`);
866
+ return;
867
+ }
868
+ default:
869
+ this.push(`if(!(${this.predicate(node, v)}))return false;`);
870
+ }
871
+ }
872
+ buildAllows(ir) {
873
+ this.emitAllows(ir, "v");
874
+ const src = `return function(v){${this.#lines.join("")}return true}`;
875
+ const make = new Function("R", "AE", "own", src);
876
+ return make(this.#refs, OmpErrors, own);
877
+ }
878
+ }
879
+ function prefixErrors(errs, parts) {
880
+ for (let i = parts.length - 1; i >= 0; i--)
881
+ errs.prefix(parts[i]);
882
+ return errs;
883
+ }
884
+ const kWalk = Symbol("omptype.boundWalk");
885
+ /** Cached interpreter closure for recursive aliases and predicate-only fallbacks. */
886
+ function boundWalk(node) {
887
+ const tagged = node;
888
+ let fn = tagged[kWalk];
889
+ if (!fn) {
890
+ fn = (value, path) => walk(node, value, path);
891
+ tagged[kWalk] = fn;
892
+ }
893
+ return fn;
894
+ }
895
+ function resolvedRoot(ir) {
896
+ return ir.k === "alias" ? ir.resolve() : ir;
897
+ }
898
+ const compiledCache = new WeakMap();
899
+ const allowsCache = new WeakMap();
900
+ /** Compile `ir` into a specialized validator. */
901
+ export function compile(ir) {
902
+ const root = resolvedRoot(ir);
903
+ const validator = compiledCache.get(root);
904
+ if (validator === undefined) {
905
+ // Publish a deferred wrapper before building: recursive schemas re-enter
906
+ // compile() for the same root mid-build (e.g. an alias element inside an
907
+ // array), and each re-entry must reuse this build instead of starting a
908
+ // fresh one forever. The wrapper resolves to the built validator by call
909
+ // time; the interpreter is a safety net that never triggers post-build.
910
+ let built;
911
+ compiledCache.set(root, value => (built === undefined ? walk(root, value) : built(value)));
912
+ built = new Builder().build(root);
913
+ compiledCache.set(root, built);
914
+ return built;
915
+ }
916
+ return validator;
917
+ }
918
+ /** Compile `ir` into an allocation-free boolean validator. */
919
+ export function compileAllows(ir) {
920
+ const root = resolvedRoot(ir);
921
+ const validator = allowsCache.get(root);
922
+ if (validator === undefined) {
923
+ let built;
924
+ allowsCache.set(root, ((value) => built === undefined ? !(walk(root, value) instanceof OmpErrors) : built(value)));
925
+ built = new Builder().buildAllows(root);
926
+ allowsCache.set(root, built);
927
+ return built;
928
+ }
929
+ return validator;
930
+ }
931
+ /** Generated source for inspection/debugging. */
932
+ export function compileToSource(ir) {
933
+ const root = resolvedRoot(ir);
934
+ const builder = new Builder();
935
+ if (hasMorph(root)) {
936
+ builder.push("let o;");
937
+ builder.emitCollectProduce(root, "v", [], "o", "e", "L0");
938
+ return `function(v){/* refs elided */return o}`;
939
+ }
940
+ builder.emitCollectCheck(root, "v", [], "e");
941
+ return `function(v){/* refs elided */return v}`;
942
+ }