aontu 0.45.1 → 0.48.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 (68) hide show
  1. package/dist/aontu.js +5 -0
  2. package/dist/aontu.js.map +1 -1
  3. package/dist/cli.d.ts +9 -0
  4. package/dist/cli.js +203 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/err.js +1 -1
  7. package/dist/err.js.map +1 -1
  8. package/dist/hints.js +1 -0
  9. package/dist/hints.js.map +1 -1
  10. package/dist/lang.d.ts +1 -1
  11. package/dist/lang.js +45 -18
  12. package/dist/lang.js.map +1 -1
  13. package/dist/lsp-server.d.ts +14 -0
  14. package/dist/lsp-server.js +87 -0
  15. package/dist/lsp-server.js.map +1 -0
  16. package/dist/lsp.d.ts +71 -0
  17. package/dist/lsp.js +375 -0
  18. package/dist/lsp.js.map +1 -0
  19. package/dist/tsconfig.tsbuildinfo +1 -1
  20. package/dist/type.d.ts +1 -1
  21. package/dist/unify.js +31 -4
  22. package/dist/unify.js.map +1 -1
  23. package/dist/utility.js +9 -2
  24. package/dist/utility.js.map +1 -1
  25. package/dist/val/BagVal.js +20 -2
  26. package/dist/val/BagVal.js.map +1 -1
  27. package/dist/val/DisjunctVal.js +25 -16
  28. package/dist/val/DisjunctVal.js.map +1 -1
  29. package/dist/val/IntegerVal.js +1 -0
  30. package/dist/val/IntegerVal.js.map +1 -1
  31. package/dist/val/LowerFuncVal.d.ts +1 -1
  32. package/dist/val/LowerFuncVal.js +1 -1
  33. package/dist/val/LowerFuncVal.js.map +1 -1
  34. package/dist/val/MapVal.js +127 -16
  35. package/dist/val/MapVal.js.map +1 -1
  36. package/dist/val/NumberVal.js +7 -2
  37. package/dist/val/NumberVal.js.map +1 -1
  38. package/dist/val/OpBaseVal.d.ts +1 -1
  39. package/dist/val/OpBaseVal.js +1 -1
  40. package/dist/val/OpBaseVal.js.map +1 -1
  41. package/dist/val/ScalarVal.js +3 -1
  42. package/dist/val/ScalarVal.js.map +1 -1
  43. package/dist/val/Val.d.ts +2 -0
  44. package/dist/val/Val.js +16 -1
  45. package/dist/val/Val.js.map +1 -1
  46. package/dist/val/VarVal.js +5 -3
  47. package/dist/val/VarVal.js.map +1 -1
  48. package/package.json +25 -13
  49. package/src/aontu.ts +5 -0
  50. package/src/cli.ts +193 -0
  51. package/src/err.ts +1 -1
  52. package/src/hints.ts +2 -0
  53. package/src/lang.ts +46 -18
  54. package/src/lsp-server.ts +109 -0
  55. package/src/lsp.ts +486 -0
  56. package/src/type.ts +1 -1
  57. package/src/unify.ts +33 -4
  58. package/src/utility.ts +11 -2
  59. package/src/val/BagVal.ts +22 -2
  60. package/src/val/DisjunctVal.ts +27 -18
  61. package/src/val/IntegerVal.ts +1 -0
  62. package/src/val/LowerFuncVal.ts +1 -1
  63. package/src/val/MapVal.ts +138 -23
  64. package/src/val/NumberVal.ts +7 -2
  65. package/src/val/OpBaseVal.ts +1 -1
  66. package/src/val/ScalarVal.ts +3 -1
  67. package/src/val/Val.ts +18 -1
  68. package/src/val/VarVal.ts +6 -4
@@ -23,7 +23,7 @@ import { FuncBaseVal } from './FuncBaseVal'
23
23
 
24
24
 
25
25
  class LowerFuncVal extends FuncBaseVal {
26
- isLower = true
26
+ isLowerFunc = true
27
27
 
28
28
  constructor(
29
29
  spec: ValSpec,
package/src/val/MapVal.ts CHANGED
@@ -16,6 +16,7 @@ import { unite } from '../unify'
16
16
 
17
17
  import {
18
18
  propagateMarks,
19
+ walk,
19
20
  explainOpen,
20
21
  ec,
21
22
  explainClose,
@@ -33,6 +34,54 @@ import { NilVal } from './NilVal'
33
34
  import { BagVal } from './BagVal'
34
35
 
35
36
 
37
+ // Structural snapshots of ref spreads (see MapVal.unify), keyed by the
38
+ // ref's canon + source site rather than object identity: spread
39
+ // application clones templates (and the refs inside them) freely, and a
40
+ // clone must find the snapshot its parse-origin ref captured on an early
41
+ // pass. The map lives on the unify root ctx (see Unify), so it persists
42
+ // across fixpoint passes and is GC'd with the run.
43
+ function spreadSnapKey(cj: any): string {
44
+ return cj.canon + '~' + (cj.site?.row ?? -1) + ':' + (cj.site?.col ?? -1)
45
+ }
46
+
47
+ // Snapshot a path-dependent ref spread to its structural target once,
48
+ // while inner key()/path() funcs in the target are still unresolved (see
49
+ // the call site comments in MapVal.unify). Shared by the direct
50
+ // application path and the deferred-spread early-snapshot walk.
51
+ function snapshotRefSpread(cj: any, ctx: AontuContext): Val | undefined {
52
+ let snapmap: Map<string, Val> | undefined = (ctx as any).snapmap
53
+ if (undefined === snapmap) {
54
+ // Direct Val.unify use without a Unify run: degrade to a ctx-local
55
+ // map (snapshots then live only for that subtree, as before).
56
+ snapmap = new Map()
57
+ ; (ctx as any).snapmap = snapmap
58
+ }
59
+ const sk = spreadSnapKey(cj)
60
+ let snap: Val | undefined = snapmap.get(sk)
61
+ if (undefined === snap) {
62
+ let tgt: Val | undefined = cj.find(ctx)
63
+ // A ref to a type() resolves to its inner template — snapshot that,
64
+ // so a type-wrapped ref behaves like a plain-map ref spread.
65
+ if (tgt && (tgt as any).isTypeFunc) tgt = (tgt as any).peg?.[0]
66
+ // Only snapshot a found, path-dependent target. If the target is not
67
+ // present yet (it may be introduced by a later conjunct/merge), do
68
+ // NOT cache — retry on the next fixpoint pass.
69
+ if (tgt && tgt.isVal && tgt.isPathDependent) {
70
+ snap = tgt.clone(ctx)
71
+ // Clear TYPE marks on the snapshot (recursively): a type() template
72
+ // constrains values but must not make the spread destination
73
+ // type-invisible at any depth. HIDE marks are preserved.
74
+ walk(snap, (_k: any, v: Val) => {
75
+ v.mark.type = false
76
+ return v
77
+ })
78
+ snapmap.set(sk, snap)
79
+ }
80
+ }
81
+ return snap
82
+ }
83
+
84
+
36
85
  class MapVal extends BagVal {
37
86
  isMap = true
38
87
 
@@ -112,12 +161,22 @@ class MapVal extends BagVal {
112
161
  }
113
162
 
114
163
  if (!exit) {
164
+ // Combine two spread constraints. Identical templates (same canon)
165
+ // collapse to one: re-unifying them resolves key()/path() at the
166
+ // shared intermediate path, producing spurious values (f1bb1063).
167
+ // Distinct templates are unified in place — unite is idempotent,
168
+ // whereas deferring the distinct case into a fresh ConjunctVal (as
169
+ // f1bb1063 did) re-wraps every fixpoint pass, growing the conjunct
170
+ // without bound and non-terminating on real models (the apidef +
171
+ // sdkgen entity schemas each contribute a `&:` spread with name:key(),
172
+ // combined here). unite resolves key()/path() at each destination via
173
+ // spreadClone below, so nested + sibling key() cases stay correct
174
+ // (test/spec/spread-nested-key, spread-key-all).
115
175
  out.spread.cj = null == out.spread.cj ? peer.spread.cj : (
116
- null == peer.spread.cj ? out.spread.cj : (
117
- out.spread.cj =
118
- unite(te ? ctx.clone({ explain: ec(te, 'SPR') }) : ctx,
119
- out.spread.cj, peer.spread.cj, 'map-self')
120
- )
176
+ null == peer.spread.cj ? out.spread.cj :
177
+ out.spread.cj.canon === peer.spread.cj.canon ? out.spread.cj :
178
+ unite(te ? ctx.clone({ explain: ec(te, 'SPR') }) : ctx,
179
+ out.spread.cj, peer.spread.cj, 'map-self')
121
180
  )
122
181
  }
123
182
  }
@@ -133,25 +192,69 @@ class MapVal extends BagVal {
133
192
 
134
193
  let spread_cj = out.spread.cj ?? TOP
135
194
 
195
+ // Snapshot a path-dependent *ref* spread to its structural target
196
+ // once (while inner key()/path() funcs are still unresolved), so
197
+ // later fixpoint passes don't re-resolve the ref against the mutated
198
+ // tree and capture the target's own resolved key()/path() literals,
199
+ // which would leak the source key into the spread destination.
200
+ if (spread_cj.isRef && (spread_cj as any).find) {
201
+ const snap = snapshotRefSpread(spread_cj, ctx)
202
+ if (snap) spread_cj = snap
203
+ }
204
+
205
+ // A type() used as a spread applies as its inner template: emit the
206
+ // (constrained) values at each destination rather than marking the
207
+ // destination as a type. I.e. `&:type({k:key(),x:number})` behaves
208
+ // like the non-type spread `&:{k:key(),x:number}` — key() resolves
209
+ // to the destination key, kinds constrain, fields are emitted.
210
+ if ((spread_cj as any).isTypeFunc) {
211
+ spread_cj = (spread_cj as any).peg?.[0] ?? TOP
212
+ }
213
+
136
214
  // Always unify own children first
137
215
  for (let key in this.peg) {
138
- const keyctx = ctx.descend(key)
139
-
140
- const key_spread_cj = spread_cj.spreadClone(keyctx)
141
216
  const child = this.peg[key]
217
+ const keyctx = ctx.descend(key)
142
218
 
143
219
  propagateMarks(this, child)
144
220
 
145
- out.peg[key] =
146
- undefined === child ? key_spread_cj :
147
- child.isNil ? child :
148
- key_spread_cj.isNil ? key_spread_cj :
149
- key_spread_cj.isTop && child.done ? child :
150
- child.isTop && key_spread_cj.done ? key_spread_cj :
151
- unite(te ? keyctx.clone({ explain: ec(te, 'KEY:' + key) }) : keyctx,
152
- child, key_spread_cj, 'map-own')
221
+ // Apply the spread constraint ONCE per child (marked with the
222
+ // constraint's id below): the first application merges the
223
+ // template into the child (with key()/path() placeholders that
224
+ // resolve in place on later passes), so the constraint content
225
+ // is inside the child from then on and only self-unification is
226
+ // needed to progress it. Re-applying on every fixpoint pass and
227
+ // every conjunct-fold step is the identity (unite is idempotent)
228
+ // but costs O(keys) deep template clones per pass on large
229
+ // models — the dominant cost on generated-SDK model trees.
230
+ let oval: Val
231
+ if (undefined !== child && !spread_cj.isTop
232
+ && (child as any)._spr === (spread_cj as any).id) {
233
+ oval = child.done ? child :
234
+ unite(te ? keyctx.clone({ explain: ec(te, 'KEY:' + key) }) : keyctx,
235
+ child, TOP, 'map-own')
236
+ ; (oval as any)._spr = (spread_cj as any).id
237
+ }
238
+ else {
239
+ const key_spread_cj = spread_cj.spreadClone(keyctx)
240
+
241
+ oval =
242
+ undefined === child ? key_spread_cj :
243
+ child.isNil ? child :
244
+ key_spread_cj.isNil ? key_spread_cj :
245
+ key_spread_cj.isTop && child.done ? child :
246
+ child.isTop && key_spread_cj.done ? key_spread_cj :
247
+ unite(te ? keyctx.clone({ explain: ec(te, 'KEY:' + key) }) : keyctx,
248
+ child, key_spread_cj, 'map-own')
249
+
250
+ if (!spread_cj.isTop && !oval.isNil) {
251
+ ; (oval as any)._spr = (spread_cj as any).id
252
+ }
253
+ }
153
254
 
154
- done = (done && DONE === out.peg[key].dc)
255
+ out.peg[key] = oval
256
+
257
+ done = (done && DONE === oval.dc)
155
258
  }
156
259
 
157
260
  const allowedKeys: string[] = this.closed ? Object.keys(this.peg) : []
@@ -187,11 +290,20 @@ class MapVal extends BagVal {
187
290
  child, peerchild, 'map-peer')
188
291
 
189
292
  if (this.spread.cj) {
190
- let key_spread_cj = spread_cj.spreadClone(peerctx)
191
-
192
- oval = out.peg[peerkey] =
193
- unite(te ? peerctx.clone({ explain: ec(te, 'PSP:' + peerkey) }) : peerctx,
194
- oval, key_spread_cj, 'map-peer-spread')
293
+ // Same apply-once discipline as the own-key loop: once the
294
+ // constraint is merged into the value (marked with the
295
+ // constraint's id), later passes only self-unify.
296
+ if ((oval as any)._spr !== (spread_cj as any).id) {
297
+ let key_spread_cj = spread_cj.spreadClone(peerctx)
298
+
299
+ oval = out.peg[peerkey] =
300
+ unite(te ? peerctx.clone({ explain: ec(te, 'PSP:' + peerkey) }) : peerctx,
301
+ oval, key_spread_cj, 'map-peer-spread')
302
+
303
+ if (!spread_cj.isTop && !oval.isNil) {
304
+ ; (oval as any)._spr = (spread_cj as any).id
305
+ }
306
+ }
195
307
  }
196
308
 
197
309
  propagateMarks(this, oval)
@@ -307,7 +419,10 @@ class MapVal extends BagVal {
307
419
 
308
420
 
309
421
  get canon() {
310
- let keys = Object.keys(this.peg)
422
+ // Keys are emitted alphabetically so the canonical form is
423
+ // independent of insertion/unification order (and matches the Go
424
+ // port, whose JSON marshaling also sorts keys).
425
+ let keys = Object.keys(this.peg).sort()
311
426
  return '' +
312
427
  // this.errcanon() +
313
428
  // (this.mark.type ? '<type>' : '') +
@@ -27,7 +27,9 @@ class NumberVal extends ScalarVal {
27
27
  spec: ValSpec,
28
28
  ctx?: AontuContext
29
29
  ) {
30
- if (isNaN(spec.peg)) {
30
+ // Number.isFinite (not the coercing global isNaN, which lets null/''
31
+ // through as "numbers") — only an actual finite number is valid.
32
+ if (!Number.isFinite(spec.peg)) {
31
33
  // TODO: use Nil?
32
34
  throw new AontuError('not-number: ' + spec.peg)
33
35
  }
@@ -47,9 +49,12 @@ class NumberVal extends ScalarVal {
47
49
  }
48
50
  else if (
49
51
  peer.isScalar &&
52
+ peer.kind === this.kind &&
50
53
  peer.peg === this.peg
51
54
  ) {
52
- out = peer.isInteger ? peer : this
55
+ // Same kind (both Number) and equal value: integer/float no
56
+ // longer cross-unify, so peer is necessarily a NumberVal here.
57
+ out = this
53
58
  }
54
59
  else if (peer.isTop) {
55
60
  out = this
@@ -36,7 +36,7 @@ import { FeatureVal } from './FeatureVal'
36
36
 
37
37
 
38
38
  class OpBaseVal extends FeatureVal {
39
- isPlusOp = true
39
+ isOp = true
40
40
 
41
41
  constructor(
42
42
  spec: ValSpec,
@@ -92,7 +92,9 @@ class ScalarVal extends Val {
92
92
 
93
93
 
94
94
  gen(_ctx?: AontuContext) {
95
- return this.peg
95
+ // Normalize negative zero to 0 for deterministic output (JSON has
96
+ // no -0, and the Go port produces 0).
97
+ return Object.is(this.peg, -0) ? 0 : this.peg
96
98
  }
97
99
 
98
100
 
package/src/val/Val.ts CHANGED
@@ -54,6 +54,12 @@ const SPREAD = Symbol('spread')
54
54
  const EMPTY_ERR: any[] = Object.freeze([]) as unknown as any[]
55
55
 
56
56
 
57
+ // Process-global, monotonic Val id source. Correctness only requires ids
58
+ // to be unique within a single unify run (fast-path identity checks,
59
+ // `same()`), which holds. It is NOT reset between generate() calls, so in
60
+ // a long-running host (e.g. the LSP) it grows for the process lifetime;
61
+ // that is acceptable — an id is a small number and is never used as a
62
+ // memory key. TODO: switch to the per-run ctx.vc counter (see ctx.ts).
57
63
  let ID = 1000
58
64
 
59
65
 
@@ -65,6 +71,7 @@ abstract class Val {
65
71
 
66
72
  declare isTop: boolean
67
73
  declare isNil: boolean
74
+ declare isNull: boolean
68
75
  declare isMap: boolean
69
76
  declare isList: boolean
70
77
  declare isScalar: boolean
@@ -79,6 +86,7 @@ abstract class Val {
79
86
  declare isBoolean: boolean
80
87
  declare isConjunct: boolean
81
88
  declare isDisjunct: boolean
89
+ declare isExpect: boolean
82
90
  declare isJunction: boolean
83
91
 
84
92
  // Conjunct sort order. Lower values sort first in norm().
@@ -272,7 +280,14 @@ abstract class Val {
272
280
  return v
273
281
  }
274
282
 
275
- // NOTE: MUST not mutate! Val immutability is a critical assumption.
283
+ // CONTRACT: implementations should treat `this` and `peer` as
284
+ // immutable and return a new Val. KNOWN EXCEPTION: the MapVal/ListVal
285
+ // fast-path for a TOP peer returns and refines `this` in place (an
286
+ // intentional optimization for the fixpoint loop). The practical
287
+ // consequence is that a parsed/unified tree is SINGLE-USE — do not
288
+ // re-unify or re-generate the same Val, and do not share it across
289
+ // threads. The public Aontu.unify/generate entry points re-parse per
290
+ // call, so this only matters if you hold and reuse a Val yourself.
276
291
  unify(_peer: Val, _ctx: AontuContext): Val { return this }
277
292
 
278
293
  // TODO: indicate marks in some way that is ignored by reparse.
@@ -363,6 +378,7 @@ Object.assign(Val.prototype, {
363
378
 
364
379
  isTop: false,
365
380
  isNil: false,
381
+ isNull: false,
366
382
  isMap: false,
367
383
  isList: false,
368
384
  isScalar: false,
@@ -377,6 +393,7 @@ Object.assign(Val.prototype, {
377
393
  isBoolean: false,
378
394
  isConjunct: false,
379
395
  isDisjunct: false,
396
+ isExpect: false,
380
397
  isJunction: false,
381
398
 
382
399
  cjo: 99999,
package/src/val/VarVal.ts CHANGED
@@ -79,13 +79,15 @@ class VarVal extends FeatureVal {
79
79
  if (!(nameVal.isRef) && DONE === nameVal.dc) {
80
80
  if (nameVal instanceof StringVal) {
81
81
  let found = ctx.vars[nameVal.peg]
82
- if (undefined === found) {
83
- out = makeNilErr(ctx, 'unknown_var', this, peer)
84
- }
85
82
 
86
83
  // TODO: support complex values
87
84
  const ft = typeof found
88
- if (null === found) {
85
+ // Single ladder: a missing var must report `unknown_var` and not
86
+ // fall through to the `invalid_var_kind` default below.
87
+ if (undefined === found) {
88
+ out = makeNilErr(ctx, 'unknown_var', this, peer)
89
+ }
90
+ else if (null === found) {
89
91
  out = this.place(new NullVal({ peg: null }))
90
92
  }
91
93
  else if ('string' === ft) {