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
@@ -13,6 +13,7 @@ import {
13
13
  import { makeNilErr } from '../err'
14
14
 
15
15
  import { NilVal } from '../val/NilVal'
16
+ import { PathVal } from '../val/PathVal'
16
17
 
17
18
  import {
18
19
  walk
@@ -26,6 +27,60 @@ import { PrefFuncVal } from './PrefFuncVal'
26
27
 
27
28
 
28
29
 
30
+ // Navigate the tree using a PathVal's resolved path and hide the source.
31
+ function hideAtPath(root: Val, pv: PathVal) {
32
+ // Compute the refpath the same way PathVal.find does
33
+ const parts: string[] = []
34
+ for (const part of pv.peg) {
35
+ if ('string' === typeof part) parts.push(part)
36
+ }
37
+
38
+ let refpath: string[]
39
+ if (pv.absolute) {
40
+ refpath = parts
41
+ }
42
+ else {
43
+ refpath = pv.path.slice(0, -1).concat(parts)
44
+ }
45
+
46
+ // Walk to the source, handling conjuncts/disjuncts
47
+ let node: any = root
48
+ for (let i = 0; i < refpath.length; i++) {
49
+ const part = refpath[i]
50
+ if (node?.isMap || node?.isList) {
51
+ node = node.peg[part]
52
+ }
53
+ else if (node?.isConjunct || node?.isDisjunct) {
54
+ // Search junction terms for the key
55
+ let found = null
56
+ const stack = [...node.peg]
57
+ while (stack.length > 0) {
58
+ const term = stack.pop()
59
+ if (term?.isConjunct || term?.isDisjunct) {
60
+ stack.push(...term.peg)
61
+ }
62
+ else if ((term?.isMap || term?.isList) && term.peg[part] != null) {
63
+ found = term.peg[part]
64
+ break
65
+ }
66
+ }
67
+ node = found
68
+ }
69
+ else {
70
+ return
71
+ }
72
+ if (node == null) return
73
+ }
74
+
75
+ // Mark the source value hidden
76
+ node.mark.hide = true
77
+ walk(node, (_key: string | number | undefined, val: Val) => {
78
+ val.mark.hide = true
79
+ return val
80
+ })
81
+ }
82
+
83
+
29
84
  class MoveFuncVal extends FuncBaseVal {
30
85
  isMoveFunc = true
31
86
 
@@ -50,30 +105,40 @@ class MoveFuncVal extends FuncBaseVal {
50
105
  }
51
106
 
52
107
  resolve(ctx: AontuContext, args: Val[]) {
53
- let out = args[0] ?? makeNilErr(ctx, 'arg', this)
108
+ let arg = args[0] ?? makeNilErr(ctx, 'arg', this)
109
+ if (arg.isNil) return arg
54
110
 
55
- const orig = out
111
+ let src: Val
56
112
 
57
- if (!orig.isNil) {
58
- const src = orig.clone(ctx)
113
+ if (arg instanceof PathVal) {
114
+ // Get clone for the destination
115
+ src = arg.find(ctx) as Val
116
+ if (src == null || src.isNil) return makeNilErr(ctx, 'arg', this)
59
117
 
60
- if (src.isRef) {
61
- src.mark._hide_found = true
62
- }
118
+ // Hide the source in the tree by navigating to it
119
+ hideAtPath(ctx.root as Val, arg)
120
+ }
121
+ else {
122
+ src = arg.clone(ctx)
63
123
 
64
- walk(orig, (_key: string | number | undefined, val: Val) => {
124
+ // Hide the original
125
+ arg.mark.hide = true
126
+ walk(arg, (_key: string | number | undefined, val: Val) => {
65
127
  val.mark.hide = true
66
128
  return val
67
129
  })
68
-
69
- // out = new CopyFuncVal({ peg: [src] }, ctx)
70
- out = new PrefFuncVal({ peg: [src] }, ctx)
71
- // out = src
72
130
  }
73
131
 
74
- // console.log('MOVE-resolve', orig, out)
132
+ // Clear marks on the clone (like copy)
133
+ src.mark.type = false
134
+ src.mark.hide = false
135
+ walk(src, (_key: string | number | undefined, val: Val) => {
136
+ val.mark.type = false
137
+ val.mark.hide = false
138
+ return val
139
+ })
75
140
 
76
- return out
141
+ return new PrefFuncVal({ peg: [src] }, ctx)
77
142
  }
78
143
  }
79
144
 
@@ -6,6 +6,10 @@ import type {
6
6
  ValSpec,
7
7
  } from '../type'
8
8
 
9
+ import {
10
+ DONE,
11
+ } from '../type'
12
+
9
13
  import {
10
14
  AontuContext,
11
15
  } from '../ctx'
@@ -13,7 +17,7 @@ import {
13
17
  import { makeNilErr } from '../err'
14
18
 
15
19
  import { NilVal } from '../val/NilVal'
16
- import { RefVal } from '../val/RefVal'
20
+ import { PathVal } from '../val/PathVal'
17
21
  import { FuncBaseVal } from './FuncBaseVal'
18
22
 
19
23
 
@@ -45,12 +49,26 @@ class PathFuncVal extends FuncBaseVal {
45
49
  let arg = args[0]
46
50
 
47
51
  if (0 === this.prepared) {
48
- if (arg.isScalar) {
49
- arg = this.place(new RefVal({ peg: [arg], absolute: false }))
52
+ if (arg instanceof PathVal) {
53
+ // PathVal from dotted arg resolve via find().
54
+ const found = arg.find(ctx)
55
+ if (found != null && !found.isNil) {
56
+ arg = found
57
+ }
58
+ else {
59
+ arg.dc = DONE
60
+ }
61
+ }
62
+ else if (arg.isScalar && arg.peg != null && arg.peg !== ''
63
+ && ('string' === typeof arg.peg || arg.isNumber)) {
64
+ // String or number arg like path("foo") or path(0.2) — create a path value
65
+ arg = this.place(new PathVal({ peg: [arg], absolute: false })) as PathVal
66
+ arg.dc = DONE
50
67
  }
51
- else if (!arg.isRef) {
68
+ else if (arg.isNil || (arg.isScalar && (arg.peg === '' || arg.peg == null))) {
52
69
  arg = makeNilErr(ctx, 'invalid-arg', this)
53
70
  }
71
+ // else: already resolved by preprocessing — pass through
54
72
  }
55
73
 
56
74
  args[0] = arg
@@ -64,6 +82,13 @@ class PathFuncVal extends FuncBaseVal {
64
82
  resolve(ctx: AontuContext, args: Val[]) {
65
83
  let out = args[0] ?? makeNilErr(ctx, 'arg', this)
66
84
 
85
+ if (out instanceof PathVal) {
86
+ const found = out.find(ctx)
87
+ if (found != null && !found.isNil) {
88
+ out = found
89
+ }
90
+ }
91
+
67
92
  return out
68
93
  }
69
94
 
@@ -0,0 +1,435 @@
1
+ /* Copyright (c) 2025 Richard Rodger, MIT License */
2
+
3
+
4
+ import type {
5
+ Val,
6
+ ValSpec,
7
+ } from '../type'
8
+
9
+ import {
10
+ DONE,
11
+ } from '../type'
12
+
13
+ import {
14
+ walk,
15
+ } from '../utility'
16
+
17
+ import { AontuContext } from '../ctx'
18
+ import { unite } from '../unify'
19
+
20
+ import { makeNilErr } from '../err'
21
+
22
+ import {
23
+ top
24
+ } from './top'
25
+
26
+ import { StringVal } from './StringVal'
27
+ import { IntegerVal } from './IntegerVal'
28
+ import { NumberVal } from './NumberVal'
29
+ import { VarVal } from './VarVal'
30
+ import { ConjunctVal } from './ConjunctVal'
31
+ import { DisjunctVal } from './DisjunctVal'
32
+ import { FeatureVal } from './FeatureVal'
33
+
34
+
35
+ class PathVal extends FeatureVal {
36
+ isPath = true
37
+ isGenable = true
38
+ cjo = 32500
39
+
40
+ absolute: boolean = false
41
+ prefix: boolean = false
42
+ _resolved: Val | undefined = undefined
43
+
44
+ constructor(
45
+ spec: {
46
+ peg: any[],
47
+ absolute?: boolean,
48
+ prefix?: boolean
49
+ },
50
+ ctx?: AontuContext
51
+ ) {
52
+ super(spec, ctx)
53
+ this.peg = []
54
+
55
+ this.absolute = true === this.absolute ? true : // absolute sticks
56
+ true === spec.absolute ? true : false
57
+
58
+ this.prefix = true === spec.prefix
59
+
60
+ for (let pI = 0; pI < spec.peg.length; pI++) {
61
+ this.append(spec.peg[pI])
62
+ }
63
+ }
64
+
65
+
66
+ append(part: any) {
67
+ let partval
68
+
69
+ if ('string' === typeof part) {
70
+ partval = part
71
+ this.peg.push(partval)
72
+ }
73
+
74
+ else if (part instanceof StringVal) {
75
+ partval = part.peg
76
+ this.peg.push(partval)
77
+ }
78
+
79
+ else if (part instanceof IntegerVal) {
80
+ partval = part.src
81
+ this.peg.push(partval)
82
+ }
83
+
84
+ else if (part instanceof NumberVal) {
85
+ let partvals: string[] = part.src.split('.')
86
+ this.peg.push(...partvals)
87
+ }
88
+
89
+ else if (part instanceof VarVal) {
90
+ partval = part
91
+ this.peg.push(partval)
92
+ }
93
+
94
+ else if (part instanceof PathVal) {
95
+ if (part.absolute) {
96
+ this.absolute = true
97
+ }
98
+
99
+ if (this.prefix) {
100
+ if (part.prefix) {
101
+ this.peg.push('.')
102
+ }
103
+ }
104
+ else {
105
+ if (part.prefix) {
106
+ if (0 === this.peg.length) {
107
+ this.prefix = true
108
+ }
109
+
110
+ else if (0 < this.peg.length) {
111
+ this.peg.push('.')
112
+ }
113
+ }
114
+ }
115
+
116
+ this.peg.push(...part.peg)
117
+ }
118
+ }
119
+
120
+
121
+ unify(peer: Val, ctx: AontuContext): Val {
122
+ peer = peer ?? top()
123
+
124
+ // Already resolved (e.g. path value from path() function) — skip find
125
+ if (this.done) return this
126
+
127
+ let out: Val = this
128
+ const found = this.find(ctx)
129
+
130
+ if (found != null && !found.isNil) {
131
+ out = unite(ctx, found, peer, 'path')
132
+ }
133
+ else if (found?.isNil) {
134
+ out = found
135
+ }
136
+ else {
137
+ // Not yet resolvable — increment dc to signal not done
138
+ this.dc = DONE === this.dc ? DONE : this.dc + 1
139
+ }
140
+
141
+ return out
142
+ }
143
+
144
+
145
+ find(ctx: AontuContext) {
146
+ let out: Val | undefined = undefined
147
+
148
+ // Check if self.path starts with peg (cycle detection).
149
+ // Element-by-element comparison avoids string join+startsWith allocations.
150
+ let isprefixpath = this.peg.length <= this.path.length
151
+ if (isprefixpath) {
152
+ for (let i = 0; i < this.peg.length; i++) {
153
+ if (this.peg[i] !== this.path[i]) {
154
+ isprefixpath = false
155
+ break
156
+ }
157
+ }
158
+ }
159
+ // Degenerate case: peg is all empty strings (e.g. path("")) and path is empty.
160
+ if (!isprefixpath && this.peg.length > 0 && this.path.length === 0) {
161
+ let allEmpty = true
162
+ for (let i = 0; i < this.peg.length; i++) {
163
+ if ('' !== this.peg[i]) { allEmpty = false; break }
164
+ }
165
+ isprefixpath = allEmpty
166
+ }
167
+
168
+ let refpath: string[] = []
169
+ let pI = 0
170
+ // let descent = ''
171
+
172
+ if (isprefixpath) {
173
+ // console.log('SELFPATH', selfpath, 'PEGPATH', pegpath)
174
+ out = makeNilErr(ctx, 'path_cycle', this)
175
+ }
176
+ else {
177
+
178
+ let parts: string[] = []
179
+
180
+ let modes: string[] = []
181
+
182
+ for (let pI = 0; pI < this.peg.length; pI++) {
183
+ let part = this.peg[pI]
184
+ if (part instanceof VarVal) {
185
+ let strval = (part as VarVal).peg
186
+ let name = strval ? '' + strval.peg : ''
187
+
188
+ if ('KEY' === name) {
189
+ if (pI === this.peg.length - 1) {
190
+ modes.push(name)
191
+ }
192
+ else {
193
+ // TODO: return a Nil explaining error
194
+ return
195
+ }
196
+ }
197
+
198
+ if ('SELF' === name) {
199
+ if (pI === 0) {
200
+ modes.push(name)
201
+ }
202
+ else {
203
+ // TODO: return a Nil explaining error
204
+ return
205
+ }
206
+ }
207
+ else if ('PARENT' === name) {
208
+ if (pI === 0) {
209
+ modes.push(name)
210
+ }
211
+ else {
212
+ // TODO: return a Nil explaining error
213
+ return
214
+ }
215
+ }
216
+ else if (0 === modes.length) {
217
+ part = (part as VarVal).unify(top(), ctx)
218
+ if (part.isNil) {
219
+ // TODO: var not found, so can't find path
220
+ return
221
+ }
222
+ else {
223
+ part = '' + part.peg
224
+ }
225
+ }
226
+ }
227
+ else {
228
+ parts.push(part)
229
+ }
230
+ }
231
+
232
+ if (this.absolute) {
233
+ refpath = parts
234
+ }
235
+ else {
236
+ // TODO: deprecate $KEY, etc
237
+ refpath = this.path.slice(
238
+ 0,
239
+ (
240
+ modes.includes('SELF') ? 0 :
241
+ modes.includes('PARENT') ? -1 :
242
+ -1 // siblings
243
+ )
244
+ ).concat(parts)
245
+ }
246
+
247
+ let sep = '.'
248
+ refpath = refpath
249
+ .reduce(((a: string[], p: string) =>
250
+ (p === sep ? a.length = a.length - 1 : a.push(p), a)), [])
251
+
252
+ if (modes.includes('KEY')) {
253
+ let key = this.path[this.path.length - 2]
254
+ let sv = new StringVal({ peg: null == key ? '' : key }, ctx)
255
+
256
+ // TODO: other props?
257
+ sv.dc = DONE
258
+ sv.path = this.path
259
+
260
+ return sv
261
+ }
262
+
263
+ let node: Val | null = ctx.root as Val
264
+
265
+ let nopath = false
266
+
267
+ if (null != node) {
268
+ for (; pI < refpath.length; pI++) {
269
+ let part = refpath[pI]
270
+ // console.log('PART', pI, part, node)
271
+
272
+ // descent += (' | ' + pI + '=' + node.canon) // Util.inspect(node))
273
+
274
+ if (node.isMap) {
275
+ node = node.peg[part]
276
+ }
277
+ else if (node.isList) {
278
+ node = node.peg[part]
279
+ }
280
+ else if (node.isConjunct || node.isDisjunct) {
281
+ // Collect matching children from all junction terms,
282
+ // flattening nested conjuncts and disjuncts.
283
+ // Spreads match any key — their peg is always included.
284
+ const matches: Val[] = []
285
+ const stack = [...node.peg]
286
+ while (stack.length > 0) {
287
+ const term = stack.pop()!
288
+ if (term.isConjunct || term.isDisjunct) {
289
+ stack.push(...term.peg)
290
+ }
291
+ else if (term.isSpread) {
292
+ matches.push(term.peg)
293
+ }
294
+ else if ((term.isMap || term.isList) && term.peg[part] != null) {
295
+ matches.push(term.peg[part])
296
+ }
297
+ }
298
+ if (matches.length === 1) {
299
+ node = matches[0]
300
+ }
301
+ else if (matches.length > 1) {
302
+ node = node.isConjunct
303
+ ? new ConjunctVal({ peg: matches })
304
+ : new DisjunctVal({ peg: matches })
305
+ }
306
+ else {
307
+ node = null
308
+ }
309
+ }
310
+ else if (node.done) {
311
+ nopath = true
312
+ break;
313
+ }
314
+ else {
315
+ break;
316
+ }
317
+
318
+ if (null == node) {
319
+ nopath = true
320
+ break
321
+ }
322
+
323
+ }
324
+ }
325
+
326
+ // console.log('REFPATH', ctx.cc, pI, refpath, nopath, ctx.root, node)
327
+
328
+
329
+ if (nopath) {
330
+ out = makeNilErr(ctx, 'no_path', this)
331
+ }
332
+ else if (pI === refpath.length && node != null) {
333
+ out = node
334
+
335
+ // Types and hidden values are cloned and made concrete
336
+ if (null != out) { // && (out.mark.type || out.mark.hide)) {
337
+
338
+ // console.log('FOUND-A', out)
339
+
340
+ if (this.mark.type || this.mark.hide) {
341
+ out.mark.type = this.mark.type
342
+ out.mark.hide = this.mark.hide
343
+ }
344
+
345
+ if (this.mark._hide_found) {
346
+ out.mark.hide = true
347
+ }
348
+
349
+ // Cache clone+walk results per (ref, target) per iteration.
350
+ const cacheKey = this.id + '|' + out.id
351
+ const cache = ctx._refCloneCache
352
+ const cached = cache?.get(cacheKey)
353
+ if (cached !== undefined) {
354
+ out = cached
355
+ }
356
+ else {
357
+ out = out.clone(ctx)
358
+ out.mark.type = false
359
+ out.mark.hide = false
360
+
361
+ walk(out, (_key: string | number | undefined, val: Val) => {
362
+ val.mark.type = false
363
+ val.mark.hide = false
364
+ return val
365
+ })
366
+
367
+ cache?.set(cacheKey, out)
368
+ }
369
+ }
370
+ }
371
+ }
372
+
373
+ // console.log('REF-FIND', ctx.cc, this.id, selfpath, 'PEG=', pegpath, 'RP', pI, refpath.join('.'), descent, 'O=', out?.id, out?.canon, out?.done)
374
+
375
+ return out
376
+ }
377
+
378
+
379
+
380
+ same(peer: Val): boolean {
381
+ return null == peer ? false : this.peg === peer.peg
382
+ }
383
+
384
+
385
+ clone(ctx: AontuContext, spec?: ValSpec): Val {
386
+ let out = (super.clone(ctx, {
387
+ peg: this.peg,
388
+ absolute: this.absolute,
389
+ ...(spec || {})
390
+ }) as PathVal)
391
+ return out
392
+ }
393
+
394
+
395
+ get canon() {
396
+ let str =
397
+ (this.absolute ? '$' : '') +
398
+ (0 < this.peg.length ? '.' : '') +
399
+ this.peg.map((p: any) => '.' === p ? '' :
400
+ (p.isVal ? p.canon : '' + p))
401
+ .join('.')
402
+ return str
403
+ }
404
+
405
+
406
+ gen(ctx: AontuContext) {
407
+ let nil = makeNilErr(
408
+ ctx,
409
+ 'ref',
410
+ this,
411
+ undefined,
412
+ )
413
+
414
+ nil.path = this.path
415
+ nil.site.url = this.site.url
416
+ nil.site.row = this.site.row
417
+ nil.site.col = this.site.col
418
+
419
+ return undefined
420
+ }
421
+
422
+
423
+ inspection() {
424
+ return [
425
+ this.absolute ? 'absolute' : '',
426
+ this.prefix ? 'prefix' : '',
427
+ ].filter(p => '' != p).join(',')
428
+ }
429
+
430
+ }
431
+
432
+
433
+ export {
434
+ PathVal,
435
+ }
@@ -22,6 +22,7 @@ import {
22
22
 
23
23
  import { top } from './top'
24
24
 
25
+ import { ConjunctVal } from './ConjunctVal'
25
26
  import { FeatureVal } from './FeatureVal'
26
27
 
27
28
 
@@ -64,10 +65,13 @@ class PrefVal extends FeatureVal {
64
65
  if (!this.peg.done) {
65
66
  const resolved = unite(te ? ctx.clone({ explain: ec(te, 'RES') }) : ctx,
66
67
  this.peg, top(), 'pref/resolve')
67
- // console.log('PREF-RESOLVED', this.peg.canon, '->', resolved)
68
68
  this.peg = resolved
69
69
  this.superpeg = this.peg.superior()
70
70
  }
71
+ else if (this.superpeg.isTop && !this.peg.isTop) {
72
+ // Stale superpeg from pre-resolved PathVal — recalculate
73
+ this.superpeg = this.peg.superior()
74
+ }
71
75
 
72
76
  if (peer instanceof PrefVal) {
73
77
  why += 'pref-'
@@ -106,11 +110,20 @@ class PrefVal extends FeatureVal {
106
110
  else if (!peer.isTop) {
107
111
  why += 'super-'
108
112
 
109
- out = unite(te ? ctx.clone({ explain: ec(te, 'SUPER') }) : ctx,
110
- this.superpeg, peer, 'pref-super/' + this.id)
111
- if (out.same(this.superpeg)) {
112
- out = this.peg
113
- why += 'same'
113
+ // If peg is unresolved (e.g. deferred ref), don't decide yet
114
+ // preserve both sides so a later pass can resolve.
115
+ if (!this.peg.done) {
116
+ out = new ConjunctVal({ peg: [this, peer] }, ctx)
117
+ done = false
118
+ why += 'defer'
119
+ }
120
+ else {
121
+ out = unite(te ? ctx.clone({ explain: ec(te, 'SUPER') }) : ctx,
122
+ this.superpeg, peer, 'pref-super/' + this.id)
123
+ if (out.same(this.superpeg)) {
124
+ out = this.peg
125
+ why += 'same'
126
+ }
114
127
  }
115
128
 
116
129
  // }