@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/dist/js/compile.js
CHANGED
|
@@ -15,13 +15,10 @@
|
|
|
15
15
|
* members compile to inline predicates
|
|
16
16
|
*/
|
|
17
17
|
import { MISSING, OmpErrors } from "./errors.js";
|
|
18
|
-
import { canRefineUnionFailure, unionFail, walk } from "./interp.js";
|
|
18
|
+
import { canRefineUnionFailure, materializeDefault, unionFail, walk } from "./interp.js";
|
|
19
19
|
import { expectedOf, hasMorph } from "./ir.js";
|
|
20
20
|
const own = Object.prototype.hasOwnProperty;
|
|
21
21
|
const IDENT = /^[A-Za-z_$][\w$]*$/;
|
|
22
|
-
function access(base, key) {
|
|
23
|
-
return IDENT.test(key) ? `${base}.${key}` : `${base}[${JSON.stringify(key)}]`;
|
|
24
|
-
}
|
|
25
22
|
/** Inline-able literal, else undefined (caller hoists into the refs pool). */
|
|
26
23
|
function litSource(v) {
|
|
27
24
|
if (v === null)
|
|
@@ -98,8 +95,11 @@ class Builder {
|
|
|
98
95
|
lit(v) {
|
|
99
96
|
return litSource(v) ?? this.ref(v);
|
|
100
97
|
}
|
|
98
|
+
access(base, key) {
|
|
99
|
+
return typeof key === "string" && IDENT.test(key) ? `${base}.${key}` : `${base}[${this.lit(key)}]`;
|
|
100
|
+
}
|
|
101
101
|
pathExpr(segs) {
|
|
102
|
-
const parts = segs.map(seg => ("s" in seg ?
|
|
102
|
+
const parts = segs.map(seg => ("s" in seg ? this.lit(seg.s) : seg.d));
|
|
103
103
|
return `[${parts.join(",")}]`;
|
|
104
104
|
}
|
|
105
105
|
storedPathExpr(segs) {
|
|
@@ -107,7 +107,7 @@ class Builder {
|
|
|
107
107
|
return "undefined";
|
|
108
108
|
if (segs.length === 1) {
|
|
109
109
|
const seg = segs[0];
|
|
110
|
-
return "s" in seg ?
|
|
110
|
+
return "s" in seg ? this.lit(seg.s) : seg.d;
|
|
111
111
|
}
|
|
112
112
|
const staticParts = [];
|
|
113
113
|
for (const seg of segs) {
|
|
@@ -117,8 +117,14 @@ class Builder {
|
|
|
117
117
|
}
|
|
118
118
|
return this.ref(staticParts);
|
|
119
119
|
}
|
|
120
|
+
error(segs, expected, dataExpr) {
|
|
121
|
+
return `new AE(${this.storedPathExpr(segs)},${JSON.stringify(expected)},${dataExpr})`;
|
|
122
|
+
}
|
|
120
123
|
fail(segs, expected, dataExpr) {
|
|
121
|
-
return `return
|
|
124
|
+
return `return ${this.error(segs, expected, dataExpr)}`;
|
|
125
|
+
}
|
|
126
|
+
appendError(errors, error) {
|
|
127
|
+
return `if(${errors}===undefined)${errors}=${error};else ${errors}.append(${error});`;
|
|
122
128
|
}
|
|
123
129
|
/** Pure boolean predicate for a morph-free subtree. */
|
|
124
130
|
predicate(node, v) {
|
|
@@ -193,10 +199,10 @@ class Builder {
|
|
|
193
199
|
return out;
|
|
194
200
|
}
|
|
195
201
|
case "object": {
|
|
196
|
-
const checks = [`typeof ${v}==="object"`, `${v}!==null
|
|
202
|
+
const checks = [`typeof ${v}==="object"`, `${v}!==null`];
|
|
197
203
|
for (const p of node.props) {
|
|
198
|
-
const av = access(v, p.key);
|
|
199
|
-
const present = `${
|
|
204
|
+
const av = this.access(v, p.key);
|
|
205
|
+
const present = `${this.lit(p.key)} in ${v}`;
|
|
200
206
|
const predicate = this.predicate(p.val, av);
|
|
201
207
|
checks.push(p.opt || p.hasDefault
|
|
202
208
|
? rejectsUndefined(p.val)
|
|
@@ -206,13 +212,29 @@ class Builder {
|
|
|
206
212
|
? predicate
|
|
207
213
|
: `((${present})&&(${predicate}))`);
|
|
208
214
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
checks.push(`(()=>{for(const ${
|
|
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})()`);
|
|
212
218
|
}
|
|
213
|
-
|
|
214
|
-
const
|
|
215
|
-
|
|
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
|
+
}
|
|
216
238
|
}
|
|
217
239
|
return `(${checks.join("&&")})`;
|
|
218
240
|
}
|
|
@@ -229,125 +251,207 @@ class Builder {
|
|
|
229
251
|
const set = this.ref(new Set(props.map(p => p.key)));
|
|
230
252
|
return `${set}.has(${keyVar})`;
|
|
231
253
|
}
|
|
232
|
-
return `(${props.map(p => `${keyVar}===${
|
|
254
|
+
return `(${props.map(p => `${keyVar}===${this.lit(p.key)}`).join("||")})`;
|
|
233
255
|
}
|
|
234
|
-
|
|
235
|
-
|
|
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);
|
|
236
267
|
const result = this.next("r");
|
|
237
|
-
|
|
238
|
-
this.push(
|
|
239
|
-
|
|
240
|
-
|
|
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};`}}`);
|
|
241
272
|
if (out !== undefined)
|
|
242
273
|
this.push(`${out}=${result};`);
|
|
243
274
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
this.push(`if(${v}.length<${minimum})${this.fail(segs, `an array of at least length ${minimum}`, failureData)};`);
|
|
250
|
-
}
|
|
251
|
-
if (node.variadic === undefined) {
|
|
252
|
-
const maximum = node.prefix.length + node.postfix.length;
|
|
253
|
-
this.push(`if(${v}.length>${maximum})${this.fail(segs, `an array of at most length ${maximum}`, failureData)};`);
|
|
254
|
-
}
|
|
255
|
-
let postfixStart = `${v}.length`;
|
|
256
|
-
if (node.postfix.length > 0) {
|
|
257
|
-
postfixStart = this.next("p");
|
|
258
|
-
this.push(`const ${postfixStart}=${v}.length-${node.postfix.length};`);
|
|
259
|
-
}
|
|
260
|
-
let prefixCount = String(node.prefix.length);
|
|
261
|
-
if (requiredPrefix !== node.prefix.length) {
|
|
262
|
-
prefixCount = this.next("n");
|
|
263
|
-
this.push(`const ${prefixCount}=Math.min(${node.prefix.length},${postfixStart});`);
|
|
264
|
-
}
|
|
265
|
-
return { postfixStart, prefixCount, requiredPrefix };
|
|
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;
|
|
266
280
|
}
|
|
267
|
-
/**
|
|
268
|
-
|
|
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
|
+
}
|
|
269
291
|
switch (node.k) {
|
|
270
292
|
case "unknown":
|
|
271
293
|
return;
|
|
272
294
|
case "array": {
|
|
273
|
-
|
|
274
|
-
if (node.min !== undefined)
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
this.
|
|
282
|
-
this.
|
|
283
|
-
this.
|
|
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("}}");
|
|
284
307
|
return;
|
|
285
308
|
}
|
|
286
309
|
case "tuple": {
|
|
287
|
-
|
|
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
|
+
}
|
|
288
330
|
for (let index = 0; index < node.prefix.length; index++) {
|
|
289
331
|
if (index >= requiredPrefix)
|
|
290
332
|
this.push(`if(${index}<${prefixCount}){`);
|
|
291
|
-
this.
|
|
333
|
+
this.emitCollectCheck(node.prefix[index].val, `${v}[${index}]`, [...segs, { d: String(index) }], errors);
|
|
292
334
|
if (index >= requiredPrefix)
|
|
293
335
|
this.push("}");
|
|
294
336
|
}
|
|
295
337
|
if (node.variadic !== undefined) {
|
|
296
338
|
const index = this.next("i");
|
|
297
339
|
this.push(`for(let ${index}=${prefixCount};${index}<${postfixStart};${index}++){`);
|
|
298
|
-
this.
|
|
340
|
+
this.emitCollectCheck(node.variadic, `${v}[${index}]`, [...segs, { d: index }], errors);
|
|
299
341
|
this.push("}");
|
|
300
342
|
}
|
|
301
343
|
for (let index = 0; index < node.postfix.length; index++) {
|
|
302
344
|
const inputIndex = index === 0 ? postfixStart : `${postfixStart}+${index}`;
|
|
303
|
-
this.
|
|
345
|
+
this.emitCollectCheck(node.postfix[index], `${v}[${inputIndex}]`, [...segs, { d: inputIndex }], errors);
|
|
304
346
|
}
|
|
347
|
+
if (node.variadic === undefined)
|
|
348
|
+
this.push("}");
|
|
349
|
+
if (minimum > 0)
|
|
350
|
+
this.push("}");
|
|
351
|
+
this.push("}");
|
|
305
352
|
return;
|
|
306
353
|
}
|
|
307
354
|
case "object": {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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) {
|
|
313
366
|
this.push(`if(${present}){`);
|
|
314
|
-
this.
|
|
367
|
+
this.emitCollectCheck(prop.val, this.access(v, prop.key), propSegs, errors);
|
|
315
368
|
this.push("}");
|
|
316
369
|
}
|
|
317
370
|
else {
|
|
318
|
-
this.push(`if(!(${present}))${this.
|
|
319
|
-
this.
|
|
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("}");
|
|
320
374
|
}
|
|
321
375
|
}
|
|
322
|
-
if (node.index) {
|
|
323
|
-
const
|
|
324
|
-
this.push(`for(const ${
|
|
325
|
-
this.
|
|
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);
|
|
326
380
|
this.push("}");
|
|
327
381
|
}
|
|
328
382
|
else if (node.extras === "reject") {
|
|
329
|
-
const
|
|
330
|
-
this.push(`for(const ${
|
|
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}]`))}}`);
|
|
331
391
|
}
|
|
392
|
+
this.push("}");
|
|
332
393
|
return;
|
|
333
394
|
}
|
|
334
|
-
case "sub":
|
|
335
|
-
this.emitDelegate(node, v, segs);
|
|
336
|
-
return;
|
|
337
395
|
case "union": {
|
|
338
396
|
const failure = node.members.some(canRefineUnionFailure)
|
|
339
|
-
? `
|
|
340
|
-
: this.
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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`))}}`);
|
|
345
406
|
}
|
|
346
|
-
|
|
347
|
-
this.push(`if(
|
|
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`))}}`);
|
|
348
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("}");
|
|
349
440
|
return;
|
|
350
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;
|
|
351
455
|
case "null":
|
|
352
456
|
case "undefined":
|
|
353
457
|
case "boolean":
|
|
@@ -355,53 +459,87 @@ class Builder {
|
|
|
355
459
|
case "symbol":
|
|
356
460
|
case "never":
|
|
357
461
|
case "anyobject":
|
|
358
|
-
case "lit":
|
|
359
|
-
case "string":
|
|
360
|
-
case "number":
|
|
361
462
|
case "instance":
|
|
362
|
-
this.push(`if(!(${this.predicate(node, v)}))${this.
|
|
363
|
-
return;
|
|
364
|
-
case "refine": {
|
|
365
|
-
this.emitCheck(node.base, v, segs, failureData);
|
|
366
|
-
const failure = this.fail(segs, node.expected, v);
|
|
367
|
-
this.push(`try{if(!${this.ref(node.pred)}(${v}))${failure};}catch{${failure};}`);
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
|
-
case "intersection":
|
|
371
|
-
for (const member of node.members)
|
|
372
|
-
this.emitCheck(member, v, segs, failureData);
|
|
463
|
+
this.push(`if(!(${this.predicate(node, v)})){${this.appendError(errors, this.error(segs, expectedOf(node), failureData))}}`);
|
|
373
464
|
return;
|
|
374
465
|
default:
|
|
375
|
-
this.
|
|
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)})`};`);
|
|
376
506
|
}
|
|
377
507
|
}
|
|
378
508
|
/**
|
|
379
509
|
* Validate `v` against a morphing subtree and assign the produced output
|
|
380
|
-
* to `out` (an already-declared `let`).
|
|
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.
|
|
381
513
|
*/
|
|
382
|
-
|
|
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
|
+
}
|
|
383
519
|
if (!hasMorph(node)) {
|
|
384
|
-
this.
|
|
520
|
+
this.emitCollectCheck(node, v, segs, errors, failureData);
|
|
385
521
|
this.push(`${out}=${v};`);
|
|
386
522
|
return;
|
|
387
523
|
}
|
|
388
524
|
switch (node.k) {
|
|
389
525
|
case "sub":
|
|
390
|
-
this.
|
|
526
|
+
this.emitCollectDelegate(node, v, segs, errors, brk, out);
|
|
391
527
|
return;
|
|
392
528
|
case "morph": {
|
|
393
529
|
const input = this.next("t");
|
|
394
530
|
this.push(`let ${input};`);
|
|
395
|
-
this.
|
|
531
|
+
const mark = this.markErrors(errors);
|
|
532
|
+
this.emitCollectProduce(node.input, v, segs, input, errors, brk, failureData);
|
|
533
|
+
this.guardGrowth(errors, mark, brk);
|
|
396
534
|
const context = this.next("c");
|
|
397
535
|
const result = this.next("r");
|
|
398
536
|
this.push(`const ${context}=new MC(${this.storedPathExpr(segs)},${input});`);
|
|
399
537
|
this.push(`const ${result}=${this.ref(node.fn)}(${input},${context});`);
|
|
400
|
-
this.push(`if(${result} instanceof AE)
|
|
538
|
+
this.push(`if(${result} instanceof AE){${this.appendError(errors, result)}break ${brk};}`);
|
|
401
539
|
if (node.out === undefined)
|
|
402
540
|
this.push(`${out}=${result};`);
|
|
403
541
|
else
|
|
404
|
-
this.
|
|
542
|
+
this.emitCollectProduce(node.out, result, segs, out, errors, brk);
|
|
405
543
|
return;
|
|
406
544
|
}
|
|
407
545
|
case "alias": {
|
|
@@ -411,12 +549,12 @@ class Builder {
|
|
|
411
549
|
this.#activeAliases = active;
|
|
412
550
|
}
|
|
413
551
|
if (active.has(node)) {
|
|
414
|
-
this.
|
|
552
|
+
this.emitCollectDelegate(node, v, segs, errors, brk, out);
|
|
415
553
|
return;
|
|
416
554
|
}
|
|
417
555
|
active.add(node);
|
|
418
556
|
try {
|
|
419
|
-
this.
|
|
557
|
+
this.emitCollectProduce(node.resolve(), v, segs, out, errors, brk, failureData);
|
|
420
558
|
}
|
|
421
559
|
finally {
|
|
422
560
|
active.delete(node);
|
|
@@ -442,47 +580,58 @@ class Builder {
|
|
|
442
580
|
}
|
|
443
581
|
}
|
|
444
582
|
this.push("}");
|
|
445
|
-
|
|
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};}`);
|
|
446
585
|
return;
|
|
447
586
|
}
|
|
448
587
|
case "array": {
|
|
449
|
-
|
|
450
|
-
if (node.min !== undefined)
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
const
|
|
457
|
-
const
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
this.
|
|
461
|
-
this.
|
|
462
|
-
this.push(
|
|
463
|
-
this.
|
|
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};`);
|
|
464
605
|
return;
|
|
465
606
|
}
|
|
466
607
|
case "tuple": {
|
|
467
|
-
const { postfixStart, prefixCount, requiredPrefix } = this.emitTupleShape(node, v, segs, failureData);
|
|
608
|
+
const { postfixStart, prefixCount, requiredPrefix } = this.emitTupleShape(node, v, segs, errors, brk, failureData);
|
|
468
609
|
const tuple = this.next("a");
|
|
469
610
|
this.push(`const ${tuple}=[...${v}];`);
|
|
470
611
|
for (let index = 0; index < node.prefix.length; index++) {
|
|
471
612
|
const item = node.prefix[index];
|
|
613
|
+
const itemSegs = [...segs, { s: index }];
|
|
472
614
|
const input = `${v}[${index}]`;
|
|
473
615
|
const output = `${tuple}[${index}]`;
|
|
616
|
+
const label = this.next("L");
|
|
474
617
|
if (index >= requiredPrefix)
|
|
475
618
|
this.push(`if(${index}<${prefixCount}){`);
|
|
476
|
-
|
|
477
|
-
|
|
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
|
+
}
|
|
478
626
|
else {
|
|
479
|
-
this.
|
|
480
|
-
this.push(`${output}=${input};`);
|
|
627
|
+
this.emitCollectCheck(item.val, input, itemSegs, errors);
|
|
481
628
|
}
|
|
629
|
+
this.push("}");
|
|
482
630
|
if (index >= requiredPrefix) {
|
|
483
631
|
if (item.hasDefault) {
|
|
484
|
-
|
|
485
|
-
this.
|
|
632
|
+
this.push("}else{");
|
|
633
|
+
this.emitDefaultFill(item.val, item.def, item.defFactory === true, output, itemSegs, errors);
|
|
634
|
+
this.push("}");
|
|
486
635
|
}
|
|
487
636
|
else {
|
|
488
637
|
this.push("}");
|
|
@@ -492,125 +641,142 @@ class Builder {
|
|
|
492
641
|
if (node.variadic !== undefined) {
|
|
493
642
|
const index = this.next("i");
|
|
494
643
|
const input = this.next("x");
|
|
495
|
-
this.
|
|
644
|
+
const label = this.next("L");
|
|
645
|
+
this.push(`for(let ${index}=${prefixCount};${index}<${postfixStart};${index}++){const ${input}=${v}[${index}];${label}:{`);
|
|
496
646
|
if (hasMorph(node.variadic)) {
|
|
497
|
-
this.
|
|
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};`);
|
|
498
651
|
}
|
|
499
652
|
else {
|
|
500
|
-
this.
|
|
501
|
-
this.push(`${tuple}[${index}]=${input};`);
|
|
653
|
+
this.emitCollectCheck(node.variadic, input, [...segs, { d: index }], errors);
|
|
502
654
|
}
|
|
503
|
-
this.push("}");
|
|
655
|
+
this.push("}}");
|
|
504
656
|
}
|
|
505
657
|
for (let index = 0; index < node.postfix.length; index++) {
|
|
506
658
|
const inputIndex = index === 0 ? postfixStart : `${postfixStart}+${index}`;
|
|
507
659
|
const input = `${v}[${inputIndex}]`;
|
|
508
|
-
const output = `${tuple}[${inputIndex}]`;
|
|
509
660
|
const item = node.postfix[index];
|
|
510
|
-
|
|
511
|
-
|
|
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
|
+
}
|
|
512
669
|
else {
|
|
513
|
-
this.
|
|
514
|
-
this.push(`${output}=${input};`);
|
|
670
|
+
this.emitCollectCheck(item, input, [...segs, { d: inputIndex }], errors);
|
|
515
671
|
}
|
|
672
|
+
this.push("}");
|
|
516
673
|
}
|
|
517
674
|
this.push(`${out}=${tuple};`);
|
|
518
675
|
return;
|
|
519
676
|
}
|
|
520
677
|
case "object": {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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);
|
|
535
697
|
}
|
|
536
|
-
else if (!
|
|
537
|
-
|
|
698
|
+
else if (!prop.opt) {
|
|
699
|
+
this.push(this.appendError(errors, this.error(propSegs, expectedOf(prop.val), "M")));
|
|
538
700
|
}
|
|
539
|
-
this.push(`
|
|
540
|
-
if (
|
|
541
|
-
const
|
|
542
|
-
this.push(`let ${
|
|
543
|
-
this.
|
|
544
|
-
this.push(`${
|
|
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};`);
|
|
545
707
|
}
|
|
546
708
|
else {
|
|
547
|
-
this.
|
|
709
|
+
this.emitCollectCheck(prop.val, input, propSegs, errors);
|
|
548
710
|
if (fresh)
|
|
549
|
-
this.push(`${
|
|
711
|
+
this.push(`${output}=${input};`);
|
|
550
712
|
}
|
|
551
|
-
this.push("}");
|
|
713
|
+
this.push("}}");
|
|
552
714
|
}
|
|
553
|
-
if (node.index) {
|
|
554
|
-
const
|
|
555
|
-
|
|
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}:{`);
|
|
556
719
|
if (hasMorph(node.index)) {
|
|
557
|
-
const
|
|
558
|
-
this.push(`let ${
|
|
559
|
-
this.
|
|
560
|
-
this.push(`${
|
|
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};`);
|
|
561
724
|
}
|
|
562
725
|
else {
|
|
563
|
-
this.
|
|
726
|
+
this.emitCollectCheck(node.index, `${v}[${key}]`, [...segs, { d: key }], errors);
|
|
564
727
|
}
|
|
565
|
-
this.push("}");
|
|
728
|
+
this.push("}}");
|
|
566
729
|
}
|
|
567
730
|
else if (node.extras === "reject") {
|
|
568
|
-
const
|
|
569
|
-
this.push(`for(const ${
|
|
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}]`))}}`);
|
|
570
733
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
this.push(`let ${refined};`);
|
|
577
|
-
this.emitProduce(node.base, v, segs, refined, failureData);
|
|
578
|
-
const failure = this.fail(segs, node.expected, refined);
|
|
579
|
-
this.push(`try{if(!${this.ref(node.pred)}(${refined}))${failure};}catch{${failure};}`);
|
|
580
|
-
this.push(`${out}=${refined};`);
|
|
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};`);
|
|
581
739
|
return;
|
|
582
740
|
}
|
|
583
741
|
case "intersection": {
|
|
584
742
|
const current = this.next("t");
|
|
585
743
|
this.push(`let ${current}=${v};`);
|
|
586
|
-
|
|
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];
|
|
587
749
|
if (hasMorph(member))
|
|
588
|
-
this.
|
|
750
|
+
this.emitCollectProduce(member, current, segs, current, errors, brk);
|
|
589
751
|
else
|
|
590
|
-
this.
|
|
752
|
+
this.emitCollectCheck(member, current, segs, errors);
|
|
591
753
|
}
|
|
592
754
|
this.push(`${out}=${current};`);
|
|
593
755
|
return;
|
|
594
756
|
}
|
|
595
757
|
default:
|
|
596
|
-
this.
|
|
758
|
+
this.emitCollectDelegate(node, v, segs, errors, brk, out);
|
|
597
759
|
}
|
|
598
760
|
}
|
|
599
761
|
build(ir) {
|
|
762
|
+
const errors = this.next("e");
|
|
600
763
|
let ret;
|
|
601
764
|
if (hasMorph(ir)) {
|
|
602
|
-
this.
|
|
603
|
-
|
|
604
|
-
this.
|
|
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("}");
|
|
605
769
|
ret = "o";
|
|
606
770
|
}
|
|
607
771
|
else {
|
|
608
|
-
this.
|
|
772
|
+
this.push(`let ${errors};`);
|
|
773
|
+
this.emitCollectCheck(ir, "v", [], errors);
|
|
609
774
|
ret = "v";
|
|
610
775
|
}
|
|
776
|
+
this.push(`if(${errors}!==undefined)return ${errors};`);
|
|
611
777
|
const src = `return function(v){${this.#lines.join("")}return ${ret}}`;
|
|
612
|
-
const make = new Function("R", "AE", "M", "PF", "UF", "MC", "own", src);
|
|
613
|
-
return make(this.#refs, OmpErrors, MISSING, prefixErrors, unionFail, CompiledMorphContext, own);
|
|
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);
|
|
614
780
|
}
|
|
615
781
|
emitAllows(node, v) {
|
|
616
782
|
switch (node.k) {
|
|
@@ -629,11 +795,11 @@ class Builder {
|
|
|
629
795
|
}
|
|
630
796
|
case "object": {
|
|
631
797
|
const object = this.next("o");
|
|
632
|
-
this.push(`const ${object}=${v};if(typeof ${object}!=="object"||${object}===null
|
|
798
|
+
this.push(`const ${object}=${v};if(typeof ${object}!=="object"||${object}===null)return false;`);
|
|
633
799
|
for (const prop of node.props) {
|
|
634
800
|
const value = this.next("p");
|
|
635
|
-
const present = `${
|
|
636
|
-
this.push(`const ${value}=${access(object, prop.key)};`);
|
|
801
|
+
const present = `${this.lit(prop.key)} in ${object}`;
|
|
802
|
+
this.push(`const ${value}=${this.access(object, prop.key)};`);
|
|
637
803
|
if (prop.opt || prop.hasDefault) {
|
|
638
804
|
if (rejectsUndefined(prop.val)) {
|
|
639
805
|
this.push(`if(${value}!==undefined){`);
|
|
@@ -652,15 +818,33 @@ class Builder {
|
|
|
652
818
|
this.emitAllows(prop.val, value);
|
|
653
819
|
}
|
|
654
820
|
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
this.push(`for(const ${
|
|
658
|
-
this.emitAllows(node.index, `${object}[${
|
|
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}]`);
|
|
659
825
|
this.push("}");
|
|
660
826
|
}
|
|
661
|
-
|
|
662
|
-
const
|
|
663
|
-
|
|
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
|
+
}
|
|
664
848
|
}
|
|
665
849
|
return;
|
|
666
850
|
}
|
|
@@ -703,7 +887,7 @@ function boundWalk(node) {
|
|
|
703
887
|
const tagged = node;
|
|
704
888
|
let fn = tagged[kWalk];
|
|
705
889
|
if (!fn) {
|
|
706
|
-
fn = (value) => walk(node, value);
|
|
890
|
+
fn = (value, path) => walk(node, value, path);
|
|
707
891
|
tagged[kWalk] = fn;
|
|
708
892
|
}
|
|
709
893
|
return fn;
|
|
@@ -716,20 +900,31 @@ const allowsCache = new WeakMap();
|
|
|
716
900
|
/** Compile `ir` into a specialized validator. */
|
|
717
901
|
export function compile(ir) {
|
|
718
902
|
const root = resolvedRoot(ir);
|
|
719
|
-
|
|
903
|
+
const validator = compiledCache.get(root);
|
|
720
904
|
if (validator === undefined) {
|
|
721
|
-
|
|
722
|
-
|
|
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;
|
|
723
915
|
}
|
|
724
916
|
return validator;
|
|
725
917
|
}
|
|
726
918
|
/** Compile `ir` into an allocation-free boolean validator. */
|
|
727
919
|
export function compileAllows(ir) {
|
|
728
920
|
const root = resolvedRoot(ir);
|
|
729
|
-
|
|
921
|
+
const validator = allowsCache.get(root);
|
|
730
922
|
if (validator === undefined) {
|
|
731
|
-
|
|
732
|
-
allowsCache.set(root,
|
|
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;
|
|
733
928
|
}
|
|
734
929
|
return validator;
|
|
735
930
|
}
|
|
@@ -739,9 +934,9 @@ export function compileToSource(ir) {
|
|
|
739
934
|
const builder = new Builder();
|
|
740
935
|
if (hasMorph(root)) {
|
|
741
936
|
builder.push("let o;");
|
|
742
|
-
builder.
|
|
937
|
+
builder.emitCollectProduce(root, "v", [], "o", "e", "L0");
|
|
743
938
|
return `function(v){/* refs elided */return o}`;
|
|
744
939
|
}
|
|
745
|
-
builder.
|
|
940
|
+
builder.emitCollectCheck(root, "v", [], "e");
|
|
746
941
|
return `function(v){/* refs elided */return v}`;
|
|
747
942
|
}
|