aontu 0.40.0 → 0.42.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 (99) hide show
  1. package/dist/aontu.js +7 -0
  2. package/dist/aontu.js.map +1 -1
  3. package/dist/ctx.d.ts +14 -2
  4. package/dist/ctx.js +56 -9
  5. package/dist/ctx.js.map +1 -1
  6. package/dist/err.js +36 -15
  7. package/dist/err.js.map +1 -1
  8. package/dist/lang.d.ts +5 -1
  9. package/dist/lang.js +100 -24
  10. package/dist/lang.js.map +1 -1
  11. package/dist/tsconfig.tsbuildinfo +1 -1
  12. package/dist/type.d.ts +5 -0
  13. package/dist/type.js.map +1 -1
  14. package/dist/unify.js +372 -55
  15. package/dist/unify.js.map +1 -1
  16. package/dist/utility.js +6 -2
  17. package/dist/utility.js.map +1 -1
  18. package/dist/val/BagVal.d.ts +0 -3
  19. package/dist/val/BagVal.js +6 -6
  20. package/dist/val/BagVal.js.map +1 -1
  21. package/dist/val/ConjunctVal.d.ts +1 -1
  22. package/dist/val/ConjunctVal.js +138 -15
  23. package/dist/val/ConjunctVal.js.map +1 -1
  24. package/dist/val/CopyFuncVal.js +3 -2
  25. package/dist/val/CopyFuncVal.js.map +1 -1
  26. package/dist/val/DisjunctVal.js +40 -10
  27. package/dist/val/DisjunctVal.js.map +1 -1
  28. package/dist/val/ExpectVal.js +17 -4
  29. package/dist/val/ExpectVal.js.map +1 -1
  30. package/dist/val/FuncBaseVal.js +2 -2
  31. package/dist/val/FuncBaseVal.js.map +1 -1
  32. package/dist/val/IntegerVal.js +1 -1
  33. package/dist/val/IntegerVal.js.map +1 -1
  34. package/dist/val/JunctionVal.d.ts +1 -0
  35. package/dist/val/JunctionVal.js +6 -1
  36. package/dist/val/JunctionVal.js.map +1 -1
  37. package/dist/val/KeyFuncVal.js +0 -2
  38. package/dist/val/KeyFuncVal.js.map +1 -1
  39. package/dist/val/ListVal.d.ts +1 -0
  40. package/dist/val/ListVal.js +36 -65
  41. package/dist/val/ListVal.js.map +1 -1
  42. package/dist/val/MapVal.d.ts +3 -2
  43. package/dist/val/MapVal.js +71 -91
  44. package/dist/val/MapVal.js.map +1 -1
  45. package/dist/val/MoveFuncVal.d.ts +2 -1
  46. package/dist/val/MoveFuncVal.js +78 -13
  47. package/dist/val/MoveFuncVal.js.map +1 -1
  48. package/dist/val/NilVal.d.ts +2 -1
  49. package/dist/val/NilVal.js +15 -1
  50. package/dist/val/NilVal.js.map +1 -1
  51. package/dist/val/OpBaseVal.js +1 -1
  52. package/dist/val/OpBaseVal.js.map +1 -1
  53. package/dist/val/PathFuncVal.js +25 -4
  54. package/dist/val/PathFuncVal.js.map +1 -1
  55. package/dist/val/{RefVal.d.ts → PathVal.d.ts} +4 -3
  56. package/dist/val/{RefVal.js → PathVal.js} +75 -77
  57. package/dist/val/PathVal.js.map +1 -0
  58. package/dist/val/PlusOpVal.d.ts +1 -1
  59. package/dist/val/PrefVal.js +20 -7
  60. package/dist/val/PrefVal.js.map +1 -1
  61. package/dist/val/SpreadVal.d.ts +20 -0
  62. package/dist/val/SpreadVal.js +194 -0
  63. package/dist/val/SpreadVal.js.map +1 -0
  64. package/dist/val/Val.d.ts +4 -1
  65. package/dist/val/Val.js +88 -44
  66. package/dist/val/Val.js.map +1 -1
  67. package/dist/val/VarVal.js +3 -3
  68. package/dist/val/VarVal.js.map +1 -1
  69. package/package.json +6 -7
  70. package/src/aontu.ts +9 -1
  71. package/src/ctx.ts +95 -11
  72. package/src/err.ts +37 -17
  73. package/src/lang.ts +113 -23
  74. package/src/tsconfig.json +2 -1
  75. package/src/type.ts +5 -0
  76. package/src/unify.ts +390 -64
  77. package/src/utility.ts +5 -2
  78. package/src/val/BagVal.ts +6 -7
  79. package/src/val/ConjunctVal.ts +133 -15
  80. package/src/val/CopyFuncVal.ts +3 -2
  81. package/src/val/DisjunctVal.ts +43 -11
  82. package/src/val/ExpectVal.ts +19 -5
  83. package/src/val/FuncBaseVal.ts +2 -2
  84. package/src/val/IntegerVal.ts +1 -1
  85. package/src/val/JunctionVal.ts +5 -1
  86. package/src/val/KeyFuncVal.ts +0 -3
  87. package/src/val/ListVal.ts +40 -86
  88. package/src/val/MapVal.ts +78 -119
  89. package/src/val/MoveFuncVal.ts +79 -14
  90. package/src/val/NilVal.ts +17 -1
  91. package/src/val/OpBaseVal.ts +1 -1
  92. package/src/val/PathFuncVal.ts +29 -4
  93. package/src/val/PathVal.ts +435 -0
  94. package/src/val/PrefVal.ts +21 -8
  95. package/src/val/{RefVal.ts → RefVal.ts.old} +31 -20
  96. package/src/val/SpreadVal.ts +275 -0
  97. package/src/val/Val.ts +141 -50
  98. package/src/val/VarVal.ts +3 -3
  99. package/dist/val/RefVal.js.map +0 -1
package/src/unify.ts CHANGED
@@ -10,6 +10,9 @@ import { DONE } from './type'
10
10
  import { makeNilErr } from './err'
11
11
 
12
12
  import { NilVal } from './val/NilVal'
13
+ import { StringVal } from './val/StringVal'
14
+ import { PathVal } from './val/PathVal'
15
+ import { KeyFuncVal } from './val/KeyFuncVal'
13
16
 
14
17
  import {
15
18
  Lang
@@ -26,12 +29,48 @@ import {
26
29
  } from './val/top'
27
30
 
28
31
 
32
+
29
33
  // TODO: FIX: false positive when too many top unifications
30
- const MAXCYCLE = 999
34
+ const MAXCYCLE = 9999
31
35
 
32
36
  // Vals should only have to unify downwards (in .unify) over Vals they understand.
33
37
  // and for complex Vals, TOP, which means self unify if not yet done
34
38
  const unite = (ctx: AontuContext, a: any, b: any, whence: string) => {
39
+ // Fast paths that don't recurse and so don't need cycle-detection:
40
+ // short-circuit before the saw-key build and seen-map lookup (which
41
+ // together cost ~2.5µs per call). Only return early when the result
42
+ // is already `done` — a non-done result would need the trailing
43
+ // top() unify below.
44
+ //
45
+ // A6a: same ref, already done
46
+ // A6b: different ref but same id + both done
47
+ // P1: exact-equal scalars that are already done (14% of calls
48
+ // in foo-sdk, ~100% with a.done=true)
49
+ if (a !== undefined && a !== null) {
50
+ if (a === b) {
51
+ if (a.done) return a
52
+ }
53
+ else if (b !== undefined && b !== null) {
54
+ if (a.done && b.done) {
55
+ if (a.id === b.id) return a
56
+ if (a.constructor === b.constructor && a.peg === b.peg
57
+ && !a.isNil && !b.isNil
58
+ && !a.isConjunct && !a.isDisjunct
59
+ && !a.isPath && !a.isPref && !a.isFunc && !a.isExpect) {
60
+ return a
61
+ }
62
+
63
+ // Id-keyed cache: reuse results for the exact same Val pair.
64
+ const uc = ctx._uniteCache
65
+ if (uc !== undefined) {
66
+ const ucKey = a.id + '|' + b.id
67
+ const ucHit = uc.get(ucKey)
68
+ if (ucHit !== undefined) return ucHit
69
+ }
70
+ }
71
+ }
72
+ }
73
+
35
74
  const te = ctx.explain && explainOpen(ctx, ctx.explain, 'unite', a, b)
36
75
 
37
76
  let out = a
@@ -39,80 +78,89 @@ const unite = (ctx: AontuContext, a: any, b: any, whence: string) => {
39
78
 
40
79
  // Cycle-detection key. Use numeric path index for speed; fall back to
41
80
  // full string key when debug is enabled so the saw value is human-readable.
42
- const saw = ctx.opts.debug
43
- ? (a ? a.id + (a.done ? '' : '*') : '') + '~' +
81
+ let saw: string
82
+ if (ctx.opts.debug) {
83
+ saw = (a ? a.id + (a.done ? '' : '*') : '') + '~' +
44
84
  (b ? b.id + (b.done ? '' : '*') : '') + '@' + ctx.pathstr
45
- : (a ? a.id + (a.done ? 'd' : '') : 0) + '~' +
85
+ }
86
+ else {
87
+ saw = (a ? a.id + (a.done ? 'd' : '') : 0) + '~' +
46
88
  (b ? b.id + (b.done ? 'd' : '') : 0) + '~' + ctx.pathidx
89
+ }
47
90
 
48
91
  // NOTE: if this error occurs "unreasonably", attemp to avoid unnecesary unification
49
92
  // See for example PrefVal peg.id equality inspection.
50
- const sawCount = ctx.seen[saw] ?? 0
93
+ const sawCount = ctx.seen.get(saw) ?? 0
51
94
  if (MAXCYCLE < sawCount) {
52
95
  // console.log('SAW', sawCount, saw, a?.id, a?.canon, b?.id, b?.canon, ctx.cc)
53
96
  out = makeNilErr(ctx, 'unify_cycle', a, b)
54
97
  }
55
98
  else {
56
- ctx.seen[saw] = sawCount + 1
99
+ ctx.seen.set(saw, sawCount + 1)
57
100
 
58
101
  try {
59
-
60
102
  let unified = false
61
103
 
62
- if (b && (!a || a.isTop)) {
104
+ // Dispatch ladder. Structure note:
105
+ // - `a == null` is degenerate (shouldn't happen in practice:
106
+ // the top-level call seeds with a real Val). Kept for safety.
107
+ // - TOP is the unit element: unifying with it returns the
108
+ // other side. Handle both sides.
109
+ // - Otherwise route by Val type. Complex Vals (Conjunct,
110
+ // Disjunct, Ref, Pref, Func, Expect) have their own unify
111
+ // that knows how to absorb the peer; prefer `a.unify` when
112
+ // `a` is complex, else `b.unify` when `b` is complex. If
113
+ // neither is complex and it's not a plain-scalar match, fall
114
+ // through to the generic `a.unify` (concrete Val classes
115
+ // each handle their own peer case).
116
+ if (a == null) {
63
117
  out = b
64
118
  why = 'b'
65
119
  }
66
-
67
- else if (a && (!b || b.isTop)) {
120
+ else if (b == null || b.isTop) {
68
121
  out = a
69
122
  why = 'a'
70
123
  }
71
-
72
- else if (a && b && !b.isTop) {
73
- if (a.isNil) {
74
- out = update(a, b)
75
- why = 'an'
76
- }
77
- else if (b.isNil) {
78
- out = update(b, a)
79
- why = 'bn'
80
- }
81
- else if (a.isConjunct) {
82
- out = a.unify(b, te ? ctx.clone({ explain: ec(te, 'CJ') }) : ctx)
83
- unified = true
84
- why = 'acj'
85
- }
86
- else if (a.isExpect) {
87
- out = a.unify(b, te ? ctx.clone({ explain: ec(te, 'AE') }) : ctx)
88
- unified = true
89
- why = 'ae'
90
- }
91
- else if (
92
- b.isConjunct
93
- || b.isDisjunct
94
- || b.isRef
95
- || b.isPref
96
- || b.isFunc
97
- || b.isExpect
98
- ) {
99
-
100
- out = b.unify(a, te ? ctx.clone({ explain: ec(te, 'BW') }) : ctx)
101
- unified = true
102
- why = 'bv'
103
- }
104
-
105
- // Exactly equal scalars.
106
- else if (a.constructor === b.constructor && a.peg === b.peg) {
107
- out = update(a, b)
108
- why = 'up'
109
- }
110
-
111
- else {
112
- out = a.unify(b, te ? ctx.clone({ explain: ec(te, 'GN') }) : ctx)
113
- unified = true
114
- why = 'ab'
115
- }
124
+ else if (a.isTop) {
125
+ out = b
126
+ why = 'b'
127
+ }
128
+ else if (a.isNil) {
129
+ out = update(a, b)
130
+ why = 'an'
131
+ }
132
+ else if (b.isNil) {
133
+ out = update(b, a)
134
+ why = 'bn'
135
+ }
136
+ else if (a.isConjunct || a.isExpect || a.isSpread) {
137
+ out = a.unify(b, te ? ctx.clone({ explain: ec(te, 'AC') }) : ctx)
138
+ unified = true
139
+ why = 'a*'
140
+ }
141
+ else if (
142
+ b.isConjunct
143
+ || b.isDisjunct
144
+ || b.isPath
145
+ || b.isPref
146
+ || b.isFunc
147
+ || b.isExpect
148
+ || b.isSpread
149
+ ) {
150
+ out = b.unify(a, te ? ctx.clone({ explain: ec(te, 'BW') }) : ctx)
151
+ unified = true
152
+ why = 'bv'
153
+ }
154
+ // Exactly equal scalars (not caught by early fast-path — e.g.
155
+ // because a or b isn't .done yet).
156
+ else if (a.constructor === b.constructor && a.peg === b.peg) {
157
+ out = update(a, b)
158
+ why = 'up'
159
+ }
160
+ else {
161
+ out = a.unify(b, te ? ctx.clone({ explain: ec(te, 'GN') }) : ctx)
162
+ unified = true
163
+ why = 'ab'
116
164
  }
117
165
 
118
166
  if (!out || !out.unify) {
@@ -120,19 +168,16 @@ const unite = (ctx: AontuContext, a: any, b: any, whence: string) => {
120
168
  why += 'N'
121
169
  }
122
170
 
123
- // console.log('UNITE-DONE', out.id, out.canon, out.done)
124
-
125
- // if (DONE !== out.dc && !unified) {
171
+ // Any non-done top-level result self-unifies with TOP to ensure
172
+ // its children finish converging. Skipped when `unified` is true
173
+ // because the branch that set `out = X.unify(Y, ctx)` already
174
+ // ran that Val's own unify logic.
126
175
  if (!out.done && !unified) {
127
- let nout = out.unify(top(), te ? ctx.clone({ explain: ec(te, 'ND') }) : ctx)
128
- out = nout
176
+ out = out.unify(top(), te ? ctx.clone({ explain: ec(te, 'ND') }) : ctx)
129
177
  why += 'T'
130
178
  }
131
-
132
- // console.log('UNITE', why, a?.id, a?.canon, a?.done, b?.id, b?.canon, b?.done, '->', out?.id, out?.canon, out?.done)
133
179
  }
134
180
  catch (err: any) {
135
- // console.log(err)
136
181
  // TODO: handle unexpected
137
182
  out = makeNilErr(ctx, 'internal', a, b)
138
183
  }
@@ -140,6 +185,11 @@ const unite = (ctx: AontuContext, a: any, b: any, whence: string) => {
140
185
 
141
186
  ctx.explain && explainClose(te, out)
142
187
 
188
+ // Store in id-keyed cache when both operands were done.
189
+ if (a?.done && b?.done && out?.done && ctx._uniteCache !== undefined) {
190
+ ctx._uniteCache.set(a.id + '|' + b.id, out)
191
+ }
192
+
143
193
  return out
144
194
  }
145
195
 
@@ -150,6 +200,269 @@ function update(x: Val, _y: Val) {
150
200
  }
151
201
 
152
202
 
203
+ // Resolve all PathVals using the pre-collected paths list.
204
+ // Mutates the tree in place, replacing each PathVal with its cloned target.
205
+ function resolvePaths(root: Val, ctx: AontuContext, paths: PathVal[]) {
206
+ for (const pv of paths) {
207
+ if (pv.done) continue
208
+
209
+ // Resolve: find target, following chains.
210
+ // Suppress errors during pre-resolution — intermediate PathVals
211
+ // (from multi-dot expressions like ..a.q) may fail here but the
212
+ // final PathVal resolves correctly during unification.
213
+ const savedErr = ctx.err
214
+ ctx.err = []
215
+ let found: any = pv.find(ctx)
216
+ ctx.err = savedErr
217
+ if (found == null || found.isNil) continue
218
+
219
+ // Skip if target or container contains a mark-setting function
220
+ // (type/hide/move) — let unification resolve it first.
221
+ if (hasMarkFunc(found)) continue
222
+ if (hasMarkFuncAtPath(root, pv.path)) continue
223
+
224
+ while (found instanceof PathVal) {
225
+ if (found.done && found._resolved) {
226
+ found = found._resolved
227
+ break
228
+ }
229
+ const next = found.find(ctx)
230
+ if (next == null || next.isNil) break
231
+ found.dc = DONE
232
+ found._resolved = next
233
+ found = next
234
+ }
235
+
236
+ // If found value is a key() function, set its path to the
237
+ // destination so it evaluates at the right position.
238
+ if (found.isKeyFunc) {
239
+ found.path = pv.path
240
+ }
241
+
242
+ pv.dc = DONE
243
+ pv._resolved = found
244
+
245
+ // Replace PathVal in tree using its path
246
+ replaceAtPath(root, pv.path, pv, found)
247
+
248
+ // Walk the placed value to resolve any PathVals cloned into it
249
+ resolveNestedPaths(found, ctx)
250
+ }
251
+ }
252
+
253
+
254
+ // Check if a value contains a type() or hide() function.
255
+ // Check if the value AT the path position is inside a mark-setting function.
256
+ function hasMarkFuncAtPath(root: Val, path: string[]): boolean {
257
+ let node: any = root
258
+ for (let i = 0; i < path.length; i++) {
259
+ const part = path[i]
260
+ if (node.isMap || node.isList) {
261
+ node = node.peg[part]
262
+ }
263
+ else if (node.isConjunct || node.isDisjunct) {
264
+ let found = null
265
+ const stack = [...node.peg]
266
+ while (stack.length > 0) {
267
+ const term = stack.pop()
268
+ if (term?.isConjunct || term?.isDisjunct) {
269
+ stack.push(...term.peg)
270
+ }
271
+ else if ((term?.isMap || term?.isList) && term.peg[part] != null) {
272
+ found = term.peg[part]
273
+ break
274
+ }
275
+ }
276
+ node = found
277
+ }
278
+ else {
279
+ return false
280
+ }
281
+ if (node == null) return false
282
+ }
283
+ return hasMarkFunc(node)
284
+ }
285
+
286
+
287
+ function hasMarkFunc(val: any): boolean {
288
+ if (val == null || !val.isVal) return false
289
+ if (val.isTypeFunc || val.isHideFunc || val.isMoveFunc) return true
290
+ if (val.isConjunct || val.isDisjunct) {
291
+ for (const t of val.peg) {
292
+ if (hasMarkFunc(t)) return true
293
+ }
294
+ }
295
+ return false
296
+ }
297
+
298
+
299
+ // Resolve any PathVals found inside a value (e.g. cloned subtrees).
300
+ // Iterative stack-based walk — no recursion.
301
+ function resolveNestedPaths(val: any, ctx: AontuContext) {
302
+ const stack: any[] = [val]
303
+ while (stack.length > 0) {
304
+ const v = stack.pop()
305
+ if (v == null || !v.isVal) continue
306
+
307
+ if (v.isMap || v.isList) {
308
+ for (const k in v.peg) {
309
+ const child = v.peg[k]
310
+ if (child instanceof PathVal && !child.done) {
311
+ const found = child.find(ctx)
312
+ if (found != null && !found.isNil) {
313
+ v.peg[k] = found
314
+ child.dc = DONE
315
+ child._resolved = found
316
+ stack.push(found)
317
+ }
318
+ }
319
+ else if (child?.isVal) {
320
+ stack.push(child)
321
+ }
322
+ }
323
+ }
324
+ else if (v.isConjunct || v.isDisjunct) {
325
+ for (let i = 0; i < v.peg.length; i++) {
326
+ const child = v.peg[i]
327
+ if (child instanceof PathVal && !child.done) {
328
+ const found = child.find(ctx)
329
+ if (found != null && !found.isNil) {
330
+ v.peg[i] = found
331
+ child.dc = DONE
332
+ child._resolved = found
333
+ stack.push(found)
334
+ }
335
+ }
336
+ else if (child?.isVal) {
337
+ stack.push(child)
338
+ }
339
+ }
340
+ }
341
+ else if (v.isFeature) {
342
+ if (v.peg instanceof PathVal && !v.peg.done) {
343
+ const found = v.peg.find(ctx)
344
+ if (found != null && !found.isNil) {
345
+ v.peg.dc = DONE
346
+ v.peg._resolved = found
347
+ v.peg = found
348
+ stack.push(found)
349
+ }
350
+ }
351
+ else if (Array.isArray(v.peg)) {
352
+ for (let i = 0; i < v.peg.length; i++) {
353
+ if (v.peg[i]?.isVal) stack.push(v.peg[i])
354
+ }
355
+ }
356
+ else if (v.peg?.isVal) {
357
+ stack.push(v.peg)
358
+ }
359
+ }
360
+ }
361
+ }
362
+
363
+
364
+ // Replace target Val in the tree, using path to navigate and a stack
365
+ // to search through junctions, features, and nested structures.
366
+ // No recursion — uses a single loop with an explicit stack.
367
+ function replaceAtPath(root: Val, path: string[], target: Val, replacement: Val,
368
+ intoSpreads: boolean = true): boolean {
369
+ let node: any = root
370
+
371
+ // Descend through map/list using path segments.
372
+ // When a non-map/list is encountered (junction, feature),
373
+ // push its children onto the search stack and stop descending.
374
+ let pi = 0
375
+ for (; pi < path.length; pi++) {
376
+ if (node.isMap || node.isList) {
377
+ const child = node.peg[path[pi]]
378
+ if (child == null) break
379
+
380
+ // Last segment: check for direct replacement
381
+ if (pi === path.length - 1) {
382
+ if (child === target) {
383
+ node.peg[path[pi]] = replacement
384
+ return true
385
+ }
386
+ // Target not at this position — search within child
387
+ node = child
388
+ pi++
389
+ break
390
+ }
391
+
392
+ node = child
393
+ }
394
+ else {
395
+ // Hit a non-navigable node — search it
396
+ break
397
+ }
398
+ }
399
+
400
+ // If path fully consumed with no match, or hit a junction/feature,
401
+ // search the current node using a stack.
402
+ const stack: any[] = [node]
403
+
404
+ while (stack.length > 0) {
405
+ const val = stack.pop()
406
+ if (val == null || !val.isVal) continue
407
+
408
+ if (val.isMap || val.isList) {
409
+ for (const k in val.peg) {
410
+ if (val.peg[k] === target) {
411
+ val.peg[k] = replacement
412
+ return true
413
+ }
414
+ if (val.peg[k]?.isVal) stack.push(val.peg[k])
415
+ }
416
+ }
417
+ else if (val.isConjunct || val.isDisjunct) {
418
+ for (let i = 0; i < val.peg.length; i++) {
419
+ if (val.peg[i] === target) {
420
+ val.peg[i] = replacement
421
+ return true
422
+ }
423
+ stack.push(val.peg[i])
424
+ }
425
+ }
426
+ else if (val.isFeature) {
427
+ if (val.peg === target) {
428
+ val.peg = replacement
429
+ return true
430
+ }
431
+ if (intoSpreads || !val.isSpread) {
432
+ if (Array.isArray(val.peg)) {
433
+ for (let i = 0; i < val.peg.length; i++) {
434
+ if (val.peg[i] === target) {
435
+ val.peg[i] = replacement
436
+ return true
437
+ }
438
+ if (val.peg[i]?.isVal) stack.push(val.peg[i])
439
+ }
440
+ }
441
+ else if (val.peg?.isVal) {
442
+ stack.push(val.peg)
443
+ }
444
+ }
445
+ }
446
+ }
447
+
448
+ return false
449
+ }
450
+
451
+
452
+ // Resolve all KeyFuncVals (not inside spreads) to StringVals.
453
+ // Uses the KeyFuncVal's path to determine the key name.
454
+ function resolveKeys(root: Val, keys: KeyFuncVal[]) {
455
+ for (const kv of keys) {
456
+ const resolved = kv.resolve(null as any, kv.peg)
457
+ if (resolved instanceof StringVal) {
458
+ resolved.dc = DONE
459
+ resolved.path = kv.path
460
+ replaceAtPath(root, kv.path, kv, resolved, false)
461
+ }
462
+ }
463
+ }
464
+
465
+
153
466
  class Unify {
154
467
  root: Val
155
468
  res: Val
@@ -200,15 +513,28 @@ class Unify {
200
513
  uctx.err = this.err
201
514
  uctx.explain = this.explain
202
515
 
516
+ // Path resolution phase: replace all PathVals with cloned targets.
517
+ // Pure structural replacement — no unification.
518
+ resolvePaths(res, uctx, this.lang.paths)
519
+ uctx = uctx.clone({ root: res })
520
+
521
+ // Key resolution phase: replace key() functions (not in spreads)
522
+ // with their resolved StringVal.
523
+ resolveKeys(res, this.lang.keys)
524
+
203
525
  const explain = null == ctx?.explain ? undefined : ctx?.explain
204
526
  const te = explain && explainOpen(uctx, explain, 'root', res)
205
527
 
206
528
  // NOTE: if true === res.done already, then this loop never needs to run.
529
+ // RefVals defer on cc=0 and while ctx.sc > 0.
530
+ // SpreadVal.unify maintains ctx.sc via increment/decrement.
207
531
  let maxcc = 9 // 99
208
532
  for (; this.cc < maxcc && DONE !== res.dc; this.cc++) {
209
533
  // console.log('CC', this.cc, res.canon)
210
534
  uctx.cc = this.cc
211
- uctx.seen = {}
535
+ uctx.seen = new Map()
536
+ uctx._refCloneCache = new Map()
537
+ uctx._uniteCache = new Map()
212
538
  res = unite(te ? uctx.clone({ explain: ec(te, 'run') }) : uctx, res, top(), 'unify')
213
539
 
214
540
  if (0 < uctx.err.length) {
package/src/utility.ts CHANGED
@@ -11,8 +11,11 @@ function propagateMarks(source: Val, target: Val): void {
11
11
  if (source.isTop || target.isTop) {
12
12
  return
13
13
  }
14
- for (let name in source.mark) {
15
- (target.mark as any)[name] = (target.mark as any)[name] || (source.mark as any)[name]
14
+ const sm = source.mark as any
15
+ if (!sm.type && !sm.hide) return
16
+ const tm = target.mark as any
17
+ for (let name in sm) {
18
+ tm[name] = tm[name] || sm[name]
16
19
  }
17
20
  }
18
21
 
package/src/val/BagVal.ts CHANGED
@@ -29,10 +29,6 @@ abstract class BagVal extends FeatureVal {
29
29
  closed: boolean = false
30
30
  optionalKeys: string[] = []
31
31
 
32
- spread = {
33
- cj: (undefined as Val | undefined),
34
- }
35
-
36
32
  constructor(
37
33
  spec: ValSpec,
38
34
  ctx?: AontuContext
@@ -42,7 +38,6 @@ abstract class BagVal extends FeatureVal {
42
38
 
43
39
  clone(ctx: AontuContext, spec?: ValSpec): Val {
44
40
  const bag = super.clone(ctx, spec) as BagVal
45
- bag.spread = this.spread
46
41
  return bag
47
42
  }
48
43
 
@@ -92,11 +87,15 @@ abstract class BagVal extends FeatureVal {
92
87
  || child.isMap
93
88
  || child.isList
94
89
  || child.isPref
95
- || child.isRef
90
+ || child.isPath
96
91
  || child.isDisjunct
92
+ || child.isConjunct
97
93
  || child.isNil
98
94
  ) {
99
- let cval = child.gen(ctx)
95
+ // Optional keys: use separate error context so gen errors
96
+ // don't propagate when the key is dropped.
97
+ const genctx = optional ? ctx.clone({ err: [], collect: true }) : ctx
98
+ let cval = child.gen(genctx)
100
99
 
101
100
  if (optional && (undefined === cval || empty(cval))) {
102
101
  continue