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.
Files changed (179) hide show
  1. package/bin/aontu-lsp.js +6 -0
  2. package/bin/aontu.js +7 -0
  3. package/dist/aontu.d.ts +1 -1
  4. package/dist/aontu.js +4 -7
  5. package/dist/aontu.js.map +1 -1
  6. package/dist/cli.d.ts +0 -1
  7. package/dist/cli.js +10 -46
  8. package/dist/cli.js.map +1 -1
  9. package/dist/ctx.js +1 -1
  10. package/dist/ctx.js.map +1 -1
  11. package/dist/err.js +3 -2
  12. package/dist/err.js.map +1 -1
  13. package/dist/exactjson.js +1 -1
  14. package/dist/exactjson.js.map +1 -1
  15. package/dist/hints.d.ts +3 -1
  16. package/dist/hints.js +126 -1
  17. package/dist/hints.js.map +1 -1
  18. package/dist/keyorder.js +1 -1
  19. package/dist/keyorder.js.map +1 -1
  20. package/dist/lang.js +64 -27
  21. package/dist/lang.js.map +1 -1
  22. package/dist/lsp-server.d.ts +2 -2
  23. package/dist/lsp-server.js +10 -10
  24. package/dist/lsp-server.js.map +1 -1
  25. package/dist/lsp.js +27 -19
  26. package/dist/lsp.js.map +1 -1
  27. package/dist/site.js +1 -1
  28. package/dist/site.js.map +1 -1
  29. package/dist/tsconfig.tsbuildinfo +1 -1
  30. package/dist/unify.js +48 -1
  31. package/dist/unify.js.map +1 -1
  32. package/dist/utility.js +3 -8
  33. package/dist/utility.js.map +1 -1
  34. package/dist/val/BagVal.js +1 -1
  35. package/dist/val/BagVal.js.map +1 -1
  36. package/dist/val/BigDecimalVal.js +2 -2
  37. package/dist/val/BigDecimalVal.js.map +1 -1
  38. package/dist/val/BigIntegerVal.js +2 -2
  39. package/dist/val/BigIntegerVal.js.map +1 -1
  40. package/dist/val/BooleanVal.js +1 -1
  41. package/dist/val/BooleanVal.js.map +1 -1
  42. package/dist/val/CloseFuncVal.js +1 -1
  43. package/dist/val/CloseFuncVal.js.map +1 -1
  44. package/dist/val/ConjunctVal.js +1 -1
  45. package/dist/val/ConjunctVal.js.map +1 -1
  46. package/dist/val/ConstraintVal.d.ts +56 -0
  47. package/dist/val/ConstraintVal.js +407 -0
  48. package/dist/val/ConstraintVal.js.map +1 -0
  49. package/dist/val/CopyFuncVal.js +1 -1
  50. package/dist/val/CopyFuncVal.js.map +1 -1
  51. package/dist/val/Decimal.js +1 -1
  52. package/dist/val/Decimal.js.map +1 -1
  53. package/dist/val/DisjunctVal.js +1 -29
  54. package/dist/val/DisjunctVal.js.map +1 -1
  55. package/dist/val/ExpectVal.js +1 -1
  56. package/dist/val/ExpectVal.js.map +1 -1
  57. package/dist/val/FeatureVal.js +1 -1
  58. package/dist/val/FeatureVal.js.map +1 -1
  59. package/dist/val/FuncBaseVal.js +1 -1
  60. package/dist/val/FuncBaseVal.js.map +1 -1
  61. package/dist/val/HideFuncVal.js +1 -1
  62. package/dist/val/HideFuncVal.js.map +1 -1
  63. package/dist/val/IntegerVal.js +2 -2
  64. package/dist/val/IntegerVal.js.map +1 -1
  65. package/dist/val/JunctionVal.js +1 -1
  66. package/dist/val/JunctionVal.js.map +1 -1
  67. package/dist/val/KeyFuncVal.js +1 -1
  68. package/dist/val/KeyFuncVal.js.map +1 -1
  69. package/dist/val/ListVal.js +7 -7
  70. package/dist/val/ListVal.js.map +1 -1
  71. package/dist/val/LowerFuncVal.js +1 -1
  72. package/dist/val/LowerFuncVal.js.map +1 -1
  73. package/dist/val/MapVal.js +11 -9
  74. package/dist/val/MapVal.js.map +1 -1
  75. package/dist/val/MoveFuncVal.js +1 -1
  76. package/dist/val/MoveFuncVal.js.map +1 -1
  77. package/dist/val/NilVal.d.ts +1 -0
  78. package/dist/val/NilVal.js +10 -3
  79. package/dist/val/NilVal.js.map +1 -1
  80. package/dist/val/NullVal.js +1 -1
  81. package/dist/val/NullVal.js.map +1 -1
  82. package/dist/val/NumberVal.js +2 -2
  83. package/dist/val/NumberVal.js.map +1 -1
  84. package/dist/val/OpBaseVal.js +10 -8
  85. package/dist/val/OpBaseVal.js.map +1 -1
  86. package/dist/val/OpenFuncVal.js +1 -1
  87. package/dist/val/OpenFuncVal.js.map +1 -1
  88. package/dist/val/PathFuncVal.js +1 -1
  89. package/dist/val/PathFuncVal.js.map +1 -1
  90. package/dist/val/PlusOpVal.js +1 -1
  91. package/dist/val/PlusOpVal.js.map +1 -1
  92. package/dist/val/PrefFuncVal.js +1 -1
  93. package/dist/val/PrefFuncVal.js.map +1 -1
  94. package/dist/val/PrefVal.js +4 -3
  95. package/dist/val/PrefVal.js.map +1 -1
  96. package/dist/val/RefVal.d.ts +2 -0
  97. package/dist/val/RefVal.js +78 -9
  98. package/dist/val/RefVal.js.map +1 -1
  99. package/dist/val/ScalarKindVal.js +8 -2
  100. package/dist/val/ScalarKindVal.js.map +1 -1
  101. package/dist/val/ScalarVal.js +2 -2
  102. package/dist/val/ScalarVal.js.map +1 -1
  103. package/dist/val/StringVal.js +1 -1
  104. package/dist/val/StringVal.js.map +1 -1
  105. package/dist/val/SuperFuncVal.js +1 -1
  106. package/dist/val/SuperFuncVal.js.map +1 -1
  107. package/dist/val/TopVal.js +1 -1
  108. package/dist/val/TopVal.js.map +1 -1
  109. package/dist/val/TypeFuncVal.js +1 -1
  110. package/dist/val/TypeFuncVal.js.map +1 -1
  111. package/dist/val/UpperFuncVal.js +1 -1
  112. package/dist/val/UpperFuncVal.js.map +1 -1
  113. package/dist/val/Val.d.ts +1 -1
  114. package/dist/val/Val.js +11 -21
  115. package/dist/val/Val.js.map +1 -1
  116. package/dist/val/VarVal.js +1 -1
  117. package/dist/val/VarVal.js.map +1 -1
  118. package/dist/val/numcmp.d.ts +14 -0
  119. package/dist/val/numcmp.js +134 -0
  120. package/dist/val/numcmp.js.map +1 -0
  121. package/dist/val/numkind.js +1 -1
  122. package/dist/val/numkind.js.map +1 -1
  123. package/package.json +6 -4
  124. package/src/aontu.ts +4 -8
  125. package/src/cli.ts +12 -13
  126. package/src/ctx.ts +1 -1
  127. package/src/err.ts +3 -2
  128. package/src/exactjson.ts +1 -1
  129. package/src/hints.ts +140 -0
  130. package/src/keyorder.ts +1 -1
  131. package/src/lang.ts +75 -27
  132. package/src/lsp-server.ts +17 -15
  133. package/src/lsp.ts +27 -12
  134. package/src/site.ts +1 -1
  135. package/src/type.ts +1 -1
  136. package/src/unify.ts +52 -1
  137. package/src/utility.ts +3 -9
  138. package/src/val/BagVal.ts +1 -1
  139. package/src/val/BigDecimalVal.ts +2 -2
  140. package/src/val/BigIntegerVal.ts +2 -2
  141. package/src/val/BooleanVal.ts +1 -1
  142. package/src/val/CloseFuncVal.ts +1 -1
  143. package/src/val/ConjunctVal.ts +1 -1
  144. package/src/val/ConstraintVal.ts +561 -0
  145. package/src/val/CopyFuncVal.ts +1 -1
  146. package/src/val/Decimal.ts +1 -1
  147. package/src/val/DisjunctVal.ts +1 -37
  148. package/src/val/ExpectVal.ts +1 -1
  149. package/src/val/FeatureVal.ts +1 -1
  150. package/src/val/FuncBaseVal.ts +1 -1
  151. package/src/val/HideFuncVal.ts +1 -1
  152. package/src/val/IntegerVal.ts +2 -2
  153. package/src/val/JunctionVal.ts +1 -1
  154. package/src/val/KeyFuncVal.ts +1 -1
  155. package/src/val/ListVal.ts +3 -5
  156. package/src/val/LowerFuncVal.ts +1 -1
  157. package/src/val/MapVal.ts +7 -5
  158. package/src/val/MoveFuncVal.ts +1 -1
  159. package/src/val/NilVal.ts +13 -3
  160. package/src/val/NullVal.ts +1 -1
  161. package/src/val/NumberVal.ts +2 -2
  162. package/src/val/OpBaseVal.ts +10 -10
  163. package/src/val/OpenFuncVal.ts +1 -1
  164. package/src/val/PathFuncVal.ts +1 -1
  165. package/src/val/PlusOpVal.ts +1 -1
  166. package/src/val/PrefFuncVal.ts +1 -1
  167. package/src/val/PrefVal.ts +4 -3
  168. package/src/val/RefVal.ts +82 -9
  169. package/src/val/ScalarKindVal.ts +8 -2
  170. package/src/val/ScalarVal.ts +2 -2
  171. package/src/val/StringVal.ts +1 -1
  172. package/src/val/SuperFuncVal.ts +1 -1
  173. package/src/val/TopVal.ts +1 -1
  174. package/src/val/TypeFuncVal.ts +1 -1
  175. package/src/val/UpperFuncVal.ts +1 -1
  176. package/src/val/Val.ts +13 -8
  177. package/src/val/VarVal.ts +1 -1
  178. package/src/val/numcmp.ts +189 -0
  179. 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
+ }
@@ -74,7 +74,7 @@ class CopyFuncVal extends FuncBaseVal {
74
74
 
75
75
  return out
76
76
  }
77
- }
77
+ } /* node:coverage ignore next 6 */
78
78
 
79
79
 
80
80
  export {
@@ -371,7 +371,7 @@ function readBigLiteral(m: RegExpExecArray | (string | undefined)[]): BigLiteral
371
371
  leaf: 'bigdecimal',
372
372
  dec: new Decimal(BigInt(intd + (fracd ?? '')), scale),
373
373
  }
374
- }
374
+ } /* node:coverage ignore next 12 */
375
375
 
376
376
 
377
377
  export {
@@ -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
 
@@ -87,7 +87,7 @@ class ExpectVal extends FeatureVal {
87
87
  ',parent=' + this.parent?.inspect(d)
88
88
  }
89
89
 
90
- }
90
+ } /* node:coverage ignore next 6 */
91
91
 
92
92
 
93
93
  export {
@@ -48,7 +48,7 @@ abstract class FeatureVal extends Val {
48
48
  return undefined
49
49
  }
50
50
 
51
- }
51
+ } /* node:coverage ignore next 6 */
52
52
 
53
53
 
54
54
  export {
@@ -197,7 +197,7 @@ class FuncBaseVal extends FeatureVal {
197
197
  }
198
198
 
199
199
 
200
- }
200
+ } /* node:coverage ignore next 6 */
201
201
 
202
202
 
203
203
  export {
@@ -77,7 +77,7 @@ class HideFuncVal extends FuncBaseVal {
77
77
 
78
78
  return out
79
79
  }
80
- }
80
+ } /* node:coverage ignore next 6 */
81
81
 
82
82
 
83
83
  export {
@@ -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 {
@@ -46,7 +46,7 @@ abstract class JunctionVal extends FeatureVal {
46
46
 
47
47
  // Abstract method to be implemented by subclasses to define their junction symbol
48
48
  abstract getJunctionSymbol(): string
49
- }
49
+ } /* node:coverage ignore next 6 */
50
50
 
51
51
 
52
52
  export {
@@ -127,7 +127,7 @@ class KeyFuncVal extends FuncBaseVal {
127
127
  return undefined
128
128
  }
129
129
 
130
- }
130
+ } /* node:coverage ignore next 6 */
131
131
 
132
132
 
133
133
  export {