aontu 0.52.0 → 0.53.0
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/README.md +88 -0
- package/bin/aontu-mcp.js +4 -0
- package/dist/agentsmd.d.ts +16 -0
- package/dist/agentsmd.js +107 -0
- package/dist/agentsmd.js.map +1 -0
- package/dist/aontu.d.ts +14 -3
- package/dist/aontu.js +145 -4
- package/dist/aontu.js.map +1 -1
- package/dist/cli.d.ts +44 -1
- package/dist/cli.js +2401 -44
- package/dist/cli.js.map +1 -1
- package/dist/ctx.d.ts +16 -0
- package/dist/ctx.js +44 -0
- package/dist/ctx.js.map +1 -1
- package/dist/diff.d.ts +22 -0
- package/dist/diff.js +141 -0
- package/dist/diff.js.map +1 -0
- package/dist/err.d.ts +3 -1
- package/dist/err.js +48 -8
- package/dist/err.js.map +1 -1
- package/dist/graph.d.ts +16 -0
- package/dist/graph.js +73 -0
- package/dist/graph.js.map +1 -0
- package/dist/hcanon.d.ts +3 -0
- package/dist/hcanon.js +146 -0
- package/dist/hcanon.js.map +1 -0
- package/dist/hints.js +223 -5
- package/dist/hints.js.map +1 -1
- package/dist/jsonschema.d.ts +20 -0
- package/dist/jsonschema.js +391 -0
- package/dist/jsonschema.js.map +1 -0
- package/dist/lang.js +698 -35
- package/dist/lang.js.map +1 -1
- package/dist/lsp.d.ts +9 -2
- package/dist/lsp.js +262 -46
- package/dist/lsp.js.map +1 -1
- package/dist/mcp-server.d.ts +20 -0
- package/dist/mcp-server.js +147 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/mcp.d.ts +42 -0
- package/dist/mcp.js +814 -0
- package/dist/mcp.js.map +1 -0
- package/dist/mod-tool.d.ts +58 -0
- package/dist/mod-tool.js +498 -0
- package/dist/mod-tool.js.map +1 -0
- package/dist/mod.d.ts +31 -0
- package/dist/mod.js +250 -0
- package/dist/mod.js.map +1 -0
- package/dist/patch.d.ts +44 -0
- package/dist/patch.js +506 -0
- package/dist/patch.js.map +1 -0
- package/dist/provenance.d.ts +40 -0
- package/dist/provenance.js +335 -0
- package/dist/provenance.js.map +1 -0
- package/dist/query.d.ts +27 -0
- package/dist/query.js +294 -0
- package/dist/query.js.map +1 -0
- package/dist/reach.d.ts +14 -0
- package/dist/reach.js +140 -0
- package/dist/reach.js.map +1 -0
- package/dist/relation.d.ts +19 -0
- package/dist/relation.js +305 -0
- package/dist/relation.js.map +1 -0
- package/dist/report-sarif.d.ts +14 -0
- package/dist/report-sarif.js +102 -0
- package/dist/report-sarif.js.map +1 -0
- package/dist/site.d.ts +4 -0
- package/dist/site.js +31 -0
- package/dist/site.js.map +1 -1
- package/dist/std.d.ts +1 -0
- package/dist/std.js +73 -0
- package/dist/std.js.map +1 -0
- package/dist/subsume.d.ts +39 -0
- package/dist/subsume.js +526 -0
- package/dist/subsume.js.map +1 -0
- package/dist/trim.d.ts +19 -0
- package/dist/trim.js +155 -0
- package/dist/trim.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/type.d.ts +17 -1
- package/dist/type.js.map +1 -1
- package/dist/unify.d.ts +3 -1
- package/dist/unify.js +287 -18
- package/dist/unify.js.map +1 -1
- package/dist/utility.d.ts +9 -1
- package/dist/utility.js +122 -1
- package/dist/utility.js.map +1 -1
- package/dist/val/AggFuncVal.d.ts +33 -0
- package/dist/val/AggFuncVal.js +202 -0
- package/dist/val/AggFuncVal.js.map +1 -0
- package/dist/val/ArithFuncVal.d.ts +31 -0
- package/dist/val/ArithFuncVal.js +62 -0
- package/dist/val/ArithFuncVal.js.map +1 -0
- package/dist/val/BagVal.d.ts +5 -0
- package/dist/val/BagVal.js +96 -5
- package/dist/val/BagVal.js.map +1 -1
- package/dist/val/CloseFuncVal.js +9 -1
- package/dist/val/CloseFuncVal.js.map +1 -1
- package/dist/val/ConjunctVal.d.ts +1 -1
- package/dist/val/ConjunctVal.js +19 -0
- package/dist/val/ConjunctVal.js.map +1 -1
- package/dist/val/ConstraintVal.d.ts +48 -1
- package/dist/val/ConstraintVal.js +1501 -110
- package/dist/val/ConstraintVal.js.map +1 -1
- package/dist/val/CopyFuncVal.d.ts +1 -2
- package/dist/val/CopyFuncVal.js +7 -0
- package/dist/val/CopyFuncVal.js.map +1 -1
- package/dist/val/Decimal.d.ts +1 -0
- package/dist/val/Decimal.js +13 -0
- package/dist/val/Decimal.js.map +1 -1
- package/dist/val/DeprecateFuncVal.d.ts +11 -0
- package/dist/val/DeprecateFuncVal.js +47 -0
- package/dist/val/DeprecateFuncVal.js.map +1 -0
- package/dist/val/DisjunctVal.js +130 -21
- package/dist/val/DisjunctVal.js.map +1 -1
- package/dist/val/EachFuncVal.d.ts +15 -0
- package/dist/val/EachFuncVal.js +75 -0
- package/dist/val/EachFuncVal.js.map +1 -0
- package/dist/val/ExpectVal.d.ts +1 -0
- package/dist/val/ExpectVal.js +41 -4
- package/dist/val/ExpectVal.js.map +1 -1
- package/dist/val/FeatureVal.js +1 -1
- package/dist/val/FeatureVal.js.map +1 -1
- package/dist/val/FilterFuncVal.d.ts +15 -0
- package/dist/val/FilterFuncVal.js +91 -0
- package/dist/val/FilterFuncVal.js.map +1 -0
- package/dist/val/FuncBaseVal.d.ts +6 -1
- package/dist/val/FuncBaseVal.js +178 -2
- package/dist/val/FuncBaseVal.js.map +1 -1
- package/dist/val/HideFuncVal.js.map +1 -1
- package/dist/val/IdFuncVal.d.ts +13 -0
- package/dist/val/IdFuncVal.js +54 -0
- package/dist/val/IdFuncVal.js.map +1 -0
- package/dist/val/JunctionVal.js +7 -1
- package/dist/val/JunctionVal.js.map +1 -1
- package/dist/val/KeyFuncVal.d.ts +1 -1
- package/dist/val/KeyFuncVal.js +38 -30
- package/dist/val/KeyFuncVal.js.map +1 -1
- package/dist/val/ListVal.js +117 -17
- package/dist/val/ListVal.js.map +1 -1
- package/dist/val/LowerFuncVal.js.map +1 -1
- package/dist/val/MapVal.js +102 -8
- package/dist/val/MapVal.js.map +1 -1
- package/dist/val/MatchFuncVal.d.ts +15 -0
- package/dist/val/MatchFuncVal.js +107 -0
- package/dist/val/MatchFuncVal.js.map +1 -0
- package/dist/val/MoveFuncVal.js.map +1 -1
- package/dist/val/NilVal.js +24 -0
- package/dist/val/NilVal.js.map +1 -1
- package/dist/val/OpBaseVal.d.ts +1 -1
- package/dist/val/OpBaseVal.js +24 -2
- package/dist/val/OpBaseVal.js.map +1 -1
- package/dist/val/OpenFuncVal.js +4 -1
- package/dist/val/OpenFuncVal.js.map +1 -1
- package/dist/val/PackFuncVal.d.ts +15 -0
- package/dist/val/PackFuncVal.js +108 -0
- package/dist/val/PackFuncVal.js.map +1 -0
- package/dist/val/PathFuncVal.js.map +1 -1
- package/dist/val/PlaceVal.d.ts +13 -0
- package/dist/val/PlaceVal.js +131 -0
- package/dist/val/PlaceVal.js.map +1 -0
- package/dist/val/PlusOpVal.js +11 -2
- package/dist/val/PlusOpVal.js.map +1 -1
- package/dist/val/PrefFuncVal.js.map +1 -1
- package/dist/val/PrefVal.d.ts +2 -2
- package/dist/val/PrefVal.js +78 -23
- package/dist/val/PrefVal.js.map +1 -1
- package/dist/val/RefVal.d.ts +1 -1
- package/dist/val/RefVal.js +158 -30
- package/dist/val/RefVal.js.map +1 -1
- package/dist/val/ReferFuncVal.d.ts +36 -0
- package/dist/val/ReferFuncVal.js +303 -0
- package/dist/val/ReferFuncVal.js.map +1 -0
- package/dist/val/ScalarKindVal.d.ts +1 -2
- package/dist/val/ScalarKindVal.js +0 -11
- package/dist/val/ScalarKindVal.js.map +1 -1
- package/dist/val/TopVal.js.map +1 -1
- package/dist/val/TypeFuncVal.js.map +1 -1
- package/dist/val/UpperFuncVal.js.map +1 -1
- package/dist/val/Val.d.ts +9 -2
- package/dist/val/Val.js +150 -4
- package/dist/val/Val.js.map +1 -1
- package/dist/val/VarVal.js.map +1 -1
- package/dist/val/arith.d.ts +6 -0
- package/dist/val/arith.js +170 -0
- package/dist/val/arith.js.map +1 -0
- package/dist/vet.d.ts +45 -0
- package/dist/vet.js +776 -0
- package/dist/vet.js.map +1 -0
- package/dist/walk.d.ts +2 -0
- package/dist/walk.js +91 -0
- package/dist/walk.js.map +1 -0
- package/grammar/aontu.gbnf +130 -0
- package/grammar/aontu.lark +113 -0
- package/package.json +30 -15
- package/skill/SKILL.md +37 -0
- package/skill/error-codes.md +62 -0
- package/skill/examples.md +99 -0
- package/skill/grammar-card.md +57 -0
- package/src/agentsmd.ts +135 -0
- package/src/aontu.ts +192 -4
- package/src/cli.ts +2858 -71
- package/src/ctx.ts +81 -0
- package/src/diff.ts +196 -0
- package/src/err.ts +52 -8
- package/src/graph.ts +135 -0
- package/src/hcanon.ts +169 -0
- package/src/hints.ts +271 -5
- package/src/jsonschema.ts +511 -0
- package/src/lang.ts +779 -37
- package/src/lsp.ts +281 -47
- package/src/mcp-server.ts +187 -0
- package/src/mcp.ts +993 -0
- package/src/mod-tool.ts +679 -0
- package/src/mod.ts +344 -0
- package/src/patch.ts +624 -0
- package/src/provenance.ts +430 -0
- package/src/query.ts +379 -0
- package/src/reach.ts +184 -0
- package/src/relation.ts +395 -0
- package/src/report-sarif.ts +137 -0
- package/src/site.ts +36 -1
- package/src/std.ts +73 -0
- package/src/subsume.ts +690 -0
- package/src/trim.ts +195 -0
- package/src/tsconfig.json +10 -4
- package/src/type.ts +51 -2
- package/src/unify.ts +311 -16
- package/src/utility.ts +139 -1
- package/src/val/AggFuncVal.ts +319 -0
- package/src/val/ArithFuncVal.ts +108 -0
- package/src/val/BagVal.ts +101 -4
- package/src/val/CloseFuncVal.ts +9 -1
- package/src/val/ConjunctVal.ts +20 -0
- package/src/val/ConstraintVal.ts +1699 -116
- package/src/val/CopyFuncVal.ts +7 -1
- package/src/val/Decimal.ts +15 -0
- package/src/val/DeprecateFuncVal.ts +84 -0
- package/src/val/DisjunctVal.ts +139 -28
- package/src/val/EachFuncVal.ts +133 -0
- package/src/val/ExpectVal.ts +43 -6
- package/src/val/FeatureVal.ts +1 -1
- package/src/val/FilterFuncVal.ts +154 -0
- package/src/val/FuncBaseVal.ts +200 -3
- package/src/val/HideFuncVal.ts +0 -2
- package/src/val/IdFuncVal.ts +91 -0
- package/src/val/JunctionVal.ts +7 -1
- package/src/val/KeyFuncVal.ts +39 -35
- package/src/val/ListVal.ts +125 -18
- package/src/val/LowerFuncVal.ts +0 -1
- package/src/val/MapVal.ts +110 -8
- package/src/val/MatchFuncVal.ts +176 -0
- package/src/val/MoveFuncVal.ts +0 -2
- package/src/val/NilVal.ts +25 -0
- package/src/val/OpBaseVal.ts +26 -3
- package/src/val/OpenFuncVal.ts +4 -2
- package/src/val/PackFuncVal.ts +175 -0
- package/src/val/PathFuncVal.ts +0 -1
- package/src/val/PlaceVal.ts +193 -0
- package/src/val/PlusOpVal.ts +11 -2
- package/src/val/PrefFuncVal.ts +0 -1
- package/src/val/PrefVal.ts +79 -36
- package/src/val/RefVal.ts +163 -30
- package/src/val/ReferFuncVal.ts +387 -0
- package/src/val/ScalarKindVal.ts +0 -13
- package/src/val/TopVal.ts +0 -1
- package/src/val/TypeFuncVal.ts +0 -2
- package/src/val/UpperFuncVal.ts +0 -1
- package/src/val/Val.ts +213 -3
- package/src/val/VarVal.ts +0 -1
- package/src/val/arith.ts +316 -0
- package/src/vet.ts +992 -0
- package/src/walk.ts +99 -0
package/src/val/ConstraintVal.ts
CHANGED
|
@@ -49,8 +49,22 @@ import {
|
|
|
49
49
|
explainOpen,
|
|
50
50
|
explainClose,
|
|
51
51
|
propagateMarks,
|
|
52
|
+
items,
|
|
52
53
|
} from '../utility'
|
|
53
54
|
|
|
55
|
+
import { empty } from './Val'
|
|
56
|
+
|
|
57
|
+
import { cmpCodePoint } from '../keyorder'
|
|
58
|
+
|
|
59
|
+
import { ConjunctVal } from './ConjunctVal'
|
|
60
|
+
import { sizingResidue } from './BagVal'
|
|
61
|
+
|
|
62
|
+
import { top } from './top'
|
|
63
|
+
|
|
64
|
+
import { unite, withDepth } from '../unify'
|
|
65
|
+
|
|
66
|
+
import { IntegerVal } from './IntegerVal'
|
|
67
|
+
|
|
54
68
|
import { makeNilErr } from '../err'
|
|
55
69
|
|
|
56
70
|
import { FeatureVal } from './FeatureVal'
|
|
@@ -77,16 +91,447 @@ type Bound = {
|
|
|
77
91
|
open: boolean // true for above/below, false for min/max
|
|
78
92
|
}
|
|
79
93
|
|
|
94
|
+
type ReAtom = {
|
|
95
|
+
v: any // the stored pattern StringVal (canon renders the literal)
|
|
96
|
+
src: string // the pattern text AS WRITTEN — canon and dedup use this,
|
|
97
|
+
// never the normalised form, because canon round-trips
|
|
98
|
+
// source and G6's hash will be taken over canon
|
|
99
|
+
norm: string // the engine-neutral rewrite actually compiled (ADR-003)
|
|
100
|
+
re: RegExp // compiled by the host engine, from `norm`
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
type MustAtom = {
|
|
104
|
+
v: any // the value the peer must unify with (any Aontu value)
|
|
105
|
+
msg: any // the author's message StringVal (canon renders the literal)
|
|
106
|
+
}
|
|
107
|
+
|
|
80
108
|
type ConstraintState = {
|
|
81
109
|
domain?: 'number' | 'string'
|
|
82
110
|
kind?: any // numeric leaf marker (Integer | Float | ...) or undefined
|
|
83
111
|
lo?: Bound
|
|
84
112
|
hi?: Bound
|
|
85
113
|
neqs: any[] // excluded scalars, identity per leaf+value
|
|
114
|
+
res: ReAtom[] // accumulated patterns, sorted by source (never simplified)
|
|
115
|
+
count?: ConstraintState // the COUNT residual (length()), itself a residual
|
|
116
|
+
// over the integer domain -- the count atom reuses
|
|
117
|
+
// this same algebra recursively
|
|
118
|
+
uniq: boolean // members must be pairwise distinct (unique())
|
|
119
|
+
uniqBy: string[] // ... and distinct ON EACH OF THESE KEYS
|
|
120
|
+
// (unique(k)), sorted and deduplicated
|
|
121
|
+
musts: MustAtom[] // Band B checks, kept in written order, never simplified
|
|
122
|
+
clash?: boolean // a kind disagreement inside a length() argument, recorded
|
|
123
|
+
// rather than raised: the argument's own meet has no
|
|
124
|
+
// ctx to report through, so emptiness carries the news
|
|
86
125
|
invalid?: string // why-code when the atom's arguments were unusable
|
|
87
126
|
}
|
|
88
127
|
|
|
89
128
|
|
|
129
|
+
// THE PATTERN SUBSET, AND HOW IT IS ENFORCED (G1 phase 2; ADR-003).
|
|
130
|
+
//
|
|
131
|
+
// `re(p)` must mean the same thing in both engines and cost about the
|
|
132
|
+
// same, and the two host engines guarantee neither: TypeScript compiles
|
|
133
|
+
// with JavaScript's backtracking RegExp, Go with RE2 — a different
|
|
134
|
+
// language in a different complexity class, over a different alphabet.
|
|
135
|
+
//
|
|
136
|
+
// The enforcement is NORMALISATION, not refusal (ADR-003). Every
|
|
137
|
+
// construct whose expansion is engine-defined is rewritten here, by
|
|
138
|
+
// this function, into an explicit form that cannot be read two ways;
|
|
139
|
+
// only the rewritten pattern reaches a host engine. The alternative —
|
|
140
|
+
// refusing everything that might differ — was tried first and leaked
|
|
141
|
+
// three times, because its correctness depended on this comment knowing
|
|
142
|
+
// every difference between two large engines.
|
|
143
|
+
//
|
|
144
|
+
// Aontu therefore DEFINES the abbreviations rather than inheriting
|
|
145
|
+
// either host's. The definitions are deliberately the small ASCII ones,
|
|
146
|
+
// because a config value containing U+00A0 is a mistake to catch, not a
|
|
147
|
+
// space to accept silently:
|
|
148
|
+
//
|
|
149
|
+
// \d [0-9] \D [^0-9]
|
|
150
|
+
// \w [0-9A-Za-z_] \W [^0-9A-Za-z_]
|
|
151
|
+
// \s [ \t\n\r\f\v] \S [^ \t\n\r\f\v]
|
|
152
|
+
// . [^\n]
|
|
153
|
+
// \A ^ \z $
|
|
154
|
+
//
|
|
155
|
+
// Neither host agreed with all of these before rewriting: JavaScript's
|
|
156
|
+
// \s also matches U+00A0 and other Unicode spaces, RE2's omits \v, and
|
|
157
|
+
// the two `.` sets differ by \r and the Unicode line separators. After
|
|
158
|
+
// rewriting, both engines see one explicit class and cannot disagree.
|
|
159
|
+
//
|
|
160
|
+
// What is still REFUSED, and why refusal is right for these:
|
|
161
|
+
//
|
|
162
|
+
// 1. Constructs one engine simply lacks — backreferences and
|
|
163
|
+
// lookaround (not in RE2, and not expressible as regular
|
|
164
|
+
// expressions at all), POSIX classes, `\p{...}`, `\x{...}`, `\u`.
|
|
165
|
+
// There is nothing to normalise them TO.
|
|
166
|
+
// 2. `(?` other than `(?:` — named groups are spelled differently
|
|
167
|
+
// (`(?P<n>` in RE2, `(?<n>` in JavaScript) and inline flags change
|
|
168
|
+
// the meaning of everything after them.
|
|
169
|
+
// 3. A quantifier applied to a group containing a quantifier or an
|
|
170
|
+
// alternation. This one is about TIME, not meaning: `(a+)+$`
|
|
171
|
+
// against twenty-nine characters takes 45 SECONDS in JavaScript
|
|
172
|
+
// and 0.065s under RE2, and a regex match is counted by no
|
|
173
|
+
// evaluator budget, so an untrusted schema could otherwise stall
|
|
174
|
+
// the TypeScript evaluator indefinitely (docs/trust.md clause 2).
|
|
175
|
+
// Normalisation cannot fix a complexity difference; only owning the
|
|
176
|
+
// matcher could, which ADR-003 records as the future option.
|
|
177
|
+
//
|
|
178
|
+
// The alphabet is fixed separately, by compiling with the `u` flag:
|
|
179
|
+
// JavaScript otherwise matches UTF-16 code units where RE2 matches code
|
|
180
|
+
// points, so `^.$` accepted U+1D11E in Go and refused it in TypeScript.
|
|
181
|
+
//
|
|
182
|
+
// This function is mirrored statement for statement in go/constraint.go,
|
|
183
|
+
// and `test/spec/files/regex-corpus.txt` pins that the two produce
|
|
184
|
+
// byte-identical output for every pattern in a generated corpus.
|
|
185
|
+
|
|
186
|
+
// The largest repeat count Aontu's pattern language admits. RE2 caps a
|
|
187
|
+
// repeat at 1000 and refuses to compile past it, where JavaScript's
|
|
188
|
+
// backtracking engine accepts any count -- so `re("^a{1001}$")` was
|
|
189
|
+
// VALID in TypeScript and an `error` verdict in Go, a cross-port
|
|
190
|
+
// verdict flip on a schema an agent could be handed
|
|
191
|
+
// (status-2026-08-21.md section 4). Under ADR-003 the host's own
|
|
192
|
+
// compile failure must never be what a user sees, so the bound is
|
|
193
|
+
// Aontu's: at most RE_REPEAT_MAX, refused identically in both ports
|
|
194
|
+
// before either engine compiles. Nested products beyond the cap were
|
|
195
|
+
// already refused, by the quantified-group rule below.
|
|
196
|
+
const RE_REPEAT_MAX = 1000
|
|
197
|
+
|
|
198
|
+
// The normative expansions. These are Aontu's definitions, not either
|
|
199
|
+
// host's; both hosts are rewritten to them.
|
|
200
|
+
const RE_CLASS_DIGIT = '0-9'
|
|
201
|
+
const RE_CLASS_WORD = '0-9A-Za-z_'
|
|
202
|
+
const RE_CLASS_SPACE = ' \\t\\n\\r\\f\\v'
|
|
203
|
+
|
|
204
|
+
// Metacharacters that may be escaped to mean themselves, in both
|
|
205
|
+
// engines. `-` is handled separately: it is legal escaped only INSIDE a
|
|
206
|
+
// character class, because RE2 accepts `a\-b` and JavaScript's unicode
|
|
207
|
+
// mode makes it a syntax error.
|
|
208
|
+
const RE_ESCAPE_PUNCT = '\\.+*?()[]{}|^$/'
|
|
209
|
+
|
|
210
|
+
// Escapes passed through unchanged: the control characters, the ASCII
|
|
211
|
+
// word boundary, and `\xHH`. Each was probed in both engines.
|
|
212
|
+
const RE_ESCAPE_PASS = 'tnrfv'
|
|
213
|
+
|
|
214
|
+
// A counted quantifier `{n}`, `{n,}` or `{n,m}` starting at `src[at]`.
|
|
215
|
+
// Two rules, both about what the hosts do NOT share:
|
|
216
|
+
//
|
|
217
|
+
// - A bound above RE_REPEAT_MAX: RE2 refuses to compile it, JavaScript
|
|
218
|
+
// accepts it.
|
|
219
|
+
// - A brace that does not open a well-formed counted quantifier at
|
|
220
|
+
// all (`x{y}`, `a{`, `{,5}`): JavaScript compiled with the `u` flag
|
|
221
|
+
// makes it a SYNTAX ERROR, while RE2 reads it as a literal brace, so
|
|
222
|
+
// `re("^x{y}$")` was `constraint_pattern` in TypeScript and a plain
|
|
223
|
+
// unresolved value in Go -- the same class of verdict flip as the
|
|
224
|
+
// repeat cap, found by sweeping around it. A literal brace is
|
|
225
|
+
// written escaped (`a\\{1001\\}`), which both engines share.
|
|
226
|
+
function repeatWhy(src: string, at: number): [string, number] {
|
|
227
|
+
const bad = (what: string): [string, number] => [
|
|
228
|
+
'a ' + what + ', which the two engines do not read the same way', -1]
|
|
229
|
+
|
|
230
|
+
let i = at + 1
|
|
231
|
+
let digits = ''
|
|
232
|
+
const bounds: number[] = []
|
|
233
|
+
let commas = 0
|
|
234
|
+
for (; i < src.length; i++) {
|
|
235
|
+
const c = src[i]
|
|
236
|
+
if ('0' <= c && c <= '9') {
|
|
237
|
+
digits += c
|
|
238
|
+
continue
|
|
239
|
+
}
|
|
240
|
+
if (',' === c) {
|
|
241
|
+
if ('' === digits || 0 < commas) {
|
|
242
|
+
return bad('{ that does not open a counted quantifier')
|
|
243
|
+
}
|
|
244
|
+
bounds.push(parseInt(digits, 10))
|
|
245
|
+
digits = ''
|
|
246
|
+
commas++
|
|
247
|
+
continue
|
|
248
|
+
}
|
|
249
|
+
if ('}' === c) {
|
|
250
|
+
if ('' !== digits) {
|
|
251
|
+
bounds.push(parseInt(digits, 10))
|
|
252
|
+
}
|
|
253
|
+
else if (0 === commas) {
|
|
254
|
+
return bad('{ that does not open a counted quantifier')
|
|
255
|
+
}
|
|
256
|
+
break
|
|
257
|
+
}
|
|
258
|
+
return bad('{ that does not open a counted quantifier')
|
|
259
|
+
}
|
|
260
|
+
if (i >= src.length) {
|
|
261
|
+
return bad('{ that does not open a counted quantifier')
|
|
262
|
+
}
|
|
263
|
+
for (const b of bounds) {
|
|
264
|
+
if (RE_REPEAT_MAX < b) {
|
|
265
|
+
return ['a repeat count above ' + RE_REPEAT_MAX +
|
|
266
|
+
', which RE2 refuses to compile', -1]
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// A descending range (`{5,2}`) is refused by both engines' own
|
|
270
|
+
// compilers, so it needs no rule here.
|
|
271
|
+
return ['', i]
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
function isHexDigit(c: string | undefined): boolean {
|
|
276
|
+
return null != c && (
|
|
277
|
+
('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F'))
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
// normaliseEscape rewrites one `\<n>` into its engine-neutral form.
|
|
282
|
+
// Returns [emitted, why, extra]: `why` non-empty means refused, and
|
|
283
|
+
// `extra` counts source characters consumed beyond the backslash and n.
|
|
284
|
+
function normaliseEscape(
|
|
285
|
+
n: string | undefined, src: string, i: number, inClass: boolean
|
|
286
|
+
): [string, string, number] {
|
|
287
|
+
if (null == n) {
|
|
288
|
+
return ['', 'a trailing backslash', 0]
|
|
289
|
+
}
|
|
290
|
+
if ('1' <= n && n <= '9') {
|
|
291
|
+
return ['', 'a backreference (\\' + n + '): RE2 has no equivalent, and a' +
|
|
292
|
+
' pattern with one is not a regular expression', 0]
|
|
293
|
+
}
|
|
294
|
+
if ('k' === n) {
|
|
295
|
+
return ['', 'a named backreference (\\k): RE2 has no equivalent', 0]
|
|
296
|
+
}
|
|
297
|
+
if ('u' === n) {
|
|
298
|
+
return ['', 'a \\u escape, which RE2 spells \\x{...}: write the character' +
|
|
299
|
+
' itself, or \\xHH for a byte', 0]
|
|
300
|
+
}
|
|
301
|
+
if ('p' === n || 'P' === n) {
|
|
302
|
+
return ['', 'a Unicode class (\\' + n + '), which JavaScript reads as a' +
|
|
303
|
+
' literal "' + n + '" without a flag Aontu does not set', 0]
|
|
304
|
+
}
|
|
305
|
+
if ('Z' === n) {
|
|
306
|
+
return ['', '\\Z, which RE2 does not accept and JavaScript reads as a' +
|
|
307
|
+
' literal "Z": write $ for end of text', 0]
|
|
308
|
+
}
|
|
309
|
+
if ('x' === n) {
|
|
310
|
+
if ('{' === src[i + 2]) {
|
|
311
|
+
return ['', 'a \\x{...} escape, which JavaScript spells \\u: write the' +
|
|
312
|
+
' character itself', 0]
|
|
313
|
+
}
|
|
314
|
+
if (!isHexDigit(src[i + 2]) || !isHexDigit(src[i + 3])) {
|
|
315
|
+
return ['', 'an \\x escape without two hex digits', 0]
|
|
316
|
+
}
|
|
317
|
+
return ['\\x' + src[i + 2] + src[i + 3], '', 2]
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// The abbreviations, rewritten to Aontu's definitions. Inside a class
|
|
321
|
+
// the expansion splices without its brackets (`[\dx]` -> `[0-9x]`).
|
|
322
|
+
if ('d' === n || 'w' === n || 's' === n) {
|
|
323
|
+
const set = 'd' === n ? RE_CLASS_DIGIT :
|
|
324
|
+
'w' === n ? RE_CLASS_WORD : RE_CLASS_SPACE
|
|
325
|
+
return [inClass ? set : '[' + set + ']', '', 0]
|
|
326
|
+
}
|
|
327
|
+
if ('D' === n || 'W' === n || 'S' === n) {
|
|
328
|
+
if (inClass) {
|
|
329
|
+
// `[^...]` cannot be spliced into an enclosing class: the negation
|
|
330
|
+
// would apply to the whole class rather than this member.
|
|
331
|
+
return ['', 'a negated abbreviation (\\' + n + ') inside a character' +
|
|
332
|
+
' class, which cannot be expanded in place: write the characters out', 0]
|
|
333
|
+
}
|
|
334
|
+
const set = 'D' === n ? RE_CLASS_DIGIT :
|
|
335
|
+
'W' === n ? RE_CLASS_WORD : RE_CLASS_SPACE
|
|
336
|
+
return ['[^' + set + ']', '', 0]
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Anchors. `\A`/`\z` are RE2 spellings that JavaScript reads as
|
|
340
|
+
// literals, so they are rewritten rather than refused. Inside a class
|
|
341
|
+
// an anchor is meaningless, and `[\b]` is a BACKSPACE in JavaScript.
|
|
342
|
+
if ('A' === n || 'z' === n || 'b' === n || 'B' === n) {
|
|
343
|
+
if (inClass) {
|
|
344
|
+
return ['', '\\' + n + ' inside a character class, where the two' +
|
|
345
|
+
' engines do not agree what it means', 0]
|
|
346
|
+
}
|
|
347
|
+
return ['A' === n ? '^' : 'z' === n ? '$' : '\\' + n, '', 0]
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if ('-' === n) {
|
|
351
|
+
return inClass ? ['\\-', '', 0] :
|
|
352
|
+
['', '\\- outside a character class: it is a range separator inside' +
|
|
353
|
+
' one and a syntax error outside one (write a bare -)', 0]
|
|
354
|
+
}
|
|
355
|
+
if (RE_ESCAPE_PASS.includes(n) || RE_ESCAPE_PUNCT.includes(n)) {
|
|
356
|
+
return ['\\' + n, '', 0]
|
|
357
|
+
}
|
|
358
|
+
return ['', '\\' + n + ', an escape whose meaning the two engines do not' +
|
|
359
|
+
' share', 0]
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
// normaliseRe rewrites a pattern into the engine-neutral subset.
|
|
364
|
+
// Returns [normalised, why]: a non-empty `why` means the pattern is
|
|
365
|
+
// outside the subset and names the construct.
|
|
366
|
+
function normaliseRe(src: string): [string, string] {
|
|
367
|
+
let inClass = false
|
|
368
|
+
const out: string[] = []
|
|
369
|
+
|
|
370
|
+
// One frame per open group, recording whether it contains a quantifier
|
|
371
|
+
// or an alternation. A group carrying either may not itself be
|
|
372
|
+
// quantified; containment is transitive, so a frame hands its flags up
|
|
373
|
+
// to its parent when it closes.
|
|
374
|
+
const groups: { q: boolean, alt: boolean }[] = []
|
|
375
|
+
const mark = (k: 'q' | 'alt') => {
|
|
376
|
+
if (0 < groups.length) {
|
|
377
|
+
groups[groups.length - 1][k] = true
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Where the counted quantifier validated below closes, so its own
|
|
382
|
+
// `}` is told apart from a stray one; and whether the atom just
|
|
383
|
+
// emitted was `^` or `$`, which cannot be quantified.
|
|
384
|
+
let repeatEnd = -1
|
|
385
|
+
let anchorPrev = false
|
|
386
|
+
|
|
387
|
+
for (let i = 0; i < src.length; i++) {
|
|
388
|
+
const c = src[i]
|
|
389
|
+
const afterAnchor = anchorPrev
|
|
390
|
+
anchorPrev = false
|
|
391
|
+
|
|
392
|
+
if ('\\' === c) {
|
|
393
|
+
const [emit, why, extra] = normaliseEscape(src[i + 1], src, i, inClass)
|
|
394
|
+
if ('' !== why) {
|
|
395
|
+
return ['', why]
|
|
396
|
+
}
|
|
397
|
+
out.push(emit)
|
|
398
|
+
// `\b` AND `\B` ARE ASSERTIONS TOO, and quantifying one is the
|
|
399
|
+
// same disagreement `^{1}` is: JavaScript under `u` calls it a
|
|
400
|
+
// syntax error, RE2 quantifies the assertion happily. The rule
|
|
401
|
+
// said "`\b` and `\B` quantify identically in both and are left
|
|
402
|
+
// alone" and that was measured wrong -- `re("\\b{1}x")` is
|
|
403
|
+
// `constraint_pattern` in TypeScript and an accepted schema in
|
|
404
|
+
// Go. Only OUTSIDE a class: inside one, `\b` is a backspace in
|
|
405
|
+
// both engines and repeats like any other character.
|
|
406
|
+
if (!inClass && ('b' === src[i + 1] || 'B' === src[i + 1])) {
|
|
407
|
+
anchorPrev = true
|
|
408
|
+
}
|
|
409
|
+
i += 1 + extra
|
|
410
|
+
continue
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// A POSIX class opener, anywhere: the form lives inside an ordinary
|
|
414
|
+
// class (`[[:alpha:]]`), and refusing it everywhere is one rule
|
|
415
|
+
// rather than two.
|
|
416
|
+
if ('[' === c && ':' === src[i + 1]) {
|
|
417
|
+
return ['', 'a POSIX class ([:...:]), which JavaScript does not have']
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (inClass) {
|
|
421
|
+
if (']' === c) {
|
|
422
|
+
inClass = false
|
|
423
|
+
}
|
|
424
|
+
out.push(c)
|
|
425
|
+
continue
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if ('[' === c) {
|
|
429
|
+
// `[]` is a never-matching class in JavaScript and a parse error in
|
|
430
|
+
// RE2; `[^]` is the same disagreement one character along.
|
|
431
|
+
const first = '^' === src[i + 1] ? src[i + 2] : src[i + 1]
|
|
432
|
+
if (']' === first) {
|
|
433
|
+
return ['', 'an empty character class, which RE2 refuses']
|
|
434
|
+
}
|
|
435
|
+
inClass = true
|
|
436
|
+
out.push(c)
|
|
437
|
+
continue
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if ('.' === c) {
|
|
441
|
+
out.push('[^\\n]')
|
|
442
|
+
continue
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if ('(' === c) {
|
|
446
|
+
if ('?' === src[i + 1]) {
|
|
447
|
+
if (':' !== src[i + 2]) {
|
|
448
|
+
return ['', 'a (?...) group other than the non-capturing (?:']
|
|
449
|
+
}
|
|
450
|
+
out.push('(?:')
|
|
451
|
+
i += 2
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
out.push(c)
|
|
455
|
+
}
|
|
456
|
+
groups.push({ q: false, alt: false })
|
|
457
|
+
continue
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (')' === c) {
|
|
461
|
+
const g = groups.pop()
|
|
462
|
+
if (null == g) {
|
|
463
|
+
return ['', 'an unbalanced group']
|
|
464
|
+
}
|
|
465
|
+
const nx = src[i + 1]
|
|
466
|
+
const quantified = '*' === nx || '+' === nx || '?' === nx || '{' === nx
|
|
467
|
+
if (quantified && (g.q || g.alt)) {
|
|
468
|
+
return ['', 'a quantifier applied to a group containing ' +
|
|
469
|
+
(g.q ? 'another quantifier' : 'an alternation') +
|
|
470
|
+
', which backtracks exponentially in JavaScript']
|
|
471
|
+
}
|
|
472
|
+
if (g.q) mark('q')
|
|
473
|
+
if (g.alt) mark('alt')
|
|
474
|
+
out.push(c)
|
|
475
|
+
continue
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if ('|' === c) {
|
|
479
|
+
mark('alt')
|
|
480
|
+
out.push(c)
|
|
481
|
+
continue
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if ('*' === c || '+' === c || '?' === c || '{' === c) {
|
|
485
|
+
// Nothing to repeat. JavaScript under the `u` flag makes this a
|
|
486
|
+
// SYNTAX ERROR, where RE2 quantifies the assertion happily and
|
|
487
|
+
// matches, so `re("^{1}")` was `constraint_pattern` in
|
|
488
|
+
// TypeScript and an accepted schema in Go. The four assertions
|
|
489
|
+
// behave alike here -- `^`, `$`, `\b` and `\B` -- so the rule
|
|
490
|
+
// names all four.
|
|
491
|
+
if (afterAnchor) {
|
|
492
|
+
return ['', 'a quantifier applied to `^`, `$`, `\\b` or ' +
|
|
493
|
+
'`\\B`, which has nothing to repeat']
|
|
494
|
+
}
|
|
495
|
+
if ('{' === c) {
|
|
496
|
+
const [why, end] = repeatWhy(src, i)
|
|
497
|
+
if ('' !== why) {
|
|
498
|
+
return ['', why]
|
|
499
|
+
}
|
|
500
|
+
repeatEnd = end
|
|
501
|
+
}
|
|
502
|
+
mark('q')
|
|
503
|
+
out.push(c)
|
|
504
|
+
continue
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// A `}` that closes no counted quantifier: JavaScript under `u`
|
|
508
|
+
// refuses it as a lone quantifier bracket, RE2 reads it as a
|
|
509
|
+
// literal. Written escaped inside a class (`[}]`) it is a literal
|
|
510
|
+
// in both, which is the spelling that survives.
|
|
511
|
+
if ('}' === c) {
|
|
512
|
+
if (i !== repeatEnd) {
|
|
513
|
+
return ['', 'a `}` that closes no counted quantifier, which ' +
|
|
514
|
+
'the two engines do not read the same way']
|
|
515
|
+
}
|
|
516
|
+
out.push(c)
|
|
517
|
+
continue
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
out.push(c)
|
|
521
|
+
anchorPrev = ('^' === c || '$' === c)
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (inClass) {
|
|
525
|
+
return ['', 'an unterminated character class']
|
|
526
|
+
}
|
|
527
|
+
if (0 < groups.length) {
|
|
528
|
+
return ['', 'an unclosed group']
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return [out.join(''), '']
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
|
|
90
535
|
// True for a scalar Val the algebra can order: a numeric leaf or a
|
|
91
536
|
// string. (Booleans and null have no order and no bounds.)
|
|
92
537
|
function numericLeaf(v: any): boolean {
|
|
@@ -128,6 +573,27 @@ function leafMarker(v: any): any {
|
|
|
128
573
|
}
|
|
129
574
|
|
|
130
575
|
|
|
576
|
+
// Conjunct sort order for an atom that must see the WHOLE value.
|
|
577
|
+
// Every other value sorts below the container default (99999), so such
|
|
578
|
+
// an atom is the LAST term to fold: `a:length(2) a:{x:1} a:{y:2}` must
|
|
579
|
+
// count the MERGED map, and a constraint that folded at 50000 would
|
|
580
|
+
// count `{x:1}` alone and refuse the layering that is the whole point
|
|
581
|
+
// of the language.
|
|
582
|
+
//
|
|
583
|
+
// The order atoms keep the low slot: `min(2) & 1 & 2` may decide as
|
|
584
|
+
// soon as it sees a scalar, because meeting more scalars can only
|
|
585
|
+
// narrow. Meeting more containers GROWS the member set, which is why
|
|
586
|
+
// the two orders differ.
|
|
587
|
+
//
|
|
588
|
+
// Three atoms are late: `length` and `unique` (they count members), and
|
|
589
|
+
// `must` (an evaluate-only check against the finished value).
|
|
590
|
+
const LATE_CJO = 150000
|
|
591
|
+
|
|
592
|
+
function lateAtom(atom: string): boolean {
|
|
593
|
+
return 'length' === atom || 'unique' === atom || 'must' === atom
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
|
|
131
597
|
class ConstraintVal extends FeatureVal {
|
|
132
598
|
isConstraint = true
|
|
133
599
|
cjo = 50000
|
|
@@ -137,7 +603,18 @@ class ConstraintVal extends FeatureVal {
|
|
|
137
603
|
lo?: Bound
|
|
138
604
|
hi?: Bound
|
|
139
605
|
neqs: any[] = []
|
|
606
|
+
res: ReAtom[] = []
|
|
607
|
+
count?: ConstraintState
|
|
608
|
+
uniq = false
|
|
609
|
+
uniqBy: string[] = []
|
|
610
|
+
musts: MustAtom[] = []
|
|
611
|
+
// An atom whose arguments have not settled yet (G1 phase 4). Held
|
|
612
|
+
// until unify has a ctx to resolve them through; never present on a
|
|
613
|
+
// residual.
|
|
614
|
+
pending?: { atom: string, args: any[] }
|
|
615
|
+
clash?: boolean
|
|
140
616
|
invalid?: string
|
|
617
|
+
invalidWhy?: string
|
|
141
618
|
|
|
142
619
|
constructor(
|
|
143
620
|
spec: ValSpec & { atom?: string, state?: ConstraintState },
|
|
@@ -151,39 +628,118 @@ class ConstraintVal extends FeatureVal {
|
|
|
151
628
|
this.lo = spec.state.lo
|
|
152
629
|
this.hi = spec.state.hi
|
|
153
630
|
this.neqs = spec.state.neqs
|
|
631
|
+
// A state built by an embedder (or by a per-port test) may predate
|
|
632
|
+
// the pattern field; an absent one means "no patterns", not undefined.
|
|
633
|
+
this.res = spec.state.res ?? []
|
|
634
|
+
this.count = spec.state.count
|
|
635
|
+
this.uniq = spec.state.uniq ?? false
|
|
636
|
+
this.uniqBy = spec.state.uniqBy ?? []
|
|
637
|
+
this.musts = spec.state.musts ?? []
|
|
154
638
|
this.invalid = spec.state.invalid
|
|
155
639
|
}
|
|
156
640
|
else if (spec.atom) {
|
|
157
|
-
|
|
641
|
+
const args = atomArgs(spec.atom, (spec.peg as any[]) ?? [])
|
|
642
|
+
// An argument that is not yet concrete — a reference, an
|
|
643
|
+
// arithmetic expression, a conjunct of atoms — makes the whole
|
|
644
|
+
// atom PENDING rather than invalid (G1 phase 4). It is resolved
|
|
645
|
+
// in unify, where there is a ctx to resolve through, and the
|
|
646
|
+
// residual is built from the settled arguments. Only a settled
|
|
647
|
+
// argument of the wrong shape is an `invalid-arg`.
|
|
648
|
+
// The effectful-argument refusal happens HERE, on the written
|
|
649
|
+
// form, and not in fromAtom: settling is what runs the effect, so
|
|
650
|
+
// by the time fromAtom sees a settled `move($.b)` the move has
|
|
651
|
+
// already happened and the argument is just its result.
|
|
652
|
+
if ('must' === spec.atom && args.some((a: any) => holdsMove(a))) {
|
|
653
|
+
this.invalid = 'invalid-arg'
|
|
654
|
+
}
|
|
655
|
+
else if (args.some((a: any) => true !== a?.done)) {
|
|
656
|
+
this.pending = { atom: spec.atom, args }
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
this.fromAtom(spec.atom, args)
|
|
660
|
+
}
|
|
158
661
|
}
|
|
159
662
|
|
|
160
|
-
|
|
161
|
-
|
|
663
|
+
if (null != this.count || this.uniq || 0 < this.uniqBy.length ||
|
|
664
|
+
0 < this.musts.length ||
|
|
665
|
+
(null != this.pending && lateAtom(this.pending.atom))) {
|
|
666
|
+
this.cjo = LATE_CJO
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// A residual constraint is stable, like a ScalarKindVal — but a
|
|
670
|
+
// pending atom is not a residual yet, and must be re-entered on
|
|
671
|
+
// later passes until its arguments settle.
|
|
672
|
+
if (null == this.pending) {
|
|
673
|
+
this.dc = DONE
|
|
674
|
+
}
|
|
675
|
+
else {
|
|
676
|
+
this.notdone()
|
|
677
|
+
}
|
|
162
678
|
}
|
|
163
679
|
|
|
164
680
|
|
|
165
|
-
// Normalise one atom call
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
//
|
|
171
|
-
|
|
681
|
+
// Normalise one atom call into state. Every argument here is already
|
|
682
|
+
// SETTLED — the constructor routes an unsettled one to `pending` —
|
|
683
|
+
// so a shape this cannot use is a genuine `invalid-arg`, not a
|
|
684
|
+
// not-yet.
|
|
685
|
+
private fromAtom(atom: string, args: any[]): void {
|
|
686
|
+
// Mark the residual invalid. It answers NOTHING: every caller
|
|
687
|
+
// spells its exit `return bad(...)` and none reads a value, so a
|
|
688
|
+
// return type would be a value nobody consumes -- and a routine
|
|
689
|
+
// that answers on some paths and not others is the shape a reader
|
|
690
|
+
// (and a static analyser) has to stop and check.
|
|
691
|
+
const bad = (why: string): void => {
|
|
172
692
|
this.invalid = why
|
|
173
|
-
return true
|
|
174
693
|
}
|
|
175
694
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
695
|
+
// `unique()` is a property of the container -- not a comparison
|
|
696
|
+
// against a value -- so with no argument it says the members are
|
|
697
|
+
// pairwise DISTINCT. THE ONE ARGUMENT IS A PROJECTOR (the review's
|
|
698
|
+
// finding I: "unique()-by-field is reserved but absent", and the
|
|
699
|
+
// arity was reserved for exactly this): `unique(port)` says no two
|
|
700
|
+
// members share a `port`, which is how "no two services share a
|
|
701
|
+
// port" and "event ids are unique" are said.
|
|
702
|
+
if ('unique' === atom) {
|
|
703
|
+
if (0 === args.length) {
|
|
704
|
+
this.uniq = true
|
|
705
|
+
return
|
|
183
706
|
}
|
|
184
|
-
|
|
185
|
-
|
|
707
|
+
if (1 !== args.length || !stringLeaf(args[0])) {
|
|
708
|
+
return bad('invalid-arg')
|
|
186
709
|
}
|
|
710
|
+
this.uniqBy = [args[0].peg]
|
|
711
|
+
return
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// `must(c, msg)` is Band B: an evaluate-only check against the
|
|
715
|
+
// finished value, carrying the author's own message. It is KEPT,
|
|
716
|
+
// never simplified and never consulted for emptiness or
|
|
717
|
+
// subsumption — Band B is opaque by construction, which is exactly
|
|
718
|
+
// what makes it the honest channel for a rule the algebra cannot
|
|
719
|
+
// reason about (docs/reference-language.md, "Band B: `must`").
|
|
720
|
+
if ('must' === atom) {
|
|
721
|
+
if (2 !== args.length) {
|
|
722
|
+
return bad('arg')
|
|
723
|
+
}
|
|
724
|
+
if (!stringLeaf(args[1])) {
|
|
725
|
+
return bad('invalid-arg')
|
|
726
|
+
}
|
|
727
|
+
// A check carrying a nil can never be satisfied, so it is refused
|
|
728
|
+
// as an ARGUMENT rather than left to fail against every value with
|
|
729
|
+
// the author's message attached -- which would blame the data for
|
|
730
|
+
// a mistake in the check. `must([1-x],m)` is the reachable case: a
|
|
731
|
+
// degenerate expression leaves a nil inside the written list.
|
|
732
|
+
if (holdsNil(args[0])) {
|
|
733
|
+
return bad('invalid-arg')
|
|
734
|
+
}
|
|
735
|
+
// (An effectful argument is refused at construction, in the
|
|
736
|
+
// constructor: by the time this arm sees a settled `move($.b)`
|
|
737
|
+
// the move has already run.)
|
|
738
|
+
this.musts = [{ v: args[0], msg: args[1] }]
|
|
739
|
+
return
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
if ('neq' === atom) {
|
|
187
743
|
if (0 === args.length) {
|
|
188
744
|
return bad('arg')
|
|
189
745
|
}
|
|
@@ -204,6 +760,74 @@ class ConstraintVal extends FeatureVal {
|
|
|
204
760
|
return bad('arg')
|
|
205
761
|
}
|
|
206
762
|
const a = args[0]
|
|
763
|
+
|
|
764
|
+
// `re` is the one atom whose argument is not an ORDER point: a
|
|
765
|
+
// pattern is a membership test, so it takes the string domain
|
|
766
|
+
// outright rather than inferring a domain from the argument's leaf.
|
|
767
|
+
if ('re' === atom) {
|
|
768
|
+
if (!stringLeaf(a)) {
|
|
769
|
+
return bad('invalid-arg')
|
|
770
|
+
}
|
|
771
|
+
const src = a.peg as string
|
|
772
|
+
// Normalise BEFORE compiling: the host engines only ever see a
|
|
773
|
+
// pattern that cannot be read two ways (ADR-003).
|
|
774
|
+
const [norm, why] = normaliseRe(src)
|
|
775
|
+
if ('' !== why) {
|
|
776
|
+
this.invalidWhy = why
|
|
777
|
+
return bad('constraint_pattern')
|
|
778
|
+
}
|
|
779
|
+
let re: RegExp
|
|
780
|
+
try {
|
|
781
|
+
// The `u` flag is REQUIRED for parity, not an optimisation.
|
|
782
|
+
// Without it JavaScript matches UTF-16 code units while RE2
|
|
783
|
+
// matches code points, so `re("^.$")` accepted U+1D11E in Go
|
|
784
|
+
// and refused it in TypeScript (and `^..$` did the reverse).
|
|
785
|
+
// With it, `.` and every quantifier count code points in both.
|
|
786
|
+
// It also makes JavaScript refuse the identity escapes this
|
|
787
|
+
// scanner rejects by hand, which is defence in depth rather
|
|
788
|
+
// than a substitute: RE2 accepts some of them, so the scanner
|
|
789
|
+
// is what keeps the two ports agreeing.
|
|
790
|
+
re = new RegExp(norm, 'u')
|
|
791
|
+
}
|
|
792
|
+
catch (e: any) {
|
|
793
|
+
// The host engine refuses what the subset scanner passed — a
|
|
794
|
+
// malformed quantifier, an unbalanced group. Same refusal under
|
|
795
|
+
// the same code: the author gets one rule, not two. The message
|
|
796
|
+
// is the host's, so it is NOT pinned by a shared row; the code
|
|
797
|
+
// and the located frame are.
|
|
798
|
+
this.invalidWhy = 'not a valid pattern'
|
|
799
|
+
return bad('constraint_pattern')
|
|
800
|
+
}
|
|
801
|
+
this.domain = 'string'
|
|
802
|
+
this.res = [{ v: a, src, norm, re }]
|
|
803
|
+
return
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// `length` is the other non-ORDER atom: its argument constrains the
|
|
807
|
+
// COUNT, not the value, so it is itself a residual over the integer
|
|
808
|
+
// domain (docs/reference-language.md, "`length` semantics"). It is
|
|
809
|
+
// resolved HERE, at construction, by walking the written argument
|
|
810
|
+
// rather than by unifying it: the func-paren handler builds atoms
|
|
811
|
+
// without an AontuContext, so there is nothing to fold a nested
|
|
812
|
+
// conjunct through. Walking is enough because a length argument is by
|
|
813
|
+
// definition a meet of concrete Band A atoms; anything else (a
|
|
814
|
+
// reference, an expression) is refused rather than deferred, the
|
|
815
|
+
// same discipline min/max apply to their own arguments.
|
|
816
|
+
if ('length' === atom) {
|
|
817
|
+
const arg = countArgState(a)
|
|
818
|
+
if (null == arg) {
|
|
819
|
+
return bad('invalid-arg')
|
|
820
|
+
}
|
|
821
|
+
const inner = meetCount(countBase(), arg)
|
|
822
|
+
this.count = inner
|
|
823
|
+
// `length(min(5)&max(3))` is unsatisfiable with no peer in sight, so
|
|
824
|
+
// it is refused at composition time like any other empty meet.
|
|
825
|
+
if (stateEmpty(inner)) {
|
|
826
|
+
return bad('constraint')
|
|
827
|
+
}
|
|
828
|
+
return
|
|
829
|
+
}
|
|
830
|
+
|
|
207
831
|
const domain = numericLeaf(a) ? 'number' : stringLeaf(a) ? 'string' : undefined
|
|
208
832
|
if (null == domain) {
|
|
209
833
|
return bad('invalid-arg')
|
|
@@ -228,8 +852,12 @@ class ConstraintVal extends FeatureVal {
|
|
|
228
852
|
// residual is stable and the ladder is total.
|
|
229
853
|
let out: Val
|
|
230
854
|
|
|
231
|
-
if (null != this.
|
|
232
|
-
out =
|
|
855
|
+
if (null != this.pending) {
|
|
856
|
+
out = this.settle(peer, ctx)
|
|
857
|
+
}
|
|
858
|
+
else if (null != this.invalid) {
|
|
859
|
+
out = makeNilErr(ctx, this.invalid, this, undefined, 'constrain',
|
|
860
|
+
null == this.invalidWhy ? undefined : { reason: this.invalidWhy })
|
|
233
861
|
}
|
|
234
862
|
else if (null == peer || (peer as any).isTop) {
|
|
235
863
|
out = this
|
|
@@ -246,9 +874,20 @@ class ConstraintVal extends FeatureVal {
|
|
|
246
874
|
else if ((peer as any).isScalar) {
|
|
247
875
|
out = this.admit(peer, ctx)
|
|
248
876
|
}
|
|
877
|
+
else if ((peer as any).isMap || (peer as any).isList) {
|
|
878
|
+
out = this.admitContainer(peer, ctx)
|
|
879
|
+
}
|
|
880
|
+
/* node:coverage ignore next 12 */
|
|
249
881
|
else {
|
|
250
|
-
//
|
|
251
|
-
//
|
|
882
|
+
// Every other shape: no order, no membership, nothing to count —
|
|
883
|
+
// a conflict of the constraint family. The ladder above is total
|
|
884
|
+
// in practice: every remaining Val kind either sorts BELOW a
|
|
885
|
+
// constraint in a conjunct (conjunct, disjunct, pref, ref) and so
|
|
886
|
+
// drives the meet from its own side, or resolves to a
|
|
887
|
+
// scalar/container before a constraint sees it (func, op, var,
|
|
888
|
+
// expect). The arm is kept because "in practice" depends on the
|
|
889
|
+
// cjo table, and a future value class would land here rather than
|
|
890
|
+
// falling out of unify with no result.
|
|
252
891
|
out = this.fail(ctx, peer)
|
|
253
892
|
}
|
|
254
893
|
|
|
@@ -258,61 +897,374 @@ class ConstraintVal extends FeatureVal {
|
|
|
258
897
|
}
|
|
259
898
|
|
|
260
899
|
|
|
900
|
+
// Resolve a pending atom's arguments and, once they have all settled,
|
|
901
|
+
// become the residual they describe (G1 phase 4).
|
|
902
|
+
//
|
|
903
|
+
// This is the residuation discipline FuncBaseVal already follows for
|
|
904
|
+
// its own operands: push each unsettled argument one step by unifying
|
|
905
|
+
// it with `top`, and if any is still moving, mark not-done and defer
|
|
906
|
+
// — either as this same pending atom (against a `top` peer) or
|
|
907
|
+
// wrapped with the peer in a conjunct the next pass will re-enter.
|
|
908
|
+
// Nothing is decided from a half-resolved argument, which is what
|
|
909
|
+
// keeps `min($.lo)` from reporting a conflict against a bound that
|
|
910
|
+
// has not arrived yet.
|
|
911
|
+
private settle(peer: Val, ctx: AontuContext): Val {
|
|
912
|
+
const TOP = top()
|
|
913
|
+
const pend = this.pending as { atom: string, args: any[] }
|
|
914
|
+
|
|
915
|
+
let settled = true
|
|
916
|
+
const args: any[] = []
|
|
917
|
+
for (const arg of pend.args) {
|
|
918
|
+
let next = arg
|
|
919
|
+
if (true !== arg?.done) {
|
|
920
|
+
// Charged to the depth budget: this recurses without going
|
|
921
|
+
// through `unite`, so the counter would otherwise stay flat
|
|
922
|
+
// while the stack grows (the rule FuncBaseVal follows).
|
|
923
|
+
next = withDepth(ctx, arg, TOP, () => arg.unify(TOP, ctx))
|
|
924
|
+
}
|
|
925
|
+
settled = settled && true === next?.done
|
|
926
|
+
args.push(next)
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
if (settled) {
|
|
930
|
+
// Build the residual the atom always meant, at this atom's site,
|
|
931
|
+
// then let the ordinary ladder meet it with the peer.
|
|
932
|
+
const built = new ConstraintVal({ peg: args, atom: pend.atom }, ctx)
|
|
933
|
+
built.path = this.path
|
|
934
|
+
built.site.row = this.site.row
|
|
935
|
+
built.site.col = this.site.col
|
|
936
|
+
built.site.url = this.site.url
|
|
937
|
+
propagateMarks(this, built)
|
|
938
|
+
return built.unify(peer, ctx)
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
this.notdone()
|
|
942
|
+
|
|
943
|
+
// A fresh pending atom carrying the partially-resolved arguments, so
|
|
944
|
+
// the next pass starts from the progress this one made rather than
|
|
945
|
+
// re-resolving from source.
|
|
946
|
+
const again = new ConstraintVal({ peg: args, atom: pend.atom }, ctx)
|
|
947
|
+
again.path = this.path
|
|
948
|
+
again.site.row = this.site.row
|
|
949
|
+
again.site.col = this.site.col
|
|
950
|
+
again.site.url = this.site.url
|
|
951
|
+
propagateMarks(this, again)
|
|
952
|
+
|
|
953
|
+
if (null == peer || (peer as any).isTop) {
|
|
954
|
+
return again
|
|
955
|
+
}
|
|
956
|
+
// No nil-peer arm: `unite` returns a nil operand before dispatching
|
|
957
|
+
// to any Val's unify (ts/src/unify.ts), so a nil never reaches here
|
|
958
|
+
// — and were one to, the conjunct below folds to it unchanged.
|
|
959
|
+
return new ConjunctVal({ peg: [again, peer] }, ctx)
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
|
|
261
963
|
// Membership: the peer scalar passes every part of the residual, or
|
|
262
964
|
// the whole meet is a located conflict.
|
|
263
965
|
private admit(peer: any, ctx: AontuContext): Val {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
if (domainOf !== this.domain) {
|
|
966
|
+
// No scalar has members, so a `unique()` residual admits none --
|
|
967
|
+
// and neither does a `unique(k)` one, for the same reason.
|
|
968
|
+
if (this.uniq || 0 < this.uniqBy.length) {
|
|
268
969
|
return this.fail(ctx, peer)
|
|
269
970
|
}
|
|
270
|
-
if (
|
|
971
|
+
if (!stateAdmits(this, peer)) {
|
|
271
972
|
return this.fail(ctx, peer)
|
|
272
973
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
974
|
+
if (null != this.count) {
|
|
975
|
+
// Only a string among the scalars has a length, and it is counted
|
|
976
|
+
// in CODE POINTS -- not UTF-16 units (this host's native count)
|
|
977
|
+
// and not bytes (Go's). Iterating a string yields code points.
|
|
978
|
+
if (!stringLeaf(peer)) {
|
|
277
979
|
return this.fail(ctx, peer)
|
|
278
980
|
}
|
|
279
|
-
|
|
280
|
-
if (null != this.hi) {
|
|
281
|
-
const c = cmpVal(d, peer, this.hi.v)
|
|
282
|
-
if (c > 0 || (0 === c && this.hi.open)) {
|
|
981
|
+
if (!stateAdmits(this.count, countVal([...peer.peg].length))) {
|
|
283
982
|
return this.fail(ctx, peer)
|
|
284
983
|
}
|
|
285
984
|
}
|
|
286
|
-
|
|
287
|
-
|
|
985
|
+
// A SCALAR HAS NO MEMBERS to accumulate, so its musts are decided
|
|
986
|
+
// here and never residuate: the final reading is the only reading
|
|
987
|
+
// a scalar has.
|
|
988
|
+
const bad = this.checkMusts(peer, ctx, true)
|
|
989
|
+
if (null != bad) {
|
|
990
|
+
return bad
|
|
991
|
+
}
|
|
992
|
+
return peer
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
|
|
996
|
+
// Band B, applied to a finished value. Each check is a plain
|
|
997
|
+
// unification against a CLONE of the peer: `must` reports, it never
|
|
998
|
+
// contributes: whatever the check would have added to the value is
|
|
999
|
+
// discarded, and `peer` is returned untouched by the callers.
|
|
1000
|
+
//
|
|
1001
|
+
// The trial runs in an isolated collect context so a failing check
|
|
1002
|
+
// leaves nothing on the caller's error list — only the located nil
|
|
1003
|
+
// this returns, carrying the author's own message.
|
|
1004
|
+
// `final` is the generation-time reading (see settleContainer): a
|
|
1005
|
+
// must whose own check is a SIZING atom inherits that atom's
|
|
1006
|
+
// provisionality, because `must(length(2), …)` against a map that may
|
|
1007
|
+
// still gain members has not failed yet -- it has not been answered.
|
|
1008
|
+
// Without this the must would decide against whatever the container
|
|
1009
|
+
// held when it first settled, which is the very defect the sizing
|
|
1010
|
+
// atoms were fixed for (use-cases/BUGS.md §16, §17).
|
|
1011
|
+
private checkMusts(
|
|
1012
|
+
peer: any, ctx: AontuContext, final?: boolean): Val | undefined {
|
|
1013
|
+
for (const m of this.musts) {
|
|
1014
|
+
const trial = ctx.clone({ err: [], collect: true })
|
|
1015
|
+
let got: any = unite(trial, m.v.clone(trial), peer.clone(trial), 'must')
|
|
1016
|
+
// A must whose check is a SIZING atom answers a residue rather
|
|
1017
|
+
// than a verdict, because the atom is waiting for members that
|
|
1018
|
+
// may still arrive. Before generation that residue IS the answer
|
|
1019
|
+
// -- the must has not failed, it has not been asked yet -- and at
|
|
1020
|
+
// generation it is settled, because nothing more can arrive.
|
|
1021
|
+
const residue = sizingResidue(got)
|
|
1022
|
+
if (undefined !== residue) {
|
|
1023
|
+
if (true !== final) {
|
|
1024
|
+
continue
|
|
1025
|
+
}
|
|
1026
|
+
got = residue.con.settleContainer(residue.bag, trial)
|
|
1027
|
+
}
|
|
1028
|
+
if (true === (got as any)?.isNil || 0 < trial.err.length) {
|
|
1029
|
+
return makeNilErr(ctx, 'must', this, peer, undefined, {
|
|
1030
|
+
message: m.msg.peg,
|
|
1031
|
+
expected: m.v.canon,
|
|
1032
|
+
actual: peer.canon,
|
|
1033
|
+
})
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
return undefined
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
|
|
1040
|
+
// THE FINAL READING of a sizing atom over a container: the same
|
|
1041
|
+
// checks admitContainer makes, with the provisional ones taken as
|
|
1042
|
+
// decided. Called from ConjunctVal.gen, which is where no more
|
|
1043
|
+
// members can arrive. Returns the container (to generate) or the
|
|
1044
|
+
// constraint's own nil (to report).
|
|
1045
|
+
settleContainer(peer: any, ctx: AontuContext): Val {
|
|
1046
|
+
return this.admitContainer(peer, ctx, true)
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
|
|
1050
|
+
// Membership for a container peer. Only the SIZING atoms have anything
|
|
1051
|
+
// to say about a map or a list; every other atom is scalar-domain and
|
|
1052
|
+
// refuses one.
|
|
1053
|
+
//
|
|
1054
|
+
// The members that count are the members that GENERATE
|
|
1055
|
+
// (docs/reference-language.md, "`length` semantics"), and rather than
|
|
1056
|
+
// mirror generation's filter — type/hide marks, optional keys that
|
|
1057
|
+
// drop, empty optional values — this asks generation itself, in an
|
|
1058
|
+
// isolated collect context so nothing leaks into the caller's errors.
|
|
1059
|
+
// A mirror would be a second copy of a filter that has already grown
|
|
1060
|
+
// subtle, free to drift from it; asking is correct by construction.
|
|
1061
|
+
private admitContainer(
|
|
1062
|
+
peer: any, ctx: AontuContext, final?: boolean): Val {
|
|
1063
|
+
// A scalar-domain residual has no reading over a container.
|
|
1064
|
+
if (null != this.domain) {
|
|
1065
|
+
return this.fail(ctx, peer)
|
|
1066
|
+
}
|
|
1067
|
+
// Not yet settled: the container, or an optional child, may still
|
|
1068
|
+
// resolve, so the member set is not final. Defer rather than decide
|
|
1069
|
+
// — the same discipline OpBaseVal follows for a non-concrete operand.
|
|
1070
|
+
if (!containerSettled(peer)) {
|
|
1071
|
+
this.dc = 0
|
|
1072
|
+
return new ConjunctVal({ peg: [this, peer] }, ctx)
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
const bad = this.checkMusts(peer, ctx, final)
|
|
1076
|
+
if (null != bad) {
|
|
1077
|
+
return bad
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// A MUST OVER A CONTAINER RESIDUATES with the atoms, for the same
|
|
1081
|
+
// reason (use-cases/BUGS.md §17): `must({t: max(60)}, …)` beside a
|
|
1082
|
+
// `{t: integer}` schema was answered against the schema layer
|
|
1083
|
+
// ALONE, and discharged before the data it was written to judge
|
|
1084
|
+
// ever arrived. It is decided at generation, where the value is
|
|
1085
|
+
// whole -- unless there is nothing else on the constraint AND the
|
|
1086
|
+
// reading is already final.
|
|
1087
|
+
if (!this.uniq && 0 === this.uniqBy.length && null == this.count) {
|
|
1088
|
+
if (true === final || 0 === this.musts.length) {
|
|
1089
|
+
return peer
|
|
1090
|
+
}
|
|
1091
|
+
return this.hold(peer, ctx)
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
const members = emittedMembers(peer, ctx)
|
|
1095
|
+
if (null == members) {
|
|
1096
|
+
// NOT COUNTABLE YET IS NOT A PASS. A container that cannot
|
|
1097
|
+
// generate cannot be counted, and a SCHEMA is exactly that: the
|
|
1098
|
+
// members of `{a: integer}` are types, so nothing is emitted and
|
|
1099
|
+
// nothing can be counted. Discharging the atom here was the §16
|
|
1100
|
+
// defect wearing its other face -- `length(min(2)) & {a: integer}`
|
|
1101
|
+
// dropped its bound while still alone, so the data half of a
|
|
1102
|
+
// `vet` meet was never measured at all and short data vetted
|
|
1103
|
+
// clean against a bound the evaluator enforces.
|
|
1104
|
+
//
|
|
1105
|
+
// At generation the reading IS final: a container that still
|
|
1106
|
+
// cannot generate has its own error, and that error is the one
|
|
1107
|
+
// worth reporting, so it passes through untouched.
|
|
1108
|
+
if (true === final) {
|
|
1109
|
+
return peer
|
|
1110
|
+
}
|
|
1111
|
+
return this.hold(peer, ctx)
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// MONOTONE READINGS ONLY (the review's finding C, use-cases/BUGS.md
|
|
1115
|
+
// §16). Members ACCUMULATE under unification -- a meet adds keys and
|
|
1116
|
+
// never removes them -- so a settled container is settled ON ITS
|
|
1117
|
+
// OWN and not against every value that may still contribute. The
|
|
1118
|
+
// schema half of a `vet` meet, a document that is about to receive
|
|
1119
|
+
// an `@` include, a bag a later `pack` will fill: each settles, and
|
|
1120
|
+
// an atom that decided there decided too early.
|
|
1121
|
+
//
|
|
1122
|
+
// So a verdict is taken only when MORE MEMBERS CANNOT CHANGE IT:
|
|
1123
|
+
// - an upper bound VIOLATED is permanent -> refuse now;
|
|
1124
|
+
// satisfied is provisional -> keep the atom, re-check later.
|
|
1125
|
+
// - a lower bound SATISFIED is permanent -> that part may go;
|
|
1126
|
+
// violated is provisional -> keep the atom (which is why
|
|
1127
|
+
// `length(min(1))` beside a template no longer kills the schema
|
|
1128
|
+
// it was written for).
|
|
1129
|
+
// - a DUPLICATE is permanent -> refuse now; distinctness is
|
|
1130
|
+
// provisional -> keep the atom.
|
|
1131
|
+
// Anything provisional residuates, exactly as an unsettled
|
|
1132
|
+
// container does above, and the two spellings then agree.
|
|
1133
|
+
const count = null == this.count ? undefined : this.count
|
|
1134
|
+
const n = members.length
|
|
1135
|
+
if (null != count) {
|
|
1136
|
+
if (null != count.hi && !stateAdmits({ ...count, lo: undefined },
|
|
1137
|
+
countVal(n))) {
|
|
1138
|
+
return this.fail(ctx, peer)
|
|
1139
|
+
}
|
|
1140
|
+
if (0 < count.neqs.length && !stateAdmits(
|
|
1141
|
+
{ ...count, lo: undefined, hi: undefined }, countVal(n))) {
|
|
1142
|
+
return this.fail(ctx, peer)
|
|
1143
|
+
}
|
|
1144
|
+
// The provisional half, decided only when nothing more can
|
|
1145
|
+
// arrive: a lower bound still short is a refusal at generation
|
|
1146
|
+
// and a residue before it.
|
|
1147
|
+
if (true === final && !stateAdmits(count, countVal(n))) {
|
|
288
1148
|
return this.fail(ctx, peer)
|
|
289
1149
|
}
|
|
290
1150
|
}
|
|
291
|
-
|
|
1151
|
+
|
|
1152
|
+
if (this.uniq) {
|
|
1153
|
+
// Members compare by CANONICAL FORM, which reduces to scalar
|
|
1154
|
+
// identity for scalars (so [1, 1.0] is distinct) and gives
|
|
1155
|
+
// structural equality for container members without a second
|
|
1156
|
+
// rule. A generated value could not express the first: `1` and
|
|
1157
|
+
// `1.0` generate the same JSON number.
|
|
1158
|
+
const seen = new Set<string>()
|
|
1159
|
+
for (const m of members) {
|
|
1160
|
+
const key = m.canon
|
|
1161
|
+
if (seen.has(key)) {
|
|
1162
|
+
return this.fail(ctx, peer)
|
|
1163
|
+
}
|
|
1164
|
+
seen.add(key)
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// ... and the same test per PROJECTED key. A member that has no such
|
|
1169
|
+
// key FAILS the constraint rather than being skipped: distinctness
|
|
1170
|
+
// it cannot be shown to have is distinctness it does not have, and
|
|
1171
|
+
// skipping would let one keyless record hide a duplicate.
|
|
1172
|
+
for (const field of this.uniqBy) {
|
|
1173
|
+
const seen = new Set<string>()
|
|
1174
|
+
for (const m of members) {
|
|
1175
|
+
const at: any = true === (m as any).isMap ?
|
|
1176
|
+
(m as any).peg[field] : undefined
|
|
1177
|
+
if (null == at) {
|
|
1178
|
+
return this.fail(ctx, peer)
|
|
1179
|
+
}
|
|
1180
|
+
const key = at.canon
|
|
1181
|
+
if (seen.has(key)) {
|
|
1182
|
+
return this.fail(ctx, peer)
|
|
1183
|
+
}
|
|
1184
|
+
seen.add(key)
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
// WHAT IS LEFT IS PROVISIONAL, so the atom stays on the value. A
|
|
1189
|
+
// lower bound already met is the one reading that cannot be undone,
|
|
1190
|
+
// and an atom holding nothing else is spent: that is when it goes.
|
|
1191
|
+
const spent = true === final || (0 === this.musts.length &&
|
|
1192
|
+
!this.uniq && 0 === this.uniqBy.length &&
|
|
1193
|
+
(null == count ||
|
|
1194
|
+
(null == count.hi && 0 === count.neqs.length &&
|
|
1195
|
+
stateAdmits(count, countVal(n)))))
|
|
1196
|
+
if (spent) {
|
|
1197
|
+
return peer
|
|
1198
|
+
}
|
|
1199
|
+
// DONE, unlike the unsettled case above. The container HAS settled;
|
|
1200
|
+
// the atom is kept only because a LATER value could still add
|
|
1201
|
+
// members, and a residue that reported itself unresolved would
|
|
1202
|
+
// leave every enclosing value unresolved with it -- a `type()`
|
|
1203
|
+
// waiting on its argument for ever, and use case 09's whole
|
|
1204
|
+
// registry with it.
|
|
1205
|
+
// The ATOM is done too, not just the conjunct. An earlier pass may
|
|
1206
|
+
// have set `dc = 0` on the unsettled branch above, and a conjunct
|
|
1207
|
+
// recomputes its own doneness from its terms on every later meet --
|
|
1208
|
+
// so a stale 0 here would drag the residue, and every value holding
|
|
1209
|
+
// it, back to unresolved for ever.
|
|
1210
|
+
return this.hold(peer, ctx)
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
|
|
1214
|
+
// The atom kept on a value whose reading is still provisional.
|
|
1215
|
+
// DONE, unlike the unsettled container above: the value HAS settled;
|
|
1216
|
+
// the atom is kept only because a LATER value could still add
|
|
1217
|
+
// members, and a residue that reported itself unresolved would leave
|
|
1218
|
+
// every enclosing value unresolved with it -- a `type()` waiting on
|
|
1219
|
+
// its argument for ever, and use case 09's whole registry with it.
|
|
1220
|
+
// The ATOM is done too, not just the conjunct. An earlier pass may
|
|
1221
|
+
// have set `dc = 0` on the unsettled branch, and a conjunct
|
|
1222
|
+
// recomputes its own doneness from its terms on every later meet --
|
|
1223
|
+
// so a stale 0 here would drag the residue, and every value holding
|
|
1224
|
+
// it, back to unresolved for ever.
|
|
1225
|
+
private hold(peer: any, ctx: AontuContext): Val {
|
|
1226
|
+
this.dc = DONE
|
|
1227
|
+
const held: any = new ConjunctVal({ peg: [this, peer] }, ctx)
|
|
1228
|
+
held.dc = DONE
|
|
1229
|
+
return held
|
|
292
1230
|
}
|
|
293
1231
|
|
|
294
1232
|
|
|
295
1233
|
// Meet with a kind: `number` (or `string` on the string domain) is
|
|
296
|
-
// already implied; a numeric LEAF narrows
|
|
297
|
-
// else has an empty intersection with the
|
|
1234
|
+
// already implied by an ORDER atom's argument; a numeric LEAF narrows
|
|
1235
|
+
// the residual; anything else has an empty intersection with the
|
|
1236
|
+
// constraint's domain.
|
|
1237
|
+
//
|
|
1238
|
+
// A sizing residual (`length`, `unique`) has no domain of its own -- a
|
|
1239
|
+
// count says nothing about what is counted -- so a kind here SETS one
|
|
1240
|
+
// rather than merely agreeing with it: `string & length(3)` is a
|
|
1241
|
+
// three-character string, and `number & length(3)` is empty because a
|
|
1242
|
+
// number has no length (stateEmpty decides that, not this).
|
|
298
1243
|
private meetKind(peer: any, ctx: AontuContext): Val {
|
|
299
1244
|
const marker = peer.peg
|
|
1245
|
+
const merged = this.cloneState()
|
|
300
1246
|
|
|
301
|
-
if (Number === marker) {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
1247
|
+
if (Number === marker || String === marker) {
|
|
1248
|
+
const d = Number === marker ? 'number' : 'string'
|
|
1249
|
+
if (d === this.domain) {
|
|
1250
|
+
return this
|
|
1251
|
+
}
|
|
1252
|
+
if (null != this.domain) {
|
|
1253
|
+
return this.fail(ctx, peer)
|
|
1254
|
+
}
|
|
1255
|
+
merged.domain = d
|
|
1256
|
+
return this.finish(merged, ctx, peer)
|
|
306
1257
|
}
|
|
1258
|
+
|
|
307
1259
|
const isLeaf = Integer === marker || Float === marker ||
|
|
308
1260
|
BigInteger === marker || BigDecimal === marker
|
|
309
|
-
if (!isLeaf || '
|
|
1261
|
+
if (!isLeaf || 'string' === this.domain) {
|
|
310
1262
|
return this.fail(ctx, peer)
|
|
311
1263
|
}
|
|
312
1264
|
if (null != this.kind && this.kind !== marker) {
|
|
313
1265
|
return this.fail(ctx, peer)
|
|
314
1266
|
}
|
|
315
|
-
|
|
1267
|
+
merged.domain = 'number'
|
|
316
1268
|
merged.kind = marker
|
|
317
1269
|
return this.finish(merged, ctx, peer)
|
|
318
1270
|
}
|
|
@@ -322,7 +1274,8 @@ class ConstraintVal extends FeatureVal {
|
|
|
322
1274
|
// union, kind union — then the eager emptiness rules.
|
|
323
1275
|
private meetConstraint(peer: ConstraintVal, ctx: AontuContext): Val {
|
|
324
1276
|
if (null != peer.invalid) {
|
|
325
|
-
return makeNilErr(ctx, peer.invalid, peer, undefined, 'constrain'
|
|
1277
|
+
return makeNilErr(ctx, peer.invalid, peer, undefined, 'constrain',
|
|
1278
|
+
null == peer.invalidWhy ? undefined : { reason: peer.invalidWhy })
|
|
326
1279
|
}
|
|
327
1280
|
if (null != this.domain && null != peer.domain && this.domain !== peer.domain) {
|
|
328
1281
|
return this.fail(ctx, peer)
|
|
@@ -338,6 +1291,23 @@ class ConstraintVal extends FeatureVal {
|
|
|
338
1291
|
merged.lo = tighter(d, this.lo, peer.lo, true)
|
|
339
1292
|
merged.hi = tighter(d, this.hi, peer.hi, false)
|
|
340
1293
|
merged.neqs = dedupSorted(d, [...this.neqs, ...peer.neqs])
|
|
1294
|
+
merged.res = dedupSortedRes([...this.res, ...peer.res])
|
|
1295
|
+
// `length(c1) & length(c2)` is `length(c1 & c2)`: the count atom reuses
|
|
1296
|
+
// numeric algebra recursively, over the counts rather than the
|
|
1297
|
+
// values.
|
|
1298
|
+
merged.count = null == this.count ? peer.count :
|
|
1299
|
+
null == peer.count ? this.count : meetCount(this.count, peer.count)
|
|
1300
|
+
// `unique()` is idempotent: two of them are one.
|
|
1301
|
+
merged.uniq = this.uniq || peer.uniq
|
|
1302
|
+
// `unique(a) & unique(b)` is BOTH, not the later one: each names a
|
|
1303
|
+
// key on which the members must differ, and dropping either would
|
|
1304
|
+
// silently weaken the constraint. Sorted and deduplicated, so the
|
|
1305
|
+
// meet is commutative and `unique(a) & unique(a)` is one atom.
|
|
1306
|
+
merged.uniqBy = [...new Set([...this.uniqBy, ...peer.uniqBy])].sort()
|
|
1307
|
+
// Band B checks accumulate in written order and are never merged,
|
|
1308
|
+
// deduplicated or reordered: each carries its own author message,
|
|
1309
|
+
// and two checks with the same shape may still say different things.
|
|
1310
|
+
merged.musts = [...this.musts, ...peer.musts]
|
|
341
1311
|
|
|
342
1312
|
return this.finish(merged, ctx, peer)
|
|
343
1313
|
}
|
|
@@ -345,51 +1315,8 @@ class ConstraintVal extends FeatureVal {
|
|
|
345
1315
|
|
|
346
1316
|
// Build the merged residual, applying the eager emptiness rules.
|
|
347
1317
|
private finish(state: ConstraintState, ctx: AontuContext, peer: Val): Val {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
if (null != state.lo && null != state.hi) {
|
|
351
|
-
const c = cmpVal(d, state.hi.v, state.lo.v)
|
|
352
|
-
if (c < 0 || (0 === c && (state.lo.open || state.hi.open))) {
|
|
353
|
-
return this.fail(ctx, peer)
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
const integral = Integer === state.kind || BigInteger === state.kind
|
|
358
|
-
|
|
359
|
-
// Integral gap: an integer-narrowed interval containing no whole
|
|
360
|
-
// number is empty (integer & above(1) & below(2)).
|
|
361
|
-
if (integral && null != state.lo && null != state.hi) {
|
|
362
|
-
const lo = scaledOfNumeric(state.lo.v)
|
|
363
|
-
const hi = scaledOfNumeric(state.hi.v)
|
|
364
|
-
if (!lo.inf && !hi.inf) {
|
|
365
|
-
// Smallest admissible integer above/at the lower bound.
|
|
366
|
-
let n = scaledFloor(lo)
|
|
367
|
-
if (!scaledIsIntegral(lo) || state.lo.open) {
|
|
368
|
-
n += 1n
|
|
369
|
-
}
|
|
370
|
-
// Largest admissible integer below/at the upper bound.
|
|
371
|
-
let m = scaledFloor(hi)
|
|
372
|
-
if (state.hi.open && scaledIsIntegral(hi)) {
|
|
373
|
-
m -= 1n
|
|
374
|
-
}
|
|
375
|
-
if (m < n) {
|
|
376
|
-
return this.fail(ctx, peer)
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// Point deletion under a narrowed leaf: a closed point interval
|
|
382
|
-
// whose single value of the narrowed leaf is excluded is empty
|
|
383
|
-
// (integer & min(3) & max(3) & neq(3)). Without a narrowing the
|
|
384
|
-
// point survives in the other leaves.
|
|
385
|
-
if (null != state.kind && null != state.lo && null != state.hi &&
|
|
386
|
-
!state.lo.open && !state.hi.open &&
|
|
387
|
-
0 === cmpVal(d, state.lo.v, state.hi.v)) {
|
|
388
|
-
for (const n of state.neqs) {
|
|
389
|
-
if (leafMarker(n) === state.kind && 0 === cmpNumeric(n, state.lo.v)) {
|
|
390
|
-
return this.fail(ctx, peer)
|
|
391
|
-
}
|
|
392
|
-
}
|
|
1318
|
+
if (stateEmpty(state)) {
|
|
1319
|
+
return this.fail(ctx, peer)
|
|
393
1320
|
}
|
|
394
1321
|
|
|
395
1322
|
const out = new ConstraintVal({ peg: [], state }, ctx)
|
|
@@ -422,6 +1349,11 @@ class ConstraintVal extends FeatureVal {
|
|
|
422
1349
|
lo: this.lo,
|
|
423
1350
|
hi: this.hi,
|
|
424
1351
|
neqs: [...this.neqs],
|
|
1352
|
+
res: [...this.res],
|
|
1353
|
+
count: this.count,
|
|
1354
|
+
uniq: this.uniq,
|
|
1355
|
+
uniqBy: [...this.uniqBy],
|
|
1356
|
+
musts: [...this.musts],
|
|
425
1357
|
invalid: this.invalid,
|
|
426
1358
|
}
|
|
427
1359
|
}
|
|
@@ -437,33 +1369,30 @@ class ConstraintVal extends FeatureVal {
|
|
|
437
1369
|
out.lo = this.lo
|
|
438
1370
|
out.hi = this.hi
|
|
439
1371
|
out.neqs = [...this.neqs]
|
|
1372
|
+
out.res = [...this.res]
|
|
1373
|
+
out.count = this.count
|
|
1374
|
+
out.uniq = this.uniq
|
|
1375
|
+
out.uniqBy = [...this.uniqBy]
|
|
1376
|
+
out.musts = [...this.musts]
|
|
1377
|
+
out.pending = this.pending
|
|
1378
|
+
out.cjo = this.cjo
|
|
440
1379
|
out.invalid = this.invalid
|
|
1380
|
+
out.invalidWhy = this.invalidWhy
|
|
441
1381
|
return out
|
|
442
1382
|
}
|
|
443
1383
|
|
|
444
1384
|
|
|
445
1385
|
// The fixed canonical atom order: kind, lower, upper, neq (arguments
|
|
446
|
-
// sorted). No spaces; reparses to a conjunct that
|
|
447
|
-
// this exact residual.
|
|
1386
|
+
// sorted), re, length, unique. No spaces; reparses to a conjunct that
|
|
1387
|
+
// normalises back to this exact residual.
|
|
448
1388
|
get canon() {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
1389
|
+
if (null != this.pending) {
|
|
1390
|
+
// A pending atom has no residual yet, so canon renders the call as
|
|
1391
|
+
// written — the same shape FuncBaseVal renders while deferring.
|
|
1392
|
+
return this.pending.atom +
|
|
1393
|
+
'(' + this.pending.args.map((a: any) => a.canon).join(',') + ')'
|
|
452
1394
|
}
|
|
453
|
-
|
|
454
|
-
parts.push((this.lo.open ? 'above(' : 'min(') + this.lo.v.canon + ')')
|
|
455
|
-
}
|
|
456
|
-
if (null != this.hi) {
|
|
457
|
-
parts.push((this.hi.open ? 'below(' : 'max(') + this.hi.v.canon + ')')
|
|
458
|
-
}
|
|
459
|
-
if (0 < this.neqs.length) {
|
|
460
|
-
parts.push('neq(' + this.neqs.map((n: any) => n.canon).join(',') + ')')
|
|
461
|
-
}
|
|
462
|
-
if (0 === parts.length) {
|
|
463
|
-
// Raw invalid atom: render the call so the error frame shows it.
|
|
464
|
-
return 'constraint()'
|
|
465
|
-
}
|
|
466
|
-
return parts.join('&')
|
|
1395
|
+
return canonState(this)
|
|
467
1396
|
}
|
|
468
1397
|
|
|
469
1398
|
|
|
@@ -517,7 +1446,624 @@ function dedupSorted(domain: 'number' | 'string', neqs: any[]): any[] {
|
|
|
517
1446
|
}
|
|
518
1447
|
|
|
519
1448
|
|
|
520
|
-
//
|
|
1449
|
+
// Is this value, or anything inside it, a nil? A written argument that
|
|
1450
|
+
// holds one can never be satisfied, so the atom refuses it as an
|
|
1451
|
+
// argument rather than reporting a mystery failure against every peer.
|
|
1452
|
+
// Every caller passes a Val: `args[0]` comes from atomArgs, and the
|
|
1453
|
+
// walk below descends only into container pegs, which hold Vals. The
|
|
1454
|
+
// null/non-Val guard this function used to open with was therefore
|
|
1455
|
+
// unreachable, and the coverage gate said so.
|
|
1456
|
+
function holdsNil(v: any): boolean {
|
|
1457
|
+
if (true === v.isNil) {
|
|
1458
|
+
return true
|
|
1459
|
+
}
|
|
1460
|
+
const peg = v.peg
|
|
1461
|
+
if (Array.isArray(peg)) {
|
|
1462
|
+
return peg.some((c: any) => holdsNil(c))
|
|
1463
|
+
}
|
|
1464
|
+
if (null != peg && 'object' === typeof peg) {
|
|
1465
|
+
for (const k in peg) {
|
|
1466
|
+
if (holdsNil(peg[k])) {
|
|
1467
|
+
return true
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
return false
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
|
|
1475
|
+
// Whether a value, or anything inside it, is an effectful call — one
|
|
1476
|
+
// whose evaluation changes a node OTHER than the one being computed.
|
|
1477
|
+
// `move()` is the only builtin that does: it hides its resolution
|
|
1478
|
+
// target in place. Band B refuses one as an argument, because settling
|
|
1479
|
+
// it runs the effect against the live root before `must`'s trial clone
|
|
1480
|
+
// is taken, and a check that mutates cannot be report-only.
|
|
1481
|
+
function holdsMove(v: any): boolean {
|
|
1482
|
+
if (true === v.isFunc && 'move' === v.funcname?.()) {
|
|
1483
|
+
return true
|
|
1484
|
+
}
|
|
1485
|
+
const peg = v.peg
|
|
1486
|
+
if (Array.isArray(peg)) {
|
|
1487
|
+
return peg.some((c: any) => holdsMove(c))
|
|
1488
|
+
}
|
|
1489
|
+
if (null != peg && 'object' === typeof peg) {
|
|
1490
|
+
for (const k in peg) {
|
|
1491
|
+
if (holdsMove(peg[k])) {
|
|
1492
|
+
return true
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
return false
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
|
|
1500
|
+
// The written arguments of an atom, flattened.
|
|
1501
|
+
//
|
|
1502
|
+
// A multi-argument call arrives from the func-paren grammar as ONE
|
|
1503
|
+
// entry holding the comma group, and `neq([3,1,2])` means the same as
|
|
1504
|
+
// `neq(3,1,2)`. The group is always a ListVal by the time it reaches
|
|
1505
|
+
// here -- the func-paren handler rawToVals every argument (issue #49) --
|
|
1506
|
+
// so there is no raw-array case to unwrap. Flattening happens before
|
|
1507
|
+
// the settled check, because an unsettled member hiding inside the
|
|
1508
|
+
// group would otherwise make the atom look ready.
|
|
1509
|
+
function atomArgs(atom: string, args: any[]): any[] {
|
|
1510
|
+
if (('neq' === atom || 'must' === atom) && 1 === args.length &&
|
|
1511
|
+
true === (args[0] as any)?.isList) {
|
|
1512
|
+
return (args[0] as any).peg
|
|
1513
|
+
}
|
|
1514
|
+
return args
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
|
|
1518
|
+
// The canonical rendering of a residual, in the fixed atom order:
|
|
1519
|
+
// kind, lower bound, upper bound, neq, re, length, unique. Taken over the
|
|
1520
|
+
// STATE rather than the Val because `length`'s argument is a residual too,
|
|
1521
|
+
// and renders by exactly the same rules.
|
|
1522
|
+
function canonState(s: ConstraintState): string {
|
|
1523
|
+
const parts: string[] = []
|
|
1524
|
+
if (null != s.kind) {
|
|
1525
|
+
parts.push((s.kind as any).name.toLowerCase())
|
|
1526
|
+
}
|
|
1527
|
+
// An ORDER atom's argument implies the domain, so it is not spelled
|
|
1528
|
+
// out. A SIZING residual carries no order, and there `string` is the
|
|
1529
|
+
// only thing saying what is being sized -- drop it and the reparse
|
|
1530
|
+
// would admit lists and maps too.
|
|
1531
|
+
else if ('string' === s.domain &&
|
|
1532
|
+
null == s.lo && null == s.hi && 0 === s.neqs.length && 0 === s.res.length) {
|
|
1533
|
+
parts.push('string')
|
|
1534
|
+
}
|
|
1535
|
+
if (null != s.lo) {
|
|
1536
|
+
parts.push((s.lo.open ? 'above(' : 'min(') + s.lo.v.canon + ')')
|
|
1537
|
+
}
|
|
1538
|
+
if (null != s.hi) {
|
|
1539
|
+
parts.push((s.hi.open ? 'below(' : 'max(') + s.hi.v.canon + ')')
|
|
1540
|
+
}
|
|
1541
|
+
if (0 < s.neqs.length) {
|
|
1542
|
+
parts.push('neq(' + s.neqs.map((n: any) => n.canon).join(',') + ')')
|
|
1543
|
+
}
|
|
1544
|
+
for (const r of s.res) {
|
|
1545
|
+
parts.push('re(' + r.v.canon + ')')
|
|
1546
|
+
}
|
|
1547
|
+
if (null != s.count) {
|
|
1548
|
+
// Rendered UNABRIDGED, implied parts and all: `length(3)` canonicalises
|
|
1549
|
+
// to `length(integer&min(3)&max(3))` because that IS the residual the
|
|
1550
|
+
// count must satisfy, and canon is a normal form (G6 hashes it),
|
|
1551
|
+
// not a pretty-printer. Abbreviating would mean a second set of
|
|
1552
|
+
// rules for when the implied `integer & min(0)` may be dropped.
|
|
1553
|
+
parts.push('length(' + canonState(s.count) + ')')
|
|
1554
|
+
}
|
|
1555
|
+
if (s.uniq) {
|
|
1556
|
+
parts.push('unique()')
|
|
1557
|
+
}
|
|
1558
|
+
// After the bare atom, and sorted: canon is a normal form, so two
|
|
1559
|
+
// documents writing the same keys in different orders must render
|
|
1560
|
+
// the same string.
|
|
1561
|
+
for (const key of s.uniqBy) {
|
|
1562
|
+
parts.push('unique(' + JSON.stringify(key) + ')')
|
|
1563
|
+
}
|
|
1564
|
+
for (const m of s.musts) {
|
|
1565
|
+
parts.push('must(' + m.v.canon + ',' + m.msg.canon + ')')
|
|
1566
|
+
}
|
|
1567
|
+
if (0 === parts.length) {
|
|
1568
|
+
// Raw invalid atom: render the call so the error frame shows it.
|
|
1569
|
+
return 'constraint()'
|
|
1570
|
+
}
|
|
1571
|
+
return parts.join('&')
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
|
|
1575
|
+
// SUBSUMPTION over two residuals (G3, the query built on the table in
|
|
1576
|
+
// docs/reference-language.md "Subsumption"): does every value `s`
|
|
1577
|
+
// admits pass `g` too? Implemented here because the compare machinery
|
|
1578
|
+
// (cmpVal, sameScalar, the recursive count state) is this module's.
|
|
1579
|
+
//
|
|
1580
|
+
// Three-valued: true / false / 'undecided'. `must` on the GENERAL side
|
|
1581
|
+
// is undecided by construction (a Band B predicate is opaque — G3 maps
|
|
1582
|
+
// the table's "never" to the query's honest sub_evaluate_only); every
|
|
1583
|
+
// other approximation folds toward false, the safe direction.
|
|
1584
|
+
function constraintStateSubsumes(
|
|
1585
|
+
g: {
|
|
1586
|
+
domain?: 'number' | 'string', kind?: any, lo?: Bound, hi?: Bound,
|
|
1587
|
+
neqs: any[], res: ReAtom[], count?: ConstraintState, uniq: boolean,
|
|
1588
|
+
uniqBy: string[],
|
|
1589
|
+
musts: MustAtom[],
|
|
1590
|
+
},
|
|
1591
|
+
s: {
|
|
1592
|
+
domain?: 'number' | 'string', kind?: any, lo?: Bound, hi?: Bound,
|
|
1593
|
+
neqs: any[], res: ReAtom[], count?: ConstraintState, uniq: boolean,
|
|
1594
|
+
uniqBy: string[],
|
|
1595
|
+
musts: MustAtom[],
|
|
1596
|
+
},
|
|
1597
|
+
): boolean | 'undecided' {
|
|
1598
|
+
// A Band B predicate on the general side makes its admitted set
|
|
1599
|
+
// unknowable; an extra `must` on the SPECIFIC side only narrows it
|
|
1600
|
+
// and is ignored.
|
|
1601
|
+
if (0 < g.musts.length) {
|
|
1602
|
+
return 'undecided'
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
// Domains must agree where both constrain one; a sizing-only residual
|
|
1606
|
+
// has no domain and passes this gate.
|
|
1607
|
+
if (null != g.domain && g.domain !== s.domain) {
|
|
1608
|
+
return false
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
// A general leaf restriction requires the same leaf on the specific
|
|
1612
|
+
// side (leaves are disjoint; an unrestricted specific admits other
|
|
1613
|
+
// leaves the general refuses).
|
|
1614
|
+
if (null != g.kind && g.kind !== s.kind) {
|
|
1615
|
+
return false
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
// Interval containment: the general's endpoints at or beyond the
|
|
1619
|
+
// specific's, and where they coincide the general's may not be the
|
|
1620
|
+
// open one.
|
|
1621
|
+
const d = g.domain ?? s.domain
|
|
1622
|
+
if (null != g.lo) {
|
|
1623
|
+
if (null == s.lo || null == d) {
|
|
1624
|
+
return false
|
|
1625
|
+
}
|
|
1626
|
+
const c = cmpVal(d, g.lo.v, s.lo.v)
|
|
1627
|
+
if (0 < c || (0 === c && g.lo.open && !s.lo.open)) {
|
|
1628
|
+
return false
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
if (null != g.hi) {
|
|
1632
|
+
if (null == s.hi || null == d) {
|
|
1633
|
+
return false
|
|
1634
|
+
}
|
|
1635
|
+
const c = cmpVal(d, g.hi.v, s.hi.v)
|
|
1636
|
+
if (c < 0 || (0 === c && g.hi.open && !s.hi.open)) {
|
|
1637
|
+
return false
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
// Excluding FEWER values is more general: every general exclusion
|
|
1642
|
+
// must be excluded by the specific too.
|
|
1643
|
+
for (const n of g.neqs) {
|
|
1644
|
+
if (!s.neqs.some((m: any) => sameScalar(n, m))) {
|
|
1645
|
+
return false
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
// Patterns compare as TEXT sets (the sanctioned approximation:
|
|
1650
|
+
// deciding regex containment is what this algebra refuses to do).
|
|
1651
|
+
for (const r of g.res) {
|
|
1652
|
+
if (!s.res.some((q: ReAtom) => q.src === r.src)) {
|
|
1653
|
+
return false
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
// `unique()` subsumes only itself on that axis: a general uniqueness
|
|
1658
|
+
// demand admits no container the unconstrained specific also admits.
|
|
1659
|
+
// ... and a general `unique(k)` needs the same key on the specific
|
|
1660
|
+
// side: distinctness on `port` says nothing about distinctness on
|
|
1661
|
+
// `name`.
|
|
1662
|
+
if (g.uniqBy.some((k: string) => !s.uniqBy.includes(k))) {
|
|
1663
|
+
return false
|
|
1664
|
+
}
|
|
1665
|
+
if (g.uniq && !s.uniq) {
|
|
1666
|
+
return false
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
// The count atom reuses this same table over the integer domain.
|
|
1670
|
+
if (null != g.count) {
|
|
1671
|
+
if (null == s.count) {
|
|
1672
|
+
return false
|
|
1673
|
+
}
|
|
1674
|
+
return constraintStateSubsumes(g.count, s.count)
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
return true
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
|
|
1681
|
+
// The query surface for ts/src/subsume.ts: residual-vs-residual, and
|
|
1682
|
+
// residual-vs-scalar membership (with `must` and `unique` making the
|
|
1683
|
+
// scalar case undecided/false exactly as the meet would).
|
|
1684
|
+
function constraintSubsumesConstraint(
|
|
1685
|
+
g: ConstraintVal, s: ConstraintVal): boolean | 'undecided' {
|
|
1686
|
+
return constraintStateSubsumes(g as any, s as any)
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
function constraintAdmitsScalar(
|
|
1690
|
+
g: ConstraintVal, scalar: any): boolean | 'undecided' {
|
|
1691
|
+
if (0 < g.musts.length) {
|
|
1692
|
+
return 'undecided'
|
|
1693
|
+
}
|
|
1694
|
+
if (g.uniq || 0 < g.uniqBy.length) {
|
|
1695
|
+
return false
|
|
1696
|
+
}
|
|
1697
|
+
// A count residual admits by LENGTH, which is the meet's business;
|
|
1698
|
+
// the query answers false (the safe direction) rather than
|
|
1699
|
+
// reimplementing the sizing walk here.
|
|
1700
|
+
if (null != (g as any).count) {
|
|
1701
|
+
return false
|
|
1702
|
+
}
|
|
1703
|
+
return stateAdmits(g as any, scalar)
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
|
|
1707
|
+
// Does this residual's ORDER and MEMBERSHIP part admit the scalar? The
|
|
1708
|
+
// sizing atoms are deliberately not consulted: `admit` applies them to
|
|
1709
|
+
// the peer's length, and the count check applies this same function to
|
|
1710
|
+
// the count.
|
|
1711
|
+
function stateAdmits(s: ConstraintState, peer: any): boolean {
|
|
1712
|
+
const domainOf = numericLeaf(peer) ? 'number' :
|
|
1713
|
+
stringLeaf(peer) ? 'string' : undefined
|
|
1714
|
+
|
|
1715
|
+
if (null == s.domain) {
|
|
1716
|
+
// A sizing residual has no domain, and admits any scalar the sizing
|
|
1717
|
+
// atoms can then rule on. Booleans and null are not among them:
|
|
1718
|
+
// they have no order, no length and no members.
|
|
1719
|
+
if (null == domainOf) {
|
|
1720
|
+
return false
|
|
1721
|
+
}
|
|
1722
|
+
return true
|
|
1723
|
+
}
|
|
1724
|
+
if (domainOf !== s.domain) {
|
|
1725
|
+
return false
|
|
1726
|
+
}
|
|
1727
|
+
if (null != s.kind && leafMarker(peer) !== s.kind) {
|
|
1728
|
+
return false
|
|
1729
|
+
}
|
|
1730
|
+
const d = s.domain
|
|
1731
|
+
if (null != s.lo) {
|
|
1732
|
+
const c = cmpVal(d, peer, s.lo.v)
|
|
1733
|
+
if (c < 0 || (0 === c && s.lo.open)) {
|
|
1734
|
+
return false
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
if (null != s.hi) {
|
|
1738
|
+
const c = cmpVal(d, peer, s.hi.v)
|
|
1739
|
+
if (c > 0 || (0 === c && s.hi.open)) {
|
|
1740
|
+
return false
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
for (const n of s.neqs) {
|
|
1744
|
+
if (sameScalar(peer, n)) {
|
|
1745
|
+
return false
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
// Every accumulated pattern must match: the meet of two `re` atoms
|
|
1749
|
+
// is conjunction, and matching is UNANCHORED in both engines
|
|
1750
|
+
// (JS RegExp.test, Go regexp.MatchString), so `re("el")` admits
|
|
1751
|
+
// "hello". Anchor with ^ and $ to mean the whole string.
|
|
1752
|
+
for (const r of s.res) {
|
|
1753
|
+
if (!r.re.test(peer.peg)) {
|
|
1754
|
+
return false
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
return true
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
|
|
1761
|
+
// The eager emptiness rules. Each is EXACT -- it reports empty only
|
|
1762
|
+
// where no value could satisfy the residual -- so the algebra stays
|
|
1763
|
+
// sound; the incompleteness it accepts is documented in
|
|
1764
|
+
// docs/reference-language.md, "Emptiness".
|
|
1765
|
+
function stateEmpty(s: ConstraintState): boolean {
|
|
1766
|
+
// Two disagreeing kind narrowings inside a length argument, recorded by
|
|
1767
|
+
// meetCount because that meet has no ctx to fail through.
|
|
1768
|
+
if (s.clash) {
|
|
1769
|
+
return true
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
const d = s.domain as 'number' | 'string'
|
|
1773
|
+
|
|
1774
|
+
// Empty interval.
|
|
1775
|
+
if (null != s.lo && null != s.hi) {
|
|
1776
|
+
const c = cmpVal(d, s.hi.v, s.lo.v)
|
|
1777
|
+
if (c < 0 || (0 === c && (s.lo.open || s.hi.open))) {
|
|
1778
|
+
return true
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
const integral = Integer === s.kind || BigInteger === s.kind
|
|
1783
|
+
|
|
1784
|
+
// Integral gap: an integer-narrowed interval containing no whole
|
|
1785
|
+
// number is empty (integer & above(1) & below(2)).
|
|
1786
|
+
if (integral && null != s.lo && null != s.hi) {
|
|
1787
|
+
const lo = scaledOfNumeric(s.lo.v)
|
|
1788
|
+
const hi = scaledOfNumeric(s.hi.v)
|
|
1789
|
+
if (!lo.inf && !hi.inf) {
|
|
1790
|
+
// Smallest admissible integer above/at the lower bound.
|
|
1791
|
+
let n = scaledFloor(lo)
|
|
1792
|
+
if (!scaledIsIntegral(lo) || s.lo.open) {
|
|
1793
|
+
n += 1n
|
|
1794
|
+
}
|
|
1795
|
+
// Largest admissible integer below/at the upper bound.
|
|
1796
|
+
let m = scaledFloor(hi)
|
|
1797
|
+
if (s.hi.open && scaledIsIntegral(hi)) {
|
|
1798
|
+
m -= 1n
|
|
1799
|
+
}
|
|
1800
|
+
if (m < n) {
|
|
1801
|
+
return true
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
// Point deletion under a narrowed leaf: a closed point interval
|
|
1807
|
+
// whose single value of the narrowed leaf is excluded is empty
|
|
1808
|
+
// (integer & min(3) & max(3) & neq(3)). Without a narrowing the
|
|
1809
|
+
// point survives in the other leaves.
|
|
1810
|
+
if (null != s.kind && null != s.lo && null != s.hi &&
|
|
1811
|
+
!s.lo.open && !s.hi.open &&
|
|
1812
|
+
0 === cmpVal(d, s.lo.v, s.hi.v)) {
|
|
1813
|
+
for (const n of s.neqs) {
|
|
1814
|
+
if (leafMarker(n) === s.kind && 0 === cmpNumeric(n, s.lo.v)) {
|
|
1815
|
+
return true
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
// Sizing over the number domain: a number has neither a length nor
|
|
1821
|
+
// members, so `integer & length(3)` and `min(2) & unique()` admit
|
|
1822
|
+
// nothing. Uniqueness over the string domain is empty for the same
|
|
1823
|
+
// reason -- a string's members are not values the algebra compares.
|
|
1824
|
+
if ('number' === d && (null != s.count || s.uniq ||
|
|
1825
|
+
0 < s.uniqBy.length)) {
|
|
1826
|
+
return true
|
|
1827
|
+
}
|
|
1828
|
+
if ('string' === d && s.uniq) {
|
|
1829
|
+
return true
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
// An empty count residual makes the whole thing empty: no container
|
|
1833
|
+
// and no string has a length no integer can take.
|
|
1834
|
+
if (null != s.count && stateEmpty(s.count)) {
|
|
1835
|
+
return true
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
return false
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
|
|
1842
|
+
// The base every `length` argument meets: a count is a non-negative
|
|
1843
|
+
// integer, whatever else the argument says (docs/reference-language.md,
|
|
1844
|
+
// "`length` semantics" -- `length(c)` is empty iff `c & integer & min(0)`
|
|
1845
|
+
// is).
|
|
1846
|
+
function countBase(): ConstraintState {
|
|
1847
|
+
return {
|
|
1848
|
+
domain: 'number',
|
|
1849
|
+
kind: Integer,
|
|
1850
|
+
lo: { v: countVal(0), open: false },
|
|
1851
|
+
neqs: [],
|
|
1852
|
+
res: [],
|
|
1853
|
+
musts: [],
|
|
1854
|
+
// A COUNT is a number, and a number has no members to be distinct.
|
|
1855
|
+
uniq: false,
|
|
1856
|
+
uniqBy: [],
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
|
|
1861
|
+
// A count as a Val, so the count residual can be applied by exactly the
|
|
1862
|
+
// same membership function as any other numeric residual.
|
|
1863
|
+
function countVal(n: number): any {
|
|
1864
|
+
return new IntegerVal({ peg: n })
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
|
|
1868
|
+
// The pure meet of two count residuals. Both are number-domain and
|
|
1869
|
+
// carry no pattern or sizing atom of their own, so the merge is the
|
|
1870
|
+
// interval/exclusion part alone. A kind disagreement becomes a `clash`
|
|
1871
|
+
// rather than an error: this meet runs at construction, where there is
|
|
1872
|
+
// no AontuContext to raise through, and an empty residual carries the
|
|
1873
|
+
// same news to `unify`.
|
|
1874
|
+
function meetCount(a: ConstraintState, b: ConstraintState): ConstraintState {
|
|
1875
|
+
return {
|
|
1876
|
+
domain: 'number',
|
|
1877
|
+
// `a` is always a countBase()-seeded residual, so its kind is
|
|
1878
|
+
// always Integer — there is no kindless side to fall back from.
|
|
1879
|
+
kind: a.kind,
|
|
1880
|
+
lo: tighter('number', a.lo, b.lo, true),
|
|
1881
|
+
hi: tighter('number', a.hi, b.hi, false),
|
|
1882
|
+
neqs: dedupSorted('number', [...a.neqs, ...b.neqs]),
|
|
1883
|
+
res: [],
|
|
1884
|
+
musts: [],
|
|
1885
|
+
uniq: false,
|
|
1886
|
+
uniqBy: [],
|
|
1887
|
+
clash: true === a.clash || true === b.clash ||
|
|
1888
|
+
(null != a.kind && null != b.kind && a.kind !== b.kind),
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
|
|
1893
|
+
// Read a WRITTEN `length` argument as a count residual, or undefined when
|
|
1894
|
+
// it is not one. Accepted: an integer literal (an exact count), a
|
|
1895
|
+
// numeric kind, a Band A residual over the number domain, and any
|
|
1896
|
+
// conjunct of those. A conjunct never reaches here any more: an
|
|
1897
|
+
// unsettled argument is held as `pending` and folded before the count
|
|
1898
|
+
// reads it (G1 phase 4), so `length(min(2)&max(5))` arrives as the
|
|
1899
|
+
// single residual it folds to.
|
|
1900
|
+
//
|
|
1901
|
+
// Anything else is refused rather than deferred. A reference or an
|
|
1902
|
+
// expression would have to residuate, and the sizing atoms do not
|
|
1903
|
+
// residuate on their ARGUMENT -- only on the peer whose members are
|
|
1904
|
+
// still settling.
|
|
1905
|
+
function countArgState(arg: any): ConstraintState | undefined {
|
|
1906
|
+
if (numericLeaf(arg)) {
|
|
1907
|
+
return {
|
|
1908
|
+
domain: 'number',
|
|
1909
|
+
lo: { v: arg, open: false },
|
|
1910
|
+
hi: { v: arg, open: false },
|
|
1911
|
+
neqs: [], res: [], musts: [], uniq: false, uniqBy: [],
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
if (true === arg?.isConstraint) {
|
|
1916
|
+
const c = arg as ConstraintVal
|
|
1917
|
+
// A pattern, a sizing atom or a string bound inside a count is not
|
|
1918
|
+
// a count constraint at all, and neither is a broken one.
|
|
1919
|
+
if (null != c.invalid || 0 < c.res.length || c.uniq ||
|
|
1920
|
+
0 < c.uniqBy.length || null != c.count ||
|
|
1921
|
+
'number' !== c.domain) {
|
|
1922
|
+
return undefined
|
|
1923
|
+
}
|
|
1924
|
+
return {
|
|
1925
|
+
domain: 'number',
|
|
1926
|
+
kind: c.kind,
|
|
1927
|
+
lo: c.lo,
|
|
1928
|
+
hi: c.hi,
|
|
1929
|
+
neqs: [...c.neqs],
|
|
1930
|
+
res: [], musts: [], uniq: false, uniqBy: [],
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
if (true === arg?.isScalarKind) {
|
|
1935
|
+
const marker = arg.peg
|
|
1936
|
+
if (Number === marker) {
|
|
1937
|
+
return {
|
|
1938
|
+
domain: 'number', neqs: [], res: [], musts: [],
|
|
1939
|
+
uniq: false, uniqBy: [],
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
if (Integer === marker || Float === marker ||
|
|
1943
|
+
BigInteger === marker || BigDecimal === marker) {
|
|
1944
|
+
return {
|
|
1945
|
+
domain: 'number', kind: marker, neqs: [], res: [], musts: [],
|
|
1946
|
+
uniq: false, uniqBy: [],
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
return undefined
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
return undefined
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
|
|
1956
|
+
// A container is SETTLED when it and every child have converged. Until
|
|
1957
|
+
// then the member set can still change — an optional key whose value is
|
|
1958
|
+
// a still-resolving reference may yet generate — so a sizing atom must
|
|
1959
|
+
// defer rather than decide (docs/reference-language.md, "`length`
|
|
1960
|
+
// semantics"). Note that an optional holding a settled-but-ungenerable
|
|
1961
|
+
// value, `{x:1,y?:number}`, IS settled: the map converges on the first
|
|
1962
|
+
// pass and `y` is simply never emitted.
|
|
1963
|
+
function containerSettled(bag: any): boolean {
|
|
1964
|
+
// The bag's OWN done-counter is enough: BagVal.unify sets it from the
|
|
1965
|
+
// AND over its children, so an unsettled child already leaves the bag
|
|
1966
|
+
// unsettled. Walking the children again would be a second, drifting
|
|
1967
|
+
// copy of that rule.
|
|
1968
|
+
return true === bag.done
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
|
|
1972
|
+
// The child kinds `BagVal.gen` will attempt to generate. Anything else
|
|
1973
|
+
// is residue: dropped when the key is optional, and a bag-level error
|
|
1974
|
+
// otherwise.
|
|
1975
|
+
function genable(child: any): boolean {
|
|
1976
|
+
return true === child.isScalar || true === child.isMap ||
|
|
1977
|
+
true === child.isList || true === child.isPref ||
|
|
1978
|
+
true === child.isRef || true === child.isDisjunct ||
|
|
1979
|
+
true === child.isNil ||
|
|
1980
|
+
// A settled sizing residue generates through ConjunctVal.gen, so it
|
|
1981
|
+
// is a member like any other -- and a `length` that did not count
|
|
1982
|
+
// its sibling's `unique` would be the early-fold defect again, one
|
|
1983
|
+
// level up.
|
|
1984
|
+
undefined !== sizingResidue(child)
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
|
|
1988
|
+
// The children a bag would EMIT, mirroring the selection in
|
|
1989
|
+
// `BagVal.gen` — type/hide marks skipped, non-generable residue and
|
|
1990
|
+
// values that generate nothing dropped, optional keys dropped when
|
|
1991
|
+
// they generate empty.
|
|
1992
|
+
//
|
|
1993
|
+
// It returns the member VALS rather than their generated values,
|
|
1994
|
+
// because uniqueness compares canon and a generated value cannot
|
|
1995
|
+
// express that distinction: `1` and `1.0` generate the same JSON number
|
|
1996
|
+
// and canon differently. Counting uses the same list, so both sizing
|
|
1997
|
+
// atoms see exactly one definition of "member".
|
|
1998
|
+
//
|
|
1999
|
+
// Returns undefined when a REQUIRED child is residue: the container's
|
|
2000
|
+
// own `gen` raises there, and that error is the one worth reporting.
|
|
2001
|
+
function emittedMembers(bag: any, ctx: AontuContext): any[] | undefined {
|
|
2002
|
+
const out: any[] = []
|
|
2003
|
+
|
|
2004
|
+
let entries = items(bag.peg)
|
|
2005
|
+
if (bag.isMap) {
|
|
2006
|
+
// Code-point order, because the two ports disagree on raw key order
|
|
2007
|
+
// — JavaScript hoists integer-like keys, Go keeps insertion order —
|
|
2008
|
+
// and a duplicate report must name the same pair in both.
|
|
2009
|
+
entries = entries
|
|
2010
|
+
.slice()
|
|
2011
|
+
.sort((a: any, b: any) => cmpCodePoint(String(a[0]), String(b[0])))
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
for (const item of entries) {
|
|
2015
|
+
const key = item[0]
|
|
2016
|
+
const child: any = item[1]
|
|
2017
|
+
|
|
2018
|
+
if (child.mark.type || child.mark.hide) {
|
|
2019
|
+
continue
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
const optional = bag.optionalKeys.includes('' + key)
|
|
2023
|
+
|
|
2024
|
+
if (!genable(child)) {
|
|
2025
|
+
if (optional) {
|
|
2026
|
+
continue
|
|
2027
|
+
}
|
|
2028
|
+
return undefined
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
// Generation decides in an isolated collect context, so an
|
|
2032
|
+
// unresolved inner value neither raises here nor pollutes the
|
|
2033
|
+
// caller's errors — the same isolation BagVal.gen uses for an
|
|
2034
|
+
// optional child.
|
|
2035
|
+
const cval = child.gen(ctx.clone({ err: [], collect: true }))
|
|
2036
|
+
|
|
2037
|
+
if (undefined === cval || (optional && empty(cval))) {
|
|
2038
|
+
continue
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
out.push(child)
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
return out
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
|
|
2048
|
+
// Sort accumulated patterns by source in code-point order and drop
|
|
2049
|
+
// exact duplicates. Patterns are NEVER simplified or compared for
|
|
2050
|
+
// containment: deciding `re("a")` subsumes `re("a|b")` is regex
|
|
2051
|
+
// containment, which the algebra deliberately does not do (emptiness
|
|
2052
|
+
// stays approximate — sound, incomplete). Two spellings of one language
|
|
2053
|
+
// therefore both survive, and both are tested.
|
|
2054
|
+
function dedupSortedRes(res: ReAtom[]): ReAtom[] {
|
|
2055
|
+
const sorted = [...res].sort((a, b) => cmpCodePoints(a.src, b.src))
|
|
2056
|
+
const out: ReAtom[] = []
|
|
2057
|
+
for (const r of sorted) {
|
|
2058
|
+
if (0 === out.length || out[out.length - 1].src !== r.src) {
|
|
2059
|
+
out.push(r)
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
return out
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
|
|
2066
|
+
// The atom classes registered in the parser's funcMap: each is a
|
|
521
2067
|
// ConstraintVal that knows its atom name. Constructed by the
|
|
522
2068
|
// func-paren handler as `new funcval({peg: args})`.
|
|
523
2069
|
class MinConstraintVal extends ConstraintVal {
|
|
@@ -548,14 +2094,51 @@ class NeqConstraintVal extends ConstraintVal {
|
|
|
548
2094
|
constructor(spec: ValSpec, ctx?: AontuContext) {
|
|
549
2095
|
super({ ...spec, atom: 'neq' }, ctx)
|
|
550
2096
|
}
|
|
551
|
-
}
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
class ReConstraintVal extends ConstraintVal {
|
|
2100
|
+
constructor(spec: ValSpec, ctx?: AontuContext) {
|
|
2101
|
+
super({ ...spec, atom: 're' }, ctx)
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
class MustConstraintVal extends ConstraintVal {
|
|
2106
|
+
constructor(spec: ValSpec, ctx?: AontuContext) {
|
|
2107
|
+
super({ ...spec, atom: 'must' }, ctx)
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
class LengthConstraintVal extends ConstraintVal {
|
|
2112
|
+
constructor(spec: ValSpec, ctx?: AontuContext) {
|
|
2113
|
+
super({ ...spec, atom: 'length' }, ctx)
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
class UniqueConstraintVal extends ConstraintVal {
|
|
2118
|
+
constructor(spec: ValSpec, ctx?: AontuContext) {
|
|
2119
|
+
super({ ...spec, atom: 'unique' }, ctx)
|
|
2120
|
+
}
|
|
2121
|
+
} /* node:coverage ignore next 23 */
|
|
552
2122
|
|
|
553
2123
|
|
|
554
2124
|
export {
|
|
2125
|
+
// Exported for the differential corpus test (ADR-003): the two ports'
|
|
2126
|
+
// normalisers must produce byte-identical output, and that can only be
|
|
2127
|
+
// checked by calling them.
|
|
2128
|
+
normaliseRe,
|
|
2129
|
+
// Exported for the subsumption query (G3, ts/src/subsume.ts): the
|
|
2130
|
+
// residual-vs-residual and residual-vs-scalar rules live here beside
|
|
2131
|
+
// the compare machinery they reuse.
|
|
2132
|
+
constraintSubsumesConstraint,
|
|
2133
|
+
constraintAdmitsScalar,
|
|
555
2134
|
ConstraintVal,
|
|
556
2135
|
MinConstraintVal,
|
|
557
2136
|
MaxConstraintVal,
|
|
558
2137
|
AboveConstraintVal,
|
|
559
2138
|
BelowConstraintVal,
|
|
560
2139
|
NeqConstraintVal,
|
|
2140
|
+
ReConstraintVal,
|
|
2141
|
+
LengthConstraintVal,
|
|
2142
|
+
UniqueConstraintVal,
|
|
2143
|
+
MustConstraintVal,
|
|
561
2144
|
}
|