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.
- package/dist/aontu.d.ts +1 -1
- package/dist/aontu.js +50 -1
- package/dist/aontu.js.map +1 -1
- package/dist/ctx.d.ts +3 -0
- package/dist/ctx.js +1 -0
- package/dist/ctx.js.map +1 -1
- package/dist/err.js +10 -1
- package/dist/err.js.map +1 -1
- package/dist/hints.js +56 -1
- package/dist/hints.js.map +1 -1
- package/dist/lang.js +235 -15
- package/dist/lang.js.map +1 -1
- package/dist/lsp.js +4 -3
- package/dist/lsp.js.map +1 -1
- package/dist/unify.d.ts +2 -1
- package/dist/unify.js +55 -3
- package/dist/unify.js.map +1 -1
- package/dist/val/ConstraintVal.d.ts +42 -1
- package/dist/val/ConstraintVal.js +1048 -107
- package/dist/val/ConstraintVal.js.map +1 -1
- package/dist/val/ExpectVal.d.ts +1 -0
- package/dist/val/ExpectVal.js +14 -0
- package/dist/val/ExpectVal.js.map +1 -1
- package/dist/val/FuncBaseVal.js +7 -1
- package/dist/val/FuncBaseVal.js.map +1 -1
- package/dist/val/ListVal.js +17 -4
- package/dist/val/ListVal.js.map +1 -1
- package/dist/val/MapVal.js +9 -0
- package/dist/val/MapVal.js.map +1 -1
- package/dist/val/OpBaseVal.js +5 -1
- package/dist/val/OpBaseVal.js.map +1 -1
- package/dist/val/RefVal.js +69 -23
- package/dist/val/RefVal.js.map +1 -1
- package/dist/val/Val.d.ts +1 -1
- package/dist/val/Val.js +0 -3
- package/dist/val/Val.js.map +1 -1
- package/package.json +10 -10
- package/src/aontu.ts +58 -1
- package/src/ctx.ts +9 -0
- package/src/err.ts +10 -1
- package/src/hints.ts +63 -1
- package/src/lang.ts +258 -16
- package/src/lsp.ts +4 -3
- package/src/unify.ts +59 -2
- package/src/val/ConstraintVal.ts +1195 -111
- package/src/val/ExpectVal.ts +15 -0
- package/src/val/FuncBaseVal.ts +8 -2
- package/src/val/ListVal.ts +18 -4
- package/src/val/MapVal.ts +11 -0
- package/src/val/OpBaseVal.ts +6 -2
- package/src/val/RefVal.ts +72 -22
- 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
|
-
|
|
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
|
|
64
|
-
//
|
|
65
|
-
//
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
82
|
-
|
|
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.
|
|
123
|
-
out = (
|
|
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
|
-
//
|
|
142
|
-
//
|
|
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
|
-
|
|
152
|
-
|
|
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 (
|
|
624
|
+
if (!stateAdmits(this, peer)) {
|
|
157
625
|
return this.fail(ctx, peer);
|
|
158
626
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
|
181
|
-
// else has an empty intersection with the
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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 || '
|
|
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
|
-
|
|
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
|
-
|
|
226
|
-
|
|
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
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
}
|
|
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
|