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
package/src/lang.ts CHANGED
@@ -102,6 +102,13 @@ import { PrefFuncVal } from './val/PrefFuncVal'
102
102
  import { CloseFuncVal } from './val/CloseFuncVal'
103
103
  import { OpenFuncVal } from './val/OpenFuncVal'
104
104
  import { SuperFuncVal } from './val/SuperFuncVal'
105
+ import {
106
+ MinConstraintVal,
107
+ MaxConstraintVal,
108
+ AboveConstraintVal,
109
+ BelowConstraintVal,
110
+ NeqConstraintVal,
111
+ } from './val/ConstraintVal'
105
112
 
106
113
 
107
114
  const asPlugin = (p: unknown): Plugin => p as Plugin
@@ -120,7 +127,11 @@ const asPlugin = (p: unknown): Plugin => p as Plugin
120
127
  // not part of the literal, so the text has to be rebuilt here to keep
121
128
  // `src` meaning "how this value is spelled" (see bigVal).
122
129
  function negsrc(src: string): string {
123
- return '' === src ? '' : src.startsWith('-') ? src.slice(1) : '-' + src
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
124
135
  }
125
136
 
126
137
 
@@ -151,6 +162,12 @@ let AontuJsonic: Plugin = function AontuLang(jsonic: Jsonic) {
151
162
  // docs/reference-language.md; go/lang.go sets the same). Clear the
152
163
  // underlying jsonic comment markers entirely, then define # directly,
153
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
+
154
171
  jsonic.options({ comment: { def: null } })
155
172
  jsonic.options({
156
173
  comment: {
@@ -266,7 +283,8 @@ let AontuJsonic: Plugin = function AontuLang(jsonic: Jsonic) {
266
283
  v.site.row = null == r.o0 ? -1 : r.o0.rI
267
284
  v.site.col = null == r.o0 ? -1 : r.o0.cI
268
285
  v.site.url = ctx.meta.multisource ? ctx.meta.multisource.path : ''
269
- 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] : []
270
288
 
271
289
  return v
272
290
  }
@@ -392,6 +410,16 @@ help isolate the syntax error.`,
392
410
  close: CloseFuncVal,
393
411
  open: OpenFuncVal,
394
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,
395
423
  }
396
424
 
397
425
 
@@ -412,17 +440,14 @@ help isolate the syntax error.`,
412
440
  'disjunct-infix': (r: Rule, ctx: JsonicContext, _op: Op, terms: any) =>
413
441
  addsite(new DisjunctVal({ peg: dropUnfilled(terms) }), r, ctx),
414
442
 
415
- 'dot-prefix': (r: Rule, ctx: JsonicContext, _op: Op, terms: any) => {
416
- terms = dropUnfilled(terms)
417
- if (0 === terms.length) return incompleteNil(r, ctx)
418
- return addsite(new RefVal({ peg: terms, prefix: true }), r, ctx)
419
- },
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),
420
448
 
421
- 'dot-infix': (r: Rule, ctx: JsonicContext, _op: Op, terms: any) => {
422
- terms = dropUnfilled(terms)
423
- if (0 === terms.length) return incompleteNil(r, ctx)
424
- return addsite(new RefVal({ peg: terms }), r, ctx)
425
- },
449
+ 'dot-infix': (r: Rule, ctx: JsonicContext, _op: Op, terms: any) =>
450
+ dotRef(r, ctx, terms, false),
426
451
 
427
452
  'star-prefix': (r: Rule, ctx: JsonicContext, _op: Op, terms: any) => {
428
453
  if (null == terms[0]) return incompleteNil(r, ctx)
@@ -809,18 +834,10 @@ help isolate the syntax error.`,
809
834
  }
810
835
  }
811
836
 
812
- if (ao.___merge) {
813
- let aop = [...ao]
814
- delete (aop as any).___merge
815
-
816
- // TODO: needs addpath?
817
- let aopv = new ListVal({ peg: aop })
818
- aopv.optionalKeys = optionalKeys
819
-
820
- r.node =
821
- addsite(new ConjunctVal({ peg: [aopv, ...ao.___merge] }), r, ctx)
822
- }
823
- 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
+ {
824
841
  r.node = addsite(new ListVal({ peg: ao }), r, ctx)
825
842
  r.node.optionalKeys = optionalKeys
826
843
  }
@@ -1000,7 +1017,10 @@ function makeModelResolver(options: any) {
1000
1017
  jsonic: Tabnas
1001
1018
  ) {
1002
1019
 
1003
- 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
1004
1024
 
1005
1025
  // A bare `@` with no path (`a:@`) has nothing to resolve; report
1006
1026
  // not-found instead of crashing in the underlying resolvers.
@@ -1042,6 +1062,34 @@ function makeModelResolver(options: any) {
1042
1062
  // the aontu val rule conversions (mirrors asVal in go/lang.go; like
1043
1063
  // there, source text is unavailable, so an integral number is an
1044
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
+
1045
1093
  function rawToVal(n: any): Val {
1046
1094
  if (null == n) {
1047
1095
  return new NullVal({ peg: null })
@@ -1164,7 +1212,7 @@ class Lang {
1164
1212
  why: 'parse',
1165
1213
  err: new NilVal({
1166
1214
  why: 'syntax',
1167
- msg: e.message,
1215
+ msg: e.message + opCharHint(src),
1168
1216
  err: e,
1169
1217
  })
1170
1218
  })
@@ -1176,7 +1224,7 @@ class Lang {
1176
1224
 
1177
1225
  return val
1178
1226
  }
1179
- }
1227
+ } /* node:coverage ignore next 6 */
1180
1228
 
1181
1229
  export {
1182
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,8 +331,7 @@ 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
337
  // NumberVal is the binary64 leaf, whose kind keyword is `float`;
@@ -343,11 +358,11 @@ type CompletionItem = {
343
358
  const COMPLETION_FUNCTION = 3
344
359
  const COMPLETION_KEYWORD = 14
345
360
 
346
- // 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
347
362
  // `lsp.test.ts`, which asserts each is recognised and no others are.
348
363
  const BUILTIN_FUNCS = [
349
- 'close', 'copy', 'hide', 'key', 'lower', 'move',
350
- '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',
351
366
  ]
352
367
 
353
368
  // Scalar-kind and literal keywords.
@@ -464,7 +479,7 @@ class LspHandler {
464
479
  private publish(uri: string): OutMessage {
465
480
  return publishDiagnosticsMsg(uri, computeDiagnostics(this.docs.get(uri) ?? ''))
466
481
  }
467
- }
482
+ } /* node:coverage ignore next 28 */
468
483
 
469
484
 
470
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,
package/src/unify.ts CHANGED
@@ -205,6 +205,30 @@ function update(x: Val, _y: Val) {
205
205
  }
206
206
 
207
207
 
208
+ // The still-refining paths named by a budget_passes error: the first
209
+ // `max` non-done nodes of the residue, as `$.dotted.paths`. Depth-first
210
+ // over bag children only -- this feeds an error message, not a report,
211
+ // so a small deterministic sample beats completeness.
212
+ function residuePaths(v: Val, max: number): string[] {
213
+ const out: string[] = []
214
+ const visit = (n: any, isroot: boolean) => {
215
+ if (null == n || max <= out.length) {
216
+ return
217
+ }
218
+ if (!isroot && !n.done) {
219
+ out.push('$' + (0 < (n.path?.length ?? 0) ? '.' + n.path.join('.') : ''))
220
+ }
221
+ if (n.isMap || n.isList) {
222
+ for (const k in n.peg) {
223
+ visit(n.peg[k], false)
224
+ }
225
+ }
226
+ }
227
+ visit(v, true)
228
+ return out
229
+ }
230
+
231
+
208
232
  class Unify {
209
233
  root: Val
210
234
  res: Val
@@ -264,6 +288,7 @@ class Unify {
264
288
 
265
289
  // NOTE: if true === res.done already, then this loop never needs to run.
266
290
  let maxcc = 9 // 99
291
+ let prevCanon: string | undefined = undefined
267
292
  for (; this.cc < maxcc && DONE !== res.dc; this.cc++) {
268
293
  // console.log('CC', this.cc, res.canon)
269
294
  uctx.cc = this.cc
@@ -274,15 +299,41 @@ class Unify {
274
299
  break
275
300
  }
276
301
 
302
+ // Snapshot the second-to-last pass's result, so exhaustion can
303
+ // tell "still refining" from "stable residue" below. Only paid
304
+ // by models still unresolved this late.
305
+ if (this.cc === maxcc - 2 && DONE !== res.dc) {
306
+ prevCanon = res.canon
307
+ }
308
+
277
309
  uctx = uctx.clone({ root: res })
278
310
  }
279
311
 
312
+ // The pass budget is spent AND the final pass still made
313
+ // progress: the model was cut off while converging, and no other
314
+ // error explains why. Silent truncation would surface later as
315
+ // ordinary incompleteness, so exhaustion is a semantic error of
316
+ // its own (class budget, docs/trust.md clause 2) -- retrying with
317
+ // a larger budget is a valid response to THIS code and useless
318
+ // for path_cycle or no_path. A STABLE residue (the final pass
319
+ // changed nothing -- e.g. a stuck `1+true`) is not a budget
320
+ // failure: it is ordinary incompleteness and stays silent here,
321
+ // surfacing at generate exactly as before.
322
+ if (maxcc <= this.cc && DONE !== res.dc && 0 === uctx.err.length
323
+ && undefined !== prevCanon && prevCanon !== res.canon) {
324
+ makeNilErr(uctx, 'budget_passes', undefined, undefined, 'resolve', {
325
+ budget: 'passes',
326
+ limit: maxcc,
327
+ paths: residuePaths(res, 4).join(' ') || '$',
328
+ })
329
+ }
330
+
280
331
  uctx.explain && explainClose(te, res)
281
332
  }
282
333
 
283
334
  this.res = res
284
335
  }
285
- }
336
+ } /* node:coverage ignore next 9 */
286
337
 
287
338
 
288
339
 
package/src/utility.ts CHANGED
@@ -84,6 +84,8 @@ function walk(
84
84
 
85
85
  // Container Vals (Map etc) have peg = plain {} or []
86
86
  if (null != child && !child.isVal) {
87
+ // A ListVal's array peg is an object too, and for-in yields its
88
+ // indices as string keys, so this one loop covers both bag shapes.
87
89
  if ('object' === typeof child) {
88
90
  for (let ckey in child) {
89
91
  if (child[ckey] && child[ckey].isVal) {
@@ -92,14 +94,6 @@ function walk(
92
94
  }
93
95
  }
94
96
  }
95
- else if (Array.isArray(child)) {
96
- for (let i = 0; i < child.length; i++) {
97
- if (child[i] && child[i].isVal) {
98
- child[i] = walk(
99
- child[i], before, after, maxdepth, i, out, [...(path || []), '' + i])
100
- }
101
- }
102
- }
103
97
  }
104
98
 
105
99
  out = null == after ? out : after(key, out, parent, path || [])
@@ -194,7 +188,7 @@ function items(o: any) {
194
188
  else {
195
189
  return []
196
190
  }
197
- }
191
+ } /* node:coverage ignore next 15 */
198
192
 
199
193
 
200
194
  export {
package/src/val/BagVal.ts CHANGED
@@ -178,7 +178,7 @@ abstract class BagVal extends FeatureVal {
178
178
  return out
179
179
  }
180
180
 
181
- }
181
+ } /* node:coverage ignore next 6 */
182
182
 
183
183
 
184
184
  export {
@@ -70,7 +70,7 @@ class BigDecimalVal extends ScalarVal {
70
70
  let out: Val = this
71
71
 
72
72
  if (null != peer) {
73
- if (peer.isScalarKind) {
73
+ if (peer.isScalarKind || (peer as any).isConstraint) {
74
74
  out = peer.unify(this, te ? ctx.clone({ explain: ec(te, 'KND') }) : ctx)
75
75
  }
76
76
  else if (
@@ -109,7 +109,7 @@ class BigDecimalVal extends ScalarVal {
109
109
  get canon() {
110
110
  return this.peg.canon()
111
111
  }
112
- }
112
+ } /* node:coverage ignore next 6 */
113
113
 
114
114
 
115
115
  export {
@@ -67,7 +67,7 @@ class BigIntegerVal extends ScalarVal {
67
67
  let out: Val = this
68
68
 
69
69
  if (null != peer) {
70
- if (peer.isScalarKind) {
70
+ if (peer.isScalarKind || (peer as any).isConstraint) {
71
71
  out = peer.unify(this, te ? ctx.clone({ explain: ec(te, 'KND') }) : ctx)
72
72
  }
73
73
  else if (
@@ -102,7 +102,7 @@ class BigIntegerVal extends ScalarVal {
102
102
  const neg = this.peg < 0n
103
103
  return (neg ? '-0d' : '0d') + (neg ? -this.peg : this.peg).toString()
104
104
  }
105
- }
105
+ } /* node:coverage ignore next 6 */
106
106
 
107
107
 
108
108
  export {
@@ -24,7 +24,7 @@ class BooleanVal extends ScalarVal {
24
24
  unify(peer: Val, ctx: AontuContext): Val {
25
25
  return super.unify(peer, ctx)
26
26
  }
27
- }
27
+ } /* node:coverage ignore next 6 */
28
28
 
29
29
 
30
30
  export {
@@ -53,7 +53,7 @@ class CloseFuncVal extends FuncBaseVal {
53
53
  return argval
54
54
  }
55
55
 
56
- }
56
+ } /* node:coverage ignore next 6 */
57
57
 
58
58
 
59
59
  export {
@@ -279,7 +279,7 @@ function norm(terms: Val[]): Val[] {
279
279
 
280
280
 
281
281
  return expand
282
- }
282
+ } /* node:coverage ignore next 7 */
283
283
 
284
284
 
285
285
  export {