aontu 0.52.0 → 0.52.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/aontu.d.ts +1 -1
  2. package/dist/aontu.js +50 -1
  3. package/dist/aontu.js.map +1 -1
  4. package/dist/ctx.d.ts +3 -0
  5. package/dist/ctx.js +1 -0
  6. package/dist/ctx.js.map +1 -1
  7. package/dist/err.js +10 -1
  8. package/dist/err.js.map +1 -1
  9. package/dist/hints.js +56 -1
  10. package/dist/hints.js.map +1 -1
  11. package/dist/lang.js +235 -15
  12. package/dist/lang.js.map +1 -1
  13. package/dist/lsp.js +4 -3
  14. package/dist/lsp.js.map +1 -1
  15. package/dist/unify.d.ts +2 -1
  16. package/dist/unify.js +55 -3
  17. package/dist/unify.js.map +1 -1
  18. package/dist/val/ConstraintVal.d.ts +42 -1
  19. package/dist/val/ConstraintVal.js +1048 -107
  20. package/dist/val/ConstraintVal.js.map +1 -1
  21. package/dist/val/ExpectVal.d.ts +1 -0
  22. package/dist/val/ExpectVal.js +14 -0
  23. package/dist/val/ExpectVal.js.map +1 -1
  24. package/dist/val/FuncBaseVal.js +7 -1
  25. package/dist/val/FuncBaseVal.js.map +1 -1
  26. package/dist/val/ListVal.js +17 -4
  27. package/dist/val/ListVal.js.map +1 -1
  28. package/dist/val/MapVal.js +9 -0
  29. package/dist/val/MapVal.js.map +1 -1
  30. package/dist/val/OpBaseVal.js +5 -1
  31. package/dist/val/OpBaseVal.js.map +1 -1
  32. package/dist/val/RefVal.js +69 -23
  33. package/dist/val/RefVal.js.map +1 -1
  34. package/dist/val/Val.d.ts +1 -1
  35. package/dist/val/Val.js +0 -3
  36. package/dist/val/Val.js.map +1 -1
  37. package/package.json +10 -10
  38. package/src/aontu.ts +58 -1
  39. package/src/ctx.ts +9 -0
  40. package/src/err.ts +10 -1
  41. package/src/hints.ts +63 -1
  42. package/src/lang.ts +258 -16
  43. package/src/lsp.ts +4 -3
  44. package/src/unify.ts +59 -2
  45. package/src/val/ConstraintVal.ts +1195 -111
  46. package/src/val/ExpectVal.ts +15 -0
  47. package/src/val/FuncBaseVal.ts +8 -2
  48. package/src/val/ListVal.ts +18 -4
  49. package/src/val/MapVal.ts +11 -0
  50. package/src/val/OpBaseVal.ts +6 -2
  51. package/src/val/RefVal.ts +72 -22
  52. package/src/val/Val.ts +8 -1
@@ -1,13 +1,275 @@
1
1
  "use strict";
2
2
  /* Copyright (c) 2025 Richard Rodger, MIT License */
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.NeqConstraintVal = exports.BelowConstraintVal = exports.AboveConstraintVal = exports.MaxConstraintVal = exports.MinConstraintVal = exports.ConstraintVal = void 0;
4
+ exports.MustConstraintVal = exports.UniqueConstraintVal = exports.LengthConstraintVal = exports.ReConstraintVal = exports.NeqConstraintVal = exports.BelowConstraintVal = exports.AboveConstraintVal = exports.MaxConstraintVal = exports.MinConstraintVal = exports.ConstraintVal = void 0;
5
+ exports.normaliseRe = normaliseRe;
5
6
  const type_1 = require("../type");
6
7
  const utility_1 = require("../utility");
8
+ const Val_1 = require("./Val");
9
+ const keyorder_1 = require("../keyorder");
10
+ const ConjunctVal_1 = require("./ConjunctVal");
11
+ const top_1 = require("./top");
12
+ const unify_1 = require("../unify");
13
+ const IntegerVal_1 = require("./IntegerVal");
7
14
  const err_1 = require("../err");
8
15
  const FeatureVal_1 = require("./FeatureVal");
9
16
  const ScalarKindVal_1 = require("./ScalarKindVal");
10
17
  const numcmp_1 = require("./numcmp");
18
+ // THE PATTERN SUBSET, AND HOW IT IS ENFORCED (G1 phase 2; ADR-003).
19
+ //
20
+ // `re(p)` must mean the same thing in both engines and cost about the
21
+ // same, and the two host engines guarantee neither: TypeScript compiles
22
+ // with JavaScript's backtracking RegExp, Go with RE2 — a different
23
+ // language in a different complexity class, over a different alphabet.
24
+ //
25
+ // The enforcement is NORMALISATION, not refusal (ADR-003). Every
26
+ // construct whose expansion is engine-defined is rewritten here, by
27
+ // this function, into an explicit form that cannot be read two ways;
28
+ // only the rewritten pattern reaches a host engine. The alternative —
29
+ // refusing everything that might differ — was tried first and leaked
30
+ // three times, because its correctness depended on this comment knowing
31
+ // every difference between two large engines.
32
+ //
33
+ // Aontu therefore DEFINES the abbreviations rather than inheriting
34
+ // either host's. The definitions are deliberately the small ASCII ones,
35
+ // because a config value containing U+00A0 is a mistake to catch, not a
36
+ // space to accept silently:
37
+ //
38
+ // \d [0-9] \D [^0-9]
39
+ // \w [0-9A-Za-z_] \W [^0-9A-Za-z_]
40
+ // \s [ \t\n\r\f\v] \S [^ \t\n\r\f\v]
41
+ // . [^\n]
42
+ // \A ^ \z $
43
+ //
44
+ // Neither host agreed with all of these before rewriting: JavaScript's
45
+ // \s also matches U+00A0 and other Unicode spaces, RE2's omits \v, and
46
+ // the two `.` sets differ by \r and the Unicode line separators. After
47
+ // rewriting, both engines see one explicit class and cannot disagree.
48
+ //
49
+ // What is still REFUSED, and why refusal is right for these:
50
+ //
51
+ // 1. Constructs one engine simply lacks — backreferences and
52
+ // lookaround (not in RE2, and not expressible as regular
53
+ // expressions at all), POSIX classes, `\p{...}`, `\x{...}`, `\u`.
54
+ // There is nothing to normalise them TO.
55
+ // 2. `(?` other than `(?:` — named groups are spelled differently
56
+ // (`(?P<n>` in RE2, `(?<n>` in JavaScript) and inline flags change
57
+ // the meaning of everything after them.
58
+ // 3. A quantifier applied to a group containing a quantifier or an
59
+ // alternation. This one is about TIME, not meaning: `(a+)+$`
60
+ // against twenty-nine characters takes 45 SECONDS in JavaScript
61
+ // and 0.065s under RE2, and a regex match is counted by no
62
+ // evaluator budget, so an untrusted schema could otherwise stall
63
+ // the TypeScript evaluator indefinitely (docs/trust.md clause 2).
64
+ // Normalisation cannot fix a complexity difference; only owning the
65
+ // matcher could, which ADR-003 records as the future option.
66
+ //
67
+ // The alphabet is fixed separately, by compiling with the `u` flag:
68
+ // JavaScript otherwise matches UTF-16 code units where RE2 matches code
69
+ // points, so `^.$` accepted U+1D11E in Go and refused it in TypeScript.
70
+ //
71
+ // This function is mirrored statement for statement in go/constraint.go,
72
+ // and `test/spec/files/regex-corpus.txt` pins that the two produce
73
+ // byte-identical output for every pattern in a generated corpus.
74
+ // The normative expansions. These are Aontu's definitions, not either
75
+ // host's; both hosts are rewritten to them.
76
+ const RE_CLASS_DIGIT = '0-9';
77
+ const RE_CLASS_WORD = '0-9A-Za-z_';
78
+ const RE_CLASS_SPACE = ' \\t\\n\\r\\f\\v';
79
+ // Metacharacters that may be escaped to mean themselves, in both
80
+ // engines. `-` is handled separately: it is legal escaped only INSIDE a
81
+ // character class, because RE2 accepts `a\-b` and JavaScript's unicode
82
+ // mode makes it a syntax error.
83
+ const RE_ESCAPE_PUNCT = '\\.+*?()[]{}|^$/';
84
+ // Escapes passed through unchanged: the control characters, the ASCII
85
+ // word boundary, and `\xHH`. Each was probed in both engines.
86
+ const RE_ESCAPE_PASS = 'tnrfv';
87
+ function isHexDigit(c) {
88
+ return null != c && (('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F'));
89
+ }
90
+ // normaliseEscape rewrites one `\<n>` into its engine-neutral form.
91
+ // Returns [emitted, why, extra]: `why` non-empty means refused, and
92
+ // `extra` counts source characters consumed beyond the backslash and n.
93
+ function normaliseEscape(n, src, i, inClass) {
94
+ if (null == n) {
95
+ return ['', 'a trailing backslash', 0];
96
+ }
97
+ if ('1' <= n && n <= '9') {
98
+ return ['', 'a backreference (\\' + n + '): RE2 has no equivalent, and a' +
99
+ ' pattern with one is not a regular expression', 0];
100
+ }
101
+ if ('k' === n) {
102
+ return ['', 'a named backreference (\\k): RE2 has no equivalent', 0];
103
+ }
104
+ if ('u' === n) {
105
+ return ['', 'a \\u escape, which RE2 spells \\x{...}: write the character' +
106
+ ' itself, or \\xHH for a byte', 0];
107
+ }
108
+ if ('p' === n || 'P' === n) {
109
+ return ['', 'a Unicode class (\\' + n + '), which JavaScript reads as a' +
110
+ ' literal "' + n + '" without a flag Aontu does not set', 0];
111
+ }
112
+ if ('Z' === n) {
113
+ return ['', '\\Z, which RE2 does not accept and JavaScript reads as a' +
114
+ ' literal "Z": write $ for end of text', 0];
115
+ }
116
+ if ('x' === n) {
117
+ if ('{' === src[i + 2]) {
118
+ return ['', 'a \\x{...} escape, which JavaScript spells \\u: write the' +
119
+ ' character itself', 0];
120
+ }
121
+ if (!isHexDigit(src[i + 2]) || !isHexDigit(src[i + 3])) {
122
+ return ['', 'an \\x escape without two hex digits', 0];
123
+ }
124
+ return ['\\x' + src[i + 2] + src[i + 3], '', 2];
125
+ }
126
+ // The abbreviations, rewritten to Aontu's definitions. Inside a class
127
+ // the expansion splices without its brackets (`[\dx]` -> `[0-9x]`).
128
+ if ('d' === n || 'w' === n || 's' === n) {
129
+ const set = 'd' === n ? RE_CLASS_DIGIT :
130
+ 'w' === n ? RE_CLASS_WORD : RE_CLASS_SPACE;
131
+ return [inClass ? set : '[' + set + ']', '', 0];
132
+ }
133
+ if ('D' === n || 'W' === n || 'S' === n) {
134
+ if (inClass) {
135
+ // `[^...]` cannot be spliced into an enclosing class: the negation
136
+ // would apply to the whole class rather than this member.
137
+ return ['', 'a negated abbreviation (\\' + n + ') inside a character' +
138
+ ' class, which cannot be expanded in place: write the characters out', 0];
139
+ }
140
+ const set = 'D' === n ? RE_CLASS_DIGIT :
141
+ 'W' === n ? RE_CLASS_WORD : RE_CLASS_SPACE;
142
+ return ['[^' + set + ']', '', 0];
143
+ }
144
+ // Anchors. `\A`/`\z` are RE2 spellings that JavaScript reads as
145
+ // literals, so they are rewritten rather than refused. Inside a class
146
+ // an anchor is meaningless, and `[\b]` is a BACKSPACE in JavaScript.
147
+ if ('A' === n || 'z' === n || 'b' === n || 'B' === n) {
148
+ if (inClass) {
149
+ return ['', '\\' + n + ' inside a character class, where the two' +
150
+ ' engines do not agree what it means', 0];
151
+ }
152
+ return ['A' === n ? '^' : 'z' === n ? '$' : '\\' + n, '', 0];
153
+ }
154
+ if ('-' === n) {
155
+ return inClass ? ['\\-', '', 0] :
156
+ ['', '\\- outside a character class: it is a range separator inside' +
157
+ ' one and a syntax error outside one (write a bare -)', 0];
158
+ }
159
+ if (RE_ESCAPE_PASS.includes(n) || RE_ESCAPE_PUNCT.includes(n)) {
160
+ return ['\\' + n, '', 0];
161
+ }
162
+ return ['', '\\' + n + ', an escape whose meaning the two engines do not' +
163
+ ' share', 0];
164
+ }
165
+ // normaliseRe rewrites a pattern into the engine-neutral subset.
166
+ // Returns [normalised, why]: a non-empty `why` means the pattern is
167
+ // outside the subset and names the construct.
168
+ function normaliseRe(src) {
169
+ let inClass = false;
170
+ const out = [];
171
+ // One frame per open group, recording whether it contains a quantifier
172
+ // or an alternation. A group carrying either may not itself be
173
+ // quantified; containment is transitive, so a frame hands its flags up
174
+ // to its parent when it closes.
175
+ const groups = [];
176
+ const mark = (k) => {
177
+ if (0 < groups.length) {
178
+ groups[groups.length - 1][k] = true;
179
+ }
180
+ };
181
+ for (let i = 0; i < src.length; i++) {
182
+ const c = src[i];
183
+ if ('\\' === c) {
184
+ const [emit, why, extra] = normaliseEscape(src[i + 1], src, i, inClass);
185
+ if ('' !== why) {
186
+ return ['', why];
187
+ }
188
+ out.push(emit);
189
+ i += 1 + extra;
190
+ continue;
191
+ }
192
+ // A POSIX class opener, anywhere: the form lives inside an ordinary
193
+ // class (`[[:alpha:]]`), and refusing it everywhere is one rule
194
+ // rather than two.
195
+ if ('[' === c && ':' === src[i + 1]) {
196
+ return ['', 'a POSIX class ([:...:]), which JavaScript does not have'];
197
+ }
198
+ if (inClass) {
199
+ if (']' === c) {
200
+ inClass = false;
201
+ }
202
+ out.push(c);
203
+ continue;
204
+ }
205
+ if ('[' === c) {
206
+ // `[]` is a never-matching class in JavaScript and a parse error in
207
+ // RE2; `[^]` is the same disagreement one character along.
208
+ const first = '^' === src[i + 1] ? src[i + 2] : src[i + 1];
209
+ if (']' === first) {
210
+ return ['', 'an empty character class, which RE2 refuses'];
211
+ }
212
+ inClass = true;
213
+ out.push(c);
214
+ continue;
215
+ }
216
+ if ('.' === c) {
217
+ out.push('[^\\n]');
218
+ continue;
219
+ }
220
+ if ('(' === c) {
221
+ if ('?' === src[i + 1]) {
222
+ if (':' !== src[i + 2]) {
223
+ return ['', 'a (?...) group other than the non-capturing (?:'];
224
+ }
225
+ out.push('(?:');
226
+ i += 2;
227
+ }
228
+ else {
229
+ out.push(c);
230
+ }
231
+ groups.push({ q: false, alt: false });
232
+ continue;
233
+ }
234
+ if (')' === c) {
235
+ const g = groups.pop();
236
+ if (null == g) {
237
+ return ['', 'an unbalanced group'];
238
+ }
239
+ const nx = src[i + 1];
240
+ const quantified = '*' === nx || '+' === nx || '?' === nx || '{' === nx;
241
+ if (quantified && (g.q || g.alt)) {
242
+ return ['', 'a quantifier applied to a group containing ' +
243
+ (g.q ? 'another quantifier' : 'an alternation') +
244
+ ', which backtracks exponentially in JavaScript'];
245
+ }
246
+ if (g.q)
247
+ mark('q');
248
+ if (g.alt)
249
+ mark('alt');
250
+ out.push(c);
251
+ continue;
252
+ }
253
+ if ('|' === c) {
254
+ mark('alt');
255
+ out.push(c);
256
+ continue;
257
+ }
258
+ if ('*' === c || '+' === c || '?' === c || '{' === c) {
259
+ mark('q');
260
+ out.push(c);
261
+ continue;
262
+ }
263
+ out.push(c);
264
+ }
265
+ if (inClass) {
266
+ return ['', 'an unterminated character class'];
267
+ }
268
+ if (0 < groups.length) {
269
+ return ['', 'an unclosed group'];
270
+ }
271
+ return [out.join(''), ''];
272
+ }
11
273
  // True for a scalar Val the algebra can order: a numeric leaf or a
12
274
  // string. (Booleans and null have no order and no bounds.)
13
275
  function numericLeaf(v) {
@@ -40,29 +302,80 @@ function leafMarker(v) {
40
302
  return v.isBigDecimal ? ScalarKindVal_1.BigDecimal : v.isBigInteger ? ScalarKindVal_1.BigInteger :
41
303
  v.isInteger ? ScalarKindVal_1.Integer : ScalarKindVal_1.Float;
42
304
  }
305
+ // Conjunct sort order for an atom that must see the WHOLE value.
306
+ // Every other value sorts below the container default (99999), so such
307
+ // an atom is the LAST term to fold: `a:length(2) a:{x:1} a:{y:2}` must
308
+ // count the MERGED map, and a constraint that folded at 50000 would
309
+ // count `{x:1}` alone and refuse the layering that is the whole point
310
+ // of the language.
311
+ //
312
+ // The order atoms keep the low slot: `min(2) & 1 & 2` may decide as
313
+ // soon as it sees a scalar, because meeting more scalars can only
314
+ // narrow. Meeting more containers GROWS the member set, which is why
315
+ // the two orders differ.
316
+ //
317
+ // Three atoms are late: `length` and `unique` (they count members), and
318
+ // `must` (an evaluate-only check against the finished value).
319
+ const LATE_CJO = 150000;
320
+ function lateAtom(atom) {
321
+ return 'length' === atom || 'unique' === atom || 'must' === atom;
322
+ }
43
323
  class ConstraintVal extends FeatureVal_1.FeatureVal {
44
324
  constructor(spec, ctx) {
45
325
  super({ ...spec, peg: spec.peg ?? [] }, ctx);
46
326
  this.isConstraint = true;
47
327
  this.cjo = 50000;
48
328
  this.neqs = [];
329
+ this.res = [];
330
+ this.uniq = false;
331
+ this.musts = [];
49
332
  if (spec.state) {
50
333
  this.domain = spec.state.domain;
51
334
  this.kind = spec.state.kind;
52
335
  this.lo = spec.state.lo;
53
336
  this.hi = spec.state.hi;
54
337
  this.neqs = spec.state.neqs;
338
+ // A state built by an embedder (or by a per-port test) may predate
339
+ // the pattern field; an absent one means "no patterns", not undefined.
340
+ this.res = spec.state.res ?? [];
341
+ this.count = spec.state.count;
342
+ this.uniq = spec.state.uniq ?? false;
343
+ this.musts = spec.state.musts ?? [];
55
344
  this.invalid = spec.state.invalid;
56
345
  }
57
346
  else if (spec.atom) {
58
- this.fromAtom(spec.atom, spec.peg ?? []);
347
+ const args = atomArgs(spec.atom, spec.peg ?? []);
348
+ // An argument that is not yet concrete — a reference, an
349
+ // arithmetic expression, a conjunct of atoms — makes the whole
350
+ // atom PENDING rather than invalid (G1 phase 4). It is resolved
351
+ // in unify, where there is a ctx to resolve through, and the
352
+ // residual is built from the settled arguments. Only a settled
353
+ // argument of the wrong shape is an `invalid-arg`.
354
+ if (args.some((a) => true !== a?.done)) {
355
+ this.pending = { atom: spec.atom, args };
356
+ }
357
+ else {
358
+ this.fromAtom(spec.atom, args);
359
+ }
360
+ }
361
+ if (null != this.count || this.uniq || 0 < this.musts.length ||
362
+ (null != this.pending && lateAtom(this.pending.atom))) {
363
+ this.cjo = LATE_CJO;
364
+ }
365
+ // A residual constraint is stable, like a ScalarKindVal — but a
366
+ // pending atom is not a residual yet, and must be re-entered on
367
+ // later passes until its arguments settle.
368
+ if (null == this.pending) {
369
+ this.dc = type_1.DONE;
370
+ }
371
+ else {
372
+ this.notdone();
59
373
  }
60
- // A residual constraint is stable, like a ScalarKindVal.
61
- this.dc = type_1.DONE;
62
374
  }
63
- // Normalise one atom call (min/max/above/below/neq) into state.
64
- // Arguments must be concrete orderable scalars in phase 1;
65
- // reference-valued arguments are phase 4 (residuation).
375
+ // Normalise one atom call into state. Every argument here is already
376
+ // SETTLED — the constructor routes an unsettled one to `pending` —
377
+ // so a shape this cannot use is a genuine `invalid-arg`, not a
378
+ // not-yet.
66
379
  fromAtom(atom, args) {
67
380
  // Mark the residual invalid and report so (a plain boolean, so no
68
381
  // void value is consumed by the callers' `return` statements).
@@ -70,17 +383,38 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
70
383
  this.invalid = why;
71
384
  return true;
72
385
  };
73
- if ('neq' === atom) {
74
- // Multiple arguments arrive from the func-paren grammar as one
75
- // entry holding the comma group: a raw array of Vals (or an
76
- // implicit ListVal via some spellings). `neq(3,1,2)` therefore
77
- // has peg [[3,1,2]], and `neq([3,1,2])` means the same thing.
78
- if (1 === args.length && Array.isArray(args[0])) {
79
- args = args[0];
386
+ // `unique()` takes no argument: it is a property of the container,
387
+ // not a comparison against a value. Arity is checked at parse, so a
388
+ // written argument never reaches here.
389
+ if ('unique' === atom) {
390
+ this.uniq = true;
391
+ return;
392
+ }
393
+ // `must(c, msg)` is Band B: an evaluate-only check against the
394
+ // finished value, carrying the author's own message. It is KEPT,
395
+ // never simplified and never consulted for emptiness or
396
+ // subsumption — Band B is opaque by construction, which is exactly
397
+ // what makes it the honest channel for a rule the algebra cannot
398
+ // reason about (docs/reference-language.md, "Band B: `must`").
399
+ if ('must' === atom) {
400
+ if (2 !== args.length) {
401
+ return bad('arg');
402
+ }
403
+ if (!stringLeaf(args[1])) {
404
+ return bad('invalid-arg');
80
405
  }
81
- else if (1 === args.length && true === args[0]?.isList) {
82
- args = args[0].peg;
406
+ // A check carrying a nil can never be satisfied, so it is refused
407
+ // as an ARGUMENT rather than left to fail against every value with
408
+ // the author's message attached -- which would blame the data for
409
+ // a mistake in the check. `must([1-x],m)` is the reachable case: a
410
+ // degenerate expression leaves a nil inside the written list.
411
+ if (holdsNil(args[0])) {
412
+ return bad('invalid-arg');
83
413
  }
414
+ this.musts = [{ v: args[0], msg: args[1] }];
415
+ return;
416
+ }
417
+ if ('neq' === atom) {
84
418
  if (0 === args.length) {
85
419
  return bad('arg');
86
420
  }
@@ -100,6 +434,71 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
100
434
  return bad('arg');
101
435
  }
102
436
  const a = args[0];
437
+ // `re` is the one atom whose argument is not an ORDER point: a
438
+ // pattern is a membership test, so it takes the string domain
439
+ // outright rather than inferring a domain from the argument's leaf.
440
+ if ('re' === atom) {
441
+ if (!stringLeaf(a)) {
442
+ return bad('invalid-arg');
443
+ }
444
+ const src = a.peg;
445
+ // Normalise BEFORE compiling: the host engines only ever see a
446
+ // pattern that cannot be read two ways (ADR-003).
447
+ const [norm, why] = normaliseRe(src);
448
+ if ('' !== why) {
449
+ this.invalidWhy = why;
450
+ return bad('constraint_pattern');
451
+ }
452
+ let re;
453
+ try {
454
+ // The `u` flag is REQUIRED for parity, not an optimisation.
455
+ // Without it JavaScript matches UTF-16 code units while RE2
456
+ // matches code points, so `re("^.$")` accepted U+1D11E in Go
457
+ // and refused it in TypeScript (and `^..$` did the reverse).
458
+ // With it, `.` and every quantifier count code points in both.
459
+ // It also makes JavaScript refuse the identity escapes this
460
+ // scanner rejects by hand, which is defence in depth rather
461
+ // than a substitute: RE2 accepts some of them, so the scanner
462
+ // is what keeps the two ports agreeing.
463
+ re = new RegExp(norm, 'u');
464
+ }
465
+ catch (e) {
466
+ // The host engine refuses what the subset scanner passed — a
467
+ // malformed quantifier, an unbalanced group. Same refusal under
468
+ // the same code: the author gets one rule, not two. The message
469
+ // is the host's, so it is NOT pinned by a shared row; the code
470
+ // and the located frame are.
471
+ this.invalidWhy = 'not a valid pattern';
472
+ return bad('constraint_pattern');
473
+ }
474
+ this.domain = 'string';
475
+ this.res = [{ v: a, src, norm, re }];
476
+ return;
477
+ }
478
+ // `length` is the other non-ORDER atom: its argument constrains the
479
+ // COUNT, not the value, so it is itself a residual over the integer
480
+ // domain (docs/reference-language.md, "`length` semantics"). It is
481
+ // resolved HERE, at construction, by walking the written argument
482
+ // rather than by unifying it: the func-paren handler builds atoms
483
+ // without an AontuContext, so there is nothing to fold a nested
484
+ // conjunct through. Walking is enough because a length argument is by
485
+ // definition a meet of concrete Band A atoms; anything else (a
486
+ // reference, an expression) is refused rather than deferred, the
487
+ // same discipline min/max apply to their own arguments.
488
+ if ('length' === atom) {
489
+ const arg = countArgState(a);
490
+ if (null == arg) {
491
+ return bad('invalid-arg');
492
+ }
493
+ const inner = meetCount(countBase(), arg);
494
+ this.count = inner;
495
+ // `length(min(5)&max(3))` is unsatisfiable with no peer in sight, so
496
+ // it is refused at composition time like any other empty meet.
497
+ if (stateEmpty(inner)) {
498
+ return bad('constraint');
499
+ }
500
+ return;
501
+ }
103
502
  const domain = numericLeaf(a) ? 'number' : stringLeaf(a) ? 'string' : undefined;
104
503
  if (null == domain) {
105
504
  return bad('invalid-arg');
@@ -119,8 +518,11 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
119
518
  // Every branch of the ladder assigns, so no initialiser: a
120
519
  // residual is stable and the ladder is total.
121
520
  let out;
122
- if (null != this.invalid) {
123
- out = (0, err_1.makeNilErr)(ctx, this.invalid, this, undefined, 'constrain');
521
+ if (null != this.pending) {
522
+ out = this.settle(peer, ctx);
523
+ }
524
+ else if (null != this.invalid) {
525
+ out = (0, err_1.makeNilErr)(ctx, this.invalid, this, undefined, 'constrain', null == this.invalidWhy ? undefined : { reason: this.invalidWhy });
124
526
  }
125
527
  else if (null == peer || peer.isTop) {
126
528
  out = this;
@@ -137,65 +539,219 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
137
539
  else if (peer.isScalar) {
138
540
  out = this.admit(peer, ctx);
139
541
  }
542
+ else if (peer.isMap || peer.isList) {
543
+ out = this.admitContainer(peer, ctx);
544
+ }
545
+ /* node:coverage ignore next 12 */
140
546
  else {
141
- // Maps, lists, and every other non-scalar shape: no order, no
142
- // membership — a conflict of the constraint family.
547
+ // Every other shape: no order, no membership, nothing to count —
548
+ // a conflict of the constraint family. The ladder above is total
549
+ // in practice: every remaining Val kind either sorts BELOW a
550
+ // constraint in a conjunct (conjunct, disjunct, pref, ref) and so
551
+ // drives the meet from its own side, or resolves to a
552
+ // scalar/container before a constraint sees it (func, op, var,
553
+ // expect). The arm is kept because "in practice" depends on the
554
+ // cjo table, and a future value class would land here rather than
555
+ // falling out of unify with no result.
143
556
  out = this.fail(ctx, peer);
144
557
  }
145
558
  ctx.explain && (0, utility_1.explainClose)(te, out);
146
559
  return out;
147
560
  }
561
+ // Resolve a pending atom's arguments and, once they have all settled,
562
+ // become the residual they describe (G1 phase 4).
563
+ //
564
+ // This is the residuation discipline FuncBaseVal already follows for
565
+ // its own operands: push each unsettled argument one step by unifying
566
+ // it with `top`, and if any is still moving, mark not-done and defer
567
+ // — either as this same pending atom (against a `top` peer) or
568
+ // wrapped with the peer in a conjunct the next pass will re-enter.
569
+ // Nothing is decided from a half-resolved argument, which is what
570
+ // keeps `min($.lo)` from reporting a conflict against a bound that
571
+ // has not arrived yet.
572
+ settle(peer, ctx) {
573
+ const TOP = (0, top_1.top)();
574
+ const pend = this.pending;
575
+ let settled = true;
576
+ const args = [];
577
+ for (const arg of pend.args) {
578
+ let next = arg;
579
+ if (true !== arg?.done) {
580
+ // Charged to the depth budget: this recurses without going
581
+ // through `unite`, so the counter would otherwise stay flat
582
+ // while the stack grows (the rule FuncBaseVal follows).
583
+ next = (0, unify_1.withDepth)(ctx, arg, TOP, () => arg.unify(TOP, ctx));
584
+ }
585
+ settled = settled && true === next?.done;
586
+ args.push(next);
587
+ }
588
+ if (settled) {
589
+ // Build the residual the atom always meant, at this atom's site,
590
+ // then let the ordinary ladder meet it with the peer.
591
+ const built = new ConstraintVal({ peg: args, atom: pend.atom }, ctx);
592
+ built.path = this.path;
593
+ built.site.row = this.site.row;
594
+ built.site.col = this.site.col;
595
+ built.site.url = this.site.url;
596
+ (0, utility_1.propagateMarks)(this, built);
597
+ return built.unify(peer, ctx);
598
+ }
599
+ this.notdone();
600
+ // A fresh pending atom carrying the partially-resolved arguments, so
601
+ // the next pass starts from the progress this one made rather than
602
+ // re-resolving from source.
603
+ const again = new ConstraintVal({ peg: args, atom: pend.atom }, ctx);
604
+ again.path = this.path;
605
+ again.site.row = this.site.row;
606
+ again.site.col = this.site.col;
607
+ again.site.url = this.site.url;
608
+ (0, utility_1.propagateMarks)(this, again);
609
+ if (null == peer || peer.isTop) {
610
+ return again;
611
+ }
612
+ if (peer.isNil) {
613
+ return peer;
614
+ }
615
+ return new ConjunctVal_1.ConjunctVal({ peg: [again, peer] }, ctx);
616
+ }
148
617
  // Membership: the peer scalar passes every part of the residual, or
149
618
  // the whole meet is a located conflict.
150
619
  admit(peer, ctx) {
151
- const domainOf = numericLeaf(peer) ? 'number' :
152
- stringLeaf(peer) ? 'string' : undefined;
153
- if (domainOf !== this.domain) {
620
+ // No scalar has members, so a `unique()` residual admits none.
621
+ if (this.uniq) {
154
622
  return this.fail(ctx, peer);
155
623
  }
156
- if (null != this.kind && leafMarker(peer) !== this.kind) {
624
+ if (!stateAdmits(this, peer)) {
157
625
  return this.fail(ctx, peer);
158
626
  }
159
- const d = this.domain;
160
- if (null != this.lo) {
161
- const c = cmpVal(d, peer, this.lo.v);
162
- if (c < 0 || (0 === c && this.lo.open)) {
627
+ if (null != this.count) {
628
+ // Only a string among the scalars has a length, and it is counted
629
+ // in CODE POINTS -- not UTF-16 units (this host's native count)
630
+ // and not bytes (Go's). Iterating a string yields code points.
631
+ if (!stringLeaf(peer)) {
163
632
  return this.fail(ctx, peer);
164
633
  }
165
- }
166
- if (null != this.hi) {
167
- const c = cmpVal(d, peer, this.hi.v);
168
- if (c > 0 || (0 === c && this.hi.open)) {
634
+ if (!stateAdmits(this.count, countVal([...peer.peg].length))) {
169
635
  return this.fail(ctx, peer);
170
636
  }
171
637
  }
172
- for (const n of this.neqs) {
173
- if (sameScalar(peer, n)) {
174
- return this.fail(ctx, peer);
638
+ const bad = this.checkMusts(peer, ctx);
639
+ if (null != bad) {
640
+ return bad;
641
+ }
642
+ return peer;
643
+ }
644
+ // Band B, applied to a finished value. Each check is a plain
645
+ // unification against a CLONE of the peer: `must` reports, it never
646
+ // contributes: whatever the check would have added to the value is
647
+ // discarded, and `peer` is returned untouched by the callers.
648
+ //
649
+ // The trial runs in an isolated collect context so a failing check
650
+ // leaves nothing on the caller's error list — only the located nil
651
+ // this returns, carrying the author's own message.
652
+ checkMusts(peer, ctx) {
653
+ for (const m of this.musts) {
654
+ const trial = ctx.clone({ err: [], collect: true });
655
+ const got = (0, unify_1.unite)(trial, m.v.clone(trial), peer.clone(trial), 'must');
656
+ if (true === got?.isNil || 0 < trial.err.length) {
657
+ return (0, err_1.makeNilErr)(ctx, 'must', this, peer, undefined, {
658
+ message: m.msg.peg,
659
+ expected: m.v.canon,
660
+ actual: peer.canon,
661
+ });
662
+ }
663
+ }
664
+ return undefined;
665
+ }
666
+ // Membership for a container peer. Only the SIZING atoms have anything
667
+ // to say about a map or a list; every other atom is scalar-domain and
668
+ // refuses one.
669
+ //
670
+ // The members that count are the members that GENERATE
671
+ // (docs/reference-language.md, "`length` semantics"), and rather than
672
+ // mirror generation's filter — type/hide marks, optional keys that
673
+ // drop, empty optional values — this asks generation itself, in an
674
+ // isolated collect context so nothing leaks into the caller's errors.
675
+ // A mirror would be a second copy of a filter that has already grown
676
+ // subtle, free to drift from it; asking is correct by construction.
677
+ admitContainer(peer, ctx) {
678
+ // A scalar-domain residual has no reading over a container.
679
+ if (null != this.domain) {
680
+ return this.fail(ctx, peer);
681
+ }
682
+ // Not yet settled: the container, or an optional child, may still
683
+ // resolve, so the member set is not final. Defer rather than decide
684
+ // — the same discipline OpBaseVal follows for a non-concrete operand.
685
+ if (!containerSettled(peer)) {
686
+ this.dc = 0;
687
+ return new ConjunctVal_1.ConjunctVal({ peg: [this, peer] }, ctx);
688
+ }
689
+ const bad = this.checkMusts(peer, ctx);
690
+ if (null != bad) {
691
+ return bad;
692
+ }
693
+ if (!this.uniq && null == this.count) {
694
+ return peer;
695
+ }
696
+ const members = emittedMembers(peer, ctx);
697
+ if (null == members) {
698
+ // The container cannot generate at all; its own error is the one
699
+ // worth reporting, so pass it through untouched.
700
+ return peer;
701
+ }
702
+ if (null != this.count && !stateAdmits(this.count, countVal(members.length))) {
703
+ return this.fail(ctx, peer);
704
+ }
705
+ if (this.uniq) {
706
+ // Members compare by CANONICAL FORM, which reduces to scalar
707
+ // identity for scalars (so [1, 1.0] is distinct) and gives
708
+ // structural equality for container members without a second
709
+ // rule. A generated value could not express the first: `1` and
710
+ // `1.0` generate the same JSON number.
711
+ const seen = new Set();
712
+ for (const m of members) {
713
+ const key = m.canon;
714
+ if (seen.has(key)) {
715
+ return this.fail(ctx, peer);
716
+ }
717
+ seen.add(key);
175
718
  }
176
719
  }
177
720
  return peer;
178
721
  }
179
722
  // Meet with a kind: `number` (or `string` on the string domain) is
180
- // already implied; a numeric LEAF narrows the residual; anything
181
- // else has an empty intersection with the constraint's domain.
723
+ // already implied by an ORDER atom's argument; a numeric LEAF narrows
724
+ // the residual; anything else has an empty intersection with the
725
+ // constraint's domain.
726
+ //
727
+ // A sizing residual (`length`, `unique`) has no domain of its own -- a
728
+ // count says nothing about what is counted -- so a kind here SETS one
729
+ // rather than merely agreeing with it: `string & length(3)` is a
730
+ // three-character string, and `number & length(3)` is empty because a
731
+ // number has no length (stateEmpty decides that, not this).
182
732
  meetKind(peer, ctx) {
183
733
  const marker = peer.peg;
184
- if (Number === marker) {
185
- return 'number' === this.domain ? this : this.fail(ctx, peer);
186
- }
187
- if (String === marker) {
188
- return 'string' === this.domain ? this : this.fail(ctx, peer);
734
+ const merged = this.cloneState();
735
+ if (Number === marker || String === marker) {
736
+ const d = Number === marker ? 'number' : 'string';
737
+ if (d === this.domain) {
738
+ return this;
739
+ }
740
+ if (null != this.domain) {
741
+ return this.fail(ctx, peer);
742
+ }
743
+ merged.domain = d;
744
+ return this.finish(merged, ctx, peer);
189
745
  }
190
746
  const isLeaf = ScalarKindVal_1.Integer === marker || ScalarKindVal_1.Float === marker ||
191
747
  ScalarKindVal_1.BigInteger === marker || ScalarKindVal_1.BigDecimal === marker;
192
- if (!isLeaf || 'number' !== this.domain) {
748
+ if (!isLeaf || 'string' === this.domain) {
193
749
  return this.fail(ctx, peer);
194
750
  }
195
751
  if (null != this.kind && this.kind !== marker) {
196
752
  return this.fail(ctx, peer);
197
753
  }
198
- const merged = this.cloneState();
754
+ merged.domain = 'number';
199
755
  merged.kind = marker;
200
756
  return this.finish(merged, ctx, peer);
201
757
  }
@@ -203,7 +759,7 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
203
759
  // union, kind union — then the eager emptiness rules.
204
760
  meetConstraint(peer, ctx) {
205
761
  if (null != peer.invalid) {
206
- return (0, err_1.makeNilErr)(ctx, peer.invalid, peer, undefined, 'constrain');
762
+ return (0, err_1.makeNilErr)(ctx, peer.invalid, peer, undefined, 'constrain', null == peer.invalidWhy ? undefined : { reason: peer.invalidWhy });
207
763
  }
208
764
  if (null != this.domain && null != peer.domain && this.domain !== peer.domain) {
209
765
  return this.fail(ctx, peer);
@@ -218,51 +774,24 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
218
774
  merged.lo = tighter(d, this.lo, peer.lo, true);
219
775
  merged.hi = tighter(d, this.hi, peer.hi, false);
220
776
  merged.neqs = dedupSorted(d, [...this.neqs, ...peer.neqs]);
777
+ merged.res = dedupSortedRes([...this.res, ...peer.res]);
778
+ // `length(c1) & length(c2)` is `length(c1 & c2)`: the count atom reuses
779
+ // numeric algebra recursively, over the counts rather than the
780
+ // values.
781
+ merged.count = null == this.count ? peer.count :
782
+ null == peer.count ? this.count : meetCount(this.count, peer.count);
783
+ // `unique()` is idempotent: two of them are one.
784
+ merged.uniq = this.uniq || peer.uniq;
785
+ // Band B checks accumulate in written order and are never merged,
786
+ // deduplicated or reordered: each carries its own author message,
787
+ // and two checks with the same shape may still say different things.
788
+ merged.musts = [...this.musts, ...peer.musts];
221
789
  return this.finish(merged, ctx, peer);
222
790
  }
223
791
  // Build the merged residual, applying the eager emptiness rules.
224
792
  finish(state, ctx, peer) {
225
- const d = state.domain;
226
- if (null != state.lo && null != state.hi) {
227
- const c = cmpVal(d, state.hi.v, state.lo.v);
228
- if (c < 0 || (0 === c && (state.lo.open || state.hi.open))) {
229
- return this.fail(ctx, peer);
230
- }
231
- }
232
- const integral = ScalarKindVal_1.Integer === state.kind || ScalarKindVal_1.BigInteger === state.kind;
233
- // Integral gap: an integer-narrowed interval containing no whole
234
- // number is empty (integer & above(1) & below(2)).
235
- if (integral && null != state.lo && null != state.hi) {
236
- const lo = (0, numcmp_1.scaledOfNumeric)(state.lo.v);
237
- const hi = (0, numcmp_1.scaledOfNumeric)(state.hi.v);
238
- if (!lo.inf && !hi.inf) {
239
- // Smallest admissible integer above/at the lower bound.
240
- let n = (0, numcmp_1.scaledFloor)(lo);
241
- if (!(0, numcmp_1.scaledIsIntegral)(lo) || state.lo.open) {
242
- n += 1n;
243
- }
244
- // Largest admissible integer below/at the upper bound.
245
- let m = (0, numcmp_1.scaledFloor)(hi);
246
- if (state.hi.open && (0, numcmp_1.scaledIsIntegral)(hi)) {
247
- m -= 1n;
248
- }
249
- if (m < n) {
250
- return this.fail(ctx, peer);
251
- }
252
- }
253
- }
254
- // Point deletion under a narrowed leaf: a closed point interval
255
- // whose single value of the narrowed leaf is excluded is empty
256
- // (integer & min(3) & max(3) & neq(3)). Without a narrowing the
257
- // point survives in the other leaves.
258
- if (null != state.kind && null != state.lo && null != state.hi &&
259
- !state.lo.open && !state.hi.open &&
260
- 0 === cmpVal(d, state.lo.v, state.hi.v)) {
261
- for (const n of state.neqs) {
262
- if (leafMarker(n) === state.kind && 0 === (0, numcmp_1.cmpNumeric)(n, state.lo.v)) {
263
- return this.fail(ctx, peer);
264
- }
265
- }
793
+ if (stateEmpty(state)) {
794
+ return this.fail(ctx, peer);
266
795
  }
267
796
  const out = new ConstraintVal({ peg: [], state }, ctx);
268
797
  out.path = this.path;
@@ -290,6 +819,10 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
290
819
  lo: this.lo,
291
820
  hi: this.hi,
292
821
  neqs: [...this.neqs],
822
+ res: [...this.res],
823
+ count: this.count,
824
+ uniq: this.uniq,
825
+ musts: [...this.musts],
293
826
  invalid: this.invalid,
294
827
  };
295
828
  }
@@ -303,31 +836,27 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
303
836
  out.lo = this.lo;
304
837
  out.hi = this.hi;
305
838
  out.neqs = [...this.neqs];
839
+ out.res = [...this.res];
840
+ out.count = this.count;
841
+ out.uniq = this.uniq;
842
+ out.musts = [...this.musts];
843
+ out.pending = this.pending;
844
+ out.cjo = this.cjo;
306
845
  out.invalid = this.invalid;
846
+ out.invalidWhy = this.invalidWhy;
307
847
  return out;
308
848
  }
309
849
  // The fixed canonical atom order: kind, lower, upper, neq (arguments
310
- // sorted). No spaces; reparses to a conjunct that normalises back to
311
- // this exact residual.
850
+ // sorted), re, length, unique. No spaces; reparses to a conjunct that
851
+ // normalises back to this exact residual.
312
852
  get canon() {
313
- const parts = [];
314
- if (null != this.kind) {
315
- parts.push(this.kind.name.toLowerCase());
853
+ if (null != this.pending) {
854
+ // A pending atom has no residual yet, so canon renders the call as
855
+ // written — the same shape FuncBaseVal renders while deferring.
856
+ return this.pending.atom +
857
+ '(' + this.pending.args.map((a) => a.canon).join(',') + ')';
316
858
  }
317
- if (null != this.lo) {
318
- parts.push((this.lo.open ? 'above(' : 'min(') + this.lo.v.canon + ')');
319
- }
320
- if (null != this.hi) {
321
- parts.push((this.hi.open ? 'below(' : 'max(') + this.hi.v.canon + ')');
322
- }
323
- if (0 < this.neqs.length) {
324
- parts.push('neq(' + this.neqs.map((n) => n.canon).join(',') + ')');
325
- }
326
- if (0 === parts.length) {
327
- // Raw invalid atom: render the call so the error frame shows it.
328
- return 'constraint()';
329
- }
330
- return parts.join('&');
859
+ return canonState(this);
331
860
  }
332
861
  same(peer) {
333
862
  return true === peer?.isConstraint && this.canon === peer.canon;
@@ -371,7 +900,395 @@ function dedupSorted(domain, neqs) {
371
900
  }
372
901
  return out;
373
902
  }
374
- // The five atom classes registered in the parser's funcMap: each is a
903
+ // Is this value, or anything inside it, a nil? A written argument that
904
+ // holds one can never be satisfied, so the atom refuses it as an
905
+ // argument rather than reporting a mystery failure against every peer.
906
+ function holdsNil(v) {
907
+ if (null == v || true !== v.isVal) {
908
+ return false;
909
+ }
910
+ if (true === v.isNil) {
911
+ return true;
912
+ }
913
+ const peg = v.peg;
914
+ if (Array.isArray(peg)) {
915
+ return peg.some((c) => holdsNil(c));
916
+ }
917
+ if (null != peg && 'object' === typeof peg) {
918
+ for (const k in peg) {
919
+ if (holdsNil(peg[k])) {
920
+ return true;
921
+ }
922
+ }
923
+ }
924
+ return false;
925
+ }
926
+ // The written arguments of an atom, flattened.
927
+ //
928
+ // A multi-argument call arrives from the func-paren grammar as ONE
929
+ // entry holding the comma group, and `neq([3,1,2])` means the same as
930
+ // `neq(3,1,2)`. The group is always a ListVal by the time it reaches
931
+ // here -- the func-paren handler rawToVals every argument (issue #49) --
932
+ // so there is no raw-array case to unwrap. Flattening happens before
933
+ // the settled check, because an unsettled member hiding inside the
934
+ // group would otherwise make the atom look ready.
935
+ function atomArgs(atom, args) {
936
+ if (('neq' === atom || 'must' === atom) && 1 === args.length &&
937
+ true === args[0]?.isList) {
938
+ return args[0].peg;
939
+ }
940
+ return args;
941
+ }
942
+ // The canonical rendering of a residual, in the fixed atom order:
943
+ // kind, lower bound, upper bound, neq, re, length, unique. Taken over the
944
+ // STATE rather than the Val because `length`'s argument is a residual too,
945
+ // and renders by exactly the same rules.
946
+ function canonState(s) {
947
+ const parts = [];
948
+ if (null != s.kind) {
949
+ parts.push(s.kind.name.toLowerCase());
950
+ }
951
+ // An ORDER atom's argument implies the domain, so it is not spelled
952
+ // out. A SIZING residual carries no order, and there `string` is the
953
+ // only thing saying what is being sized -- drop it and the reparse
954
+ // would admit lists and maps too.
955
+ else if ('string' === s.domain &&
956
+ null == s.lo && null == s.hi && 0 === s.neqs.length && 0 === s.res.length) {
957
+ parts.push('string');
958
+ }
959
+ if (null != s.lo) {
960
+ parts.push((s.lo.open ? 'above(' : 'min(') + s.lo.v.canon + ')');
961
+ }
962
+ if (null != s.hi) {
963
+ parts.push((s.hi.open ? 'below(' : 'max(') + s.hi.v.canon + ')');
964
+ }
965
+ if (0 < s.neqs.length) {
966
+ parts.push('neq(' + s.neqs.map((n) => n.canon).join(',') + ')');
967
+ }
968
+ for (const r of s.res) {
969
+ parts.push('re(' + r.v.canon + ')');
970
+ }
971
+ if (null != s.count) {
972
+ // Rendered UNABRIDGED, implied parts and all: `length(3)` canonicalises
973
+ // to `length(integer&min(3)&max(3))` because that IS the residual the
974
+ // count must satisfy, and canon is a normal form (G6 hashes it),
975
+ // not a pretty-printer. Abbreviating would mean a second set of
976
+ // rules for when the implied `integer & min(0)` may be dropped.
977
+ parts.push('length(' + canonState(s.count) + ')');
978
+ }
979
+ if (s.uniq) {
980
+ parts.push('unique()');
981
+ }
982
+ for (const m of s.musts) {
983
+ parts.push('must(' + m.v.canon + ',' + m.msg.canon + ')');
984
+ }
985
+ if (0 === parts.length) {
986
+ // Raw invalid atom: render the call so the error frame shows it.
987
+ return 'constraint()';
988
+ }
989
+ return parts.join('&');
990
+ }
991
+ // Does this residual's ORDER and MEMBERSHIP part admit the scalar? The
992
+ // sizing atoms are deliberately not consulted: `admit` applies them to
993
+ // the peer's length, and the count check applies this same function to
994
+ // the count.
995
+ function stateAdmits(s, peer) {
996
+ const domainOf = numericLeaf(peer) ? 'number' :
997
+ stringLeaf(peer) ? 'string' : undefined;
998
+ if (null == s.domain) {
999
+ // A sizing residual has no domain, and admits any scalar the sizing
1000
+ // atoms can then rule on. Booleans and null are not among them:
1001
+ // they have no order, no length and no members.
1002
+ if (null == domainOf) {
1003
+ return false;
1004
+ }
1005
+ return true;
1006
+ }
1007
+ if (domainOf !== s.domain) {
1008
+ return false;
1009
+ }
1010
+ if (null != s.kind && leafMarker(peer) !== s.kind) {
1011
+ return false;
1012
+ }
1013
+ const d = s.domain;
1014
+ if (null != s.lo) {
1015
+ const c = cmpVal(d, peer, s.lo.v);
1016
+ if (c < 0 || (0 === c && s.lo.open)) {
1017
+ return false;
1018
+ }
1019
+ }
1020
+ if (null != s.hi) {
1021
+ const c = cmpVal(d, peer, s.hi.v);
1022
+ if (c > 0 || (0 === c && s.hi.open)) {
1023
+ return false;
1024
+ }
1025
+ }
1026
+ for (const n of s.neqs) {
1027
+ if (sameScalar(peer, n)) {
1028
+ return false;
1029
+ }
1030
+ }
1031
+ // Every accumulated pattern must match: the meet of two `re` atoms
1032
+ // is conjunction, and matching is UNANCHORED in both engines
1033
+ // (JS RegExp.test, Go regexp.MatchString), so `re("el")` admits
1034
+ // "hello". Anchor with ^ and $ to mean the whole string.
1035
+ for (const r of s.res) {
1036
+ if (!r.re.test(peer.peg)) {
1037
+ return false;
1038
+ }
1039
+ }
1040
+ return true;
1041
+ }
1042
+ // The eager emptiness rules. Each is EXACT -- it reports empty only
1043
+ // where no value could satisfy the residual -- so the algebra stays
1044
+ // sound; the incompleteness it accepts is documented in
1045
+ // docs/reference-language.md, "Emptiness".
1046
+ function stateEmpty(s) {
1047
+ // Two disagreeing kind narrowings inside a length argument, recorded by
1048
+ // meetCount because that meet has no ctx to fail through.
1049
+ if (s.clash) {
1050
+ return true;
1051
+ }
1052
+ const d = s.domain;
1053
+ // Empty interval.
1054
+ if (null != s.lo && null != s.hi) {
1055
+ const c = cmpVal(d, s.hi.v, s.lo.v);
1056
+ if (c < 0 || (0 === c && (s.lo.open || s.hi.open))) {
1057
+ return true;
1058
+ }
1059
+ }
1060
+ const integral = ScalarKindVal_1.Integer === s.kind || ScalarKindVal_1.BigInteger === s.kind;
1061
+ // Integral gap: an integer-narrowed interval containing no whole
1062
+ // number is empty (integer & above(1) & below(2)).
1063
+ if (integral && null != s.lo && null != s.hi) {
1064
+ const lo = (0, numcmp_1.scaledOfNumeric)(s.lo.v);
1065
+ const hi = (0, numcmp_1.scaledOfNumeric)(s.hi.v);
1066
+ if (!lo.inf && !hi.inf) {
1067
+ // Smallest admissible integer above/at the lower bound.
1068
+ let n = (0, numcmp_1.scaledFloor)(lo);
1069
+ if (!(0, numcmp_1.scaledIsIntegral)(lo) || s.lo.open) {
1070
+ n += 1n;
1071
+ }
1072
+ // Largest admissible integer below/at the upper bound.
1073
+ let m = (0, numcmp_1.scaledFloor)(hi);
1074
+ if (s.hi.open && (0, numcmp_1.scaledIsIntegral)(hi)) {
1075
+ m -= 1n;
1076
+ }
1077
+ if (m < n) {
1078
+ return true;
1079
+ }
1080
+ }
1081
+ }
1082
+ // Point deletion under a narrowed leaf: a closed point interval
1083
+ // whose single value of the narrowed leaf is excluded is empty
1084
+ // (integer & min(3) & max(3) & neq(3)). Without a narrowing the
1085
+ // point survives in the other leaves.
1086
+ if (null != s.kind && null != s.lo && null != s.hi &&
1087
+ !s.lo.open && !s.hi.open &&
1088
+ 0 === cmpVal(d, s.lo.v, s.hi.v)) {
1089
+ for (const n of s.neqs) {
1090
+ if (leafMarker(n) === s.kind && 0 === (0, numcmp_1.cmpNumeric)(n, s.lo.v)) {
1091
+ return true;
1092
+ }
1093
+ }
1094
+ }
1095
+ // Sizing over the number domain: a number has neither a length nor
1096
+ // members, so `integer & length(3)` and `min(2) & unique()` admit
1097
+ // nothing. Uniqueness over the string domain is empty for the same
1098
+ // reason -- a string's members are not values the algebra compares.
1099
+ if ('number' === d && (null != s.count || s.uniq)) {
1100
+ return true;
1101
+ }
1102
+ if ('string' === d && s.uniq) {
1103
+ return true;
1104
+ }
1105
+ // An empty count residual makes the whole thing empty: no container
1106
+ // and no string has a length no integer can take.
1107
+ if (null != s.count && stateEmpty(s.count)) {
1108
+ return true;
1109
+ }
1110
+ return false;
1111
+ }
1112
+ // The base every `length` argument meets: a count is a non-negative
1113
+ // integer, whatever else the argument says (docs/reference-language.md,
1114
+ // "`length` semantics" -- `length(c)` is empty iff `c & integer & min(0)`
1115
+ // is).
1116
+ function countBase() {
1117
+ return {
1118
+ domain: 'number',
1119
+ kind: ScalarKindVal_1.Integer,
1120
+ lo: { v: countVal(0), open: false },
1121
+ neqs: [],
1122
+ res: [],
1123
+ musts: [],
1124
+ uniq: false,
1125
+ };
1126
+ }
1127
+ // A count as a Val, so the count residual can be applied by exactly the
1128
+ // same membership function as any other numeric residual.
1129
+ function countVal(n) {
1130
+ return new IntegerVal_1.IntegerVal({ peg: n });
1131
+ }
1132
+ // The pure meet of two count residuals. Both are number-domain and
1133
+ // carry no pattern or sizing atom of their own, so the merge is the
1134
+ // interval/exclusion part alone. A kind disagreement becomes a `clash`
1135
+ // rather than an error: this meet runs at construction, where there is
1136
+ // no AontuContext to raise through, and an empty residual carries the
1137
+ // same news to `unify`.
1138
+ function meetCount(a, b) {
1139
+ return {
1140
+ domain: 'number',
1141
+ kind: a.kind ?? b.kind,
1142
+ lo: tighter('number', a.lo, b.lo, true),
1143
+ hi: tighter('number', a.hi, b.hi, false),
1144
+ neqs: dedupSorted('number', [...a.neqs, ...b.neqs]),
1145
+ res: [],
1146
+ musts: [],
1147
+ uniq: false,
1148
+ clash: true === a.clash || true === b.clash ||
1149
+ (null != a.kind && null != b.kind && a.kind !== b.kind),
1150
+ };
1151
+ }
1152
+ // Read a WRITTEN `length` argument as a count residual, or undefined when
1153
+ // it is not one. Accepted: an integer literal (an exact count), a
1154
+ // numeric kind, a Band A residual over the number domain, and any
1155
+ // conjunct of those. A conjunct never reaches here any more: an
1156
+ // unsettled argument is held as `pending` and folded before the count
1157
+ // reads it (G1 phase 4), so `length(min(2)&max(5))` arrives as the
1158
+ // single residual it folds to.
1159
+ //
1160
+ // Anything else is refused rather than deferred. A reference or an
1161
+ // expression would have to residuate, and the sizing atoms do not
1162
+ // residuate on their ARGUMENT -- only on the peer whose members are
1163
+ // still settling.
1164
+ function countArgState(arg) {
1165
+ if (numericLeaf(arg)) {
1166
+ return {
1167
+ domain: 'number',
1168
+ lo: { v: arg, open: false },
1169
+ hi: { v: arg, open: false },
1170
+ neqs: [], res: [], musts: [], uniq: false,
1171
+ };
1172
+ }
1173
+ if (true === arg?.isConstraint) {
1174
+ const c = arg;
1175
+ // A pattern, a sizing atom or a string bound inside a count is not
1176
+ // a count constraint at all, and neither is a broken one.
1177
+ if (null != c.invalid || 0 < c.res.length || c.uniq || null != c.count ||
1178
+ 'number' !== c.domain) {
1179
+ return undefined;
1180
+ }
1181
+ return {
1182
+ domain: 'number',
1183
+ kind: c.kind,
1184
+ lo: c.lo,
1185
+ hi: c.hi,
1186
+ neqs: [...c.neqs],
1187
+ res: [], musts: [], uniq: false,
1188
+ };
1189
+ }
1190
+ if (true === arg?.isScalarKind) {
1191
+ const marker = arg.peg;
1192
+ if (Number === marker) {
1193
+ return { domain: 'number', neqs: [], res: [], musts: [], uniq: false };
1194
+ }
1195
+ if (ScalarKindVal_1.Integer === marker || ScalarKindVal_1.Float === marker ||
1196
+ ScalarKindVal_1.BigInteger === marker || ScalarKindVal_1.BigDecimal === marker) {
1197
+ return { domain: 'number', kind: marker, neqs: [], res: [], musts: [], uniq: false };
1198
+ }
1199
+ return undefined;
1200
+ }
1201
+ return undefined;
1202
+ }
1203
+ // A container is SETTLED when it and every child have converged. Until
1204
+ // then the member set can still change — an optional key whose value is
1205
+ // a still-resolving reference may yet generate — so a sizing atom must
1206
+ // defer rather than decide (docs/reference-language.md, "`length`
1207
+ // semantics"). Note that an optional holding a settled-but-ungenerable
1208
+ // value, `{x:1,y?:number}`, IS settled: the map converges on the first
1209
+ // pass and `y` is simply never emitted.
1210
+ function containerSettled(bag) {
1211
+ // The bag's OWN done-counter is enough: BagVal.unify sets it from the
1212
+ // AND over its children, so an unsettled child already leaves the bag
1213
+ // unsettled. Walking the children again would be a second, drifting
1214
+ // copy of that rule.
1215
+ return true === bag.done;
1216
+ }
1217
+ // The child kinds `BagVal.gen` will attempt to generate. Anything else
1218
+ // is residue: dropped when the key is optional, and a bag-level error
1219
+ // otherwise.
1220
+ function genable(child) {
1221
+ return true === child.isScalar || true === child.isMap ||
1222
+ true === child.isList || true === child.isPref ||
1223
+ true === child.isRef || true === child.isDisjunct ||
1224
+ true === child.isNil;
1225
+ }
1226
+ // The children a bag would EMIT, mirroring the selection in
1227
+ // `BagVal.gen` — type/hide marks skipped, non-generable residue and
1228
+ // values that generate nothing dropped, optional keys dropped when
1229
+ // they generate empty.
1230
+ //
1231
+ // It returns the member VALS rather than their generated values,
1232
+ // because uniqueness compares canon and a generated value cannot
1233
+ // express that distinction: `1` and `1.0` generate the same JSON number
1234
+ // and canon differently. Counting uses the same list, so both sizing
1235
+ // atoms see exactly one definition of "member".
1236
+ //
1237
+ // Returns undefined when a REQUIRED child is residue: the container's
1238
+ // own `gen` raises there, and that error is the one worth reporting.
1239
+ function emittedMembers(bag, ctx) {
1240
+ const out = [];
1241
+ let entries = (0, utility_1.items)(bag.peg);
1242
+ if (bag.isMap) {
1243
+ // Code-point order, because the two ports disagree on raw key order
1244
+ // — JavaScript hoists integer-like keys, Go keeps insertion order —
1245
+ // and a duplicate report must name the same pair in both.
1246
+ entries = entries
1247
+ .slice()
1248
+ .sort((a, b) => (0, keyorder_1.cmpCodePoint)(String(a[0]), String(b[0])));
1249
+ }
1250
+ for (const item of entries) {
1251
+ const key = item[0];
1252
+ const child = item[1];
1253
+ if (child.mark.type || child.mark.hide) {
1254
+ continue;
1255
+ }
1256
+ const optional = bag.optionalKeys.includes('' + key);
1257
+ if (!genable(child)) {
1258
+ if (optional) {
1259
+ continue;
1260
+ }
1261
+ return undefined;
1262
+ }
1263
+ // Generation decides in an isolated collect context, so an
1264
+ // unresolved inner value neither raises here nor pollutes the
1265
+ // caller's errors — the same isolation BagVal.gen uses for an
1266
+ // optional child.
1267
+ const cval = child.gen(ctx.clone({ err: [], collect: true }));
1268
+ if (undefined === cval || (optional && (0, Val_1.empty)(cval))) {
1269
+ continue;
1270
+ }
1271
+ out.push(child);
1272
+ }
1273
+ return out;
1274
+ }
1275
+ // Sort accumulated patterns by source in code-point order and drop
1276
+ // exact duplicates. Patterns are NEVER simplified or compared for
1277
+ // containment: deciding `re("a")` subsumes `re("a|b")` is regex
1278
+ // containment, which the algebra deliberately does not do (emptiness
1279
+ // stays approximate — sound, incomplete). Two spellings of one language
1280
+ // therefore both survive, and both are tested.
1281
+ function dedupSortedRes(res) {
1282
+ const sorted = [...res].sort((a, b) => (0, numcmp_1.cmpCodePoints)(a.src, b.src));
1283
+ const out = [];
1284
+ for (const r of sorted) {
1285
+ if (0 === out.length || out[out.length - 1].src !== r.src) {
1286
+ out.push(r);
1287
+ }
1288
+ }
1289
+ return out;
1290
+ }
1291
+ // The atom classes registered in the parser's funcMap: each is a
375
1292
  // ConstraintVal that knows its atom name. Constructed by the
376
1293
  // func-paren handler as `new funcval({peg: args})`.
377
1294
  class MinConstraintVal extends ConstraintVal {
@@ -402,6 +1319,30 @@ class NeqConstraintVal extends ConstraintVal {
402
1319
  constructor(spec, ctx) {
403
1320
  super({ ...spec, atom: 'neq' }, ctx);
404
1321
  }
405
- } /* node:coverage ignore next 11 */
1322
+ }
406
1323
  exports.NeqConstraintVal = NeqConstraintVal;
1324
+ class ReConstraintVal extends ConstraintVal {
1325
+ constructor(spec, ctx) {
1326
+ super({ ...spec, atom: 're' }, ctx);
1327
+ }
1328
+ }
1329
+ exports.ReConstraintVal = ReConstraintVal;
1330
+ class MustConstraintVal extends ConstraintVal {
1331
+ constructor(spec, ctx) {
1332
+ super({ ...spec, atom: 'must' }, ctx);
1333
+ }
1334
+ }
1335
+ exports.MustConstraintVal = MustConstraintVal;
1336
+ class LengthConstraintVal extends ConstraintVal {
1337
+ constructor(spec, ctx) {
1338
+ super({ ...spec, atom: 'length' }, ctx);
1339
+ }
1340
+ }
1341
+ exports.LengthConstraintVal = LengthConstraintVal;
1342
+ class UniqueConstraintVal extends ConstraintVal {
1343
+ constructor(spec, ctx) {
1344
+ super({ ...spec, atom: 'unique' }, ctx);
1345
+ }
1346
+ } /* node:coverage ignore next 19 */
1347
+ exports.UniqueConstraintVal = UniqueConstraintVal;
407
1348
  //# sourceMappingURL=ConstraintVal.js.map