aontu 0.43.0 → 0.45.1

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 (80) hide show
  1. package/dist/ctx.d.ts +2 -5
  2. package/dist/ctx.js +1 -2
  3. package/dist/ctx.js.map +1 -1
  4. package/dist/lang.d.ts +1 -5
  5. package/dist/lang.js +24 -100
  6. package/dist/lang.js.map +1 -1
  7. package/dist/tsconfig.tsbuildinfo +1 -1
  8. package/dist/type.d.ts +0 -5
  9. package/dist/type.js.map +1 -1
  10. package/dist/unify.js +13 -292
  11. package/dist/unify.js.map +1 -1
  12. package/dist/utility.js +2 -6
  13. package/dist/utility.js.map +1 -1
  14. package/dist/val/BagVal.d.ts +3 -0
  15. package/dist/val/BagVal.js +6 -6
  16. package/dist/val/BagVal.js.map +1 -1
  17. package/dist/val/ConjunctVal.d.ts +1 -1
  18. package/dist/val/ConjunctVal.js +14 -137
  19. package/dist/val/ConjunctVal.js.map +1 -1
  20. package/dist/val/CopyFuncVal.js +2 -3
  21. package/dist/val/CopyFuncVal.js.map +1 -1
  22. package/dist/val/DisjunctVal.js +0 -4
  23. package/dist/val/DisjunctVal.js.map +1 -1
  24. package/dist/val/ExpectVal.js +3 -16
  25. package/dist/val/ExpectVal.js.map +1 -1
  26. package/dist/val/JunctionVal.d.ts +0 -1
  27. package/dist/val/JunctionVal.js +1 -6
  28. package/dist/val/JunctionVal.js.map +1 -1
  29. package/dist/val/KeyFuncVal.js +2 -0
  30. package/dist/val/KeyFuncVal.js.map +1 -1
  31. package/dist/val/ListVal.d.ts +0 -1
  32. package/dist/val/ListVal.js +66 -33
  33. package/dist/val/ListVal.js.map +1 -1
  34. package/dist/val/MapVal.d.ts +2 -3
  35. package/dist/val/MapVal.js +95 -67
  36. package/dist/val/MapVal.js.map +1 -1
  37. package/dist/val/MoveFuncVal.d.ts +1 -2
  38. package/dist/val/MoveFuncVal.js +13 -78
  39. package/dist/val/MoveFuncVal.js.map +1 -1
  40. package/dist/val/PathFuncVal.js +4 -25
  41. package/dist/val/PathFuncVal.js.map +1 -1
  42. package/dist/val/PlusOpVal.d.ts +1 -1
  43. package/dist/val/PrefVal.js +5 -18
  44. package/dist/val/PrefVal.js.map +1 -1
  45. package/dist/val/{PathVal.d.ts → RefVal.d.ts} +3 -4
  46. package/dist/val/{PathVal.js → RefVal.js} +77 -75
  47. package/dist/val/RefVal.js.map +1 -0
  48. package/dist/val/Val.d.ts +1 -2
  49. package/dist/val/Val.js +8 -7
  50. package/dist/val/Val.js.map +1 -1
  51. package/dist/val/VarVal.js +2 -2
  52. package/dist/val/VarVal.js.map +1 -1
  53. package/package.json +6 -5
  54. package/src/ctx.ts +3 -16
  55. package/src/lang.ts +23 -113
  56. package/src/type.ts +0 -5
  57. package/src/unify.ts +13 -310
  58. package/src/utility.ts +2 -5
  59. package/src/val/BagVal.ts +7 -6
  60. package/src/val/ConjunctVal.ts +13 -131
  61. package/src/val/CopyFuncVal.ts +2 -3
  62. package/src/val/DisjunctVal.ts +0 -6
  63. package/src/val/ExpectVal.ts +4 -18
  64. package/src/val/JunctionVal.ts +1 -5
  65. package/src/val/KeyFuncVal.ts +3 -0
  66. package/src/val/ListVal.ts +88 -38
  67. package/src/val/MapVal.ts +124 -75
  68. package/src/val/MoveFuncVal.ts +14 -79
  69. package/src/val/PathFuncVal.ts +4 -29
  70. package/src/val/PrefVal.ts +6 -19
  71. package/src/val/{RefVal.ts.old → RefVal.ts} +19 -30
  72. package/src/val/Val.ts +9 -9
  73. package/src/val/VarVal.ts +2 -2
  74. package/README.md +0 -18
  75. package/dist/val/PathVal.js.map +0 -1
  76. package/dist/val/SpreadVal.d.ts +0 -20
  77. package/dist/val/SpreadVal.js +0 -194
  78. package/dist/val/SpreadVal.js.map +0 -1
  79. package/src/val/PathVal.ts +0 -435
  80. package/src/val/SpreadVal.ts +0 -275
package/src/utility.ts CHANGED
@@ -11,11 +11,8 @@ function propagateMarks(source: Val, target: Val): void {
11
11
  if (source.isTop || target.isTop) {
12
12
  return
13
13
  }
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]
14
+ for (let name in source.mark) {
15
+ (target.mark as any)[name] = (target.mark as any)[name] || (source.mark as any)[name]
19
16
  }
20
17
  }
21
18
 
package/src/val/BagVal.ts CHANGED
@@ -29,6 +29,10 @@ 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
+
32
36
  constructor(
33
37
  spec: ValSpec,
34
38
  ctx?: AontuContext
@@ -38,6 +42,7 @@ abstract class BagVal extends FeatureVal {
38
42
 
39
43
  clone(ctx: AontuContext, spec?: ValSpec): Val {
40
44
  const bag = super.clone(ctx, spec) as BagVal
45
+ bag.spread = this.spread
41
46
  return bag
42
47
  }
43
48
 
@@ -87,15 +92,11 @@ abstract class BagVal extends FeatureVal {
87
92
  || child.isMap
88
93
  || child.isList
89
94
  || child.isPref
90
- || child.isPath
95
+ || child.isRef
91
96
  || child.isDisjunct
92
- || child.isConjunct
93
97
  || child.isNil
94
98
  ) {
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)
99
+ let cval = child.gen(ctx)
99
100
 
100
101
  if (optional && (undefined === cval || empty(cval))) {
101
102
  continue
@@ -36,57 +36,6 @@ import {
36
36
  top
37
37
  } from './top'
38
38
 
39
- import { MapVal } from './MapVal'
40
-
41
-
42
- // Shallow merge two MapVals: combine keys without deep unification.
43
- // For shared keys, merge child terms from both maps into one ConjunctVal
44
- // so spreads at that level see all children from both terms.
45
- function shallowMerge(a: MapVal, b: MapVal, ctx: AontuContext): MapVal {
46
- const out = new MapVal({ peg: {} }, ctx)
47
- out.site = a.site
48
- out.closed = a.closed || b.closed
49
- out.optionalKeys = [...a.optionalKeys]
50
- for (const k of b.optionalKeys) {
51
- if (!out.optionalKeys.includes(k)) out.optionalKeys.push(k)
52
- }
53
-
54
- let done = true
55
-
56
- // Copy all keys from a
57
- for (const k in a.peg) {
58
- out.peg[k] = a.peg[k]
59
- }
60
-
61
- // Merge keys from b
62
- for (const k in b.peg) {
63
- if (k in out.peg) {
64
- const av = out.peg[k]
65
- const bv = b.peg[k]
66
-
67
- // Flatten both sides' terms into a single ConjunctVal
68
- const terms: Val[] = []
69
- if (av.isConjunct) { terms.push(...av.peg) } else { terms.push(av) }
70
- if (bv.isConjunct) { terms.push(...bv.peg) } else { terms.push(bv) }
71
-
72
- out.peg[k] = new ConjunctVal({ peg: terms }, ctx)
73
- done = false
74
- }
75
- else {
76
- out.peg[k] = b.peg[k]
77
- }
78
- }
79
-
80
- for (const k in out.peg) {
81
- done = done && (DONE === out.peg[k]?.dc)
82
- }
83
-
84
- out.dc = done ? DONE : 1
85
- propagateMarks(a, out)
86
- propagateMarks(b, out)
87
- return out
88
- }
89
-
90
39
 
91
40
 
92
41
  // TODO: move main logic to op/conjunct
@@ -122,11 +71,6 @@ class ConjunctVal extends JunctionVal {
122
71
  unify(peer: Val, ctx: AontuContext): Val {
123
72
  peer = peer ?? top()
124
73
 
125
- // Fast path: done conjunct self-unifying with TOP.
126
- if (this.done && peer.isTop) {
127
- return this
128
- }
129
-
130
74
  const te = ctx.explain && explainOpen(ctx, ctx.explain, 'Conjunct', this, peer)
131
75
 
132
76
  let done = true
@@ -152,12 +96,7 @@ class ConjunctVal extends JunctionVal {
152
96
  this.peg[vI].mark.hide = newhide
153
97
  // console.log('CONJUNCT-TERM', this.id, vI, this.peg[vI].canon)
154
98
 
155
- // Defer unify-with-TOP for map terms with spreads when other
156
- // map terms exist — the shallow merge fold handles them instead.
157
- const hasMapPeers = this.peg.length > 1 && this.peg[vI].isMap &&
158
- this.peg.some((p: Val, i: number) => i !== vI && p.isMap)
159
99
  upeer[vI] = (this.peg[vI].done && peer.isTop) ? this.peg[vI] :
160
- (hasMapPeers && hasSpreads(this.peg[vI])) ? this.peg[vI] :
161
100
  unite(te ? ctx.clone({ explain: ec(te, 'OWN') }) : ctx, this.peg[vI], peer, 'cj-own')
162
101
 
163
102
  upeer[vI].mark.type = newtype = newtype || upeer[vI].mark.type
@@ -178,22 +117,7 @@ class ConjunctVal extends JunctionVal {
178
117
  }
179
118
 
180
119
  upeer = norm(upeer)
181
-
182
- // Stable partition: fold pure terms first, spread-bearing terms
183
- // last. Pure terms (no spreads, no path-dependent functions)
184
- // unify cheaply. Dynamic terms are then folded in, applying
185
- // spread constraints once to the merged result rather than
186
- // incrementally at every intermediate fold step.
187
- const pure: Val[] = []
188
- const dyn: Val[] = []
189
- for (const term of upeer) {
190
- if (term.isPathDependent || hasSpreads(term)) {
191
- dyn.push(term)
192
- } else {
193
- pure.push(term)
194
- }
195
- }
196
- upeer = pure.concat(dyn)
120
+ // console.log('CONJUNCT-UPEER', this.id, upeer.map((v: Val) => v.canon))
197
121
 
198
122
  // Unify terms against each other
199
123
 
@@ -217,28 +141,17 @@ class ConjunctVal extends JunctionVal {
217
141
 
218
142
  // Can't unite with a RefVal, unless also a RefVal with same path.
219
143
  // else if (t0 instanceof RefVal && !(t1 instanceof RefVal)) {
220
- else if (t0.isPath && !(t1.isPath)) {
144
+ else if (t0.isRef && !(t1.isRef)) {
221
145
  outvals.push(t0)
222
146
  t0 = t1
223
147
  }
224
148
 
225
- else if (t1.isPath && !(t0.isPath)) {
149
+ else if (t1.isRef && !(t0.isRef)) {
226
150
  outvals.push(t0)
227
151
  t0 = t1
228
152
  }
229
153
 
230
154
 
231
- // Shallow merge: when both are maps AND the conjunct contains spreads,
232
- // combine keys into inner ConjunctVals instead of deep unification.
233
- // This ensures spreads see ALL children across terms before being applied.
234
- else if (t0.isMap && t1.isMap && (hasSpreads(t0) || hasSpreads(t1))) {
235
- const merged = shallowMerge(t0 as any, t1 as any, ctx)
236
- done = done && DONE === merged.dc
237
- newtype = this.mark.type || merged.mark.type
238
- newhide = this.mark.hide || merged.mark.hide
239
- t0 = merged
240
- }
241
-
242
155
  else {
243
156
  val = unite(te ? ctx.clone({ explain: ec(te, 'DEF') }) : ctx, t0, t1, 'cj-peer-t0t1')
244
157
  // console.log('CONJUNCT-T', t0.canon, t1?.canon, '->', val.canon)
@@ -246,7 +159,7 @@ class ConjunctVal extends JunctionVal {
246
159
  newtype = this.mark.type || val.mark.type
247
160
  newhide = this.mark.hide || val.mark.hide
248
161
 
249
- // Unite was just a conjunct anyway, so discard.
162
+ // Unite was just a conjunt anyway, so discard.
250
163
  if (val.isConjunct) {
251
164
  outvals.push(t0)
252
165
  t0 = t1
@@ -302,15 +215,6 @@ class ConjunctVal extends JunctionVal {
302
215
 
303
216
 
304
217
  gen(ctx?: AontuContext) {
305
- // If this conjunct contains a SpreadVal + a generable Val,
306
- // gen the generable Val (spread is an unresolved constraint
307
- // that's a no-op at gen time — e.g., empty map with spread).
308
- if (this.peg.length === 2) {
309
- const [a, b] = this.peg
310
- if (a.isSpread && b.isGenable) return b.gen(ctx as any)
311
- if (b.isSpread && a.isGenable) return a.gen(ctx as any)
312
- }
313
-
314
218
  // Unresolved conjunct cannot be generated, so always an error.
315
219
  let nil = makeNilErr(
316
220
  ctx,
@@ -343,17 +247,18 @@ class ConjunctVal extends JunctionVal {
343
247
  function norm(terms: Val[]): Val[] {
344
248
 
345
249
  let expand: Val[] = []
346
- function flattenTerms(terms: Val[]) {
347
- for (let tI = 0; tI < terms.length; tI++) {
348
- if (terms[tI].isConjunct) {
349
- flattenTerms(terms[tI].peg)
350
- }
351
- else {
352
- expand.push(terms[tI])
250
+ for (let tI = 0, pI = 0; tI < terms.length; tI++, pI++) {
251
+ if (terms[tI].isConjunct) {
252
+ const cterms = terms[tI].peg
253
+ for (let cI = 0; cI < cterms.length; cI++) {
254
+ expand[pI + cI] = cterms[cI]
353
255
  }
256
+ pI += cterms.length - 1
257
+ }
258
+ else {
259
+ expand[pI] = terms[tI]
354
260
  }
355
261
  }
356
- flattenTerms(terms)
357
262
 
358
263
 
359
264
  // Consistent ordering ensures order independent unification.
@@ -377,29 +282,6 @@ function norm(terms: Val[]): Val[] {
377
282
  }
378
283
 
379
284
 
380
- // Check if a Val tree contains any SpreadVal constraints.
381
- // Cached on _hasSpreads for performance.
382
- function hasSpreads(v: any): boolean {
383
- if (v._hasSpreads !== undefined) return v._hasSpreads
384
- let result = false
385
- if (v.isSpread) {
386
- result = true
387
- }
388
- else if (v.isMap || v.isList) {
389
- for (const k in v.peg) {
390
- if (v.peg[k]?.isVal && hasSpreads(v.peg[k])) { result = true; break }
391
- }
392
- }
393
- else if (v.isJunction) {
394
- for (const p of v.peg) {
395
- if (p?.isVal && hasSpreads(p)) { result = true; break }
396
- }
397
- }
398
- v._hasSpreads = result
399
- return result
400
- }
401
-
402
-
403
285
  export {
404
286
  norm,
405
287
  ConjunctVal,
@@ -61,10 +61,9 @@ class CopyFuncVal extends FuncBaseVal {
61
61
 
62
62
  // console.log('CR', out)
63
63
 
64
- if (!out.isPath) {
65
- out.mark.type = false
66
- out.mark.hide = false
64
+ if (!out.isRef) {
67
65
  walk(out, (_key: string | number | undefined, val: Val) => {
66
+ // console.log('WALK', val)
68
67
  val.mark.type = false
69
68
  val.mark.hide = false
70
69
  return val
@@ -30,7 +30,6 @@ import {
30
30
  top
31
31
  } from './top'
32
32
 
33
-
34
33
  import { NilVal, TRIAL_NIL } from '../val/NilVal'
35
34
  import { PrefVal } from '../val/PrefVal'
36
35
  import { JunctionVal } from '../val/JunctionVal'
@@ -68,11 +67,6 @@ class DisjunctVal extends JunctionVal {
68
67
  unify(peer: Val, ctx: AontuContext): Val {
69
68
  peer = peer ?? top()
70
69
 
71
- // Fast path: already-done disjunct unifying with TOP.
72
- if (this.done && peer.isTop) {
73
- return this
74
- }
75
-
76
70
  const te = ctx.explain && explainOpen(ctx, ctx.explain, 'Disjunct', this, peer)
77
71
 
78
72
  if (!this.prefsRanked) {
@@ -51,25 +51,11 @@ class ExpectVal extends FeatureVal {
51
51
  this.peer = undefined === this.peer ? peer :
52
52
  unite(te ? ctx.clone({ explain: ec(te, 'PEER') }) : ctx, this.peer, peer, 'expect-peer')
53
53
 
54
- // Unwrap nested ExpectVals to prevent infinite recursion
55
- let peg = this.peg
56
- while (peg?.isExpect) {
57
- peg = peg.peg
58
- }
54
+ const peeru =
55
+ unite(te ? ctx.clone({ explain: ec(te, 'EXPECT') }) : ctx, this.peer, this.peg, 'expect-self')
59
56
 
60
- // Guard against re-entrant recursion
61
- if ((this as any)._expectDepth > 0) {
62
- out.dc = this.dc + 1
63
- }
64
- else {
65
- (this as any)._expectDepth = ((this as any)._expectDepth || 0) + 1
66
- const peeru =
67
- unite(te ? ctx.clone({ explain: ec(te, 'EXPECT') }) : ctx, this.peer, peg, 'expect-self')
68
- ;(this as any)._expectDepth--
69
-
70
- if (peeru.isGenable) {
71
- out = peeru
72
- }
57
+ if (peeru.isGenable) {
58
+ out = peeru
73
59
  }
74
60
  }
75
61
 
@@ -17,7 +17,6 @@ import { FeatureVal } from './FeatureVal'
17
17
  // (ConjunctVal and DisjunctVal)
18
18
  abstract class JunctionVal extends FeatureVal {
19
19
  isJunction = true
20
- _canonCache?: string
21
20
 
22
21
  constructor(
23
22
  spec: ValSpec,
@@ -39,13 +38,10 @@ abstract class JunctionVal extends FeatureVal {
39
38
  }
40
39
 
41
40
  get canon() {
42
- if (this._canonCache !== undefined) return this._canonCache
43
- const c = this.peg.map((v: Val) => {
41
+ return this.peg.map((v: Val) => {
44
42
  return (v as any).isJunction && Array.isArray(v.peg) && 1 < v.peg.length ?
45
43
  '(' + v.canon + ')' : v.canon // v.id + '=' + v.canon
46
44
  }).join(this.getJunctionSymbol()) // + '<' + (this.mark.hide ? 'H' : '') + '>'
47
- if (this.done) this._canonCache = c
48
- return c
49
45
  }
50
46
 
51
47
  // Abstract method to be implemented by subclasses to define their junction symbol
@@ -18,6 +18,7 @@ import { ConjunctVal } from '../val/ConjunctVal'
18
18
 
19
19
  import { FuncBaseVal } from './FuncBaseVal'
20
20
 
21
+
21
22
  class KeyFuncVal extends FuncBaseVal {
22
23
  isKeyFunc = true
23
24
 
@@ -40,12 +41,14 @@ class KeyFuncVal extends FuncBaseVal {
40
41
 
41
42
 
42
43
  unify(peer: Val, ctx: AontuContext): Val {
44
+ // TODO: this delay makes keys in spreads and refs work, but it is a hack - find a better way.
43
45
  let out: Val = this
44
46
 
45
47
  if (ctx.cc < 3) {
46
48
  this.notdone()
47
49
 
48
50
  if (peer.isTop || (peer.id === this.id)) {
51
+ // TODO: clone needed to avoid triggering unify_cycle - find a better way
49
52
  out = this.clone(ctx)
50
53
  }
51
54
  else if (peer.isNil) {
@@ -9,6 +9,7 @@ import type {
9
9
 
10
10
  import {
11
11
  DONE,
12
+ SPREAD,
12
13
  } from '../type'
13
14
 
14
15
  import { AontuContext } from '../ctx'
@@ -29,6 +30,7 @@ import {
29
30
  top
30
31
  } from './top'
31
32
 
33
+ import { ConjunctVal } from './ConjunctVal'
32
34
  import { NilVal } from './NilVal'
33
35
  import { BagVal } from './BagVal'
34
36
  import { empty } from './Val'
@@ -36,7 +38,6 @@ import { empty } from './Val'
36
38
 
37
39
  class ListVal extends BagVal {
38
40
  isList = true
39
- _canonCache?: string
40
41
 
41
42
  constructor(
42
43
  spec: {
@@ -50,6 +51,24 @@ class ListVal extends BagVal {
50
51
  throw new AontuError('ListVal spec.peg undefined')
51
52
  }
52
53
 
54
+ let spread = (this.peg as any)[SPREAD]
55
+ delete (this.peg as any)[SPREAD]
56
+
57
+ if (spread) {
58
+ if ('&' === spread.o) {
59
+
60
+ // TODO: handle existing spread!
61
+ this.spread.cj =
62
+ Array.isArray(spread.v) ?
63
+ 1 < spread.v.length ?
64
+ new ConjunctVal({ peg: spread.v }, ctx) :
65
+ spread.v[0] :
66
+ spread.v
67
+
68
+ // let tmv = Array.isArray(spread.v) ? spread.v : [spread.v]
69
+ // this.spread.cj = new ConjunctVal({ peg: tmv }, ctx)
70
+ }
71
+ }
53
72
  }
54
73
 
55
74
 
@@ -67,7 +86,8 @@ class ListVal extends BagVal {
67
86
  let out: ListVal | NilVal = (peer.isTop ? this : new ListVal({ peg: [] }, ctx))
68
87
 
69
88
  out.closed = this.closed
70
- out.optionalKeys = 0 < this.optionalKeys.length ? [...this.optionalKeys] : this.optionalKeys
89
+ out.optionalKeys = [...this.optionalKeys]
90
+ out.spread.cj = this.spread.cj
71
91
  out.site = this.site
72
92
 
73
93
  if (peer instanceof ListVal) {
@@ -77,6 +97,13 @@ class ListVal extends BagVal {
77
97
  }
78
98
  else {
79
99
  out.closed = out.closed || peer.closed
100
+ out.spread.cj = null == out.spread.cj ? peer.spread.cj : (
101
+ null == peer.spread.cj ? out.spread.cj : (
102
+ out.spread.cj =
103
+ unite(te ? ctx.clone({ explain: ec(te, 'SPR') }) : ctx,
104
+ out.spread.cj, peer.spread.cj, 'list-peer')
105
+ )
106
+ )
80
107
  }
81
108
  }
82
109
 
@@ -84,39 +111,29 @@ class ListVal extends BagVal {
84
111
  if (!exit) {
85
112
  out.dc = this.dc + 1
86
113
 
87
- // Fast path: self-unify with TOP.
88
- if (peer.isTop) {
89
- let allChildrenDone = true
90
- for (let key in this.peg) {
91
- if (DONE !== this.peg[key]?.dc) {
92
- allChildrenDone = false
93
- break
94
- }
95
- }
96
- if (allChildrenDone) {
97
- out.dc = DONE
98
- ctx.explain && explainClose(te, out)
99
- return out
100
- }
101
- }
114
+ let spread_cj = out.spread.cj || TOP
102
115
 
103
- // Unify own children
116
+ // Always unify children first
104
117
  for (let key in this.peg) {
105
118
  const keyctx = ctx.descend(key)
119
+ const key_spread_cj = spread_cj.spreadClone(keyctx)
106
120
  const child = this.peg[key]
107
121
 
108
122
  propagateMarks(this, child)
109
123
 
110
124
  out.peg[key] =
111
- undefined === child ? top() :
125
+ undefined === child ? key_spread_cj :
112
126
  child.isNil ? child :
113
- child.done ? child :
114
- unite(te ? keyctx.clone({ explain: ec(te, 'PEG:' + key) }) : keyctx,
115
- child, top(), 'list-own')
127
+ key_spread_cj.isNil ? key_spread_cj :
128
+ key_spread_cj.isTop && child.done ? child :
129
+ child.isTop && key_spread_cj.done ? key_spread_cj :
130
+ unite(te ? keyctx.clone({ explain: ec(te, 'PEG:' + key) }) : keyctx,
131
+ child, key_spread_cj, 'list-own')
116
132
 
117
133
  done = (done && DONE === out.peg[key].dc)
118
134
  }
119
135
 
136
+ const allowedKeys: string[] = this.closed ? Object.keys(this.peg) : []
120
137
  let bad: NilVal | undefined = undefined
121
138
 
122
139
  if (peer instanceof ListVal) {
@@ -124,10 +141,11 @@ class ListVal extends BagVal {
124
141
  te ? ctx.clone({ explain: ec(te, 'PER') }) : ctx,
125
142
  peer, TOP, 'list-peer-list') as ListVal)
126
143
 
144
+ // NOTE: peerkey is the index
127
145
  for (let peerkey in upeer.peg) {
128
146
  let peerchild = upeer.peg[peerkey]
129
147
 
130
- if (this.closed && !(peerkey in this.peg)) {
148
+ if (this.closed && !allowedKeys.includes(peerkey)) {
131
149
  bad = makeNilErr(ctx, 'closed', peerchild, undefined)
132
150
  }
133
151
 
@@ -137,11 +155,19 @@ class ListVal extends BagVal {
137
155
 
138
156
  let oval = out.peg[peerkey] =
139
157
  undefined === child ? peerchild :
140
- child.isTop && peerchild.done ? peerchild :
141
- child.isNil ? child :
142
- peerchild.isNil ? peerchild :
143
- unite(te ? peerctx.clone({ explain: ec(te, 'CHD') }) : peerctx,
144
- child, peerchild, 'list-peer')
158
+ child.isTop && peerchild.done ? peerchild :
159
+ child.isNil ? child :
160
+ peerchild.isNil ? peerchild :
161
+ unite(te ? peerctx.clone({ explain: ec(te, 'CHD') }) : peerctx,
162
+ child, peerchild, 'list-peer')
163
+
164
+ if (this.spread.cj) {
165
+ let key_spread_cj = spread_cj.spreadClone(peerctx)
166
+
167
+ oval = out.peg[peerkey] =
168
+ unite(te ? peerctx.clone({ explain: ec(te, 'PSP:' + peerkey) }) : peerctx,
169
+ out.peg[peerkey], key_spread_cj, 'list-spread')
170
+ }
145
171
 
146
172
  propagateMarks(this, oval)
147
173
 
@@ -171,18 +197,39 @@ class ListVal extends BagVal {
171
197
  }
172
198
 
173
199
 
174
- // Spread clone: share path-independent children directly, clone
175
- // only path-dependent ones. See MapVal.spreadClone for rationale.
200
+ // Spread clone: only deep-clone children that are path-dependent
201
+ // (isFunc, isRef). Share all other children directly.
202
+ // Spread clone: when all children are ScalarKindVal (simple type
203
+ // constraints like `string`, `number`), share them directly to avoid
204
+ // N x M allocations. ScalarKindVal is safe to share: it is immutable,
205
+ // always done, never path-dependent, and never has marks mutated.
206
+ // For anything more complex, fall back to full deep clone.
176
207
  spreadClone(ctx: AontuContext): Val {
208
+ // B1: share directly when the spread tree has no path-dependent
209
+ // leaves. See MapVal.spreadClone for rationale.
177
210
  if (!this.isPathDependent) return this
178
211
 
212
+ let allScalarKind = true
213
+ for (let key in this.peg) {
214
+ if (!(this.peg[key] as any)?.isScalarKind) {
215
+ allScalarKind = false
216
+ break
217
+ }
218
+ }
219
+
220
+ if (!allScalarKind) {
221
+ return this.clone(ctx)
222
+ }
223
+
179
224
  let out = (super.clone(ctx) as ListVal)
180
225
 
181
226
  for (let entry of Object.entries(this.peg)) {
182
- const child = entry[1] as Val
183
- out.peg[entry[0]] = child?.isPathDependent
184
- ? child.clone(ctx, { mark: {} })
185
- : child
227
+ out.peg[entry[0]] = entry[1]
228
+ }
229
+
230
+ // Must create a new spread object to avoid mutating the original.
231
+ out.spread = {
232
+ cj: this.spread.cj ? this.spread.cj.spreadClone(ctx) : undefined,
186
233
  }
187
234
 
188
235
  out.closed = this.closed
@@ -198,6 +245,10 @@ class ListVal extends BagVal {
198
245
  out.peg[entry[0]] =
199
246
  (entry[1] as any)?.isVal ? (entry[1] as Val).clone(ctx, spec?.mark ? { mark: spec.mark } : {}) : entry[1]
200
247
  }
248
+ if (this.spread.cj) {
249
+ out.spread.cj = this.spread.cj.clone(ctx, spec?.mark ? { mark: spec.mark } : {})
250
+ }
251
+
201
252
  out.closed = this.closed
202
253
  out.optionalKeys = [...this.optionalKeys]
203
254
 
@@ -207,19 +258,18 @@ class ListVal extends BagVal {
207
258
 
208
259
 
209
260
  get canon() {
210
- if (this._canonCache !== undefined) return this._canonCache
211
261
  // console.log('LISTVAL-CANON', this.optionalKeys)
212
262
  let keys = Object.keys(this.peg)
213
- const c = '' +
263
+ return '' +
214
264
  // this.errcanon() +
215
265
  '[' +
266
+ (this.spread.cj ? '&:' + this.spread.cj.canon +
267
+ (0 < keys.length ? ',' : '') : '') +
216
268
  keys
217
269
  .map(k => this.optionalKeys.includes(k) ?
218
270
  k + '?:' + this.peg[k].canon :
219
271
  this.peg[k].canon).join(',') +
220
272
  ']'
221
- if (this.done) this._canonCache = c
222
- return c
223
273
  }
224
274
  }
225
275