aontu 0.40.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/aontu.js +7 -0
- package/dist/aontu.js.map +1 -1
- package/dist/ctx.d.ts +14 -2
- package/dist/ctx.js +56 -9
- package/dist/ctx.js.map +1 -1
- package/dist/err.js +36 -15
- package/dist/err.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 +372 -55
- 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 +138 -15
- 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 +40 -10
- package/dist/val/DisjunctVal.js.map +1 -1
- package/dist/val/ExpectVal.js +17 -4
- package/dist/val/ExpectVal.js.map +1 -1
- package/dist/val/FuncBaseVal.js +2 -2
- package/dist/val/FuncBaseVal.js.map +1 -1
- package/dist/val/IntegerVal.js +1 -1
- package/dist/val/IntegerVal.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 +36 -65
- package/dist/val/ListVal.js.map +1 -1
- package/dist/val/MapVal.d.ts +3 -2
- package/dist/val/MapVal.js +71 -91
- 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/NilVal.d.ts +2 -1
- package/dist/val/NilVal.js +15 -1
- package/dist/val/NilVal.js.map +1 -1
- package/dist/val/OpBaseVal.js +1 -1
- package/dist/val/OpBaseVal.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 +20 -7
- 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 +4 -1
- package/dist/val/Val.js +88 -44
- package/dist/val/Val.js.map +1 -1
- package/dist/val/VarVal.js +3 -3
- package/dist/val/VarVal.js.map +1 -1
- package/package.json +6 -7
- package/src/aontu.ts +9 -1
- package/src/ctx.ts +95 -11
- package/src/err.ts +37 -17
- package/src/lang.ts +113 -23
- package/src/tsconfig.json +2 -1
- package/src/type.ts +5 -0
- package/src/unify.ts +390 -64
- package/src/utility.ts +5 -2
- package/src/val/BagVal.ts +6 -7
- package/src/val/ConjunctVal.ts +133 -15
- package/src/val/CopyFuncVal.ts +3 -2
- package/src/val/DisjunctVal.ts +43 -11
- package/src/val/ExpectVal.ts +19 -5
- package/src/val/FuncBaseVal.ts +2 -2
- package/src/val/IntegerVal.ts +1 -1
- package/src/val/JunctionVal.ts +5 -1
- package/src/val/KeyFuncVal.ts +0 -3
- package/src/val/ListVal.ts +40 -86
- package/src/val/MapVal.ts +78 -119
- package/src/val/MoveFuncVal.ts +79 -14
- package/src/val/NilVal.ts +17 -1
- package/src/val/OpBaseVal.ts +1 -1
- package/src/val/PathFuncVal.ts +29 -4
- package/src/val/PathVal.ts +435 -0
- package/src/val/PrefVal.ts +21 -8
- package/src/val/{RefVal.ts → RefVal.ts.old} +31 -20
- package/src/val/SpreadVal.ts +275 -0
- package/src/val/Val.ts +141 -50
- package/src/val/VarVal.ts +3 -3
- 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,12 +29,48 @@ 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
|
|
34
38
|
const unite = (ctx: AontuContext, a: any, b: any, whence: string) => {
|
|
39
|
+
// Fast paths that don't recurse and so don't need cycle-detection:
|
|
40
|
+
// short-circuit before the saw-key build and seen-map lookup (which
|
|
41
|
+
// together cost ~2.5µs per call). Only return early when the result
|
|
42
|
+
// is already `done` — a non-done result would need the trailing
|
|
43
|
+
// top() unify below.
|
|
44
|
+
//
|
|
45
|
+
// A6a: same ref, already done
|
|
46
|
+
// A6b: different ref but same id + both done
|
|
47
|
+
// P1: exact-equal scalars that are already done (14% of calls
|
|
48
|
+
// in foo-sdk, ~100% with a.done=true)
|
|
49
|
+
if (a !== undefined && a !== null) {
|
|
50
|
+
if (a === b) {
|
|
51
|
+
if (a.done) return a
|
|
52
|
+
}
|
|
53
|
+
else if (b !== undefined && b !== null) {
|
|
54
|
+
if (a.done && b.done) {
|
|
55
|
+
if (a.id === b.id) return a
|
|
56
|
+
if (a.constructor === b.constructor && a.peg === b.peg
|
|
57
|
+
&& !a.isNil && !b.isNil
|
|
58
|
+
&& !a.isConjunct && !a.isDisjunct
|
|
59
|
+
&& !a.isPath && !a.isPref && !a.isFunc && !a.isExpect) {
|
|
60
|
+
return a
|
|
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
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
35
74
|
const te = ctx.explain && explainOpen(ctx, ctx.explain, 'unite', a, b)
|
|
36
75
|
|
|
37
76
|
let out = a
|
|
@@ -39,80 +78,89 @@ const unite = (ctx: AontuContext, a: any, b: any, whence: string) => {
|
|
|
39
78
|
|
|
40
79
|
// Cycle-detection key. Use numeric path index for speed; fall back to
|
|
41
80
|
// full string key when debug is enabled so the saw value is human-readable.
|
|
42
|
-
|
|
43
|
-
|
|
81
|
+
let saw: string
|
|
82
|
+
if (ctx.opts.debug) {
|
|
83
|
+
saw = (a ? a.id + (a.done ? '' : '*') : '') + '~' +
|
|
44
84
|
(b ? b.id + (b.done ? '' : '*') : '') + '@' + ctx.pathstr
|
|
45
|
-
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
saw = (a ? a.id + (a.done ? 'd' : '') : 0) + '~' +
|
|
46
88
|
(b ? b.id + (b.done ? 'd' : '') : 0) + '~' + ctx.pathidx
|
|
89
|
+
}
|
|
47
90
|
|
|
48
91
|
// NOTE: if this error occurs "unreasonably", attemp to avoid unnecesary unification
|
|
49
92
|
// See for example PrefVal peg.id equality inspection.
|
|
50
|
-
const sawCount = ctx.seen
|
|
93
|
+
const sawCount = ctx.seen.get(saw) ?? 0
|
|
51
94
|
if (MAXCYCLE < sawCount) {
|
|
52
95
|
// console.log('SAW', sawCount, saw, a?.id, a?.canon, b?.id, b?.canon, ctx.cc)
|
|
53
96
|
out = makeNilErr(ctx, 'unify_cycle', a, b)
|
|
54
97
|
}
|
|
55
98
|
else {
|
|
56
|
-
ctx.seen
|
|
99
|
+
ctx.seen.set(saw, sawCount + 1)
|
|
57
100
|
|
|
58
101
|
try {
|
|
59
|
-
|
|
60
102
|
let unified = false
|
|
61
103
|
|
|
62
|
-
|
|
104
|
+
// Dispatch ladder. Structure note:
|
|
105
|
+
// - `a == null` is degenerate (shouldn't happen in practice:
|
|
106
|
+
// the top-level call seeds with a real Val). Kept for safety.
|
|
107
|
+
// - TOP is the unit element: unifying with it returns the
|
|
108
|
+
// other side. Handle both sides.
|
|
109
|
+
// - Otherwise route by Val type. Complex Vals (Conjunct,
|
|
110
|
+
// Disjunct, Ref, Pref, Func, Expect) have their own unify
|
|
111
|
+
// that knows how to absorb the peer; prefer `a.unify` when
|
|
112
|
+
// `a` is complex, else `b.unify` when `b` is complex. If
|
|
113
|
+
// neither is complex and it's not a plain-scalar match, fall
|
|
114
|
+
// through to the generic `a.unify` (concrete Val classes
|
|
115
|
+
// each handle their own peer case).
|
|
116
|
+
if (a == null) {
|
|
63
117
|
out = b
|
|
64
118
|
why = 'b'
|
|
65
119
|
}
|
|
66
|
-
|
|
67
|
-
else if (a && (!b || b.isTop)) {
|
|
120
|
+
else if (b == null || b.isTop) {
|
|
68
121
|
out = a
|
|
69
122
|
why = 'a'
|
|
70
123
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
else {
|
|
112
|
-
out = a.unify(b, te ? ctx.clone({ explain: ec(te, 'GN') }) : ctx)
|
|
113
|
-
unified = true
|
|
114
|
-
why = 'ab'
|
|
115
|
-
}
|
|
124
|
+
else if (a.isTop) {
|
|
125
|
+
out = b
|
|
126
|
+
why = 'b'
|
|
127
|
+
}
|
|
128
|
+
else if (a.isNil) {
|
|
129
|
+
out = update(a, b)
|
|
130
|
+
why = 'an'
|
|
131
|
+
}
|
|
132
|
+
else if (b.isNil) {
|
|
133
|
+
out = update(b, a)
|
|
134
|
+
why = 'bn'
|
|
135
|
+
}
|
|
136
|
+
else if (a.isConjunct || a.isExpect || a.isSpread) {
|
|
137
|
+
out = a.unify(b, te ? ctx.clone({ explain: ec(te, 'AC') }) : ctx)
|
|
138
|
+
unified = true
|
|
139
|
+
why = 'a*'
|
|
140
|
+
}
|
|
141
|
+
else if (
|
|
142
|
+
b.isConjunct
|
|
143
|
+
|| b.isDisjunct
|
|
144
|
+
|| b.isPath
|
|
145
|
+
|| b.isPref
|
|
146
|
+
|| b.isFunc
|
|
147
|
+
|| b.isExpect
|
|
148
|
+
|| b.isSpread
|
|
149
|
+
) {
|
|
150
|
+
out = b.unify(a, te ? ctx.clone({ explain: ec(te, 'BW') }) : ctx)
|
|
151
|
+
unified = true
|
|
152
|
+
why = 'bv'
|
|
153
|
+
}
|
|
154
|
+
// Exactly equal scalars (not caught by early fast-path — e.g.
|
|
155
|
+
// because a or b isn't .done yet).
|
|
156
|
+
else if (a.constructor === b.constructor && a.peg === b.peg) {
|
|
157
|
+
out = update(a, b)
|
|
158
|
+
why = 'up'
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
out = a.unify(b, te ? ctx.clone({ explain: ec(te, 'GN') }) : ctx)
|
|
162
|
+
unified = true
|
|
163
|
+
why = 'ab'
|
|
116
164
|
}
|
|
117
165
|
|
|
118
166
|
if (!out || !out.unify) {
|
|
@@ -120,19 +168,16 @@ const unite = (ctx: AontuContext, a: any, b: any, whence: string) => {
|
|
|
120
168
|
why += 'N'
|
|
121
169
|
}
|
|
122
170
|
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
//
|
|
171
|
+
// Any non-done top-level result self-unifies with TOP to ensure
|
|
172
|
+
// its children finish converging. Skipped when `unified` is true
|
|
173
|
+
// because the branch that set `out = X.unify(Y, ctx)` already
|
|
174
|
+
// ran that Val's own unify logic.
|
|
126
175
|
if (!out.done && !unified) {
|
|
127
|
-
|
|
128
|
-
out = nout
|
|
176
|
+
out = out.unify(top(), te ? ctx.clone({ explain: ec(te, 'ND') }) : ctx)
|
|
129
177
|
why += 'T'
|
|
130
178
|
}
|
|
131
|
-
|
|
132
|
-
// console.log('UNITE', why, a?.id, a?.canon, a?.done, b?.id, b?.canon, b?.done, '->', out?.id, out?.canon, out?.done)
|
|
133
179
|
}
|
|
134
180
|
catch (err: any) {
|
|
135
|
-
// console.log(err)
|
|
136
181
|
// TODO: handle unexpected
|
|
137
182
|
out = makeNilErr(ctx, 'internal', a, b)
|
|
138
183
|
}
|
|
@@ -140,6 +185,11 @@ const unite = (ctx: AontuContext, a: any, b: any, whence: string) => {
|
|
|
140
185
|
|
|
141
186
|
ctx.explain && explainClose(te, out)
|
|
142
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
|
+
|
|
143
193
|
return out
|
|
144
194
|
}
|
|
145
195
|
|
|
@@ -150,6 +200,269 @@ function update(x: Val, _y: Val) {
|
|
|
150
200
|
}
|
|
151
201
|
|
|
152
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
|
+
|
|
153
466
|
class Unify {
|
|
154
467
|
root: Val
|
|
155
468
|
res: Val
|
|
@@ -200,15 +513,28 @@ class Unify {
|
|
|
200
513
|
uctx.err = this.err
|
|
201
514
|
uctx.explain = this.explain
|
|
202
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
|
+
|
|
203
525
|
const explain = null == ctx?.explain ? undefined : ctx?.explain
|
|
204
526
|
const te = explain && explainOpen(uctx, explain, 'root', res)
|
|
205
527
|
|
|
206
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.
|
|
207
531
|
let maxcc = 9 // 99
|
|
208
532
|
for (; this.cc < maxcc && DONE !== res.dc; this.cc++) {
|
|
209
533
|
// console.log('CC', this.cc, res.canon)
|
|
210
534
|
uctx.cc = this.cc
|
|
211
|
-
uctx.seen =
|
|
535
|
+
uctx.seen = new Map()
|
|
536
|
+
uctx._refCloneCache = new Map()
|
|
537
|
+
uctx._uniteCache = new Map()
|
|
212
538
|
res = unite(te ? uctx.clone({ explain: ec(te, 'run') }) : uctx, res, top(), 'unify')
|
|
213
539
|
|
|
214
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
|