aontu 0.41.0 → 0.43.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 (79) hide show
  1. package/dist/ctx.d.ts +5 -2
  2. package/dist/ctx.js +2 -1
  3. package/dist/ctx.js.map +1 -1
  4. package/dist/lang.d.ts +5 -1
  5. package/dist/lang.js +100 -24
  6. package/dist/lang.js.map +1 -1
  7. package/dist/tsconfig.tsbuildinfo +1 -1
  8. package/dist/type.d.ts +5 -0
  9. package/dist/type.js.map +1 -1
  10. package/dist/unify.js +292 -13
  11. package/dist/unify.js.map +1 -1
  12. package/dist/utility.js +6 -2
  13. package/dist/utility.js.map +1 -1
  14. package/dist/val/BagVal.d.ts +0 -3
  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 +137 -14
  19. package/dist/val/ConjunctVal.js.map +1 -1
  20. package/dist/val/CopyFuncVal.js +3 -2
  21. package/dist/val/CopyFuncVal.js.map +1 -1
  22. package/dist/val/DisjunctVal.js +4 -0
  23. package/dist/val/DisjunctVal.js.map +1 -1
  24. package/dist/val/ExpectVal.js +16 -3
  25. package/dist/val/ExpectVal.js.map +1 -1
  26. package/dist/val/JunctionVal.d.ts +1 -0
  27. package/dist/val/JunctionVal.js +6 -1
  28. package/dist/val/JunctionVal.js.map +1 -1
  29. package/dist/val/KeyFuncVal.js +0 -2
  30. package/dist/val/KeyFuncVal.js.map +1 -1
  31. package/dist/val/ListVal.d.ts +1 -0
  32. package/dist/val/ListVal.js +33 -66
  33. package/dist/val/ListVal.js.map +1 -1
  34. package/dist/val/MapVal.d.ts +3 -2
  35. package/dist/val/MapVal.js +67 -95
  36. package/dist/val/MapVal.js.map +1 -1
  37. package/dist/val/MoveFuncVal.d.ts +2 -1
  38. package/dist/val/MoveFuncVal.js +78 -13
  39. package/dist/val/MoveFuncVal.js.map +1 -1
  40. package/dist/val/PathFuncVal.js +25 -4
  41. package/dist/val/PathFuncVal.js.map +1 -1
  42. package/dist/val/{RefVal.d.ts → PathVal.d.ts} +4 -3
  43. package/dist/val/{RefVal.js → PathVal.js} +75 -77
  44. package/dist/val/PathVal.js.map +1 -0
  45. package/dist/val/PlusOpVal.d.ts +1 -1
  46. package/dist/val/PrefVal.js +18 -5
  47. package/dist/val/PrefVal.js.map +1 -1
  48. package/dist/val/SpreadVal.d.ts +20 -0
  49. package/dist/val/SpreadVal.js +194 -0
  50. package/dist/val/SpreadVal.js.map +1 -0
  51. package/dist/val/Val.d.ts +2 -1
  52. package/dist/val/Val.js +7 -8
  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 +5 -6
  57. package/src/ctx.ts +16 -3
  58. package/src/lang.ts +113 -23
  59. package/src/type.ts +5 -0
  60. package/src/unify.ts +310 -13
  61. package/src/utility.ts +5 -2
  62. package/src/val/BagVal.ts +6 -7
  63. package/src/val/ConjunctVal.ts +131 -13
  64. package/src/val/CopyFuncVal.ts +3 -2
  65. package/src/val/DisjunctVal.ts +6 -0
  66. package/src/val/ExpectVal.ts +18 -4
  67. package/src/val/JunctionVal.ts +5 -1
  68. package/src/val/KeyFuncVal.ts +0 -3
  69. package/src/val/ListVal.ts +38 -88
  70. package/src/val/MapVal.ts +75 -124
  71. package/src/val/MoveFuncVal.ts +79 -14
  72. package/src/val/PathFuncVal.ts +29 -4
  73. package/src/val/PathVal.ts +435 -0
  74. package/src/val/PrefVal.ts +19 -6
  75. package/src/val/{RefVal.ts → RefVal.ts.old} +30 -19
  76. package/src/val/SpreadVal.ts +275 -0
  77. package/src/val/Val.ts +9 -9
  78. package/src/val/VarVal.ts +2 -2
  79. package/dist/val/RefVal.js.map +0 -1
@@ -30,6 +30,7 @@ import {
30
30
  top
31
31
  } from './top'
32
32
 
33
+
33
34
  import { NilVal, TRIAL_NIL } from '../val/NilVal'
34
35
  import { PrefVal } from '../val/PrefVal'
35
36
  import { JunctionVal } from '../val/JunctionVal'
@@ -67,6 +68,11 @@ class DisjunctVal extends JunctionVal {
67
68
  unify(peer: Val, ctx: AontuContext): Val {
68
69
  peer = peer ?? top()
69
70
 
71
+ // Fast path: already-done disjunct unifying with TOP.
72
+ if (this.done && peer.isTop) {
73
+ return this
74
+ }
75
+
70
76
  const te = ctx.explain && explainOpen(ctx, ctx.explain, 'Disjunct', this, peer)
71
77
 
72
78
  if (!this.prefsRanked) {
@@ -51,11 +51,25 @@ 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
- const peeru =
55
- unite(te ? ctx.clone({ explain: ec(te, 'EXPECT') }) : ctx, this.peer, this.peg, 'expect-self')
54
+ // Unwrap nested ExpectVals to prevent infinite recursion
55
+ let peg = this.peg
56
+ while (peg?.isExpect) {
57
+ peg = peg.peg
58
+ }
56
59
 
57
- if (peeru.isGenable) {
58
- out = peeru
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
+ }
59
73
  }
60
74
  }
61
75
 
@@ -17,6 +17,7 @@ import { FeatureVal } from './FeatureVal'
17
17
  // (ConjunctVal and DisjunctVal)
18
18
  abstract class JunctionVal extends FeatureVal {
19
19
  isJunction = true
20
+ _canonCache?: string
20
21
 
21
22
  constructor(
22
23
  spec: ValSpec,
@@ -38,10 +39,13 @@ abstract class JunctionVal extends FeatureVal {
38
39
  }
39
40
 
40
41
  get canon() {
41
- return this.peg.map((v: Val) => {
42
+ if (this._canonCache !== undefined) return this._canonCache
43
+ const c = this.peg.map((v: Val) => {
42
44
  return (v as any).isJunction && Array.isArray(v.peg) && 1 < v.peg.length ?
43
45
  '(' + v.canon + ')' : v.canon // v.id + '=' + v.canon
44
46
  }).join(this.getJunctionSymbol()) // + '<' + (this.mark.hide ? 'H' : '') + '>'
47
+ if (this.done) this._canonCache = c
48
+ return c
45
49
  }
46
50
 
47
51
  // Abstract method to be implemented by subclasses to define their junction symbol
@@ -18,7 +18,6 @@ import { ConjunctVal } from '../val/ConjunctVal'
18
18
 
19
19
  import { FuncBaseVal } from './FuncBaseVal'
20
20
 
21
-
22
21
  class KeyFuncVal extends FuncBaseVal {
23
22
  isKeyFunc = true
24
23
 
@@ -41,14 +40,12 @@ class KeyFuncVal extends FuncBaseVal {
41
40
 
42
41
 
43
42
  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.
45
43
  let out: Val = this
46
44
 
47
45
  if (ctx.cc < 3) {
48
46
  this.notdone()
49
47
 
50
48
  if (peer.isTop || (peer.id === this.id)) {
51
- // TODO: clone needed to avoid triggering unify_cycle - find a better way
52
49
  out = this.clone(ctx)
53
50
  }
54
51
  else if (peer.isNil) {
@@ -9,7 +9,6 @@ import type {
9
9
 
10
10
  import {
11
11
  DONE,
12
- SPREAD,
13
12
  } from '../type'
14
13
 
15
14
  import { AontuContext } from '../ctx'
@@ -30,7 +29,6 @@ import {
30
29
  top
31
30
  } from './top'
32
31
 
33
- import { ConjunctVal } from './ConjunctVal'
34
32
  import { NilVal } from './NilVal'
35
33
  import { BagVal } from './BagVal'
36
34
  import { empty } from './Val'
@@ -38,6 +36,7 @@ import { empty } from './Val'
38
36
 
39
37
  class ListVal extends BagVal {
40
38
  isList = true
39
+ _canonCache?: string
41
40
 
42
41
  constructor(
43
42
  spec: {
@@ -51,24 +50,6 @@ class ListVal extends BagVal {
51
50
  throw new AontuError('ListVal spec.peg undefined')
52
51
  }
53
52
 
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
- }
72
53
  }
73
54
 
74
55
 
@@ -86,8 +67,7 @@ class ListVal extends BagVal {
86
67
  let out: ListVal | NilVal = (peer.isTop ? this : new ListVal({ peg: [] }, ctx))
87
68
 
88
69
  out.closed = this.closed
89
- out.optionalKeys = [...this.optionalKeys]
90
- out.spread.cj = this.spread.cj
70
+ out.optionalKeys = 0 < this.optionalKeys.length ? [...this.optionalKeys] : this.optionalKeys
91
71
  out.site = this.site
92
72
 
93
73
  if (peer instanceof ListVal) {
@@ -97,13 +77,6 @@ class ListVal extends BagVal {
97
77
  }
98
78
  else {
99
79
  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
- )
107
80
  }
108
81
  }
109
82
 
@@ -111,29 +84,39 @@ class ListVal extends BagVal {
111
84
  if (!exit) {
112
85
  out.dc = this.dc + 1
113
86
 
114
- let spread_cj = out.spread.cj || TOP
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
+ }
115
102
 
116
- // Always unify children first
103
+ // Unify own children
117
104
  for (let key in this.peg) {
118
105
  const keyctx = ctx.descend(key)
119
- const key_spread_cj = spread_cj.spreadClone(keyctx)
120
106
  const child = this.peg[key]
121
107
 
122
108
  propagateMarks(this, child)
123
109
 
124
110
  out.peg[key] =
125
- undefined === child ? key_spread_cj :
111
+ undefined === child ? top() :
126
112
  child.isNil ? child :
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')
113
+ child.done ? child :
114
+ unite(te ? keyctx.clone({ explain: ec(te, 'PEG:' + key) }) : keyctx,
115
+ child, top(), 'list-own')
132
116
 
133
117
  done = (done && DONE === out.peg[key].dc)
134
118
  }
135
119
 
136
- const allowedKeys: string[] = this.closed ? Object.keys(this.peg) : []
137
120
  let bad: NilVal | undefined = undefined
138
121
 
139
122
  if (peer instanceof ListVal) {
@@ -141,11 +124,10 @@ class ListVal extends BagVal {
141
124
  te ? ctx.clone({ explain: ec(te, 'PER') }) : ctx,
142
125
  peer, TOP, 'list-peer-list') as ListVal)
143
126
 
144
- // NOTE: peerkey is the index
145
127
  for (let peerkey in upeer.peg) {
146
128
  let peerchild = upeer.peg[peerkey]
147
129
 
148
- if (this.closed && !allowedKeys.includes(peerkey)) {
130
+ if (this.closed && !(peerkey in this.peg)) {
149
131
  bad = makeNilErr(ctx, 'closed', peerchild, undefined)
150
132
  }
151
133
 
@@ -155,19 +137,11 @@ class ListVal extends BagVal {
155
137
 
156
138
  let oval = out.peg[peerkey] =
157
139
  undefined === child ? peerchild :
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
- }
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')
171
145
 
172
146
  propagateMarks(this, oval)
173
147
 
@@ -197,39 +171,18 @@ class ListVal extends BagVal {
197
171
  }
198
172
 
199
173
 
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.
174
+ // Spread clone: share path-independent children directly, clone
175
+ // only path-dependent ones. See MapVal.spreadClone for rationale.
207
176
  spreadClone(ctx: AontuContext): Val {
208
- // B1: share directly when the spread tree has no path-dependent
209
- // leaves. See MapVal.spreadClone for rationale.
210
177
  if (!this.isPathDependent) return this
211
178
 
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
-
224
179
  let out = (super.clone(ctx) as ListVal)
225
180
 
226
181
  for (let entry of Object.entries(this.peg)) {
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,
182
+ const child = entry[1] as Val
183
+ out.peg[entry[0]] = child?.isPathDependent
184
+ ? child.clone(ctx, { mark: {} })
185
+ : child
233
186
  }
234
187
 
235
188
  out.closed = this.closed
@@ -245,10 +198,6 @@ class ListVal extends BagVal {
245
198
  out.peg[entry[0]] =
246
199
  (entry[1] as any)?.isVal ? (entry[1] as Val).clone(ctx, spec?.mark ? { mark: spec.mark } : {}) : entry[1]
247
200
  }
248
- if (this.spread.cj) {
249
- out.spread.cj = this.spread.cj.clone(ctx, spec?.mark ? { mark: spec.mark } : {})
250
- }
251
-
252
201
  out.closed = this.closed
253
202
  out.optionalKeys = [...this.optionalKeys]
254
203
 
@@ -258,18 +207,19 @@ class ListVal extends BagVal {
258
207
 
259
208
 
260
209
  get canon() {
210
+ if (this._canonCache !== undefined) return this._canonCache
261
211
  // console.log('LISTVAL-CANON', this.optionalKeys)
262
212
  let keys = Object.keys(this.peg)
263
- return '' +
213
+ const c = '' +
264
214
  // this.errcanon() +
265
215
  '[' +
266
- (this.spread.cj ? '&:' + this.spread.cj.canon +
267
- (0 < keys.length ? ',' : '') : '') +
268
216
  keys
269
217
  .map(k => this.optionalKeys.includes(k) ?
270
218
  k + '?:' + this.peg[k].canon :
271
219
  this.peg[k].canon).join(',') +
272
220
  ']'
221
+ if (this.done) this._canonCache = c
222
+ return c
273
223
  }
274
224
  }
275
225
 
package/src/val/MapVal.ts CHANGED
@@ -8,7 +8,6 @@ import type {
8
8
 
9
9
  import {
10
10
  DONE,
11
- SPREAD,
12
11
  } from '../type'
13
12
 
14
13
  import { AontuContext } from '../ctx'
@@ -28,13 +27,13 @@ import {
28
27
  } from './top'
29
28
 
30
29
 
31
- import { ConjunctVal } from './ConjunctVal'
32
30
  import { NilVal } from './NilVal'
33
31
  import { BagVal } from './BagVal'
34
32
 
35
33
 
36
34
  class MapVal extends BagVal {
37
35
  isMap = true
36
+ _canonCache?: string
38
37
 
39
38
  constructor(
40
39
  spec: ValSpec,
@@ -48,23 +47,6 @@ class MapVal extends BagVal {
48
47
 
49
48
  this.mark.type = !!spec.mark?.type
50
49
  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)
68
50
  }
69
51
 
70
52
 
@@ -75,6 +57,23 @@ class MapVal extends BagVal {
75
57
 
76
58
  const TOP = top()
77
59
  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
+
78
77
  const te = ctx.explain && explainOpen(ctx, ctx.explain, 'Map', this, peer)
79
78
 
80
79
  let done: boolean = true
@@ -84,8 +83,7 @@ class MapVal extends BagVal {
84
83
  let out: MapVal | NilVal = (peer.isTop ? this : new MapVal({ peg: {} }, ctx))
85
84
 
86
85
  out.closed = this.closed
87
- out.optionalKeys = [...this.optionalKeys]
88
- out.spread.cj = this.spread.cj
86
+ out.optionalKeys = 0 < this.optionalKeys.length ? [...this.optionalKeys] : this.optionalKeys
89
87
  out.site = this.site
90
88
 
91
89
  if (peer instanceof MapVal) {
@@ -108,53 +106,47 @@ class MapVal extends BagVal {
108
106
  out = peer.unify(this, te ? ctx.clone({ explain: ec(te, 'SPC') }) : ctx) as MapVal
109
107
  exit = true
110
108
  }
111
-
112
109
  }
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)
126
110
  }
127
111
 
128
112
 
129
113
  if (!exit) {
130
114
  out.dc = this.dc + 1
131
115
 
132
- // let newtype = this.type || peer.type
133
-
134
- let spread_cj = out.spread.cj ?? TOP
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
+ }
135
132
 
136
- // Always unify own children first
133
+ // Unify own children
137
134
  for (let key in this.peg) {
138
- const keyctx = ctx.descend(key)
139
-
140
- const key_spread_cj = spread_cj.spreadClone(keyctx)
141
135
  const child = this.peg[key]
136
+ const keyctx = ctx.descend(key)
142
137
 
143
138
  propagateMarks(this, child)
144
139
 
145
140
  out.peg[key] =
146
- undefined === child ? key_spread_cj :
141
+ undefined === child ? top() :
147
142
  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')
143
+ child.done ? child :
144
+ unite(te ? keyctx.clone({ explain: ec(te, 'KEY:' + key) }) : keyctx,
145
+ child, top(), 'map-own')
153
146
 
154
147
  done = (done && DONE === out.peg[key].dc)
155
148
  }
156
149
 
157
- const allowedKeys: string[] = this.closed ? Object.keys(this.peg) : []
158
150
  let bad: NilVal | undefined = undefined
159
151
 
160
152
  if (peer instanceof MapVal) {
@@ -165,12 +157,14 @@ class MapVal extends BagVal {
165
157
  for (let peerkey in upeer.peg) {
166
158
  let peerchild = upeer.peg[peerkey]
167
159
 
168
- if (this.closed && !allowedKeys.includes(peerkey)) {
160
+ if (this.closed && !(peerkey in this.peg)) {
169
161
  bad = makeNilErr(ctx, 'closed', peerchild, undefined)
170
162
  }
171
163
 
172
164
  // key optionality is additive
173
- if (upeer.optionalKeys.includes(peerkey) && !out.optionalKeys.includes(peerkey)) {
165
+ if (0 < upeer.optionalKeys.length
166
+ && upeer.optionalKeys.includes(peerkey)
167
+ && !out.optionalKeys.includes(peerkey)) {
174
168
  out.optionalKeys.push(peerkey)
175
169
  }
176
170
 
@@ -180,19 +174,11 @@ class MapVal extends BagVal {
180
174
 
181
175
  let oval = out.peg[peerkey] =
182
176
  undefined === child ? this.handleExpectedVal(peerkey, peerchild, this, ctx) :
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
- }
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')
196
182
 
197
183
  propagateMarks(this, oval)
198
184
 
@@ -213,66 +199,41 @@ class MapVal extends BagVal {
213
199
  out.dc = done ? DONE : out.dc
214
200
  propagateMarks(peer, out)
215
201
  propagateMarks(this, out)
202
+
216
203
  }
217
204
  }
218
205
 
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
-
227
206
  ctx.explain && explainClose(te, out)
228
207
 
229
208
  return out
230
209
  }
231
210
 
232
211
 
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.
212
+ // Optimized clone for use as a spread constraint: share
213
+ // path-independent children, clone only path-dependent ones.
247
214
  spreadClone(ctx: AontuContext): Val {
248
215
  if (!this.isPathDependent) return this
249
216
 
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
-
262
217
  let out = (super.clone(ctx) as MapVal)
263
218
  out.peg = {}
264
219
 
265
220
  for (let entry of Object.entries(this.peg)) {
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,
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
+ }
272
233
  }
273
234
 
274
235
  out.closed = this.closed
275
- out.optionalKeys = [...this.optionalKeys]
236
+ out.optionalKeys = 0 < this.optionalKeys.length ? [...this.optionalKeys] : this.optionalKeys
276
237
 
277
238
  return out
278
239
  }
@@ -285,36 +246,25 @@ class MapVal extends BagVal {
285
246
  for (let entry of Object.entries(this.peg)) {
286
247
  out.peg[entry[0]] =
287
248
  (entry[1] as any)?.isVal ?
288
- // (entry[1] as Val).clone(ctx, spec?.mark ? { mark: spec.mark } : {}) :
289
249
  (entry[1] as Val).clone(ctx, {
290
250
  mark: spec?.mark ?? {},
291
251
  path: [...out.path, entry[0]]
292
252
  }) :
293
253
  entry[1]
294
254
  }
295
- if (this.spread.cj) {
296
- out.spread.cj = this.spread.cj.clone(ctx, spec?.mark ? { mark: spec.mark } : {})
297
- }
298
255
 
299
256
  out.closed = this.closed
300
257
  out.optionalKeys = [...this.optionalKeys]
301
258
 
302
- // out.from = this.from
303
-
304
- // console.log('MAPVAL-CLONE', this.canon, '->', out.canon)
305
259
  return out
306
260
  }
307
261
 
308
262
 
309
263
  get canon() {
310
- let keys = Object.keys(this.peg)
311
- return '' +
312
- // this.errcanon() +
313
- // (this.mark.type ? '<type>' : '') +
314
- // (this.id + '=') +
264
+ if (this._canonCache !== undefined) return this._canonCache
265
+ let keys = Object.keys(this.peg).sort()
266
+ const c = '' +
315
267
  '{' +
316
- (this.spread.cj ? '&:' + this.spread.cj.canon +
317
- (0 < keys.length ? ',' : '') : '') +
318
268
  keys
319
269
  .map(k => [
320
270
  JSON.stringify(k) +
@@ -323,18 +273,19 @@ class MapVal extends BagVal {
323
273
  (this.peg[k]?.canon ?? this.peg[k])
324
274
  ])
325
275
  .join(',') +
326
- '}' // + '<' + (this.mark.hide ? 'H' : '') + '>'
327
-
276
+ '}'
277
+ if (this.done) this._canonCache = c
278
+ return c
328
279
  }
329
280
 
330
281
 
331
- inspection(d?: number) {
332
- return this.spread.cj ? '&:' + this.spread.cj.inspect(null == d ? 0 : d + 1) : ''
282
+ inspection(_d?: number) {
283
+ return ''
333
284
  }
334
285
 
335
286
  }
336
287
 
337
288
 
338
289
  export {
339
- MapVal
290
+ MapVal,
340
291
  }