aontu 0.44.0 → 0.46.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 (84) hide show
  1. package/dist/cli.d.ts +9 -0
  2. package/dist/cli.js +203 -0
  3. package/dist/cli.js.map +1 -0
  4. package/dist/ctx.d.ts +2 -5
  5. package/dist/ctx.js +1 -2
  6. package/dist/ctx.js.map +1 -1
  7. package/dist/lang.d.ts +1 -5
  8. package/dist/lang.js +24 -100
  9. package/dist/lang.js.map +1 -1
  10. package/dist/tsconfig.tsbuildinfo +1 -1
  11. package/dist/type.d.ts +0 -5
  12. package/dist/type.js.map +1 -1
  13. package/dist/unify.js +13 -292
  14. package/dist/unify.js.map +1 -1
  15. package/dist/utility.js +2 -6
  16. package/dist/utility.js.map +1 -1
  17. package/dist/val/BagVal.d.ts +3 -0
  18. package/dist/val/BagVal.js +6 -6
  19. package/dist/val/BagVal.js.map +1 -1
  20. package/dist/val/ConjunctVal.d.ts +1 -1
  21. package/dist/val/ConjunctVal.js +14 -137
  22. package/dist/val/ConjunctVal.js.map +1 -1
  23. package/dist/val/CopyFuncVal.js +2 -3
  24. package/dist/val/CopyFuncVal.js.map +1 -1
  25. package/dist/val/DisjunctVal.js +0 -4
  26. package/dist/val/DisjunctVal.js.map +1 -1
  27. package/dist/val/ExpectVal.js +3 -16
  28. package/dist/val/ExpectVal.js.map +1 -1
  29. package/dist/val/JunctionVal.d.ts +0 -1
  30. package/dist/val/JunctionVal.js +1 -6
  31. package/dist/val/JunctionVal.js.map +1 -1
  32. package/dist/val/KeyFuncVal.js +2 -0
  33. package/dist/val/KeyFuncVal.js.map +1 -1
  34. package/dist/val/ListVal.d.ts +0 -1
  35. package/dist/val/ListVal.js +66 -33
  36. package/dist/val/ListVal.js.map +1 -1
  37. package/dist/val/MapVal.d.ts +2 -3
  38. package/dist/val/MapVal.js +95 -67
  39. package/dist/val/MapVal.js.map +1 -1
  40. package/dist/val/MoveFuncVal.d.ts +1 -2
  41. package/dist/val/MoveFuncVal.js +13 -78
  42. package/dist/val/MoveFuncVal.js.map +1 -1
  43. package/dist/val/PathFuncVal.js +4 -25
  44. package/dist/val/PathFuncVal.js.map +1 -1
  45. package/dist/val/PlusOpVal.d.ts +1 -1
  46. package/dist/val/PrefVal.js +5 -18
  47. package/dist/val/PrefVal.js.map +1 -1
  48. package/dist/val/{PathVal.d.ts → RefVal.d.ts} +3 -4
  49. package/dist/val/{PathVal.js → RefVal.js} +77 -75
  50. package/dist/val/RefVal.js.map +1 -0
  51. package/dist/val/Val.d.ts +1 -2
  52. package/dist/val/Val.js +8 -7
  53. package/dist/val/Val.js.map +1 -1
  54. package/dist/val/VarVal.js +2 -2
  55. package/dist/val/VarVal.js.map +1 -1
  56. package/package.json +9 -4
  57. package/src/cli.ts +193 -0
  58. package/src/ctx.ts +3 -16
  59. package/src/lang.ts +23 -113
  60. package/src/type.ts +0 -5
  61. package/src/unify.ts +13 -310
  62. package/src/utility.ts +2 -5
  63. package/src/val/BagVal.ts +7 -6
  64. package/src/val/ConjunctVal.ts +13 -131
  65. package/src/val/CopyFuncVal.ts +2 -3
  66. package/src/val/DisjunctVal.ts +0 -6
  67. package/src/val/ExpectVal.ts +4 -18
  68. package/src/val/JunctionVal.ts +1 -5
  69. package/src/val/KeyFuncVal.ts +3 -0
  70. package/src/val/ListVal.ts +88 -38
  71. package/src/val/MapVal.ts +124 -75
  72. package/src/val/MoveFuncVal.ts +14 -79
  73. package/src/val/PathFuncVal.ts +4 -29
  74. package/src/val/PrefVal.ts +6 -19
  75. package/src/val/{RefVal.ts.old → RefVal.ts} +19 -30
  76. package/src/val/Val.ts +9 -9
  77. package/src/val/VarVal.ts +2 -2
  78. package/README.md +0 -18
  79. package/dist/val/PathVal.js.map +0 -1
  80. package/dist/val/SpreadVal.d.ts +0 -20
  81. package/dist/val/SpreadVal.js +0 -194
  82. package/dist/val/SpreadVal.js.map +0 -1
  83. package/src/val/PathVal.ts +0 -435
  84. package/src/val/SpreadVal.ts +0 -275
@@ -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
 
package/src/val/MapVal.ts CHANGED
@@ -8,6 +8,7 @@ import type {
8
8
 
9
9
  import {
10
10
  DONE,
11
+ SPREAD,
11
12
  } from '../type'
12
13
 
13
14
  import { AontuContext } from '../ctx'
@@ -27,13 +28,13 @@ import {
27
28
  } from './top'
28
29
 
29
30
 
31
+ import { ConjunctVal } from './ConjunctVal'
30
32
  import { NilVal } from './NilVal'
31
33
  import { BagVal } from './BagVal'
32
34
 
33
35
 
34
36
  class MapVal extends BagVal {
35
37
  isMap = true
36
- _canonCache?: string
37
38
 
38
39
  constructor(
39
40
  spec: ValSpec,
@@ -47,6 +48,23 @@ class MapVal extends BagVal {
47
48
 
48
49
  this.mark.type = !!spec.mark?.type
49
50
  this.mark.hide = !!spec.mark?.hide
51
+
52
+ let spread = (this.peg as any)[SPREAD]
53
+ delete (this.peg as any)[SPREAD]
54
+
55
+ if (spread) {
56
+ if ('&' === spread.o) {
57
+ // TODO: handle existing spread!
58
+ this.spread.cj =
59
+ Array.isArray(spread.v) ?
60
+ 1 < spread.v.length ?
61
+ new ConjunctVal({ peg: spread.v }, ctx) :
62
+ spread.v[0] :
63
+ spread.v
64
+ }
65
+ }
66
+
67
+ // console.log('MAPVAL-ctor', this.type, spec)
50
68
  }
51
69
 
52
70
 
@@ -57,23 +75,6 @@ class MapVal extends BagVal {
57
75
 
58
76
  const TOP = top()
59
77
  peer = peer ?? TOP
60
-
61
- // Fast path: both maps done, no spreads, peer's keys are a
62
- // subset of this's keys with the same child references, and
63
- // neither side has type/hide marks. No new information.
64
- if (this.done && peer instanceof MapVal && peer.done
65
- && !this.mark.type && !this.mark.hide
66
- && !peer.mark.type && !peer.mark.hide) {
67
- let canSkip = true
68
- for (const k in peer.peg) {
69
- if (this.peg[k] !== peer.peg[k]) {
70
- canSkip = false
71
- break
72
- }
73
- }
74
- if (canSkip) return this
75
- }
76
-
77
78
  const te = ctx.explain && explainOpen(ctx, ctx.explain, 'Map', this, peer)
78
79
 
79
80
  let done: boolean = true
@@ -83,7 +84,8 @@ class MapVal extends BagVal {
83
84
  let out: MapVal | NilVal = (peer.isTop ? this : new MapVal({ peg: {} }, ctx))
84
85
 
85
86
  out.closed = this.closed
86
- out.optionalKeys = 0 < this.optionalKeys.length ? [...this.optionalKeys] : this.optionalKeys
87
+ out.optionalKeys = [...this.optionalKeys]
88
+ out.spread.cj = this.spread.cj
87
89
  out.site = this.site
88
90
 
89
91
  if (peer instanceof MapVal) {
@@ -106,47 +108,53 @@ class MapVal extends BagVal {
106
108
  out = peer.unify(this, te ? ctx.clone({ explain: ec(te, 'SPC') }) : ctx) as MapVal
107
109
  exit = true
108
110
  }
111
+
109
112
  }
113
+
114
+ if (!exit) {
115
+ 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
+ )
121
+ )
122
+ }
123
+ }
124
+ else {
125
+ // console.log('MAPVAL-PEER-OTHER', this.id, this.canon, this.done, peer.id, peer.canon, peer.done)
110
126
  }
111
127
 
112
128
 
113
129
  if (!exit) {
114
130
  out.dc = this.dc + 1
115
131
 
116
- // Fast path: self-unify with TOP.
117
- // If all children are already done, the map is fully converged.
118
- if (peer.isTop) {
119
- let allChildrenDone = true
120
- for (let key in this.peg) {
121
- if (DONE !== this.peg[key]?.dc) {
122
- allChildrenDone = false
123
- break
124
- }
125
- }
126
- if (allChildrenDone) {
127
- out.dc = DONE
128
- ctx.explain && explainClose(te, out)
129
- return out
130
- }
131
- }
132
+ // let newtype = this.type || peer.type
133
+
134
+ let spread_cj = out.spread.cj ?? TOP
132
135
 
133
- // Unify own children
136
+ // Always unify own children first
134
137
  for (let key in this.peg) {
135
- const child = this.peg[key]
136
138
  const keyctx = ctx.descend(key)
137
139
 
140
+ const key_spread_cj = spread_cj.spreadClone(keyctx)
141
+ const child = this.peg[key]
142
+
138
143
  propagateMarks(this, child)
139
144
 
140
145
  out.peg[key] =
141
- undefined === child ? top() :
146
+ undefined === child ? key_spread_cj :
142
147
  child.isNil ? child :
143
- child.done ? child :
144
- unite(te ? keyctx.clone({ explain: ec(te, 'KEY:' + key) }) : keyctx,
145
- child, top(), 'map-own')
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')
146
153
 
147
154
  done = (done && DONE === out.peg[key].dc)
148
155
  }
149
156
 
157
+ const allowedKeys: string[] = this.closed ? Object.keys(this.peg) : []
150
158
  let bad: NilVal | undefined = undefined
151
159
 
152
160
  if (peer instanceof MapVal) {
@@ -157,14 +165,12 @@ class MapVal extends BagVal {
157
165
  for (let peerkey in upeer.peg) {
158
166
  let peerchild = upeer.peg[peerkey]
159
167
 
160
- if (this.closed && !(peerkey in this.peg)) {
168
+ if (this.closed && !allowedKeys.includes(peerkey)) {
161
169
  bad = makeNilErr(ctx, 'closed', peerchild, undefined)
162
170
  }
163
171
 
164
172
  // key optionality is additive
165
- if (0 < upeer.optionalKeys.length
166
- && upeer.optionalKeys.includes(peerkey)
167
- && !out.optionalKeys.includes(peerkey)) {
173
+ if (upeer.optionalKeys.includes(peerkey) && !out.optionalKeys.includes(peerkey)) {
168
174
  out.optionalKeys.push(peerkey)
169
175
  }
170
176
 
@@ -174,11 +180,19 @@ class MapVal extends BagVal {
174
180
 
175
181
  let oval = out.peg[peerkey] =
176
182
  undefined === child ? this.handleExpectedVal(peerkey, peerchild, this, ctx) :
177
- child.isTop && peerchild.done ? peerchild :
178
- child.isNil ? child :
179
- peerchild.isNil ? peerchild :
180
- unite(te ? peerctx.clone({ explain: ec(te, 'CHD') }) : peerctx,
181
- child, peerchild, 'map-peer')
183
+ child.isTop && peerchild.done ? peerchild :
184
+ child.isNil ? child :
185
+ peerchild.isNil ? peerchild :
186
+ unite(te ? peerctx.clone({ explain: ec(te, 'CHD') }) : peerctx,
187
+ child, peerchild, 'map-peer')
188
+
189
+ 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')
195
+ }
182
196
 
183
197
  propagateMarks(this, oval)
184
198
 
@@ -199,41 +213,66 @@ class MapVal extends BagVal {
199
213
  out.dc = done ? DONE : out.dc
200
214
  propagateMarks(peer, out)
201
215
  propagateMarks(this, out)
202
-
203
216
  }
204
217
  }
205
218
 
219
+ // console.log(
220
+ // 'MAPVAL-OUT', out.canon,
221
+ // '\n SELF', this,
222
+ // '\n PEER', peer,
223
+ // '\n OUT', out,
224
+ // '\n FROM', (out as any).spread.cj
225
+ // )
226
+
206
227
  ctx.explain && explainClose(te, out)
207
228
 
208
229
  return out
209
230
  }
210
231
 
211
232
 
212
- // Optimized clone for use as a spread constraint: share
213
- // path-independent children, clone only path-dependent ones.
233
+ // Spread clone: return a Val usable as the per-key spread constraint.
234
+ //
235
+ // Three tiers:
236
+ // 1. tree is path-independent (no RefVal/KeyFuncVal/PathFuncVal/
237
+ // MoveFuncVal/SuperFuncVal anywhere below): return `this` directly.
238
+ // Nothing in the unify path mutates the spread root, and no
239
+ // child depends on its own stored .path, so sharing is safe.
240
+ // 2. top-level children are all ScalarKindVal: shallow clone
241
+ // (share children, fresh MapVal wrapper).
242
+ // 3. otherwise: full deep clone via `this.clone(ctx)`.
243
+ //
244
+ // Tier 1 handles the foo-sdk common case of simple type-constraint
245
+ // spreads like `&:{active: *true | boolean, version: *'0.0.1' | string}`,
246
+ // which are cloned thousands of times per run.
214
247
  spreadClone(ctx: AontuContext): Val {
215
248
  if (!this.isPathDependent) return this
216
249
 
250
+ let allScalarKind = true
251
+ for (let key in this.peg) {
252
+ if (!(this.peg[key] as any)?.isScalarKind) {
253
+ allScalarKind = false
254
+ break
255
+ }
256
+ }
257
+
258
+ if (!allScalarKind) {
259
+ return this.clone(ctx)
260
+ }
261
+
217
262
  let out = (super.clone(ctx) as MapVal)
218
263
  out.peg = {}
219
264
 
220
265
  for (let entry of Object.entries(this.peg)) {
221
- const child = entry[1] as Val
222
- if (child?.isVal && child.isPathDependent) {
223
- out.peg[entry[0]] = child.clone(ctx, { path: [...out.path, entry[0]] })
224
- }
225
- else if (child?.isVal) {
226
- const wrapper = Object.create(child)
227
- wrapper.mark = { ...child.mark }
228
- out.peg[entry[0]] = wrapper
229
- }
230
- else {
231
- out.peg[entry[0]] = child
232
- }
266
+ out.peg[entry[0]] = entry[1]
267
+ }
268
+
269
+ // Must create a new spread object to avoid mutating the original.
270
+ out.spread = {
271
+ cj: this.spread.cj ? this.spread.cj.spreadClone(ctx) : undefined,
233
272
  }
234
273
 
235
274
  out.closed = this.closed
236
- out.optionalKeys = 0 < this.optionalKeys.length ? [...this.optionalKeys] : this.optionalKeys
275
+ out.optionalKeys = [...this.optionalKeys]
237
276
 
238
277
  return out
239
278
  }
@@ -246,25 +285,36 @@ class MapVal extends BagVal {
246
285
  for (let entry of Object.entries(this.peg)) {
247
286
  out.peg[entry[0]] =
248
287
  (entry[1] as any)?.isVal ?
288
+ // (entry[1] as Val).clone(ctx, spec?.mark ? { mark: spec.mark } : {}) :
249
289
  (entry[1] as Val).clone(ctx, {
250
290
  mark: spec?.mark ?? {},
251
291
  path: [...out.path, entry[0]]
252
292
  }) :
253
293
  entry[1]
254
294
  }
295
+ if (this.spread.cj) {
296
+ out.spread.cj = this.spread.cj.clone(ctx, spec?.mark ? { mark: spec.mark } : {})
297
+ }
255
298
 
256
299
  out.closed = this.closed
257
300
  out.optionalKeys = [...this.optionalKeys]
258
301
 
302
+ // out.from = this.from
303
+
304
+ // console.log('MAPVAL-CLONE', this.canon, '->', out.canon)
259
305
  return out
260
306
  }
261
307
 
262
308
 
263
309
  get canon() {
264
- if (this._canonCache !== undefined) return this._canonCache
265
- let keys = Object.keys(this.peg).sort()
266
- const c = '' +
310
+ let keys = Object.keys(this.peg)
311
+ return '' +
312
+ // this.errcanon() +
313
+ // (this.mark.type ? '<type>' : '') +
314
+ // (this.id + '=') +
267
315
  '{' +
316
+ (this.spread.cj ? '&:' + this.spread.cj.canon +
317
+ (0 < keys.length ? ',' : '') : '') +
268
318
  keys
269
319
  .map(k => [
270
320
  JSON.stringify(k) +
@@ -273,19 +323,18 @@ class MapVal extends BagVal {
273
323
  (this.peg[k]?.canon ?? this.peg[k])
274
324
  ])
275
325
  .join(',') +
276
- '}'
277
- if (this.done) this._canonCache = c
278
- return c
326
+ '}' // + '<' + (this.mark.hide ? 'H' : '') + '>'
327
+
279
328
  }
280
329
 
281
330
 
282
- inspection(_d?: number) {
283
- return ''
331
+ inspection(d?: number) {
332
+ return this.spread.cj ? '&:' + this.spread.cj.inspect(null == d ? 0 : d + 1) : ''
284
333
  }
285
334
 
286
335
  }
287
336
 
288
337
 
289
338
  export {
290
- MapVal,
339
+ MapVal
291
340
  }