aontu 0.51.0 → 0.52.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/bin/aontu-lsp.js +6 -0
- package/bin/aontu.js +7 -0
- package/dist/aontu.d.ts +1 -1
- package/dist/aontu.js +4 -7
- package/dist/aontu.js.map +1 -1
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +10 -46
- package/dist/cli.js.map +1 -1
- package/dist/ctx.js +1 -1
- package/dist/ctx.js.map +1 -1
- package/dist/err.js +3 -2
- package/dist/err.js.map +1 -1
- package/dist/exactjson.js +1 -1
- package/dist/exactjson.js.map +1 -1
- package/dist/hints.d.ts +3 -1
- package/dist/hints.js +126 -1
- package/dist/hints.js.map +1 -1
- package/dist/keyorder.js +1 -1
- package/dist/keyorder.js.map +1 -1
- package/dist/lang.js +64 -27
- package/dist/lang.js.map +1 -1
- package/dist/lsp-server.d.ts +2 -2
- package/dist/lsp-server.js +10 -10
- package/dist/lsp-server.js.map +1 -1
- package/dist/lsp.js +27 -19
- package/dist/lsp.js.map +1 -1
- package/dist/site.js +1 -1
- package/dist/site.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/unify.js +48 -1
- package/dist/unify.js.map +1 -1
- package/dist/utility.js +3 -8
- package/dist/utility.js.map +1 -1
- package/dist/val/BagVal.js +1 -1
- package/dist/val/BagVal.js.map +1 -1
- package/dist/val/BigDecimalVal.js +2 -2
- package/dist/val/BigDecimalVal.js.map +1 -1
- package/dist/val/BigIntegerVal.js +2 -2
- package/dist/val/BigIntegerVal.js.map +1 -1
- package/dist/val/BooleanVal.js +1 -1
- package/dist/val/BooleanVal.js.map +1 -1
- package/dist/val/CloseFuncVal.js +1 -1
- package/dist/val/CloseFuncVal.js.map +1 -1
- package/dist/val/ConjunctVal.js +1 -1
- package/dist/val/ConjunctVal.js.map +1 -1
- package/dist/val/ConstraintVal.d.ts +56 -0
- package/dist/val/ConstraintVal.js +407 -0
- package/dist/val/ConstraintVal.js.map +1 -0
- package/dist/val/CopyFuncVal.js +1 -1
- package/dist/val/CopyFuncVal.js.map +1 -1
- package/dist/val/Decimal.js +1 -1
- package/dist/val/Decimal.js.map +1 -1
- package/dist/val/DisjunctVal.js +1 -29
- package/dist/val/DisjunctVal.js.map +1 -1
- package/dist/val/ExpectVal.js +1 -1
- 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/FuncBaseVal.js +1 -1
- package/dist/val/FuncBaseVal.js.map +1 -1
- package/dist/val/HideFuncVal.js +1 -1
- package/dist/val/HideFuncVal.js.map +1 -1
- package/dist/val/IntegerVal.js +2 -2
- package/dist/val/IntegerVal.js.map +1 -1
- package/dist/val/JunctionVal.js +1 -1
- package/dist/val/JunctionVal.js.map +1 -1
- package/dist/val/KeyFuncVal.js +1 -1
- package/dist/val/KeyFuncVal.js.map +1 -1
- package/dist/val/ListVal.js +7 -7
- package/dist/val/ListVal.js.map +1 -1
- package/dist/val/LowerFuncVal.js +1 -1
- package/dist/val/LowerFuncVal.js.map +1 -1
- package/dist/val/MapVal.js +11 -9
- package/dist/val/MapVal.js.map +1 -1
- package/dist/val/MoveFuncVal.js +1 -1
- package/dist/val/MoveFuncVal.js.map +1 -1
- package/dist/val/NilVal.d.ts +1 -0
- package/dist/val/NilVal.js +10 -3
- package/dist/val/NilVal.js.map +1 -1
- package/dist/val/NullVal.js +1 -1
- package/dist/val/NullVal.js.map +1 -1
- package/dist/val/NumberVal.js +2 -2
- package/dist/val/NumberVal.js.map +1 -1
- package/dist/val/OpBaseVal.js +10 -8
- package/dist/val/OpBaseVal.js.map +1 -1
- package/dist/val/OpenFuncVal.js +1 -1
- package/dist/val/OpenFuncVal.js.map +1 -1
- package/dist/val/PathFuncVal.js +1 -1
- package/dist/val/PathFuncVal.js.map +1 -1
- package/dist/val/PlusOpVal.js +1 -1
- package/dist/val/PlusOpVal.js.map +1 -1
- package/dist/val/PrefFuncVal.js +1 -1
- package/dist/val/PrefFuncVal.js.map +1 -1
- package/dist/val/PrefVal.js +4 -3
- package/dist/val/PrefVal.js.map +1 -1
- package/dist/val/RefVal.d.ts +2 -0
- package/dist/val/RefVal.js +78 -9
- package/dist/val/RefVal.js.map +1 -1
- package/dist/val/ScalarKindVal.js +8 -2
- package/dist/val/ScalarKindVal.js.map +1 -1
- package/dist/val/ScalarVal.js +2 -2
- package/dist/val/ScalarVal.js.map +1 -1
- package/dist/val/StringVal.js +1 -1
- package/dist/val/StringVal.js.map +1 -1
- package/dist/val/SuperFuncVal.js +1 -1
- package/dist/val/SuperFuncVal.js.map +1 -1
- package/dist/val/TopVal.js +1 -1
- package/dist/val/TopVal.js.map +1 -1
- package/dist/val/TypeFuncVal.js +1 -1
- package/dist/val/TypeFuncVal.js.map +1 -1
- package/dist/val/UpperFuncVal.js +1 -1
- package/dist/val/UpperFuncVal.js.map +1 -1
- package/dist/val/Val.d.ts +1 -1
- package/dist/val/Val.js +11 -21
- package/dist/val/Val.js.map +1 -1
- package/dist/val/VarVal.js +1 -1
- package/dist/val/VarVal.js.map +1 -1
- package/dist/val/numcmp.d.ts +14 -0
- package/dist/val/numcmp.js +134 -0
- package/dist/val/numcmp.js.map +1 -0
- package/dist/val/numkind.js +1 -1
- package/dist/val/numkind.js.map +1 -1
- package/package.json +6 -4
- package/src/aontu.ts +4 -8
- package/src/cli.ts +12 -13
- package/src/ctx.ts +1 -1
- package/src/err.ts +3 -2
- package/src/exactjson.ts +1 -1
- package/src/hints.ts +140 -0
- package/src/keyorder.ts +1 -1
- package/src/lang.ts +75 -27
- package/src/lsp-server.ts +17 -15
- package/src/lsp.ts +27 -12
- package/src/site.ts +1 -1
- package/src/type.ts +1 -1
- package/src/unify.ts +52 -1
- package/src/utility.ts +3 -9
- package/src/val/BagVal.ts +1 -1
- package/src/val/BigDecimalVal.ts +2 -2
- package/src/val/BigIntegerVal.ts +2 -2
- package/src/val/BooleanVal.ts +1 -1
- package/src/val/CloseFuncVal.ts +1 -1
- package/src/val/ConjunctVal.ts +1 -1
- package/src/val/ConstraintVal.ts +561 -0
- package/src/val/CopyFuncVal.ts +1 -1
- package/src/val/Decimal.ts +1 -1
- package/src/val/DisjunctVal.ts +1 -37
- package/src/val/ExpectVal.ts +1 -1
- package/src/val/FeatureVal.ts +1 -1
- package/src/val/FuncBaseVal.ts +1 -1
- package/src/val/HideFuncVal.ts +1 -1
- package/src/val/IntegerVal.ts +2 -2
- package/src/val/JunctionVal.ts +1 -1
- package/src/val/KeyFuncVal.ts +1 -1
- package/src/val/ListVal.ts +3 -5
- package/src/val/LowerFuncVal.ts +1 -1
- package/src/val/MapVal.ts +7 -5
- package/src/val/MoveFuncVal.ts +1 -1
- package/src/val/NilVal.ts +13 -3
- package/src/val/NullVal.ts +1 -1
- package/src/val/NumberVal.ts +2 -2
- package/src/val/OpBaseVal.ts +10 -10
- package/src/val/OpenFuncVal.ts +1 -1
- package/src/val/PathFuncVal.ts +1 -1
- package/src/val/PlusOpVal.ts +1 -1
- package/src/val/PrefFuncVal.ts +1 -1
- package/src/val/PrefVal.ts +4 -3
- package/src/val/RefVal.ts +82 -9
- package/src/val/ScalarKindVal.ts +8 -2
- package/src/val/ScalarVal.ts +2 -2
- package/src/val/StringVal.ts +1 -1
- package/src/val/SuperFuncVal.ts +1 -1
- package/src/val/TopVal.ts +1 -1
- package/src/val/TypeFuncVal.ts +1 -1
- package/src/val/UpperFuncVal.ts +1 -1
- package/src/val/Val.ts +13 -8
- package/src/val/VarVal.ts +1 -1
- package/src/val/numcmp.ts +189 -0
- package/src/val/numkind.ts +1 -1
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
/* Copyright (c) 2025 Richard Rodger, MIT License */
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* The constraint algebra's residual value (G1 phase 1: bounds and neq;
|
|
5
|
+
* docs/reference-language.md, "The constraint algebra"). A
|
|
6
|
+
* ConstraintVal is the normal form of any meet of Band A atoms over
|
|
7
|
+
* one domain: an interval (endpoints with open/closed flags), an
|
|
8
|
+
* exclusion set (from neq), and an optional numeric-leaf kind
|
|
9
|
+
* narrowing (from meeting `integer` etc.). Its rules, decided in
|
|
10
|
+
* phase 0 and mirrored by go/constraint.go:
|
|
11
|
+
*
|
|
12
|
+
* - Order is a property of the number line: a numeric bound admits
|
|
13
|
+
* ANY numeric leaf at an admissible position, compared exactly
|
|
14
|
+
* (numcmp.ts), and never narrows the peer's leaf by itself.
|
|
15
|
+
* - Endpoints keep their written leaf (canon renders the stored
|
|
16
|
+
* scalar); two endpoints at the SAME point keep the tower-lowest
|
|
17
|
+
* leaf. No endpoint rewriting, ever (lazy endpoints).
|
|
18
|
+
* - neq excludes by scalar identity — leaf AND value.
|
|
19
|
+
* - Emptiness is decided eagerly where it is exact: empty interval,
|
|
20
|
+
* integral gap under an integer/biginteger narrowing, point
|
|
21
|
+
* deletion under a narrowed leaf, domain mixing. Representability
|
|
22
|
+
* holes of the int64-window integer leaf are deliberately NOT
|
|
23
|
+
* modelled (sound — no false conflicts; incomplete — those
|
|
24
|
+
* contradictions surface against data), matching the algebra's
|
|
25
|
+
* regex-approximation philosophy.
|
|
26
|
+
* - Canonical atom order: kind, lower bound, upper bound, neq
|
|
27
|
+
* (arguments sorted); `parse(canon(v)) == v` holds because the
|
|
28
|
+
* reparse is a conjunct of atoms that normalises back to the
|
|
29
|
+
* identical residual.
|
|
30
|
+
*
|
|
31
|
+
* A constraint violation raises the registered `constraint` code
|
|
32
|
+
* (class conflict), with the admissible set in NilVal.details.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import type {
|
|
36
|
+
Val,
|
|
37
|
+
ValSpec,
|
|
38
|
+
} from '../type'
|
|
39
|
+
|
|
40
|
+
import {
|
|
41
|
+
DONE,
|
|
42
|
+
} from '../type'
|
|
43
|
+
|
|
44
|
+
import {
|
|
45
|
+
AontuContext,
|
|
46
|
+
} from '../ctx'
|
|
47
|
+
|
|
48
|
+
import {
|
|
49
|
+
explainOpen,
|
|
50
|
+
explainClose,
|
|
51
|
+
propagateMarks,
|
|
52
|
+
} from '../utility'
|
|
53
|
+
|
|
54
|
+
import { makeNilErr } from '../err'
|
|
55
|
+
|
|
56
|
+
import { FeatureVal } from './FeatureVal'
|
|
57
|
+
|
|
58
|
+
import {
|
|
59
|
+
BigDecimal,
|
|
60
|
+
BigInteger,
|
|
61
|
+
Float,
|
|
62
|
+
Integer,
|
|
63
|
+
} from './ScalarKindVal'
|
|
64
|
+
|
|
65
|
+
import {
|
|
66
|
+
cmpNumeric,
|
|
67
|
+
cmpCodePoints,
|
|
68
|
+
towerRank,
|
|
69
|
+
scaledOfNumeric,
|
|
70
|
+
scaledIsIntegral,
|
|
71
|
+
scaledFloor,
|
|
72
|
+
} from './numcmp'
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
type Bound = {
|
|
76
|
+
v: any // the stored endpoint scalar Val (leaf preserved)
|
|
77
|
+
open: boolean // true for above/below, false for min/max
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
type ConstraintState = {
|
|
81
|
+
domain?: 'number' | 'string'
|
|
82
|
+
kind?: any // numeric leaf marker (Integer | Float | ...) or undefined
|
|
83
|
+
lo?: Bound
|
|
84
|
+
hi?: Bound
|
|
85
|
+
neqs: any[] // excluded scalars, identity per leaf+value
|
|
86
|
+
invalid?: string // why-code when the atom's arguments were unusable
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
// True for a scalar Val the algebra can order: a numeric leaf or a
|
|
91
|
+
// string. (Booleans and null have no order and no bounds.)
|
|
92
|
+
function numericLeaf(v: any): boolean {
|
|
93
|
+
return true === v?.isScalar &&
|
|
94
|
+
(v.isInteger || v.isNumber || v.isBigInteger || v.isBigDecimal) &&
|
|
95
|
+
!(v.isNumber && Number.isNaN(v.peg))
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function stringLeaf(v: any): boolean {
|
|
99
|
+
return true === v?.isScalar && 'string' === typeof v.peg && v.isString
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
// Scalar identity: leaf AND value, the lattice's own rule (1 and 1.0
|
|
104
|
+
// are different scalars). Exact numeric comparison decides the value
|
|
105
|
+
// half for numeric leaves.
|
|
106
|
+
function sameScalar(a: any, b: any): boolean {
|
|
107
|
+
if (numericLeaf(a) && numericLeaf(b)) {
|
|
108
|
+
return towerRank(a) === towerRank(b) && 0 === cmpNumeric(a, b)
|
|
109
|
+
}
|
|
110
|
+
if (stringLeaf(a) && stringLeaf(b)) {
|
|
111
|
+
return a.peg === b.peg
|
|
112
|
+
}
|
|
113
|
+
return false
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
// Domain-aware value comparison for bounds and neq ordering.
|
|
118
|
+
function cmpVal(domain: 'number' | 'string', a: any, b: any): number {
|
|
119
|
+
return 'number' === domain ? cmpNumeric(a, b) : cmpCodePoints(a.peg, b.peg)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
// The numeric leaf marker a concrete scalar carries, for the kind
|
|
124
|
+
// narrowing check.
|
|
125
|
+
function leafMarker(v: any): any {
|
|
126
|
+
return v.isBigDecimal ? BigDecimal : v.isBigInteger ? BigInteger :
|
|
127
|
+
v.isInteger ? Integer : Float
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class ConstraintVal extends FeatureVal {
|
|
132
|
+
isConstraint = true
|
|
133
|
+
cjo = 50000
|
|
134
|
+
|
|
135
|
+
domain?: 'number' | 'string'
|
|
136
|
+
kind?: any
|
|
137
|
+
lo?: Bound
|
|
138
|
+
hi?: Bound
|
|
139
|
+
neqs: any[] = []
|
|
140
|
+
invalid?: string
|
|
141
|
+
|
|
142
|
+
constructor(
|
|
143
|
+
spec: ValSpec & { atom?: string, state?: ConstraintState },
|
|
144
|
+
ctx?: AontuContext
|
|
145
|
+
) {
|
|
146
|
+
super({ ...spec, peg: spec.peg ?? [] }, ctx)
|
|
147
|
+
|
|
148
|
+
if (spec.state) {
|
|
149
|
+
this.domain = spec.state.domain
|
|
150
|
+
this.kind = spec.state.kind
|
|
151
|
+
this.lo = spec.state.lo
|
|
152
|
+
this.hi = spec.state.hi
|
|
153
|
+
this.neqs = spec.state.neqs
|
|
154
|
+
this.invalid = spec.state.invalid
|
|
155
|
+
}
|
|
156
|
+
else if (spec.atom) {
|
|
157
|
+
this.fromAtom(spec.atom, (spec.peg as any[]) ?? [])
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// A residual constraint is stable, like a ScalarKindVal.
|
|
161
|
+
this.dc = DONE
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
// Normalise one atom call (min/max/above/below/neq) into state.
|
|
166
|
+
// Arguments must be concrete orderable scalars in phase 1;
|
|
167
|
+
// reference-valued arguments are phase 4 (residuation).
|
|
168
|
+
private fromAtom(atom: string, args: any[]) {
|
|
169
|
+
// Mark the residual invalid and report so (a plain boolean, so no
|
|
170
|
+
// void value is consumed by the callers' `return` statements).
|
|
171
|
+
const bad = (why: string): boolean => {
|
|
172
|
+
this.invalid = why
|
|
173
|
+
return true
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if ('neq' === atom) {
|
|
177
|
+
// Multiple arguments arrive from the func-paren grammar as one
|
|
178
|
+
// entry holding the comma group: a raw array of Vals (or an
|
|
179
|
+
// implicit ListVal via some spellings). `neq(3,1,2)` therefore
|
|
180
|
+
// has peg [[3,1,2]], and `neq([3,1,2])` means the same thing.
|
|
181
|
+
if (1 === args.length && Array.isArray(args[0])) {
|
|
182
|
+
args = args[0]
|
|
183
|
+
}
|
|
184
|
+
else if (1 === args.length && true === (args[0] as any)?.isList) {
|
|
185
|
+
args = (args[0] as any).peg
|
|
186
|
+
}
|
|
187
|
+
if (0 === args.length) {
|
|
188
|
+
return bad('arg')
|
|
189
|
+
}
|
|
190
|
+
let domain: 'number' | 'string' | undefined = undefined
|
|
191
|
+
for (const a of args) {
|
|
192
|
+
const d = numericLeaf(a) ? 'number' : stringLeaf(a) ? 'string' : undefined
|
|
193
|
+
if (null == d || (null != domain && d !== domain)) {
|
|
194
|
+
return bad('invalid-arg')
|
|
195
|
+
}
|
|
196
|
+
domain = d
|
|
197
|
+
}
|
|
198
|
+
this.domain = domain
|
|
199
|
+
this.neqs = dedupSorted(domain as any, args)
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (1 !== args.length) {
|
|
204
|
+
return bad('arg')
|
|
205
|
+
}
|
|
206
|
+
const a = args[0]
|
|
207
|
+
const domain = numericLeaf(a) ? 'number' : stringLeaf(a) ? 'string' : undefined
|
|
208
|
+
if (null == domain) {
|
|
209
|
+
return bad('invalid-arg')
|
|
210
|
+
}
|
|
211
|
+
this.domain = domain
|
|
212
|
+
|
|
213
|
+
const open = 'above' === atom || 'below' === atom
|
|
214
|
+
const bound: Bound = { v: a, open }
|
|
215
|
+
if ('min' === atom || 'above' === atom) {
|
|
216
|
+
this.lo = bound
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
this.hi = bound
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
unify(peer: Val, ctx: AontuContext): Val {
|
|
225
|
+
const te = ctx.explain && explainOpen(ctx, ctx.explain, 'Constraint', this, peer)
|
|
226
|
+
|
|
227
|
+
// Every branch of the ladder assigns, so no initialiser: a
|
|
228
|
+
// residual is stable and the ladder is total.
|
|
229
|
+
let out: Val
|
|
230
|
+
|
|
231
|
+
if (null != this.invalid) {
|
|
232
|
+
out = makeNilErr(ctx, this.invalid, this, undefined, 'constrain')
|
|
233
|
+
}
|
|
234
|
+
else if (null == peer || (peer as any).isTop) {
|
|
235
|
+
out = this
|
|
236
|
+
}
|
|
237
|
+
else if ((peer as any).isNil) {
|
|
238
|
+
out = peer
|
|
239
|
+
}
|
|
240
|
+
else if ((peer as any).isConstraint) {
|
|
241
|
+
out = this.meetConstraint(peer as ConstraintVal, ctx)
|
|
242
|
+
}
|
|
243
|
+
else if ((peer as any).isScalarKind) {
|
|
244
|
+
out = this.meetKind(peer, ctx)
|
|
245
|
+
}
|
|
246
|
+
else if ((peer as any).isScalar) {
|
|
247
|
+
out = this.admit(peer, ctx)
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
// Maps, lists, and every other non-scalar shape: no order, no
|
|
251
|
+
// membership — a conflict of the constraint family.
|
|
252
|
+
out = this.fail(ctx, peer)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
ctx.explain && explainClose(te, out)
|
|
256
|
+
|
|
257
|
+
return out
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
// Membership: the peer scalar passes every part of the residual, or
|
|
262
|
+
// the whole meet is a located conflict.
|
|
263
|
+
private admit(peer: any, ctx: AontuContext): Val {
|
|
264
|
+
const domainOf = numericLeaf(peer) ? 'number' :
|
|
265
|
+
stringLeaf(peer) ? 'string' : undefined
|
|
266
|
+
|
|
267
|
+
if (domainOf !== this.domain) {
|
|
268
|
+
return this.fail(ctx, peer)
|
|
269
|
+
}
|
|
270
|
+
if (null != this.kind && leafMarker(peer) !== this.kind) {
|
|
271
|
+
return this.fail(ctx, peer)
|
|
272
|
+
}
|
|
273
|
+
const d = this.domain as 'number' | 'string'
|
|
274
|
+
if (null != this.lo) {
|
|
275
|
+
const c = cmpVal(d, peer, this.lo.v)
|
|
276
|
+
if (c < 0 || (0 === c && this.lo.open)) {
|
|
277
|
+
return this.fail(ctx, peer)
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (null != this.hi) {
|
|
281
|
+
const c = cmpVal(d, peer, this.hi.v)
|
|
282
|
+
if (c > 0 || (0 === c && this.hi.open)) {
|
|
283
|
+
return this.fail(ctx, peer)
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
for (const n of this.neqs) {
|
|
287
|
+
if (sameScalar(peer, n)) {
|
|
288
|
+
return this.fail(ctx, peer)
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return peer
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
// Meet with a kind: `number` (or `string` on the string domain) is
|
|
296
|
+
// already implied; a numeric LEAF narrows the residual; anything
|
|
297
|
+
// else has an empty intersection with the constraint's domain.
|
|
298
|
+
private meetKind(peer: any, ctx: AontuContext): Val {
|
|
299
|
+
const marker = peer.peg
|
|
300
|
+
|
|
301
|
+
if (Number === marker) {
|
|
302
|
+
return 'number' === this.domain ? this : this.fail(ctx, peer)
|
|
303
|
+
}
|
|
304
|
+
if (String === marker) {
|
|
305
|
+
return 'string' === this.domain ? this : this.fail(ctx, peer)
|
|
306
|
+
}
|
|
307
|
+
const isLeaf = Integer === marker || Float === marker ||
|
|
308
|
+
BigInteger === marker || BigDecimal === marker
|
|
309
|
+
if (!isLeaf || 'number' !== this.domain) {
|
|
310
|
+
return this.fail(ctx, peer)
|
|
311
|
+
}
|
|
312
|
+
if (null != this.kind && this.kind !== marker) {
|
|
313
|
+
return this.fail(ctx, peer)
|
|
314
|
+
}
|
|
315
|
+
const merged = this.cloneState()
|
|
316
|
+
merged.kind = marker
|
|
317
|
+
return this.finish(merged, ctx, peer)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
// Meet with another residual: interval intersection, exclusion
|
|
322
|
+
// union, kind union — then the eager emptiness rules.
|
|
323
|
+
private meetConstraint(peer: ConstraintVal, ctx: AontuContext): Val {
|
|
324
|
+
if (null != peer.invalid) {
|
|
325
|
+
return makeNilErr(ctx, peer.invalid, peer, undefined, 'constrain')
|
|
326
|
+
}
|
|
327
|
+
if (null != this.domain && null != peer.domain && this.domain !== peer.domain) {
|
|
328
|
+
return this.fail(ctx, peer)
|
|
329
|
+
}
|
|
330
|
+
if (null != this.kind && null != peer.kind && this.kind !== peer.kind) {
|
|
331
|
+
return this.fail(ctx, peer)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const d = (this.domain ?? peer.domain) as 'number' | 'string'
|
|
335
|
+
const merged = this.cloneState()
|
|
336
|
+
merged.domain = d
|
|
337
|
+
merged.kind = this.kind ?? peer.kind
|
|
338
|
+
merged.lo = tighter(d, this.lo, peer.lo, true)
|
|
339
|
+
merged.hi = tighter(d, this.hi, peer.hi, false)
|
|
340
|
+
merged.neqs = dedupSorted(d, [...this.neqs, ...peer.neqs])
|
|
341
|
+
|
|
342
|
+
return this.finish(merged, ctx, peer)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
// Build the merged residual, applying the eager emptiness rules.
|
|
347
|
+
private finish(state: ConstraintState, ctx: AontuContext, peer: Val): Val {
|
|
348
|
+
const d = state.domain as 'number' | 'string'
|
|
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
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const out = new ConstraintVal({ peg: [], state }, ctx)
|
|
396
|
+
out.path = this.path
|
|
397
|
+
out.site.row = this.site.row
|
|
398
|
+
out.site.col = this.site.col
|
|
399
|
+
out.site.url = this.site.url
|
|
400
|
+
// Marks ratchet across a meet (the propagateMarks rule): a fresh
|
|
401
|
+
// residual must carry both operands' type/hide marks, or a
|
|
402
|
+
// `type(min(0)) & max(10)` merge would silently unmark the field
|
|
403
|
+
// (the Go fold re-ratchets from its terms; this is the TS twin).
|
|
404
|
+
propagateMarks(this, out)
|
|
405
|
+
propagateMarks(peer, out)
|
|
406
|
+
return out
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
private fail(ctx: AontuContext, peer: Val): Val {
|
|
411
|
+
return makeNilErr(ctx, 'constraint', this, peer, undefined, {
|
|
412
|
+
expected: this.canon,
|
|
413
|
+
actual: (peer as any)?.canon,
|
|
414
|
+
})
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
private cloneState(): ConstraintState {
|
|
419
|
+
return {
|
|
420
|
+
domain: this.domain,
|
|
421
|
+
kind: this.kind,
|
|
422
|
+
lo: this.lo,
|
|
423
|
+
hi: this.hi,
|
|
424
|
+
neqs: [...this.neqs],
|
|
425
|
+
invalid: this.invalid,
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
clone(ctx: AontuContext, spec?: ValSpec): Val {
|
|
431
|
+
let out = (super.clone(ctx, {
|
|
432
|
+
...(spec || {}),
|
|
433
|
+
peg: this.peg,
|
|
434
|
+
}) as ConstraintVal)
|
|
435
|
+
out.domain = this.domain
|
|
436
|
+
out.kind = this.kind
|
|
437
|
+
out.lo = this.lo
|
|
438
|
+
out.hi = this.hi
|
|
439
|
+
out.neqs = [...this.neqs]
|
|
440
|
+
out.invalid = this.invalid
|
|
441
|
+
return out
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
// The fixed canonical atom order: kind, lower, upper, neq (arguments
|
|
446
|
+
// sorted). No spaces; reparses to a conjunct that normalises back to
|
|
447
|
+
// this exact residual.
|
|
448
|
+
get canon() {
|
|
449
|
+
const parts: string[] = []
|
|
450
|
+
if (null != this.kind) {
|
|
451
|
+
parts.push((this.kind as any).name.toLowerCase())
|
|
452
|
+
}
|
|
453
|
+
if (null != this.lo) {
|
|
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('&')
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
same(peer: any): boolean {
|
|
471
|
+
return true === peer?.isConstraint && this.canon === peer.canon
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
// The tighter of two like-direction bounds: the higher lower bound (or
|
|
478
|
+
// lower upper bound); on the same point the OPEN bound wins, and on a
|
|
479
|
+
// full tie the tower-lowest endpoint spelling survives.
|
|
480
|
+
function tighter(
|
|
481
|
+
domain: 'number' | 'string',
|
|
482
|
+
a: Bound | undefined,
|
|
483
|
+
b: Bound | undefined,
|
|
484
|
+
lower: boolean
|
|
485
|
+
): Bound | undefined {
|
|
486
|
+
if (null == a) return b
|
|
487
|
+
if (null == b) return a
|
|
488
|
+
const c = cmpVal(domain, a.v, b.v)
|
|
489
|
+
if (0 !== c) {
|
|
490
|
+
return (lower ? 0 < c : c < 0) ? a : b
|
|
491
|
+
}
|
|
492
|
+
if (a.open !== b.open) {
|
|
493
|
+
return a.open ? a : b
|
|
494
|
+
}
|
|
495
|
+
if ('number' === domain && towerRank(b.v) < towerRank(a.v)) {
|
|
496
|
+
return b
|
|
497
|
+
}
|
|
498
|
+
return a
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
// Sort excluded scalars for canon (numeric: by point then tower rank;
|
|
503
|
+
// string: code-point order) and drop identity duplicates.
|
|
504
|
+
function dedupSorted(domain: 'number' | 'string', neqs: any[]): any[] {
|
|
505
|
+
const sorted = [...neqs].sort((a: any, b: any) => {
|
|
506
|
+
const c = cmpVal(domain, a, b)
|
|
507
|
+
if (0 !== c) return c
|
|
508
|
+
return 'number' === domain ? towerRank(a) - towerRank(b) : 0
|
|
509
|
+
})
|
|
510
|
+
const out: any[] = []
|
|
511
|
+
for (const n of sorted) {
|
|
512
|
+
if (0 === out.length || !sameScalar(out[out.length - 1], n)) {
|
|
513
|
+
out.push(n)
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return out
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
// The five atom classes registered in the parser's funcMap: each is a
|
|
521
|
+
// ConstraintVal that knows its atom name. Constructed by the
|
|
522
|
+
// func-paren handler as `new funcval({peg: args})`.
|
|
523
|
+
class MinConstraintVal extends ConstraintVal {
|
|
524
|
+
constructor(spec: ValSpec, ctx?: AontuContext) {
|
|
525
|
+
super({ ...spec, atom: 'min' }, ctx)
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
class MaxConstraintVal extends ConstraintVal {
|
|
530
|
+
constructor(spec: ValSpec, ctx?: AontuContext) {
|
|
531
|
+
super({ ...spec, atom: 'max' }, ctx)
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
class AboveConstraintVal extends ConstraintVal {
|
|
536
|
+
constructor(spec: ValSpec, ctx?: AontuContext) {
|
|
537
|
+
super({ ...spec, atom: 'above' }, ctx)
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
class BelowConstraintVal extends ConstraintVal {
|
|
542
|
+
constructor(spec: ValSpec, ctx?: AontuContext) {
|
|
543
|
+
super({ ...spec, atom: 'below' }, ctx)
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
class NeqConstraintVal extends ConstraintVal {
|
|
548
|
+
constructor(spec: ValSpec, ctx?: AontuContext) {
|
|
549
|
+
super({ ...spec, atom: 'neq' }, ctx)
|
|
550
|
+
}
|
|
551
|
+
} /* node:coverage ignore next 11 */
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
export {
|
|
555
|
+
ConstraintVal,
|
|
556
|
+
MinConstraintVal,
|
|
557
|
+
MaxConstraintVal,
|
|
558
|
+
AboveConstraintVal,
|
|
559
|
+
BelowConstraintVal,
|
|
560
|
+
NeqConstraintVal,
|
|
561
|
+
}
|
package/src/val/CopyFuncVal.ts
CHANGED
package/src/val/Decimal.ts
CHANGED
package/src/val/DisjunctVal.ts
CHANGED
|
@@ -278,44 +278,8 @@ class DisjunctVal extends JunctionVal {
|
|
|
278
278
|
}
|
|
279
279
|
|
|
280
280
|
return super.gen(ctx)
|
|
281
|
-
|
|
282
|
-
// // // console.log('DJ-GEN', this.peg)
|
|
283
|
-
|
|
284
|
-
// if (1 === this.peg.length) {
|
|
285
|
-
// return this.peg[0].gen(ctx)
|
|
286
|
-
// }
|
|
287
|
-
// else if (1 < this.peg.length) {
|
|
288
|
-
// let peg = this.peg.filter((v: Val) => v instanceof PrefVal)
|
|
289
|
-
|
|
290
|
-
// if (1 === peg.length) {
|
|
291
|
-
// return peg[0].gen(ctx)
|
|
292
|
-
// }
|
|
293
|
-
// else {
|
|
294
|
-
|
|
295
|
-
// let nil = Nil.make(
|
|
296
|
-
// ctx,
|
|
297
|
-
// 'disjunct',
|
|
298
|
-
// this,
|
|
299
|
-
// undefined
|
|
300
|
-
// )
|
|
301
|
-
|
|
302
|
-
// // TODO: refactor to use Site
|
|
303
|
-
// nil.path = this.path
|
|
304
|
-
// nil.site.url = this.url
|
|
305
|
-
// nil.site.row = this.row
|
|
306
|
-
// nil.site.col = this.col
|
|
307
|
-
|
|
308
|
-
// // descErr(nil, ctx)
|
|
309
|
-
|
|
310
|
-
// if (null == ctx) {
|
|
311
|
-
// throw new Error(nil.msg)
|
|
312
|
-
// }
|
|
313
|
-
// }
|
|
314
|
-
|
|
315
|
-
// return undefined
|
|
316
|
-
// }
|
|
317
281
|
}
|
|
318
|
-
}
|
|
282
|
+
} /* node:coverage ignore next 8 */
|
|
319
283
|
|
|
320
284
|
|
|
321
285
|
|
package/src/val/ExpectVal.ts
CHANGED
package/src/val/FeatureVal.ts
CHANGED
package/src/val/FuncBaseVal.ts
CHANGED
package/src/val/HideFuncVal.ts
CHANGED
package/src/val/IntegerVal.ts
CHANGED
|
@@ -118,7 +118,7 @@ class IntegerVal extends ScalarVal {
|
|
|
118
118
|
let out: Val = this
|
|
119
119
|
|
|
120
120
|
if (null != peer) {
|
|
121
|
-
if (peer.isScalarKind) {
|
|
121
|
+
if (peer.isScalarKind || (peer as any).isConstraint) {
|
|
122
122
|
out = peer.unify(this, te ? ctx.clone({ explain: ec(te, 'KND') }) : ctx)
|
|
123
123
|
}
|
|
124
124
|
else if (
|
|
@@ -144,7 +144,7 @@ class IntegerVal extends ScalarVal {
|
|
|
144
144
|
|
|
145
145
|
return out
|
|
146
146
|
}
|
|
147
|
-
}
|
|
147
|
+
} /* node:coverage ignore next 6 */
|
|
148
148
|
|
|
149
149
|
|
|
150
150
|
export {
|
package/src/val/JunctionVal.ts
CHANGED