@oh-my-pi/omptype 17.2.7 → 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.
- package/CHANGELOG.md +23 -0
- package/dist/js/compile.js +414 -219
- package/dist/js/errors.js +236 -41
- package/dist/js/from-json-schema.js +234 -0
- package/dist/js/index.js +1 -0
- package/dist/js/interp.js +514 -132
- package/dist/js/ir.js +972 -202
- package/dist/js/json-schema.js +73 -34
- package/dist/js/keywords.js +108 -1
- package/dist/js/type.js +2156 -172
- package/dist/js/typebox.js +41 -32
- package/dist/js/zod.js +2 -2
- package/dist/types/compile.d.ts +1 -1
- package/dist/types/errors.d.ts +19 -18
- package/dist/types/from-json-schema.d.ts +9 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/infer.d.ts +45 -6
- package/dist/types/interp.d.ts +9 -2
- package/dist/types/ir.d.ts +57 -6
- package/dist/types/json-schema.d.ts +10 -1
- package/dist/types/type.d.ts +324 -48
- package/dist/types/typebox.d.ts +78 -50
- package/package.json +5 -1
- package/src/compile.ts +598 -243
- package/src/errors.ts +247 -49
- package/src/from-json-schema.ts +231 -0
- package/src/index.ts +1 -0
- package/src/infer.ts +107 -32
- package/src/interp.ts +457 -118
- package/src/ir.ts +981 -212
- package/src/json-schema.ts +82 -34
- package/src/keywords.ts +111 -1
- package/src/type.ts +2760 -271
- package/src/typebox.ts +141 -98
- package/src/zod.ts +1 -1
package/src/compile.ts
CHANGED
|
@@ -15,17 +15,12 @@
|
|
|
15
15
|
* members compile to inline predicates
|
|
16
16
|
*/
|
|
17
17
|
import { MISSING, OmpErrors } from "./errors";
|
|
18
|
-
import { canRefineUnionFailure, unionFail, walk } from "./interp";
|
|
18
|
+
import { canRefineUnionFailure, materializeDefault, unionFail, walk } from "./interp";
|
|
19
19
|
import { expectedOf, hasMorph, type IR, type MorphContext, type PropIR, type TupleIR } from "./ir";
|
|
20
20
|
|
|
21
21
|
const own = Object.prototype.hasOwnProperty;
|
|
22
|
-
|
|
23
22
|
const IDENT = /^[A-Za-z_$][\w$]*$/;
|
|
24
23
|
|
|
25
|
-
function access(base: string, key: string): string {
|
|
26
|
-
return IDENT.test(key) ? `${base}.${key}` : `${base}[${JSON.stringify(key)}]`;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
24
|
/** Inline-able literal, else undefined (caller hoists into the refs pool). */
|
|
30
25
|
function litSource(v: unknown): string | undefined {
|
|
31
26
|
if (v === null) return "null";
|
|
@@ -115,8 +110,12 @@ class Builder {
|
|
|
115
110
|
return litSource(v) ?? this.ref(v);
|
|
116
111
|
}
|
|
117
112
|
|
|
113
|
+
access(base: string, key: PropertyKey): string {
|
|
114
|
+
return typeof key === "string" && IDENT.test(key) ? `${base}.${key}` : `${base}[${this.lit(key)}]`;
|
|
115
|
+
}
|
|
116
|
+
|
|
118
117
|
pathExpr(segs: PathSeg[]): string {
|
|
119
|
-
const parts = segs.map(seg => ("s" in seg ?
|
|
118
|
+
const parts = segs.map(seg => ("s" in seg ? this.lit(seg.s) : seg.d));
|
|
120
119
|
return `[${parts.join(",")}]`;
|
|
121
120
|
}
|
|
122
121
|
|
|
@@ -124,7 +123,7 @@ class Builder {
|
|
|
124
123
|
if (segs.length === 0) return "undefined";
|
|
125
124
|
if (segs.length === 1) {
|
|
126
125
|
const seg = segs[0];
|
|
127
|
-
return "s" in seg ?
|
|
126
|
+
return "s" in seg ? this.lit(seg.s) : seg.d;
|
|
128
127
|
}
|
|
129
128
|
const staticParts: PropertyKey[] = [];
|
|
130
129
|
for (const seg of segs) {
|
|
@@ -134,8 +133,16 @@ class Builder {
|
|
|
134
133
|
return this.ref(staticParts);
|
|
135
134
|
}
|
|
136
135
|
|
|
136
|
+
error(segs: PathSeg[], expected: string, dataExpr: string): string {
|
|
137
|
+
return `new AE(${this.storedPathExpr(segs)},${JSON.stringify(expected)},${dataExpr})`;
|
|
138
|
+
}
|
|
139
|
+
|
|
137
140
|
fail(segs: PathSeg[], expected: string, dataExpr: string): string {
|
|
138
|
-
return `return
|
|
141
|
+
return `return ${this.error(segs, expected, dataExpr)}`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
appendError(errors: string, error: string): string {
|
|
145
|
+
return `if(${errors}===undefined)${errors}=${error};else ${errors}.append(${error});`;
|
|
139
146
|
}
|
|
140
147
|
|
|
141
148
|
/** Pure boolean predicate for a morph-free subtree. */
|
|
@@ -202,10 +209,10 @@ class Builder {
|
|
|
202
209
|
return out;
|
|
203
210
|
}
|
|
204
211
|
case "object": {
|
|
205
|
-
const checks = [`typeof ${v}==="object"`, `${v}!==null
|
|
212
|
+
const checks = [`typeof ${v}==="object"`, `${v}!==null`];
|
|
206
213
|
for (const p of node.props) {
|
|
207
|
-
const av = access(v, p.key);
|
|
208
|
-
const present = `${
|
|
214
|
+
const av = this.access(v, p.key);
|
|
215
|
+
const present = `${this.lit(p.key)} in ${v}`;
|
|
209
216
|
const predicate = this.predicate(p.val, av);
|
|
210
217
|
checks.push(
|
|
211
218
|
p.opt || p.hasDefault
|
|
@@ -217,17 +224,41 @@ class Builder {
|
|
|
217
224
|
: `((${present})&&(${predicate}))`,
|
|
218
225
|
);
|
|
219
226
|
}
|
|
220
|
-
|
|
221
|
-
|
|
227
|
+
const stringKey = this.next("k");
|
|
228
|
+
if (node.index !== undefined) {
|
|
222
229
|
checks.push(
|
|
223
|
-
`(()=>{for(const ${
|
|
230
|
+
`(()=>{for(const ${stringKey} in ${v})if(own.call(${v},${stringKey})&&!(${this.predicate(node.index, `${v}[${stringKey}]`)}))return false;return true})()`,
|
|
224
231
|
);
|
|
225
|
-
}
|
|
226
|
-
|
|
232
|
+
}
|
|
233
|
+
if (node.patternIndexes !== undefined) {
|
|
234
|
+
for (const pattern of node.patternIndexes) {
|
|
235
|
+
checks.push(
|
|
236
|
+
`(()=>{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})()`,
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (node.symbolIndex !== undefined) {
|
|
241
|
+
const symbol = this.next("s");
|
|
227
242
|
checks.push(
|
|
228
|
-
`(()=>{for(const ${
|
|
243
|
+
`(()=>{for(const ${symbol} of Object.getOwnPropertySymbols(${v}))if(Object.prototype.propertyIsEnumerable.call(${v},${symbol})&&!(${this.predicate(node.symbolIndex, `${v}[${symbol}]`)}))return false;return true})()`,
|
|
229
244
|
);
|
|
230
245
|
}
|
|
246
|
+
if (node.extras === "reject") {
|
|
247
|
+
const patternMatch =
|
|
248
|
+
node.patternIndexes?.map(pattern => `(${this.predicate(pattern.key, stringKey)})`).join("||") ??
|
|
249
|
+
"false";
|
|
250
|
+
if (node.index === undefined) {
|
|
251
|
+
checks.push(
|
|
252
|
+
`(()=>{for(const ${stringKey} in ${v})if(own.call(${v},${stringKey})&&!(${this.declaredCheck(node.props, stringKey)})&&!(${patternMatch}))return false;return true})()`,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
if (node.symbolIndex === undefined) {
|
|
256
|
+
const symbol = this.next("s");
|
|
257
|
+
checks.push(
|
|
258
|
+
`(()=>{for(const ${symbol} of Object.getOwnPropertySymbols(${v}))if(Object.prototype.propertyIsEnumerable.call(${v},${symbol})&&!(${this.declaredCheck(node.props, symbol)}))return false;return true})()`,
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
231
262
|
return `(${checks.join("&&")})`;
|
|
232
263
|
}
|
|
233
264
|
case "sub":
|
|
@@ -243,139 +274,293 @@ class Builder {
|
|
|
243
274
|
const set = this.ref(new Set(props.map(p => p.key)));
|
|
244
275
|
return `${set}.has(${keyVar})`;
|
|
245
276
|
}
|
|
246
|
-
return `(${props.map(p => `${keyVar}===${
|
|
277
|
+
return `(${props.map(p => `${keyVar}===${this.lit(p.key)}`).join("||")})`;
|
|
247
278
|
}
|
|
248
279
|
|
|
249
|
-
|
|
250
|
-
|
|
280
|
+
/**
|
|
281
|
+
* Run a node through its interpreter/sub-schema runner, appending any
|
|
282
|
+
* failure to `errors`. `brk` (when given) exits the enclosing block on
|
|
283
|
+
* failure so dependent statements (output assignment, morph fns) are
|
|
284
|
+
* skipped. Both runner kinds receive the absolute path so nested step
|
|
285
|
+
* callbacks observe ctx.path; walk-produced errors are already absolute,
|
|
286
|
+
* while sub runners return schema-relative errors that need prefixing.
|
|
287
|
+
*/
|
|
288
|
+
emitCollectDelegate(node: IR, v: string, segs: PathSeg[], errors: string, brk?: string, out?: string): void {
|
|
289
|
+
const sub = node.k === "sub";
|
|
290
|
+
const runner = sub ? node.schema.run : boundWalk(node);
|
|
251
291
|
const result = this.next("r");
|
|
252
|
-
|
|
292
|
+
const args = segs.length > 0 ? `${v},${this.pathExpr(segs)}` : v;
|
|
293
|
+
this.push(`const ${result}=${this.ref(runner)}(${args});`);
|
|
294
|
+
const failure = sub && segs.length > 0 ? `PF(${result},${this.pathExpr(segs)})` : result;
|
|
253
295
|
this.push(
|
|
254
|
-
|
|
255
|
-
? `if(${result} instanceof AE)return ${result};`
|
|
256
|
-
: `if(${result} instanceof AE)return PF(${result},${this.pathExpr(segs)});`,
|
|
296
|
+
`if(${result} instanceof AE){${this.appendError(errors, failure)}${brk === undefined ? "" : `break ${brk};`}}`,
|
|
257
297
|
);
|
|
258
298
|
if (out !== undefined) this.push(`${out}=${result};`);
|
|
259
299
|
}
|
|
260
300
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
): { postfixStart: string; prefixCount: string; requiredPrefix: number } {
|
|
267
|
-
this.push(`if(!Array.isArray(${v}))${this.fail(segs, "an array", failureData)};`);
|
|
268
|
-
const requiredPrefix = node.prefix.filter(item => !item.opt && !item.hasDefault).length;
|
|
269
|
-
const minimum = requiredPrefix + node.postfix.length;
|
|
270
|
-
if (minimum > 0) {
|
|
271
|
-
this.push(
|
|
272
|
-
`if(${v}.length<${minimum})${this.fail(segs, `an array of at least length ${minimum}`, failureData)};`,
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
if (node.variadic === undefined) {
|
|
276
|
-
const maximum = node.prefix.length + node.postfix.length;
|
|
277
|
-
this.push(
|
|
278
|
-
`if(${v}.length>${maximum})${this.fail(segs, `an array of at most length ${maximum}`, failureData)};`,
|
|
279
|
-
);
|
|
280
|
-
}
|
|
281
|
-
let postfixStart = `${v}.length`;
|
|
282
|
-
if (node.postfix.length > 0) {
|
|
283
|
-
postfixStart = this.next("p");
|
|
284
|
-
this.push(`const ${postfixStart}=${v}.length-${node.postfix.length};`);
|
|
285
|
-
}
|
|
286
|
-
let prefixCount = String(node.prefix.length);
|
|
287
|
-
if (requiredPrefix !== node.prefix.length) {
|
|
288
|
-
prefixCount = this.next("n");
|
|
289
|
-
this.push(`const ${prefixCount}=Math.min(${node.prefix.length},${postfixStart});`);
|
|
290
|
-
}
|
|
291
|
-
return { postfixStart, prefixCount, requiredPrefix };
|
|
301
|
+
/** Snapshot the error count so sequencing sites can detect soft failures. */
|
|
302
|
+
markErrors(errors: string): string {
|
|
303
|
+
const mark = this.next("n");
|
|
304
|
+
this.push(`const ${mark}=${errors}===void 0?0:${errors}.length;`);
|
|
305
|
+
return mark;
|
|
292
306
|
}
|
|
293
307
|
|
|
294
|
-
/**
|
|
295
|
-
|
|
308
|
+
/** Exit `brk` when errors were appended since `mark` (interp's return-on-error). */
|
|
309
|
+
guardGrowth(errors: string, mark: string, brk: string): void {
|
|
310
|
+
this.push(`if((${errors}===void 0?0:${errors}.length)!==${mark})break ${brk};`);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** Aggregate every independent failure in a morph-free subtree. */
|
|
314
|
+
emitCollectCheck(node: IR, v: string, segs: PathSeg[], errors: string, failureData = v): void {
|
|
315
|
+
if (node.cfg !== undefined || node.k === "refine") {
|
|
316
|
+
this.emitCollectDelegate(node, v, segs, errors);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
296
319
|
switch (node.k) {
|
|
297
320
|
case "unknown":
|
|
298
321
|
return;
|
|
299
322
|
case "array": {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
323
|
+
this.push(
|
|
324
|
+
`if(!Array.isArray(${v})){${this.appendError(errors, this.error(segs, "an array", failureData))}}`,
|
|
325
|
+
);
|
|
326
|
+
if (node.min !== undefined) {
|
|
327
|
+
this.push(
|
|
328
|
+
`else if(${v}.length<${node.min}){${this.appendError(
|
|
329
|
+
errors,
|
|
330
|
+
this.error(segs, `at least length ${node.min}`, `${v}.length`),
|
|
331
|
+
)}}`,
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
if (node.max !== undefined) {
|
|
335
|
+
this.push(
|
|
336
|
+
`else if(${v}.length>${node.max}){${this.appendError(
|
|
337
|
+
errors,
|
|
338
|
+
this.error(segs, `at most length ${node.max}`, `${v}.length`),
|
|
339
|
+
)}}`,
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
this.push("else{");
|
|
343
|
+
const index = this.next("i");
|
|
344
|
+
this.push(`for(let ${index}=0;${index}<${v}.length;${index}++){`);
|
|
345
|
+
this.emitCollectCheck(node.el, `${v}[${index}]`, [...segs, { d: index }], errors);
|
|
346
|
+
this.push("}}");
|
|
309
347
|
return;
|
|
310
348
|
}
|
|
311
349
|
case "tuple": {
|
|
312
|
-
|
|
350
|
+
this.push(
|
|
351
|
+
`if(!Array.isArray(${v})){${this.appendError(errors, this.error(segs, "an array", failureData))}}else{`,
|
|
352
|
+
);
|
|
353
|
+
const requiredPrefix = node.prefix.filter(item => !item.opt && !item.hasDefault).length;
|
|
354
|
+
const minimum = requiredPrefix + node.postfix.length;
|
|
355
|
+
const maximum = node.prefix.length + node.postfix.length;
|
|
356
|
+
if (minimum > 0) {
|
|
357
|
+
this.push(
|
|
358
|
+
`if(${v}.length<${minimum}){${this.appendError(
|
|
359
|
+
errors,
|
|
360
|
+
this.error(segs, `an array of at least length ${minimum}`, failureData),
|
|
361
|
+
)}}else{`,
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
if (node.variadic === undefined) {
|
|
365
|
+
this.push(
|
|
366
|
+
`if(${v}.length>${maximum}){${this.appendError(
|
|
367
|
+
errors,
|
|
368
|
+
this.error(segs, `an array of at most length ${maximum}`, failureData),
|
|
369
|
+
)}}else{`,
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
let postfixStart = `${v}.length`;
|
|
373
|
+
if (node.postfix.length > 0) {
|
|
374
|
+
postfixStart = this.next("p");
|
|
375
|
+
this.push(`const ${postfixStart}=${v}.length-${node.postfix.length};`);
|
|
376
|
+
}
|
|
377
|
+
let prefixCount = String(node.prefix.length);
|
|
378
|
+
if (requiredPrefix !== node.prefix.length) {
|
|
379
|
+
prefixCount = this.next("n");
|
|
380
|
+
this.push(`const ${prefixCount}=Math.min(${node.prefix.length},${postfixStart});`);
|
|
381
|
+
}
|
|
313
382
|
for (let index = 0; index < node.prefix.length; index++) {
|
|
314
383
|
if (index >= requiredPrefix) this.push(`if(${index}<${prefixCount}){`);
|
|
315
|
-
this.
|
|
384
|
+
this.emitCollectCheck(node.prefix[index].val, `${v}[${index}]`, [...segs, { d: String(index) }], errors);
|
|
316
385
|
if (index >= requiredPrefix) this.push("}");
|
|
317
386
|
}
|
|
318
387
|
if (node.variadic !== undefined) {
|
|
319
388
|
const index = this.next("i");
|
|
320
389
|
this.push(`for(let ${index}=${prefixCount};${index}<${postfixStart};${index}++){`);
|
|
321
|
-
this.
|
|
390
|
+
this.emitCollectCheck(node.variadic, `${v}[${index}]`, [...segs, { d: index }], errors);
|
|
322
391
|
this.push("}");
|
|
323
392
|
}
|
|
324
393
|
for (let index = 0; index < node.postfix.length; index++) {
|
|
325
394
|
const inputIndex = index === 0 ? postfixStart : `${postfixStart}+${index}`;
|
|
326
|
-
this.
|
|
395
|
+
this.emitCollectCheck(node.postfix[index], `${v}[${inputIndex}]`, [...segs, { d: inputIndex }], errors);
|
|
327
396
|
}
|
|
397
|
+
if (node.variadic === undefined) this.push("}");
|
|
398
|
+
if (minimum > 0) this.push("}");
|
|
399
|
+
this.push("}");
|
|
328
400
|
return;
|
|
329
401
|
}
|
|
330
402
|
case "object": {
|
|
403
|
+
if (
|
|
404
|
+
node.patternIndexes !== undefined ||
|
|
405
|
+
node.symbolIndex !== undefined ||
|
|
406
|
+
node.props.some(prop => typeof prop.key === "symbol")
|
|
407
|
+
) {
|
|
408
|
+
this.emitCollectDelegate(node, v, segs, errors);
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
331
411
|
this.push(
|
|
332
|
-
`if(typeof ${v}!=="object"||${v}===null
|
|
412
|
+
`if(typeof ${v}!=="object"||${v}===null){${this.appendError(
|
|
413
|
+
errors,
|
|
414
|
+
this.error(segs, "an object", failureData),
|
|
415
|
+
)}}else{`,
|
|
333
416
|
);
|
|
334
|
-
for (const
|
|
335
|
-
const present = `${
|
|
336
|
-
const propSegs: PathSeg[] = [...segs, { s:
|
|
337
|
-
if (
|
|
417
|
+
for (const prop of node.props) {
|
|
418
|
+
const present = `${this.lit(prop.key)} in ${v}`;
|
|
419
|
+
const propSegs: PathSeg[] = [...segs, { s: prop.key }];
|
|
420
|
+
if (prop.opt || prop.hasDefault) {
|
|
338
421
|
this.push(`if(${present}){`);
|
|
339
|
-
this.
|
|
422
|
+
this.emitCollectCheck(prop.val, this.access(v, prop.key), propSegs, errors);
|
|
340
423
|
this.push("}");
|
|
341
424
|
} else {
|
|
342
|
-
this.push(
|
|
343
|
-
|
|
425
|
+
this.push(
|
|
426
|
+
`if(!(${present})){${this.appendError(
|
|
427
|
+
errors,
|
|
428
|
+
this.error(propSegs, expectedOf(prop.val), "M"),
|
|
429
|
+
)}}else{`,
|
|
430
|
+
);
|
|
431
|
+
this.emitCollectCheck(prop.val, this.access(v, prop.key), propSegs, errors);
|
|
432
|
+
this.push("}");
|
|
344
433
|
}
|
|
345
434
|
}
|
|
346
|
-
if (node.index) {
|
|
347
|
-
const
|
|
348
|
-
this.push(`for(const ${
|
|
349
|
-
this.
|
|
435
|
+
if (node.index !== undefined) {
|
|
436
|
+
const key = this.next("k");
|
|
437
|
+
this.push(`for(const ${key} in ${v})if(own.call(${v},${key})){`);
|
|
438
|
+
this.emitCollectCheck(node.index, `${v}[${key}]`, [...segs, { d: key }], errors);
|
|
350
439
|
this.push("}");
|
|
351
440
|
} else if (node.extras === "reject") {
|
|
352
|
-
const
|
|
441
|
+
const key = this.next("k");
|
|
353
442
|
this.push(
|
|
354
|
-
`for(const ${
|
|
355
|
-
[...segs, { d: k }],
|
|
356
|
-
"removed (undeclared key)",
|
|
357
|
-
`${v}[${k}]`,
|
|
358
|
-
)};`,
|
|
443
|
+
`for(const ${key} in ${v})if(own.call(${v},${key})&&!(${this.declaredCheck(node.props, key)})){`,
|
|
359
444
|
);
|
|
445
|
+
this.push(this.appendError(errors, this.error([...segs, { d: key }], "removed", `${v}[${key}]`)));
|
|
446
|
+
this.push("}");
|
|
360
447
|
}
|
|
448
|
+
if (node.extras === "reject") {
|
|
449
|
+
const symbol = this.next("s");
|
|
450
|
+
this.push(
|
|
451
|
+
`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}]`))}}`,
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
this.push("}");
|
|
361
455
|
return;
|
|
362
456
|
}
|
|
363
|
-
case "sub":
|
|
364
|
-
this.emitDelegate(node, v, segs);
|
|
365
|
-
return;
|
|
366
457
|
case "union": {
|
|
367
458
|
const failure = node.members.some(canRefineUnionFailure)
|
|
368
|
-
? `
|
|
369
|
-
: this.
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
459
|
+
? `UF(${this.ref(node)},${failureData},${this.pathExpr(segs)},${JSON.stringify(expectedOf(node))})`
|
|
460
|
+
: this.error(segs, expectedOf(node), failureData);
|
|
461
|
+
this.push(`if(!(${this.predicate(node, v)})){${this.appendError(errors, failure)}}`);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
case "string": {
|
|
465
|
+
this.push(
|
|
466
|
+
`if(typeof ${v}!=="string"){${this.appendError(errors, this.error(segs, "a string", failureData))}}`,
|
|
467
|
+
);
|
|
468
|
+
if (node.min !== undefined) {
|
|
469
|
+
this.push(
|
|
470
|
+
`else if(${v}.length<${node.min}){${this.appendError(
|
|
471
|
+
errors,
|
|
472
|
+
this.error(segs, `at least length ${node.min}`, `${v}.length`),
|
|
473
|
+
)}}`,
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
if (node.max !== undefined) {
|
|
477
|
+
this.push(
|
|
478
|
+
`else if(${v}.length>${node.max}){${this.appendError(
|
|
479
|
+
errors,
|
|
480
|
+
this.error(segs, `at most length ${node.max}`, `${v}.length`),
|
|
481
|
+
)}}`,
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
if (node.url) {
|
|
485
|
+
this.push(
|
|
486
|
+
`else if(!URL.canParse(${v})){${this.appendError(
|
|
487
|
+
errors,
|
|
488
|
+
this.error(segs, "a URL string", failureData),
|
|
489
|
+
)}}`,
|
|
490
|
+
);
|
|
376
491
|
}
|
|
377
492
|
return;
|
|
378
493
|
}
|
|
494
|
+
case "number": {
|
|
495
|
+
this.push(
|
|
496
|
+
`if(typeof ${v}!=="number"||!Number.isFinite(${v})){${this.appendError(
|
|
497
|
+
errors,
|
|
498
|
+
this.error(segs, node.int ? "an integer" : "a number", failureData),
|
|
499
|
+
)}}else{`,
|
|
500
|
+
);
|
|
501
|
+
if (node.int) {
|
|
502
|
+
this.push(
|
|
503
|
+
`if(!Number.isInteger(${v})){${this.appendError(errors, this.error(segs, "an integer", failureData))}}`,
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
if (node.divisor !== undefined) {
|
|
507
|
+
this.push(
|
|
508
|
+
`if(${v}%${node.divisor}!==0){${this.appendError(
|
|
509
|
+
errors,
|
|
510
|
+
this.error(segs, `a number divisible by ${node.divisor}`, failureData),
|
|
511
|
+
)}}`,
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
if (node.min !== undefined) {
|
|
515
|
+
const expected =
|
|
516
|
+
node.min === 0
|
|
517
|
+
? node.xmin
|
|
518
|
+
? "positive"
|
|
519
|
+
: "non-negative"
|
|
520
|
+
: `a number ${node.xmin ? "more than" : "at least"} ${node.min}`;
|
|
521
|
+
this.push(
|
|
522
|
+
`if(${v}${node.xmin ? "<=" : "<"}${node.min}){${this.appendError(
|
|
523
|
+
errors,
|
|
524
|
+
this.error(segs, expected, failureData),
|
|
525
|
+
)}}`,
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
if (node.max !== undefined) {
|
|
529
|
+
const expected =
|
|
530
|
+
node.max === 0
|
|
531
|
+
? node.xmax
|
|
532
|
+
? "negative"
|
|
533
|
+
: "non-positive"
|
|
534
|
+
: `a number ${node.xmax ? "less than" : "at most"} ${node.max}`;
|
|
535
|
+
this.push(
|
|
536
|
+
`if(${v}${node.xmax ? ">=" : ">"}${node.max}){${this.appendError(
|
|
537
|
+
errors,
|
|
538
|
+
this.error(segs, expected, failureData),
|
|
539
|
+
)}}`,
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
this.push("}");
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
case "lit":
|
|
546
|
+
if (
|
|
547
|
+
(node.v !== null && typeof node.v === "object" && !(node.v instanceof Date)) ||
|
|
548
|
+
typeof node.v === "function"
|
|
549
|
+
) {
|
|
550
|
+
this.emitCollectDelegate(node, v, segs, errors);
|
|
551
|
+
} else {
|
|
552
|
+
this.push(
|
|
553
|
+
`if(!(${this.predicate(node, v)})){${this.appendError(
|
|
554
|
+
errors,
|
|
555
|
+
this.error(segs, expectedOf(node), failureData),
|
|
556
|
+
)}}`,
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
return;
|
|
560
|
+
case "intersection":
|
|
561
|
+
case "sub":
|
|
562
|
+
this.emitCollectDelegate(node, v, segs, errors);
|
|
563
|
+
return;
|
|
379
564
|
case "null":
|
|
380
565
|
case "undefined":
|
|
381
566
|
case "boolean":
|
|
@@ -383,51 +568,119 @@ class Builder {
|
|
|
383
568
|
case "symbol":
|
|
384
569
|
case "never":
|
|
385
570
|
case "anyobject":
|
|
386
|
-
case "lit":
|
|
387
|
-
case "string":
|
|
388
|
-
case "number":
|
|
389
571
|
case "instance":
|
|
390
|
-
this.push(
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
return;
|
|
397
|
-
}
|
|
398
|
-
case "intersection":
|
|
399
|
-
for (const member of node.members) this.emitCheck(member, v, segs, failureData);
|
|
572
|
+
this.push(
|
|
573
|
+
`if(!(${this.predicate(node, v)})){${this.appendError(
|
|
574
|
+
errors,
|
|
575
|
+
this.error(segs, expectedOf(node), failureData),
|
|
576
|
+
)}}`,
|
|
577
|
+
);
|
|
400
578
|
return;
|
|
401
579
|
default:
|
|
402
|
-
this.
|
|
580
|
+
this.emitCollectDelegate(node, v, segs, errors);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
emitTupleShape(
|
|
585
|
+
node: TupleIR,
|
|
586
|
+
v: string,
|
|
587
|
+
segs: PathSeg[],
|
|
588
|
+
errors: string,
|
|
589
|
+
brk: string,
|
|
590
|
+
failureData: string,
|
|
591
|
+
): { postfixStart: string; prefixCount: string; requiredPrefix: number } {
|
|
592
|
+
this.push(
|
|
593
|
+
`if(!Array.isArray(${v})){${this.appendError(errors, this.error(segs, "an array", failureData))}break ${brk};}`,
|
|
594
|
+
);
|
|
595
|
+
const requiredPrefix = node.prefix.filter(item => !item.opt && !item.hasDefault).length;
|
|
596
|
+
const minimum = requiredPrefix + node.postfix.length;
|
|
597
|
+
if (minimum > 0) {
|
|
598
|
+
this.push(
|
|
599
|
+
`if(${v}.length<${minimum}){${this.appendError(
|
|
600
|
+
errors,
|
|
601
|
+
this.error(segs, `an array of at least length ${minimum}`, failureData),
|
|
602
|
+
)}break ${brk};}`,
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
if (node.variadic === undefined) {
|
|
606
|
+
const maximum = node.prefix.length + node.postfix.length;
|
|
607
|
+
this.push(
|
|
608
|
+
`if(${v}.length>${maximum}){${this.appendError(
|
|
609
|
+
errors,
|
|
610
|
+
this.error(segs, `an array of at most length ${maximum}`, failureData),
|
|
611
|
+
)}break ${brk};}`,
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
let postfixStart = `${v}.length`;
|
|
615
|
+
if (node.postfix.length > 0) {
|
|
616
|
+
postfixStart = this.next("p");
|
|
617
|
+
this.push(`const ${postfixStart}=${v}.length-${node.postfix.length};`);
|
|
618
|
+
}
|
|
619
|
+
let prefixCount = String(node.prefix.length);
|
|
620
|
+
if (requiredPrefix !== node.prefix.length) {
|
|
621
|
+
prefixCount = this.next("n");
|
|
622
|
+
this.push(`const ${prefixCount}=Math.min(${node.prefix.length},${postfixStart});`);
|
|
623
|
+
}
|
|
624
|
+
return { postfixStart, prefixCount, requiredPrefix };
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/** Fill `target` with a validated default (factory output revalidated per call). */
|
|
628
|
+
emitDefaultFill(val: IR, def: unknown, isFactory: boolean, target: string, segs: PathSeg[], errors: string): void {
|
|
629
|
+
if (isFactory && typeof def === "function") {
|
|
630
|
+
const candidate = this.next("d");
|
|
631
|
+
const resolved = this.next("t");
|
|
632
|
+
const label = this.next("L");
|
|
633
|
+
this.push(`const ${candidate}=${this.ref(def)}();let ${resolved};${label}:{`);
|
|
634
|
+
this.emitCollectProduce(val, candidate, segs, resolved, errors, label);
|
|
635
|
+
this.push(`${target}=${resolved};}`);
|
|
636
|
+
} else {
|
|
637
|
+
// Static defaults were prevalidated at construction; MD clones
|
|
638
|
+
// mutable payloads so callers cannot alias the schema's copy.
|
|
639
|
+
this.push(`${target}=${litSource(def) ?? `MD(${this.ref(def)})`};`);
|
|
403
640
|
}
|
|
404
641
|
}
|
|
405
642
|
|
|
406
643
|
/**
|
|
407
644
|
* Validate `v` against a morphing subtree and assign the produced output
|
|
408
|
-
* to `out` (an already-declared `let`).
|
|
645
|
+
* to `out` (an already-declared `let`). Failures append to `errors` and
|
|
646
|
+
* `break ${brk}` (skipping the output assignment), mirroring interp: an
|
|
647
|
+
* error in one child never suppresses sibling validation or morphs.
|
|
409
648
|
*/
|
|
410
|
-
|
|
649
|
+
emitCollectProduce(
|
|
650
|
+
node: IR,
|
|
651
|
+
v: string,
|
|
652
|
+
segs: PathSeg[],
|
|
653
|
+
out: string,
|
|
654
|
+
errors: string,
|
|
655
|
+
brk: string,
|
|
656
|
+
failureData = v,
|
|
657
|
+
): void {
|
|
658
|
+
if (node.cfg !== undefined || node.k === "refine") {
|
|
659
|
+
this.emitCollectDelegate(node, v, segs, errors, brk, out);
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
411
662
|
if (!hasMorph(node)) {
|
|
412
|
-
this.
|
|
663
|
+
this.emitCollectCheck(node, v, segs, errors, failureData);
|
|
413
664
|
this.push(`${out}=${v};`);
|
|
414
665
|
return;
|
|
415
666
|
}
|
|
416
667
|
switch (node.k) {
|
|
417
668
|
case "sub":
|
|
418
|
-
this.
|
|
669
|
+
this.emitCollectDelegate(node, v, segs, errors, brk, out);
|
|
419
670
|
return;
|
|
420
671
|
case "morph": {
|
|
421
672
|
const input = this.next("t");
|
|
422
673
|
this.push(`let ${input};`);
|
|
423
|
-
this.
|
|
674
|
+
const mark = this.markErrors(errors);
|
|
675
|
+
this.emitCollectProduce(node.input, v, segs, input, errors, brk, failureData);
|
|
676
|
+
this.guardGrowth(errors, mark, brk);
|
|
424
677
|
const context = this.next("c");
|
|
425
678
|
const result = this.next("r");
|
|
426
679
|
this.push(`const ${context}=new MC(${this.storedPathExpr(segs)},${input});`);
|
|
427
680
|
this.push(`const ${result}=${this.ref(node.fn)}(${input},${context});`);
|
|
428
|
-
this.push(`if(${result} instanceof AE)
|
|
681
|
+
this.push(`if(${result} instanceof AE){${this.appendError(errors, result)}break ${brk};}`);
|
|
429
682
|
if (node.out === undefined) this.push(`${out}=${result};`);
|
|
430
|
-
else this.
|
|
683
|
+
else this.emitCollectProduce(node.out, result, segs, out, errors, brk);
|
|
431
684
|
return;
|
|
432
685
|
}
|
|
433
686
|
case "alias": {
|
|
@@ -437,12 +690,12 @@ class Builder {
|
|
|
437
690
|
this.#activeAliases = active;
|
|
438
691
|
}
|
|
439
692
|
if (active.has(node)) {
|
|
440
|
-
this.
|
|
693
|
+
this.emitCollectDelegate(node, v, segs, errors, brk, out);
|
|
441
694
|
return;
|
|
442
695
|
}
|
|
443
696
|
active.add(node);
|
|
444
697
|
try {
|
|
445
|
-
this.
|
|
698
|
+
this.emitCollectProduce(node.resolve(), v, segs, out, errors, brk, failureData);
|
|
446
699
|
} finally {
|
|
447
700
|
active.delete(node);
|
|
448
701
|
}
|
|
@@ -467,45 +720,77 @@ class Builder {
|
|
|
467
720
|
}
|
|
468
721
|
}
|
|
469
722
|
this.push("}");
|
|
470
|
-
this.
|
|
471
|
-
|
|
472
|
-
);
|
|
723
|
+
const failure = `UF(${this.ref(node)},${failureData},${this.pathExpr(segs)},${JSON.stringify(expectedOf(node))})`;
|
|
724
|
+
this.push(`if(!${ok}){${this.appendError(errors, failure)}break ${brk};}`);
|
|
473
725
|
return;
|
|
474
726
|
}
|
|
475
727
|
case "array": {
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
728
|
+
this.push(
|
|
729
|
+
`if(!Array.isArray(${v})){${this.appendError(errors, this.error(segs, "an array", failureData))}break ${brk};}`,
|
|
730
|
+
);
|
|
731
|
+
if (node.min !== undefined) {
|
|
732
|
+
this.push(
|
|
733
|
+
`if(${v}.length<${node.min}){${this.appendError(
|
|
734
|
+
errors,
|
|
735
|
+
this.error(segs, `at least length ${node.min}`, `${v}.length`),
|
|
736
|
+
)}break ${brk};}`,
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
if (node.max !== undefined) {
|
|
740
|
+
this.push(
|
|
741
|
+
`if(${v}.length>${node.max}){${this.appendError(
|
|
742
|
+
errors,
|
|
743
|
+
this.error(segs, `at most length ${node.max}`, `${v}.length`),
|
|
744
|
+
)}break ${brk};}`,
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
const array = this.next("a");
|
|
748
|
+
const index = this.next("i");
|
|
749
|
+
const input = this.next("x");
|
|
750
|
+
const element = this.next("t");
|
|
751
|
+
const label = this.next("L");
|
|
752
|
+
this.push(`const ${array}=new Array(${v}.length);`);
|
|
753
|
+
this.push(
|
|
754
|
+
`for(let ${index}=0;${index}<${v}.length;${index}++){const ${input}=${v}[${index}];let ${element};${label}:{`,
|
|
755
|
+
);
|
|
756
|
+
this.emitCollectProduce(node.el, input, [...segs, { d: index }], element, errors, label);
|
|
757
|
+
this.push(`${array}[${index}]=${element};}}`);
|
|
758
|
+
this.push(`${out}=${array};`);
|
|
489
759
|
return;
|
|
490
760
|
}
|
|
491
761
|
case "tuple": {
|
|
492
|
-
const { postfixStart, prefixCount, requiredPrefix } = this.emitTupleShape(
|
|
762
|
+
const { postfixStart, prefixCount, requiredPrefix } = this.emitTupleShape(
|
|
763
|
+
node,
|
|
764
|
+
v,
|
|
765
|
+
segs,
|
|
766
|
+
errors,
|
|
767
|
+
brk,
|
|
768
|
+
failureData,
|
|
769
|
+
);
|
|
493
770
|
const tuple = this.next("a");
|
|
494
771
|
this.push(`const ${tuple}=[...${v}];`);
|
|
495
772
|
for (let index = 0; index < node.prefix.length; index++) {
|
|
496
773
|
const item = node.prefix[index];
|
|
774
|
+
const itemSegs: PathSeg[] = [...segs, { s: index }];
|
|
497
775
|
const input = `${v}[${index}]`;
|
|
498
776
|
const output = `${tuple}[${index}]`;
|
|
777
|
+
const label = this.next("L");
|
|
499
778
|
if (index >= requiredPrefix) this.push(`if(${index}<${prefixCount}){`);
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
this.push(
|
|
779
|
+
this.push(`${label}:{`);
|
|
780
|
+
if (hasMorph(item.val)) {
|
|
781
|
+
const temporary = this.next("t");
|
|
782
|
+
this.push(`let ${temporary};`);
|
|
783
|
+
this.emitCollectProduce(item.val, input, itemSegs, temporary, errors, label);
|
|
784
|
+
this.push(`${output}=${temporary};`);
|
|
785
|
+
} else {
|
|
786
|
+
this.emitCollectCheck(item.val, input, itemSegs, errors);
|
|
504
787
|
}
|
|
788
|
+
this.push("}");
|
|
505
789
|
if (index >= requiredPrefix) {
|
|
506
790
|
if (item.hasDefault) {
|
|
507
|
-
|
|
508
|
-
this.
|
|
791
|
+
this.push("}else{");
|
|
792
|
+
this.emitDefaultFill(item.val, item.def, item.defFactory === true, output, itemSegs, errors);
|
|
793
|
+
this.push("}");
|
|
509
794
|
} else {
|
|
510
795
|
this.push("}");
|
|
511
796
|
}
|
|
@@ -514,125 +799,147 @@ class Builder {
|
|
|
514
799
|
if (node.variadic !== undefined) {
|
|
515
800
|
const index = this.next("i");
|
|
516
801
|
const input = this.next("x");
|
|
802
|
+
const label = this.next("L");
|
|
517
803
|
this.push(
|
|
518
|
-
`for(let ${index}=${prefixCount};${index}<${postfixStart};${index}++){const ${input}=${v}[${index}]
|
|
804
|
+
`for(let ${index}=${prefixCount};${index}<${postfixStart};${index}++){const ${input}=${v}[${index}];${label}:{`,
|
|
519
805
|
);
|
|
520
806
|
if (hasMorph(node.variadic)) {
|
|
521
|
-
this.
|
|
807
|
+
const temporary = this.next("t");
|
|
808
|
+
this.push(`let ${temporary};`);
|
|
809
|
+
this.emitCollectProduce(node.variadic, input, [...segs, { d: index }], temporary, errors, label);
|
|
810
|
+
this.push(`${tuple}[${index}]=${temporary};`);
|
|
522
811
|
} else {
|
|
523
|
-
this.
|
|
524
|
-
this.push(`${tuple}[${index}]=${input};`);
|
|
812
|
+
this.emitCollectCheck(node.variadic, input, [...segs, { d: index }], errors);
|
|
525
813
|
}
|
|
526
|
-
this.push("}");
|
|
814
|
+
this.push("}}");
|
|
527
815
|
}
|
|
528
816
|
for (let index = 0; index < node.postfix.length; index++) {
|
|
529
817
|
const inputIndex = index === 0 ? postfixStart : `${postfixStart}+${index}`;
|
|
530
818
|
const input = `${v}[${inputIndex}]`;
|
|
531
|
-
const output = `${tuple}[${inputIndex}]`;
|
|
532
819
|
const item = node.postfix[index];
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
this.
|
|
820
|
+
const label = this.next("L");
|
|
821
|
+
this.push(`${label}:{`);
|
|
822
|
+
if (hasMorph(item)) {
|
|
823
|
+
const temporary = this.next("t");
|
|
824
|
+
this.push(`let ${temporary};`);
|
|
825
|
+
this.emitCollectProduce(item, input, [...segs, { d: inputIndex }], temporary, errors, label);
|
|
826
|
+
this.push(`${tuple}[${inputIndex}]=${temporary};`);
|
|
827
|
+
} else {
|
|
828
|
+
this.emitCollectCheck(item, input, [...segs, { d: inputIndex }], errors);
|
|
537
829
|
}
|
|
830
|
+
this.push("}");
|
|
538
831
|
}
|
|
539
832
|
this.push(`${out}=${tuple};`);
|
|
540
833
|
return;
|
|
541
834
|
}
|
|
542
835
|
case "object": {
|
|
836
|
+
if (
|
|
837
|
+
node.patternIndexes !== undefined ||
|
|
838
|
+
node.symbolIndex !== undefined ||
|
|
839
|
+
node.props.some(prop => typeof prop.key === "symbol")
|
|
840
|
+
) {
|
|
841
|
+
this.emitCollectDelegate(node, v, segs, errors, brk, out);
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
543
844
|
this.push(
|
|
544
|
-
`if(typeof ${v}!=="object"||${v}===null
|
|
845
|
+
`if(typeof ${v}!=="object"||${v}===null){${this.appendError(
|
|
846
|
+
errors,
|
|
847
|
+
this.error(segs, "an object", failureData),
|
|
848
|
+
)}break ${brk};}`,
|
|
545
849
|
);
|
|
546
|
-
const
|
|
547
|
-
const fresh = node.extras
|
|
548
|
-
this.push(fresh ? `const ${
|
|
549
|
-
for (const
|
|
550
|
-
const present = `${
|
|
551
|
-
const propSegs: PathSeg[] = [...segs, { s:
|
|
552
|
-
const
|
|
553
|
-
const
|
|
554
|
-
const
|
|
555
|
-
|
|
556
|
-
if (
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
missing.push(`${this.fail(propSegs, expectedOf(p.val), "M")};`);
|
|
850
|
+
const object = this.next("o");
|
|
851
|
+
const fresh = node.extras === "delete" && node.index === undefined;
|
|
852
|
+
this.push(fresh ? `const ${object}={};` : `const ${object}={...${v}};`);
|
|
853
|
+
for (const prop of node.props) {
|
|
854
|
+
const present = `${this.lit(prop.key)} in ${v}`;
|
|
855
|
+
const propSegs: PathSeg[] = [...segs, { s: prop.key }];
|
|
856
|
+
const input = this.access(v, prop.key);
|
|
857
|
+
const output = this.access(object, prop.key);
|
|
858
|
+
const label = this.next("L");
|
|
859
|
+
this.push(`if(!(${present})){`);
|
|
860
|
+
if (prop.hasDefault) {
|
|
861
|
+
this.emitDefaultFill(prop.val, prop.def, prop.defFactory === true, output, propSegs, errors);
|
|
862
|
+
} else if (!prop.opt) {
|
|
863
|
+
this.push(this.appendError(errors, this.error(propSegs, expectedOf(prop.val), "M")));
|
|
561
864
|
}
|
|
562
|
-
this.push(`
|
|
563
|
-
if (
|
|
564
|
-
const
|
|
565
|
-
this.push(`let ${
|
|
566
|
-
this.
|
|
567
|
-
this.push(`${
|
|
865
|
+
this.push(`}else{${label}:{`);
|
|
866
|
+
if (hasMorph(prop.val)) {
|
|
867
|
+
const temporary = this.next("t");
|
|
868
|
+
this.push(`let ${temporary};`);
|
|
869
|
+
this.emitCollectProduce(prop.val, input, propSegs, temporary, errors, label);
|
|
870
|
+
this.push(`${output}=${temporary};`);
|
|
568
871
|
} else {
|
|
569
|
-
this.
|
|
570
|
-
if (fresh) this.push(`${
|
|
872
|
+
this.emitCollectCheck(prop.val, input, propSegs, errors);
|
|
873
|
+
if (fresh) this.push(`${output}=${input};`);
|
|
571
874
|
}
|
|
572
|
-
this.push("}");
|
|
875
|
+
this.push("}}");
|
|
573
876
|
}
|
|
574
|
-
if (node.index) {
|
|
575
|
-
const
|
|
576
|
-
|
|
877
|
+
if (node.index !== undefined) {
|
|
878
|
+
const key = this.next("k");
|
|
879
|
+
const label = this.next("L");
|
|
880
|
+
this.push(`for(const ${key} in ${v})if(own.call(${v},${key})){${label}:{`);
|
|
577
881
|
if (hasMorph(node.index)) {
|
|
578
|
-
const
|
|
579
|
-
this.push(`let ${
|
|
580
|
-
this.
|
|
581
|
-
this.push(`${
|
|
882
|
+
const temporary = this.next("t");
|
|
883
|
+
this.push(`let ${temporary};`);
|
|
884
|
+
this.emitCollectProduce(node.index, `${v}[${key}]`, [...segs, { d: key }], temporary, errors, label);
|
|
885
|
+
this.push(`${object}[${key}]=${temporary};`);
|
|
582
886
|
} else {
|
|
583
|
-
this.
|
|
887
|
+
this.emitCollectCheck(node.index, `${v}[${key}]`, [...segs, { d: key }], errors);
|
|
584
888
|
}
|
|
585
|
-
this.push("}");
|
|
889
|
+
this.push("}}");
|
|
586
890
|
} else if (node.extras === "reject") {
|
|
587
|
-
const
|
|
891
|
+
const key = this.next("k");
|
|
588
892
|
this.push(
|
|
589
|
-
`for(const ${
|
|
590
|
-
|
|
591
|
-
"removed
|
|
592
|
-
|
|
593
|
-
)};`,
|
|
893
|
+
`for(const ${key} in ${v})if(own.call(${v},${key})&&!(${this.declaredCheck(node.props, key)})){${this.appendError(
|
|
894
|
+
errors,
|
|
895
|
+
this.error([...segs, { d: key }], "removed", `${v}[${key}]`),
|
|
896
|
+
)}}`,
|
|
594
897
|
);
|
|
595
898
|
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
this.
|
|
603
|
-
const failure = this.fail(segs, node.expected, refined);
|
|
604
|
-
this.push(`try{if(!${this.ref(node.pred)}(${refined}))${failure};}catch{${failure};}`);
|
|
605
|
-
this.push(`${out}=${refined};`);
|
|
899
|
+
if (node.extras === "reject") {
|
|
900
|
+
const symbol = this.next("s");
|
|
901
|
+
this.push(
|
|
902
|
+
`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}]`))}}`,
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
this.push(`${out}=${object};`);
|
|
606
906
|
return;
|
|
607
907
|
}
|
|
608
908
|
case "intersection": {
|
|
609
909
|
const current = this.next("t");
|
|
610
910
|
this.push(`let ${current}=${v};`);
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
911
|
+
const mark = this.markErrors(errors);
|
|
912
|
+
for (let index = 0; index < node.members.length; index++) {
|
|
913
|
+
if (index > 0) this.guardGrowth(errors, mark, brk);
|
|
914
|
+
const member = node.members[index];
|
|
915
|
+
if (hasMorph(member)) this.emitCollectProduce(member, current, segs, current, errors, brk);
|
|
916
|
+
else this.emitCollectCheck(member, current, segs, errors);
|
|
614
917
|
}
|
|
615
918
|
this.push(`${out}=${current};`);
|
|
616
919
|
return;
|
|
617
920
|
}
|
|
618
921
|
default:
|
|
619
|
-
this.
|
|
922
|
+
this.emitCollectDelegate(node, v, segs, errors, brk, out);
|
|
620
923
|
}
|
|
621
924
|
}
|
|
622
925
|
|
|
623
926
|
build(ir: IR): (value: unknown) => unknown {
|
|
927
|
+
const errors = this.next("e");
|
|
624
928
|
let ret: string;
|
|
625
929
|
if (hasMorph(ir)) {
|
|
626
|
-
this.
|
|
627
|
-
|
|
628
|
-
this.
|
|
930
|
+
const label = this.next("L");
|
|
931
|
+
this.push(`let ${errors};let o;${label}:{`);
|
|
932
|
+
this.emitCollectProduce(ir, "v", [], "o", errors, label);
|
|
933
|
+
this.push("}");
|
|
629
934
|
ret = "o";
|
|
630
935
|
} else {
|
|
631
|
-
this.
|
|
936
|
+
this.push(`let ${errors};`);
|
|
937
|
+
this.emitCollectCheck(ir, "v", [], errors);
|
|
632
938
|
ret = "v";
|
|
633
939
|
}
|
|
940
|
+
this.push(`if(${errors}!==undefined)return ${errors};`);
|
|
634
941
|
const src = `return function(v){${this.#lines.join("")}return ${ret}}`;
|
|
635
|
-
const make = new Function("R", "AE", "M", "PF", "UF", "MC", "own", src) as (
|
|
942
|
+
const make = new Function("R", "AE", "M", "PF", "UF", "MC", "own", "MD", src) as (
|
|
636
943
|
refs: unknown[],
|
|
637
944
|
ae: typeof OmpErrors,
|
|
638
945
|
m: typeof MISSING,
|
|
@@ -640,8 +947,18 @@ class Builder {
|
|
|
640
947
|
uf: typeof unionFail,
|
|
641
948
|
mc: typeof CompiledMorphContext,
|
|
642
949
|
ownFn: typeof own,
|
|
950
|
+
md: typeof materializeDefault,
|
|
643
951
|
) => (value: unknown) => unknown;
|
|
644
|
-
return make(
|
|
952
|
+
return make(
|
|
953
|
+
this.#refs,
|
|
954
|
+
OmpErrors,
|
|
955
|
+
MISSING,
|
|
956
|
+
prefixErrors,
|
|
957
|
+
unionFail,
|
|
958
|
+
CompiledMorphContext,
|
|
959
|
+
own,
|
|
960
|
+
materializeDefault,
|
|
961
|
+
);
|
|
645
962
|
}
|
|
646
963
|
|
|
647
964
|
emitAllows(node: IR, v: string): void {
|
|
@@ -659,13 +976,11 @@ class Builder {
|
|
|
659
976
|
}
|
|
660
977
|
case "object": {
|
|
661
978
|
const object = this.next("o");
|
|
662
|
-
this.push(
|
|
663
|
-
`const ${object}=${v};if(typeof ${object}!=="object"||${object}===null||Array.isArray(${object}))return false;`,
|
|
664
|
-
);
|
|
979
|
+
this.push(`const ${object}=${v};if(typeof ${object}!=="object"||${object}===null)return false;`);
|
|
665
980
|
for (const prop of node.props) {
|
|
666
981
|
const value = this.next("p");
|
|
667
|
-
const present = `${
|
|
668
|
-
this.push(`const ${value}=${access(object, prop.key)};`);
|
|
982
|
+
const present = `${this.lit(prop.key)} in ${object}`;
|
|
983
|
+
this.push(`const ${value}=${this.access(object, prop.key)};`);
|
|
669
984
|
if (prop.opt || prop.hasDefault) {
|
|
670
985
|
if (rejectsUndefined(prop.val)) {
|
|
671
986
|
this.push(`if(${value}!==undefined){`);
|
|
@@ -681,16 +996,42 @@ class Builder {
|
|
|
681
996
|
this.emitAllows(prop.val, value);
|
|
682
997
|
}
|
|
683
998
|
}
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
this.push(`for(const ${
|
|
687
|
-
this.emitAllows(node.index, `${object}[${
|
|
999
|
+
const stringKey = this.next("k");
|
|
1000
|
+
if (node.index !== undefined) {
|
|
1001
|
+
this.push(`for(const ${stringKey} in ${object}){if(!own.call(${object},${stringKey}))continue;`);
|
|
1002
|
+
this.emitAllows(node.index, `${object}[${stringKey}]`);
|
|
688
1003
|
this.push("}");
|
|
689
|
-
}
|
|
690
|
-
|
|
1004
|
+
}
|
|
1005
|
+
if (node.patternIndexes !== undefined) {
|
|
1006
|
+
for (const pattern of node.patternIndexes) {
|
|
1007
|
+
this.push(
|
|
1008
|
+
`for(const ${stringKey} in ${object})if(own.call(${object},${stringKey})&&(${this.predicate(pattern.key, stringKey)})&&!(${this.predicate(pattern.val, `${object}[${stringKey}]`)}))return false;`,
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
if (node.symbolIndex !== undefined) {
|
|
1013
|
+
const symbol = this.next("s");
|
|
691
1014
|
this.push(
|
|
692
|
-
`for(const ${
|
|
1015
|
+
`for(const ${symbol} of Object.getOwnPropertySymbols(${object})){if(!Object.prototype.propertyIsEnumerable.call(${object},${symbol}))continue;`,
|
|
693
1016
|
);
|
|
1017
|
+
this.emitAllows(node.symbolIndex, `${object}[${symbol}]`);
|
|
1018
|
+
this.push("}");
|
|
1019
|
+
}
|
|
1020
|
+
if (node.extras === "reject") {
|
|
1021
|
+
const patternMatch =
|
|
1022
|
+
node.patternIndexes?.map(pattern => `(${this.predicate(pattern.key, stringKey)})`).join("||") ??
|
|
1023
|
+
"false";
|
|
1024
|
+
if (node.index === undefined) {
|
|
1025
|
+
this.push(
|
|
1026
|
+
`for(const ${stringKey} in ${object})if(own.call(${object},${stringKey})&&!(${this.declaredCheck(node.props, stringKey)})&&!(${patternMatch}))return false;`,
|
|
1027
|
+
);
|
|
1028
|
+
}
|
|
1029
|
+
if (node.symbolIndex === undefined) {
|
|
1030
|
+
const symbol = this.next("s");
|
|
1031
|
+
this.push(
|
|
1032
|
+
`for(const ${symbol} of Object.getOwnPropertySymbols(${object}))if(Object.prototype.propertyIsEnumerable.call(${object},${symbol})&&!(${this.declaredCheck(node.props, symbol)}))return false;`,
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
694
1035
|
}
|
|
695
1036
|
return;
|
|
696
1037
|
}
|
|
@@ -716,14 +1057,14 @@ class Builder {
|
|
|
716
1057
|
}
|
|
717
1058
|
}
|
|
718
1059
|
|
|
719
|
-
buildAllows(ir: IR): (value: unknown) =>
|
|
1060
|
+
buildAllows(ir: IR): (value: unknown) => value is unknown {
|
|
720
1061
|
this.emitAllows(ir, "v");
|
|
721
1062
|
const src = `return function(v){${this.#lines.join("")}return true}`;
|
|
722
1063
|
const make = new Function("R", "AE", "own", src) as (
|
|
723
1064
|
refs: unknown[],
|
|
724
1065
|
ae: typeof OmpErrors,
|
|
725
1066
|
ownFn: typeof own,
|
|
726
|
-
) => (value: unknown) =>
|
|
1067
|
+
) => (value: unknown) => value is unknown;
|
|
727
1068
|
return make(this.#refs, OmpErrors, own);
|
|
728
1069
|
}
|
|
729
1070
|
}
|
|
@@ -736,15 +1077,15 @@ function prefixErrors(errs: OmpErrors, parts: PropertyKey[]): OmpErrors {
|
|
|
736
1077
|
const kWalk = Symbol("omptype.boundWalk");
|
|
737
1078
|
|
|
738
1079
|
interface WalkTagged {
|
|
739
|
-
[kWalk]?: (value: unknown) => unknown;
|
|
1080
|
+
[kWalk]?: (value: unknown, path?: PropertyKey[]) => unknown;
|
|
740
1081
|
}
|
|
741
1082
|
|
|
742
1083
|
/** Cached interpreter closure for recursive aliases and predicate-only fallbacks. */
|
|
743
|
-
function boundWalk(node: IR): (value: unknown) => unknown {
|
|
1084
|
+
function boundWalk(node: IR): (value: unknown, path?: PropertyKey[]) => unknown {
|
|
744
1085
|
const tagged = node as IR & WalkTagged;
|
|
745
1086
|
let fn = tagged[kWalk];
|
|
746
1087
|
if (!fn) {
|
|
747
|
-
fn = (value: unknown) => walk(node, value);
|
|
1088
|
+
fn = (value: unknown, path?: PropertyKey[]) => walk(node, value, path);
|
|
748
1089
|
tagged[kWalk] = fn;
|
|
749
1090
|
}
|
|
750
1091
|
return fn;
|
|
@@ -755,26 +1096,40 @@ function resolvedRoot(ir: IR): IR {
|
|
|
755
1096
|
}
|
|
756
1097
|
|
|
757
1098
|
const compiledCache = new WeakMap<IR, (value: unknown) => unknown>();
|
|
758
|
-
const allowsCache = new WeakMap<IR, (value: unknown) =>
|
|
1099
|
+
const allowsCache = new WeakMap<IR, (value: unknown) => value is unknown>();
|
|
759
1100
|
|
|
760
1101
|
/** Compile `ir` into a specialized validator. */
|
|
761
1102
|
export function compile(ir: IR): (value: unknown) => unknown {
|
|
762
1103
|
const root = resolvedRoot(ir);
|
|
763
|
-
|
|
1104
|
+
const validator = compiledCache.get(root);
|
|
764
1105
|
if (validator === undefined) {
|
|
765
|
-
|
|
766
|
-
|
|
1106
|
+
// Publish a deferred wrapper before building: recursive schemas re-enter
|
|
1107
|
+
// compile() for the same root mid-build (e.g. an alias element inside an
|
|
1108
|
+
// array), and each re-entry must reuse this build instead of starting a
|
|
1109
|
+
// fresh one forever. The wrapper resolves to the built validator by call
|
|
1110
|
+
// time; the interpreter is a safety net that never triggers post-build.
|
|
1111
|
+
let built: ((value: unknown) => unknown) | undefined;
|
|
1112
|
+
compiledCache.set(root, value => (built === undefined ? walk(root, value) : built(value)));
|
|
1113
|
+
built = new Builder().build(root);
|
|
1114
|
+
compiledCache.set(root, built);
|
|
1115
|
+
return built;
|
|
767
1116
|
}
|
|
768
1117
|
return validator;
|
|
769
1118
|
}
|
|
770
1119
|
|
|
771
1120
|
/** Compile `ir` into an allocation-free boolean validator. */
|
|
772
|
-
export function compileAllows(ir: IR): (value: unknown) =>
|
|
1121
|
+
export function compileAllows(ir: IR): (value: unknown) => value is unknown {
|
|
773
1122
|
const root = resolvedRoot(ir);
|
|
774
|
-
|
|
1123
|
+
const validator = allowsCache.get(root);
|
|
775
1124
|
if (validator === undefined) {
|
|
776
|
-
|
|
777
|
-
allowsCache.set(root,
|
|
1125
|
+
let built: ((value: unknown) => value is unknown) | undefined;
|
|
1126
|
+
allowsCache.set(root, ((value: unknown) =>
|
|
1127
|
+
built === undefined ? !(walk(root, value) instanceof OmpErrors) : built(value)) as (
|
|
1128
|
+
value: unknown,
|
|
1129
|
+
) => value is unknown);
|
|
1130
|
+
built = new Builder().buildAllows(root);
|
|
1131
|
+
allowsCache.set(root, built);
|
|
1132
|
+
return built;
|
|
778
1133
|
}
|
|
779
1134
|
return validator;
|
|
780
1135
|
}
|
|
@@ -785,9 +1140,9 @@ export function compileToSource(ir: IR): string {
|
|
|
785
1140
|
const builder = new Builder();
|
|
786
1141
|
if (hasMorph(root)) {
|
|
787
1142
|
builder.push("let o;");
|
|
788
|
-
builder.
|
|
1143
|
+
builder.emitCollectProduce(root, "v", [], "o", "e", "L0");
|
|
789
1144
|
return `function(v){/* refs elided */return o}`;
|
|
790
1145
|
}
|
|
791
|
-
builder.
|
|
1146
|
+
builder.emitCollectCheck(root, "v", [], "e");
|
|
792
1147
|
return `function(v){/* refs elided */return v}`;
|
|
793
1148
|
}
|