aontu 0.50.1 → 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 (197) hide show
  1. package/bin/aontu-lsp.js +6 -0
  2. package/bin/aontu.js +7 -0
  3. package/dist/aontu.d.ts +4 -2
  4. package/dist/aontu.js +21 -8
  5. package/dist/aontu.js.map +1 -1
  6. package/dist/cli.d.ts +0 -1
  7. package/dist/cli.js +43 -53
  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.d.ts +37 -0
  14. package/dist/exactjson.js +228 -0
  15. package/dist/exactjson.js.map +1 -0
  16. package/dist/hints.d.ts +3 -1
  17. package/dist/hints.js +187 -5
  18. package/dist/hints.js.map +1 -1
  19. package/dist/keyorder.d.ts +9 -0
  20. package/dist/keyorder.js +65 -0
  21. package/dist/keyorder.js.map +1 -0
  22. package/dist/lang.js +263 -30
  23. package/dist/lang.js.map +1 -1
  24. package/dist/lsp-server.d.ts +2 -2
  25. package/dist/lsp-server.js +10 -10
  26. package/dist/lsp-server.js.map +1 -1
  27. package/dist/lsp.js +39 -21
  28. package/dist/lsp.js.map +1 -1
  29. package/dist/site.js +1 -1
  30. package/dist/site.js.map +1 -1
  31. package/dist/tsconfig.tsbuildinfo +1 -1
  32. package/dist/unify.js +48 -1
  33. package/dist/unify.js.map +1 -1
  34. package/dist/utility.js +3 -8
  35. package/dist/utility.js.map +1 -1
  36. package/dist/val/BagVal.js +9 -6
  37. package/dist/val/BagVal.js.map +1 -1
  38. package/dist/val/BigDecimalVal.d.ts +13 -0
  39. package/dist/val/BigDecimalVal.js +80 -0
  40. package/dist/val/BigDecimalVal.js.map +1 -0
  41. package/dist/val/BigIntegerVal.d.ts +11 -0
  42. package/dist/val/BigIntegerVal.js +75 -0
  43. package/dist/val/BigIntegerVal.js.map +1 -0
  44. package/dist/val/BooleanVal.js +1 -1
  45. package/dist/val/BooleanVal.js.map +1 -1
  46. package/dist/val/CloseFuncVal.js +1 -1
  47. package/dist/val/CloseFuncVal.js.map +1 -1
  48. package/dist/val/ConjunctVal.js +1 -1
  49. package/dist/val/ConjunctVal.js.map +1 -1
  50. package/dist/val/ConstraintVal.d.ts +56 -0
  51. package/dist/val/ConstraintVal.js +407 -0
  52. package/dist/val/ConstraintVal.js.map +1 -0
  53. package/dist/val/CopyFuncVal.js +1 -1
  54. package/dist/val/CopyFuncVal.js.map +1 -1
  55. package/dist/val/Decimal.d.ts +32 -0
  56. package/dist/val/Decimal.js +318 -0
  57. package/dist/val/Decimal.js.map +1 -0
  58. package/dist/val/DisjunctVal.js +1 -29
  59. package/dist/val/DisjunctVal.js.map +1 -1
  60. package/dist/val/ExpectVal.js +1 -1
  61. package/dist/val/ExpectVal.js.map +1 -1
  62. package/dist/val/FeatureVal.js +1 -1
  63. package/dist/val/FeatureVal.js.map +1 -1
  64. package/dist/val/FuncBaseVal.js +1 -1
  65. package/dist/val/FuncBaseVal.js.map +1 -1
  66. package/dist/val/HideFuncVal.js +1 -1
  67. package/dist/val/HideFuncVal.js.map +1 -1
  68. package/dist/val/IntegerVal.d.ts +2 -0
  69. package/dist/val/IntegerVal.js +77 -5
  70. package/dist/val/IntegerVal.js.map +1 -1
  71. package/dist/val/JunctionVal.js +1 -1
  72. package/dist/val/JunctionVal.js.map +1 -1
  73. package/dist/val/KeyFuncVal.d.ts +1 -1
  74. package/dist/val/KeyFuncVal.js +34 -4
  75. package/dist/val/KeyFuncVal.js.map +1 -1
  76. package/dist/val/ListVal.js +7 -7
  77. package/dist/val/ListVal.js.map +1 -1
  78. package/dist/val/LowerFuncVal.js +21 -4
  79. package/dist/val/LowerFuncVal.js.map +1 -1
  80. package/dist/val/MapVal.js +17 -13
  81. package/dist/val/MapVal.js.map +1 -1
  82. package/dist/val/MoveFuncVal.js +26 -9
  83. package/dist/val/MoveFuncVal.js.map +1 -1
  84. package/dist/val/NilVal.d.ts +1 -0
  85. package/dist/val/NilVal.js +10 -3
  86. package/dist/val/NilVal.js.map +1 -1
  87. package/dist/val/NullVal.js +1 -1
  88. package/dist/val/NullVal.js.map +1 -1
  89. package/dist/val/NumberVal.d.ts +1 -0
  90. package/dist/val/NumberVal.js +25 -4
  91. package/dist/val/NumberVal.js.map +1 -1
  92. package/dist/val/OpBaseVal.js +10 -8
  93. package/dist/val/OpBaseVal.js.map +1 -1
  94. package/dist/val/OpenFuncVal.js +1 -1
  95. package/dist/val/OpenFuncVal.js.map +1 -1
  96. package/dist/val/PathFuncVal.js +10 -2
  97. package/dist/val/PathFuncVal.js.map +1 -1
  98. package/dist/val/PlusOpVal.d.ts +3 -1
  99. package/dist/val/PlusOpVal.js +140 -29
  100. package/dist/val/PlusOpVal.js.map +1 -1
  101. package/dist/val/PrefFuncVal.js +1 -1
  102. package/dist/val/PrefFuncVal.js.map +1 -1
  103. package/dist/val/PrefVal.d.ts +2 -0
  104. package/dist/val/PrefVal.js +44 -7
  105. package/dist/val/PrefVal.js.map +1 -1
  106. package/dist/val/RefVal.d.ts +2 -0
  107. package/dist/val/RefVal.js +135 -11
  108. package/dist/val/RefVal.js.map +1 -1
  109. package/dist/val/ScalarKindVal.d.ts +12 -2
  110. package/dist/val/ScalarKindVal.js +108 -13
  111. package/dist/val/ScalarKindVal.js.map +1 -1
  112. package/dist/val/ScalarVal.d.ts +1 -0
  113. package/dist/val/ScalarVal.js +18 -3
  114. package/dist/val/ScalarVal.js.map +1 -1
  115. package/dist/val/StringVal.js +1 -1
  116. package/dist/val/StringVal.js.map +1 -1
  117. package/dist/val/SuperFuncVal.d.ts +1 -1
  118. package/dist/val/SuperFuncVal.js +15 -2
  119. package/dist/val/SuperFuncVal.js.map +1 -1
  120. package/dist/val/TopVal.js +1 -1
  121. package/dist/val/TopVal.js.map +1 -1
  122. package/dist/val/TypeFuncVal.js +1 -1
  123. package/dist/val/TypeFuncVal.js.map +1 -1
  124. package/dist/val/UpperFuncVal.js +21 -4
  125. package/dist/val/UpperFuncVal.js.map +1 -1
  126. package/dist/val/Val.d.ts +1 -1
  127. package/dist/val/Val.js +11 -21
  128. package/dist/val/Val.js.map +1 -1
  129. package/dist/val/VarVal.js +8 -2
  130. package/dist/val/VarVal.js.map +1 -1
  131. package/dist/val/numcmp.d.ts +14 -0
  132. package/dist/val/numcmp.js +134 -0
  133. package/dist/val/numcmp.js.map +1 -0
  134. package/dist/val/numkind.d.ts +6 -0
  135. package/dist/val/numkind.js +224 -0
  136. package/dist/val/numkind.js.map +1 -0
  137. package/dist/val/valutil.d.ts +1 -0
  138. package/dist/val/valutil.js +35 -0
  139. package/dist/val/valutil.js.map +1 -1
  140. package/package.json +7 -5
  141. package/src/aontu.ts +26 -9
  142. package/src/cli.ts +48 -21
  143. package/src/ctx.ts +1 -1
  144. package/src/err.ts +3 -2
  145. package/src/exactjson.ts +254 -0
  146. package/src/hints.ts +213 -4
  147. package/src/keyorder.ts +73 -0
  148. package/src/lang.ts +303 -31
  149. package/src/lsp-server.ts +17 -15
  150. package/src/lsp.ts +37 -14
  151. package/src/site.ts +1 -1
  152. package/src/type.ts +1 -1
  153. package/src/unify.ts +52 -1
  154. package/src/utility.ts +3 -9
  155. package/src/val/BagVal.ts +9 -6
  156. package/src/val/BigDecimalVal.ts +117 -0
  157. package/src/val/BigIntegerVal.ts +110 -0
  158. package/src/val/BooleanVal.ts +1 -1
  159. package/src/val/CloseFuncVal.ts +1 -1
  160. package/src/val/ConjunctVal.ts +1 -1
  161. package/src/val/ConstraintVal.ts +561 -0
  162. package/src/val/CopyFuncVal.ts +1 -1
  163. package/src/val/Decimal.ts +385 -0
  164. package/src/val/DisjunctVal.ts +1 -37
  165. package/src/val/ExpectVal.ts +1 -1
  166. package/src/val/FeatureVal.ts +1 -1
  167. package/src/val/FuncBaseVal.ts +1 -1
  168. package/src/val/HideFuncVal.ts +1 -1
  169. package/src/val/IntegerVal.ts +81 -5
  170. package/src/val/JunctionVal.ts +1 -1
  171. package/src/val/KeyFuncVal.ts +36 -4
  172. package/src/val/ListVal.ts +3 -5
  173. package/src/val/LowerFuncVal.ts +22 -5
  174. package/src/val/MapVal.ts +13 -9
  175. package/src/val/MoveFuncVal.ts +27 -10
  176. package/src/val/NilVal.ts +13 -3
  177. package/src/val/NullVal.ts +1 -1
  178. package/src/val/NumberVal.ts +27 -4
  179. package/src/val/OpBaseVal.ts +10 -10
  180. package/src/val/OpenFuncVal.ts +1 -1
  181. package/src/val/PathFuncVal.ts +10 -2
  182. package/src/val/PlusOpVal.ts +168 -32
  183. package/src/val/PrefFuncVal.ts +1 -1
  184. package/src/val/PrefVal.ts +70 -8
  185. package/src/val/RefVal.ts +143 -11
  186. package/src/val/ScalarKindVal.ts +123 -12
  187. package/src/val/ScalarVal.ts +20 -3
  188. package/src/val/StringVal.ts +1 -1
  189. package/src/val/SuperFuncVal.ts +15 -2
  190. package/src/val/TopVal.ts +1 -1
  191. package/src/val/TypeFuncVal.ts +1 -1
  192. package/src/val/UpperFuncVal.ts +22 -5
  193. package/src/val/Val.ts +13 -8
  194. package/src/val/VarVal.ts +8 -2
  195. package/src/val/numcmp.ts +189 -0
  196. package/src/val/numkind.ts +256 -0
  197. package/src/val/valutil.ts +36 -0
package/src/lang.ts CHANGED
@@ -63,9 +63,18 @@ import {
63
63
 
64
64
 
65
65
 
66
- import { ScalarKindVal, Integer } from './val/ScalarKindVal'
66
+ import {
67
+ ScalarKindVal,
68
+ BigDecimal,
69
+ BigInteger,
70
+ Float,
71
+ Integer,
72
+ } from './val/ScalarKindVal'
67
73
 
68
74
 
75
+ import { BigDecimalVal } from './val/BigDecimalVal'
76
+ import { BigIntegerVal } from './val/BigIntegerVal'
77
+ import { BIG_LITERAL_RE, readBigLiteral } from './val/Decimal'
69
78
  import { BooleanVal } from './val/BooleanVal'
70
79
  import { ConjunctVal } from './val/ConjunctVal'
71
80
  import { DisjunctVal } from './val/DisjunctVal'
@@ -75,6 +84,7 @@ import { MapVal } from './val/MapVal'
75
84
  import { NilVal } from './val/NilVal'
76
85
  import { NullVal } from './val/NullVal'
77
86
  import { NumberVal } from './val/NumberVal'
87
+ import { isIntegerKind, isLossyIntegerLiteral } from './val/numkind'
78
88
  import { PrefVal } from './val/PrefVal'
79
89
  import { RefVal } from './val/RefVal'
80
90
  import { StringVal } from './val/StringVal'
@@ -92,10 +102,58 @@ import { PrefFuncVal } from './val/PrefFuncVal'
92
102
  import { CloseFuncVal } from './val/CloseFuncVal'
93
103
  import { OpenFuncVal } from './val/OpenFuncVal'
94
104
  import { SuperFuncVal } from './val/SuperFuncVal'
105
+ import {
106
+ MinConstraintVal,
107
+ MaxConstraintVal,
108
+ AboveConstraintVal,
109
+ BelowConstraintVal,
110
+ NeqConstraintVal,
111
+ } from './val/ConstraintVal'
95
112
 
96
113
 
97
114
  const asPlugin = (p: unknown): Plugin => p as Plugin
98
115
 
116
+
117
+ // Build the Val for a matched `0d` literal (see the `0d` value matcher
118
+ // below). Leaf by source: digits only is a biginteger, a `.` or an
119
+ // exponent makes it a bigdecimal.
120
+ //
121
+ // A literal over the D6 exactness budget becomes a LOCATED ERROR here
122
+ // and not a rounded or expanded value: `0d1e1000000000` has a one-digit
123
+ // coefficient, so only the scale bound catches it, and it is caught at
124
+ // parse -- before plain-form rendering would try to materialise a
125
+ // gigabyte of zeros.
126
+ // The source text of a negated exact literal. `-` is a prefix OPERATOR,
127
+ // not part of the literal, so the text has to be rebuilt here to keep
128
+ // `src` meaning "how this value is spelled" (see bigVal).
129
+ function negsrc(src: string): string {
130
+ // src is never empty here: it is a BIG_LITERAL_RE match (at minimum
131
+ // the two chars `0d`) or an already-negated spelling. Computed exact
132
+ // values do carry src '', but they are built at unify time, long
133
+ // after this parse-time opmap.
134
+ return src.startsWith('-') ? src.slice(1) : '-' + src
135
+ }
136
+
137
+
138
+ function bigVal(res: RegExpExecArray): Val {
139
+ const lit = readBigLiteral(res)
140
+ // `src` is the literal's own text, and it is not decoration: a path
141
+ // segment is spelled text, so `$.a.0d1` must address the key `0d1` --
142
+ // the same key `a:{0d1:7}` creates -- rather than the number 1. See
143
+ // RefVal.append. Without it the segment was empty and the reference
144
+ // silently resolved to its own container.
145
+ const src = res[0]
146
+ return 'biginteger' === lit.leaf ? new BigIntegerVal({ peg: lit.int, src }) :
147
+ 'bigdecimal' === lit.leaf ? new BigDecimalVal({ peg: lit.dec, src }) :
148
+ new NilVal({ why: lit.code })
149
+ }
150
+
151
+
152
+ // Char codes of the literal's fixed opening, for the guard below.
153
+ const CC_0 = 48
154
+ const CC_d = 100
155
+ const CC_D = 68
156
+
99
157
  let AontuJsonic: Plugin = function AontuLang(jsonic: Jsonic) {
100
158
 
101
159
  jsonic.use(asPlugin(Path))
@@ -104,6 +162,12 @@ let AontuJsonic: Plugin = function AontuLang(jsonic: Jsonic) {
104
162
  // docs/reference-language.md; go/lang.go sets the same). Clear the
105
163
  // underlying jsonic comment markers entirely, then define # directly,
106
164
  // so the comment set is hash-only regardless of those defaults.
165
+ let dotRef = (r: Rule, ctx: JsonicContext, terms: any, prefix: boolean) => {
166
+ terms = dropUnfilled(terms)
167
+ if (0 === terms.length) return incompleteNil(r, ctx)
168
+ return addsite(new RefVal({ peg: terms, prefix }), r, ctx)
169
+ }
170
+
107
171
  jsonic.options({ comment: { def: null } })
108
172
  jsonic.options({
109
173
  comment: {
@@ -114,6 +178,104 @@ let AontuJsonic: Plugin = function AontuLang(jsonic: Jsonic) {
114
178
  },
115
179
  })
116
180
 
181
+ // Digit separators are legal only as a SINGLE separator BETWEEN
182
+ // digits (the rule test/spec/engine-parity.tsv records as the engine's
183
+ // adjudication; pinned by the sep-* rows in
184
+ // test/spec/number-model.tsv). The engine's number matcher
185
+ // enforces most of that already — `1_`, `_1`, `1_.5`, `1._5`, `1e_2`,
186
+ // `1e2_` all fall through to text — but two gaps remain: a REPEATED
187
+ // separator (`1__0` lexed as 10) and a separator at the edge of a
188
+ // base-prefixed digit run (`0x_ff`, `0xff_` lexed as 255). Both
189
+ // silently accept a typo as a different number, so aontu declines the
190
+ // whole run instead and it lexes as text ("1__0"), exactly as `1_`
191
+ // already does.
192
+ //
193
+ // `number.exclude` is tested against the matched number source and,
194
+ // when it matches, makes the matcher decline the entire span. Kept in
195
+ // lock-step with sepInvalid in go/lang.go, which is wired to the
196
+ // equivalent `Number.Exclude` hook (and to the Check hook, which
197
+ // constructs big base-prefixed tokens itself and so bypasses Exclude).
198
+ jsonic.options({
199
+ number: {
200
+ // `__` repeated separator; `0x_`/`0o_`/`0b_` separator opening a
201
+ // base-prefixed run; `_` closing any run. The prefix letter is
202
+ // matched case-insensitively so the rule does not depend on which
203
+ // prefix spellings the engine accepts.
204
+ exclude: /__|^[-+]?0[xXoObB]_|_$/,
205
+ },
206
+ })
207
+
208
+ // D3 -- the `0d` literal, the only route to the exact leaves
209
+ // (biginteger and bigdecimal). See BIG_LITERAL_RE for the grammar and
210
+ // the leaf-by-source rule.
211
+ //
212
+ // The literal is claimed by the TEXT MATCHER'S CHECK HOOK (the sibling
213
+ // of the `Number.Check` hook the Go port already uses for big
214
+ // base-prefixed literals), not by a `value.def` entry, because a `0d`
215
+ // run may contain a `.` and the two claim source differently:
216
+ //
217
+ // - A value def -- even a consuming one, matched against the full
218
+ // forward source -- is applied INSIDE the text matcher, AFTER its
219
+ // ender regexp has already carved the run at the `.`. The def
220
+ // claims `0d1.5` whole, but the matcher then still emits the
221
+ // ender's `.` as a fixed token, so `x:0d1.5` lexed as the
222
+ // bigdecimal FOLLOWED BY a dangling member-access dot (a path
223
+ // cycle). Verified, not theorised.
224
+ // - The check hook runs BEFORE that ender regexp and returns the
225
+ // token outright, so the run is claimed whole and nothing else is
226
+ // emitted.
227
+ //
228
+ // A `match.value` matcher (which runs ahead of every other matcher)
229
+ // also claims it correctly, but it is a candidate at EVERY lex
230
+ // position and materializes the forward source there: ~8% on a
231
+ // text-heavy document, for a syntax almost none of them use. The check
232
+ // hook only runs where the text matcher already runs, and measures at
233
+ // parity with not having it.
234
+ //
235
+ // The number matcher never sees these runs at all: it declines `0d…`
236
+ // outright, since `d` is not an ender.
237
+ jsonic.options({
238
+ text: {
239
+ check: (lex: any) => {
240
+ // Guard first, on char codes: this hook runs at every text
241
+ // position, and the common case (any run that cannot be a `0d`
242
+ // literal) must cost two char reads and no allocation.
243
+ const pnt = lex.pnt
244
+ const src = lex.src
245
+ if (CC_0 !== src.charCodeAt(pnt.sI)) {
246
+ return undefined
247
+ }
248
+ const c1 = src.charCodeAt(pnt.sI + 1)
249
+ if (CC_d !== c1 && CC_D !== c1) {
250
+ return undefined
251
+ }
252
+
253
+ // BIG_LITERAL_RE is `^`-anchored and read against the forward
254
+ // source (memoized per position by refwd), which is what lets
255
+ // it claim the `.` of `0d1.5`.
256
+ const res = BIG_LITERAL_RE.exec(lex.refwd())
257
+ if (null == res) {
258
+ return undefined
259
+ }
260
+
261
+ const msrc = res[0]
262
+ // The token value is a FUNCTION so Val construction happens at
263
+ // parse time, where the rule and context needed for the site
264
+ // exist (jsonic calls a #VL token's function value with them).
265
+ // A `0d` literal never spans a line, so only the source and
266
+ // column positions advance.
267
+ const tkn = lex.token(
268
+ '#VL',
269
+ (r: Rule, ctx: JsonicContext) => addsite(bigVal(res), r, ctx),
270
+ msrc,
271
+ pnt)
272
+ pnt.sI += msrc.length
273
+ pnt.cI += msrc.length
274
+ return { done: true, token: tkn }
275
+ },
276
+ },
277
+ })
278
+
117
279
  // TODO: refactor Val constructor
118
280
  // let addsite = (v: Val, p: string[]) => (v.path = [...(p || [])], v)
119
281
  let addsite = (v: Val, r: Rule, ctx: JsonicContext) => {
@@ -121,7 +283,8 @@ let AontuJsonic: Plugin = function AontuLang(jsonic: Jsonic) {
121
283
  v.site.row = null == r.o0 ? -1 : r.o0.rI
122
284
  v.site.col = null == r.o0 ? -1 : r.o0.cI
123
285
  v.site.url = ctx.meta.multisource ? ctx.meta.multisource.path : ''
124
- v.path = r.k ? [...(r.k.path || [])] : []
286
+ // A keyed rule always carries a path array; a keyless one has none.
287
+ v.path = r.k ? [...r.k.path] : []
125
288
 
126
289
  return v
127
290
  }
@@ -161,6 +324,9 @@ help isolate the syntax error.`,
161
324
  val: (r: Rule, ctx: JsonicContext) =>
162
325
  addsite(new ScalarKindVal({ peg: String }), r, ctx)
163
326
  },
327
+ // `number` is a pure supertype: it matches a concrete value of
328
+ // any numeric leaf and never tags one itself. `integer` and
329
+ // `float` are the leaves (see ScalarKindVal for the lattice).
164
330
  'number': {
165
331
  val: (r: Rule, ctx: JsonicContext) =>
166
332
  addsite(new ScalarKindVal({ peg: Number }), r, ctx)
@@ -169,6 +335,21 @@ help isolate the syntax error.`,
169
335
  val: (r: Rule, ctx: JsonicContext) =>
170
336
  addsite(new ScalarKindVal({ peg: Integer }), r, ctx)
171
337
  },
338
+ 'float': {
339
+ val: (r: Rule, ctx: JsonicContext) =>
340
+ addsite(new ScalarKindVal({ peg: Float }), r, ctx)
341
+ },
342
+ // The two exact leaves. Their keywords are the marker class
343
+ // names lowercased, which is also how ScalarKindVal canons them.
344
+ 'biginteger': {
345
+ val: (r: Rule, ctx: JsonicContext) =>
346
+ addsite(new ScalarKindVal({ peg: BigInteger }), r, ctx)
347
+ },
348
+ 'bigdecimal': {
349
+ val: (r: Rule, ctx: JsonicContext) =>
350
+ addsite(new ScalarKindVal({ peg: BigDecimal }), r, ctx)
351
+ },
352
+
172
353
  'boolean': {
173
354
  val: (r: Rule, ctx: JsonicContext) =>
174
355
  addsite(new ScalarKindVal({ peg: Boolean }), r, ctx)
@@ -229,6 +410,16 @@ help isolate the syntax error.`,
229
410
  close: CloseFuncVal,
230
411
  open: OpenFuncVal,
231
412
  super: SuperFuncVal,
413
+
414
+ // The constraint algebra's Band A atoms (G1 phase 1;
415
+ // docs/reference-language.md, "The constraint algebra"): bounds
416
+ // and exclusion enter through the function registry — the
417
+ // established extension point — with zero grammar change.
418
+ min: MinConstraintVal,
419
+ max: MaxConstraintVal,
420
+ above: AboveConstraintVal,
421
+ below: BelowConstraintVal,
422
+ neq: NeqConstraintVal,
232
423
  }
233
424
 
234
425
 
@@ -249,17 +440,14 @@ help isolate the syntax error.`,
249
440
  'disjunct-infix': (r: Rule, ctx: JsonicContext, _op: Op, terms: any) =>
250
441
  addsite(new DisjunctVal({ peg: dropUnfilled(terms) }), r, ctx),
251
442
 
252
- 'dot-prefix': (r: Rule, ctx: JsonicContext, _op: Op, terms: any) => {
253
- terms = dropUnfilled(terms)
254
- if (0 === terms.length) return incompleteNil(r, ctx)
255
- return addsite(new RefVal({ peg: terms, prefix: true }), r, ctx)
256
- },
443
+ // `.a` (prefix) and `a.b` (infix) build the same reference; only the
444
+ // prefix flag differs, and both need the same missing-operand guard,
445
+ // so they share one builder.
446
+ 'dot-prefix': (r: Rule, ctx: JsonicContext, _op: Op, terms: any) =>
447
+ dotRef(r, ctx, terms, true),
257
448
 
258
- 'dot-infix': (r: Rule, ctx: JsonicContext, _op: Op, terms: any) => {
259
- terms = dropUnfilled(terms)
260
- if (0 === terms.length) return incompleteNil(r, ctx)
261
- return addsite(new RefVal({ peg: terms }), r, ctx)
262
- },
449
+ 'dot-infix': (r: Rule, ctx: JsonicContext, _op: Op, terms: any) =>
450
+ dotRef(r, ctx, terms, false),
263
451
 
264
452
  'star-prefix': (r: Rule, ctx: JsonicContext, _op: Op, terms: any) => {
265
453
  if (null == terms[0]) return incompleteNil(r, ctx)
@@ -284,6 +472,18 @@ help isolate the syntax error.`,
284
472
  'negative-prefix': (r: Rule, ctx: JsonicContext, _op: Op, terms: any) => {
285
473
  let val = terms[0]
286
474
  if (null == val) return incompleteNil(r, ctx)
475
+ // The exact leaves negate exactly and never change kind. R2/D5
476
+ // holds here too: bigint has a single zero, and Decimal's
477
+ // normalising constructor sends every zero to the same form, so
478
+ // `-0d0` is `0d0` and `-0d0.0` is `0d0.0`.
479
+ if (val instanceof BigIntegerVal) {
480
+ return addsite(new BigIntegerVal(
481
+ { peg: -val.peg, src: negsrc(val.src) }), r, ctx)
482
+ }
483
+ if (val instanceof BigDecimalVal) {
484
+ return addsite(new BigDecimalVal(
485
+ { peg: val.peg.negate(), src: negsrc(val.src) }), r, ctx)
486
+ }
287
487
  // Negating a non-numeric operand (`k-x` splits into k, -x) is an
288
488
  // error nil, not NaN (mirrors negate() in go/lang.go).
289
489
  if (!(val instanceof IntegerVal) && !(val instanceof NumberVal)) {
@@ -296,7 +496,11 @@ help isolate the syntax error.`,
296
496
  let peg = -1 * val.peg
297
497
  // Normalize -0 to 0 (keeps the AST and canon free of negative zero).
298
498
  if (0 === peg) peg = 0
299
- const out = val instanceof IntegerVal
499
+ // Negation never narrows the kind: a number stays a number. An
500
+ // integer stays an integer unless the negation leaves the int64
501
+ // range (only -(-2^63), which no literal can express), in which
502
+ // case it widens to a number rather than failing.
503
+ const out = val instanceof IntegerVal && isIntegerKind(peg)
300
504
  ? new IntegerVal({ peg })
301
505
  : new NumberVal({ peg })
302
506
  return addsite(out, r, ctx)
@@ -347,6 +551,28 @@ help isolate the syntax error.`,
347
551
  right: 21_000_000,
348
552
  },
349
553
 
554
+ // Re-base the unary prefixes for the same reason. Every aontu
555
+ // operator sits far above the @tabnas/expr defaults, so the
556
+ // default prefix binding power of 4_000_000 left unary `-`/`+`
557
+ // LOOSER than every infix operator: `-1 & integer` parsed as
558
+ // `-(1 & integer)`, and negative-prefix (below) then rejected
559
+ // the composite operand as a `negative` error nil (likewise
560
+ // `-2+3`, `-1|2`). Unary minus must bind tighter than `+`, `&`
561
+ // and `|` — but still looser than `.` (dot-infix left is
562
+ // 25_000_000), so `-0xFF.5` stays `-(0xFF.5)` and `$`.
563
+ // Kept in lock-step with the op table in go/lang.go.
564
+ 'negative': {
565
+ src: '-',
566
+ prefix: true,
567
+ right: 22_000_000,
568
+ },
569
+
570
+ 'positive': {
571
+ src: '+',
572
+ prefix: true,
573
+ right: 22_000_000,
574
+ },
575
+
350
576
  'dollar-prefix': {
351
577
  src: '$',
352
578
  prefix: true,
@@ -489,8 +715,28 @@ help isolate the syntax error.`,
489
715
  if (!Number.isFinite(r.node)) {
490
716
  valnode = addsite(new NilVal({ why: 'not_number' }), r, ctx)
491
717
  }
492
- // 1.0 in source is *not* an integer
493
- else if (Number.isInteger(r.node) && !r.o0.src.includes('.')) {
718
+ // D7 -- A LOSSY INTEGER LITERAL IS REFUSED, NOT ROUNDED. The
719
+ // token above is already a double, so a literal the double
720
+ // cannot hold exactly (2^53+1, 0x7fffffffffffffff,
721
+ // 0xffffffffffffffff) has ALREADY become a different number by
722
+ // the time it gets here. Storing it would mean the document
723
+ // silently means something other than what it says, so the
724
+ // literal becomes a located error whose hint names the escape:
725
+ // write it `0d…` and get the exact value.
726
+ //
727
+ // The rule is EXACTNESS, not magnitude -- 10^20 and 2^124 are
728
+ // both far outside the int64 window and both land exactly on a
729
+ // binary64, so both stay values (see isLossyIntegerLiteral).
730
+ else if (isLossyIntegerLiteral(r.node, r.o0.src)) {
731
+ const nil = new NilVal({ why: 'lossy_integer_literal' })
732
+ nil.details = { src: r.o0.src }
733
+ valnode = addsite(nil, r, ctx)
734
+ }
735
+ // A literal is integer kind only if its source has no '.', its
736
+ // value is integral, and it fits the int64 range: `1.0` is a
737
+ // number, and so are 1e21 and 100000000000000000000 (see
738
+ // isIntegerKind).
739
+ else if (isIntegerKind(r.node, r.o0.src)) {
494
740
  valnode = addsite(new IntegerVal({ peg: r.node, src: r.o0.src }), r, ctx)
495
741
  }
496
742
  else {
@@ -588,18 +834,10 @@ help isolate the syntax error.`,
588
834
  }
589
835
  }
590
836
 
591
- if (ao.___merge) {
592
- let aop = [...ao]
593
- delete (aop as any).___merge
594
-
595
- // TODO: needs addpath?
596
- let aopv = new ListVal({ peg: aop })
597
- aopv.optionalKeys = optionalKeys
598
-
599
- r.node =
600
- addsite(new ConjunctVal({ peg: [aopv, ...ao.___merge] }), r, ctx)
601
- }
602
- else {
837
+ // No ___merge arm here: the deferred map.merge that writes it
838
+ // only ever fires for a `pair` rule, whose parent is always a
839
+ // `map` rule with a plain-object node — never a list.
840
+ {
603
841
  r.node = addsite(new ListVal({ peg: ao }), r, ctx)
604
842
  r.node.optionalKeys = optionalKeys
605
843
  }
@@ -779,7 +1017,10 @@ function makeModelResolver(options: any) {
779
1017
  jsonic: Tabnas
780
1018
  ) {
781
1019
 
782
- let path = 'string' === typeof spec ? spec : spec?.peg
1020
+ // The aontu val rule's ac has already wrapped every raw string node
1021
+ // as a StringVal, so spec is a Val here (or a raw object from a
1022
+ // .json/.js include, whose peg is undefined -> not found).
1023
+ let path = spec?.peg
783
1024
 
784
1025
  // A bare `@` with no path (`a:@`) has nothing to resolve; report
785
1026
  // not-found instead of crashing in the underlying resolvers.
@@ -821,6 +1062,34 @@ function makeModelResolver(options: any) {
821
1062
  // the aontu val rule conversions (mirrors asVal in go/lang.go; like
822
1063
  // there, source text is unavailable, so an integral number is an
823
1064
  // integer).
1065
+ // The targeted parse hint for CUE-trained authors and models: `>` and
1066
+ // `<` are not Aontu operators (the op-chars reservation stands), and an
1067
+ // agent that emits `number > 0` should be redirected to the bound
1068
+ // atoms, not left with a bare "unexpected character". Appended to a
1069
+ // parse error's message when the source carries an unquoted `<` or
1070
+ // `>`; the Go twin is opCharHint in go/lang.go, byte-identical text.
1071
+ function opCharHint(src: string): string {
1072
+ let q = ''
1073
+ for (let i = 0; i < src.length; i++) {
1074
+ const c = src[i]
1075
+ if ('' !== q) {
1076
+ if (c === q && '\\' !== src[i - 1]) {
1077
+ q = ''
1078
+ }
1079
+ continue
1080
+ }
1081
+ if ('"' === c || '\'' === c || '`' === c) {
1082
+ q = c
1083
+ }
1084
+ else if ('<' === c || '>' === c) {
1085
+ return '\nThe > and < characters are not Aontu operators: write the ' +
1086
+ 'bound functions min(x), max(x), above(x), below(x) instead.'
1087
+ }
1088
+ }
1089
+ return ''
1090
+ }
1091
+
1092
+
824
1093
  function rawToVal(n: any): Val {
825
1094
  if (null == n) {
826
1095
  return new NullVal({ peg: null })
@@ -836,7 +1105,10 @@ function rawToVal(n: any): Val {
836
1105
  return new StringVal({ peg: n })
837
1106
  }
838
1107
  if ('number' === t) {
839
- return Number.isInteger(n) ?
1108
+ // No source text here, so the "no '.'" condition is vacuous and the
1109
+ // integral + int64-range conditions decide (same helper as the val
1110
+ // rule, so the two paths cannot drift).
1111
+ return isIntegerKind(n) ?
840
1112
  new IntegerVal({ peg: n }) : new NumberVal({ peg: n })
841
1113
  }
842
1114
  if ('boolean' === t) {
@@ -940,7 +1212,7 @@ class Lang {
940
1212
  why: 'parse',
941
1213
  err: new NilVal({
942
1214
  why: 'syntax',
943
- msg: e.message,
1215
+ msg: e.message + opCharHint(src),
944
1216
  err: e,
945
1217
  })
946
1218
  })
@@ -952,7 +1224,7 @@ class Lang {
952
1224
 
953
1225
  return val
954
1226
  }
955
- }
1227
+ } /* node:coverage ignore next 6 */
956
1228
 
957
1229
  export {
958
1230
  Lang,
package/src/lsp-server.ts CHANGED
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  /* Copyright (c) 2025 Richard Rodger, MIT License */
3
2
 
4
3
  // Aontu Language Server (stdio).
@@ -85,25 +84,28 @@ class FrameCodec {
85
84
  }
86
85
 
87
86
 
88
- function main() {
87
+ // The streams and exit are injectable (defaulting to real stdio) so the
88
+ // full wiring is unit-testable — the same shape as the Go server's
89
+ // serve(in, out, logw).
90
+ function main(
91
+ stdin: NodeJS.ReadableStream = process.stdin,
92
+ write: (chunk: Buffer) => void = (chunk) => void process.stdout.write(chunk),
93
+ exit: (code: number) => void = (code) => process.exit(code),
94
+ ): FrameCodec {
89
95
  const handler = new LspHandler()
90
- const codec = new FrameCodec(
91
- handler,
92
- (chunk) => process.stdout.write(chunk),
93
- (code) => process.exit(code),
94
- )
95
-
96
- process.stdin.on('data', (chunk: Buffer) => codec.push(chunk))
97
- process.stdin.on('end', () => codec.end())
98
- }
96
+ const codec = new FrameCodec(handler, write, exit)
99
97
 
98
+ stdin.on('data', (chunk: Buffer) => codec.push(chunk))
99
+ stdin.on('end', () => codec.end())
100
+ return codec
101
+ } /* node:coverage ignore next 11 */
100
102
 
101
- // Only auto-run when invoked as a program, not when imported by tests.
102
- if (require.main === module) {
103
- main()
104
- }
103
+
104
+ // No require.main guard here: bin/aontu-lsp.js is the executable entry
105
+ // and calls main() itself, so this module stays import-only.
105
106
 
106
107
 
107
108
  export {
108
109
  FrameCodec,
110
+ main,
109
111
  }
package/src/lsp.ts CHANGED
@@ -78,8 +78,9 @@ function computeDiagnostics(
78
78
  const aontu = new Aontu()
79
79
 
80
80
  let root: any
81
+ let ac: any
81
82
  try {
82
- const ac = aontu.ctx({ collect: true })
83
+ ac = aontu.ctx({ collect: true })
83
84
  if (opts?.vars) {
84
85
  Object.assign(ac.vars, opts.vars)
85
86
  }
@@ -92,7 +93,21 @@ function computeDiagnostics(
92
93
  }
93
94
 
94
95
  const nils: any[] = []
95
- walkNils(root, nils, new Set())
96
+ const seen = new Set()
97
+ walkNils(root, nils, seen)
98
+
99
+ // Errors recorded on the context but not present in the tree — e.g. a
100
+ // budget_passes exhaustion nil, which is about the whole evaluation
101
+ // rather than any node — would otherwise be invisible here, and the
102
+ // trust contract forbids silent truncation (docs/trust.md clause 2).
103
+ // Tree nils are already on ctx.err too, so dedup by identity; the
104
+ // transient disjunct-trial sentinel never surfaces.
105
+ for (const e of ac.err) {
106
+ if (e?.isNil && '|:trial-nil' !== e.why && !seen.has(e)) {
107
+ seen.add(e)
108
+ nils.push(e)
109
+ }
110
+ }
96
111
 
97
112
  return nils.map(nilToDiagnostic)
98
113
  }
@@ -277,8 +292,8 @@ function collectHoverCandidates(
277
292
  if (seen.has(v)) return
278
293
  seen.add(v)
279
294
 
280
- const row = v.site?.row ?? -1
281
- const col = v.site?.col ?? -1
295
+ const row = v.site.row
296
+ const col = v.site.col
282
297
  let canon = ''
283
298
  try { canon = v.canon } catch { canon = '' }
284
299
  // Hover targets concrete values (scalars, kinds, refs, …), not
@@ -305,8 +320,9 @@ function collectHoverCandidates(
305
320
 
306
321
 
307
322
  function hoverMarkdown(val: any): string {
308
- let canon = ''
309
- try { canon = val.canon } catch { canon = '' }
323
+ // No try needed: a Val only becomes a hover candidate after
324
+ // collectHoverCandidates read this same getter successfully.
325
+ const canon = val.canon
310
326
  return '```aontu\n' + canon + '\n```\n\n' + '*' + valKind(val) + '*'
311
327
  }
312
328
 
@@ -315,11 +331,14 @@ function hoverMarkdown(val: any): string {
315
331
  function valKind(val: any): string {
316
332
  if (val.isNil) return 'error'
317
333
  if (val.isScalarKind) return 'type'
318
- if (val.isMap) return 'map'
319
- if (val.isList) return 'list'
334
+ if (val.isConstraint) return 'constraint'
320
335
  if (val.isRef) return 'reference'
321
336
  if (val.isInteger) return 'integer'
322
- if (val.isNumber) return 'number'
337
+ // NumberVal is the binary64 leaf, whose kind keyword is `float`;
338
+ // `number` is the supertype and never labels a concrete value.
339
+ if (val.isNumber) return 'float'
340
+ if (val.isBigInteger) return 'biginteger'
341
+ if (val.isBigDecimal) return 'bigdecimal'
323
342
  if (val.isString) return 'string'
324
343
  if (val.isBoolean) return 'boolean'
325
344
  if (val.isScalar) return 'scalar'
@@ -339,15 +358,19 @@ type CompletionItem = {
339
358
  const COMPLETION_FUNCTION = 3
340
359
  const COMPLETION_KEYWORD = 14
341
360
 
342
- // The twelve built-in functions. Kept in sync with the engine by
361
+ // The seventeen built-in functions. Kept in sync with the engine by
343
362
  // `lsp.test.ts`, which asserts each is recognised and no others are.
344
363
  const BUILTIN_FUNCS = [
345
- 'close', 'copy', 'hide', 'key', 'lower', 'move',
346
- 'open', 'path', 'pref', 'super', 'type', 'upper',
364
+ 'above', 'below', 'close', 'copy', 'hide', 'key', 'lower', 'max',
365
+ 'min', 'move', 'neq', 'open', 'path', 'pref', 'super', 'type', 'upper',
347
366
  ]
348
367
 
349
368
  // Scalar-kind and literal keywords.
350
- const KIND_KEYWORDS = ['string', 'number', 'integer', 'boolean']
369
+ // `number` is the numeric supertype; `integer`, `float`, `biginteger`
370
+ // and `bigdecimal` are its leaves. New leaves join this list as they land.
371
+ const KIND_KEYWORDS = [
372
+ 'string', 'number', 'integer', 'float', 'biginteger', 'bigdecimal', 'boolean',
373
+ ]
351
374
  const LITERAL_KEYWORDS = ['true', 'false', 'null', 'top']
352
375
 
353
376
 
@@ -456,7 +479,7 @@ class LspHandler {
456
479
  private publish(uri: string): OutMessage {
457
480
  return publishDiagnosticsMsg(uri, computeDiagnostics(this.docs.get(uri) ?? ''))
458
481
  }
459
- }
482
+ } /* node:coverage ignore next 28 */
460
483
 
461
484
 
462
485
  export {
package/src/site.ts CHANGED
@@ -20,7 +20,7 @@ class Site {
20
20
  this.col = site?.col ?? -1
21
21
  this.url = site?.url ?? ''
22
22
  }
23
- }
23
+ } /* node:coverage ignore next 6 */
24
24
 
25
25
 
26
26
  export {
package/src/type.ts CHANGED
@@ -50,7 +50,7 @@ type ValList = Val[]
50
50
  type ErrContext = {
51
51
  src?: string,
52
52
  fs?: FST
53
- }
53
+ } /* node:coverage ignore next 20 */
54
54
 
55
55
  export type {
56
56
  Val,