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