eyeprolog 1.5.27 → 1.5.29
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/package.json +1 -1
- package/src/iso.js +1 -1
- package/src/solver.js +78 -40
- package/src/term.js +262 -2
- package/test/run-regression.mjs +4 -0
package/package.json
CHANGED
package/src/iso.js
CHANGED
|
@@ -860,7 +860,7 @@ function* retractSolutions({ solver, goal, env }, state) {
|
|
|
860
860
|
if (!unify(parts.head, copied.args[0], next)) continue;
|
|
861
861
|
if (parts.rule && !unify(parts.body, copied.args[1], next)) continue;
|
|
862
862
|
if (!parts.rule && !(copied.args[1].type === ATOM && copied.args[1].name === 'true')) continue;
|
|
863
|
-
matches.push({ clause, next });
|
|
863
|
+
matches.push({ clause, next: next._toPersistentEnv?.() ?? next });
|
|
864
864
|
}
|
|
865
865
|
for (let index = 0; index < matches.length; index++) {
|
|
866
866
|
const { clause, next } = matches[index];
|
package/src/solver.js
CHANGED
|
@@ -392,6 +392,8 @@ export class Solver {
|
|
|
392
392
|
|
|
393
393
|
*solve(goals, env = new Env(), depth = 0) {
|
|
394
394
|
if (!Array.isArray(goals)) goals = [goals];
|
|
395
|
+
const ownsSolverBindings = env._isSolverEnv?.() !== true;
|
|
396
|
+
if (ownsSolverBindings) env = env._asSolverEnv();
|
|
395
397
|
env.setOccursCheckHandler(this.occursCheckHandler);
|
|
396
398
|
env.setAttributeHookRunner(this.attributeHookRunner);
|
|
397
399
|
if (this.isoStrict) rejectStrictIsoStringTerms(goals, env);
|
|
@@ -437,6 +439,42 @@ export class Solver {
|
|
|
437
439
|
});
|
|
438
440
|
continue;
|
|
439
441
|
}
|
|
442
|
+
if (frame.kind === 'unifyArgsBranch') {
|
|
443
|
+
if (this.solutionsSeen >= this.solutionLimit) continue;
|
|
444
|
+
const next = frame.env.clone();
|
|
445
|
+
const sourceArgs = frame.freshNonGround && !frame.args.every((arg) => termIsGround(arg))
|
|
446
|
+
? freshTerm(compound('$branch_args', frame.args), nextFreshId()).args
|
|
447
|
+
: frame.args;
|
|
448
|
+
let ok = true;
|
|
449
|
+
for (let i = 0; i < frame.goal.arity; i++) {
|
|
450
|
+
this.stats.unify_calls++;
|
|
451
|
+
if (!unify(frame.goal.args[i], sourceArgs[i], next)) { ok = false; break; }
|
|
452
|
+
}
|
|
453
|
+
if (ok) {
|
|
454
|
+
stack.push({
|
|
455
|
+
kind: 'goals',
|
|
456
|
+
goals: frame.goals,
|
|
457
|
+
env: next,
|
|
458
|
+
depth: frame.depth,
|
|
459
|
+
active: frame.active,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
if (frame.kind === 'bindBranch') {
|
|
465
|
+
if (this.solutionsSeen >= this.solutionLimit) continue;
|
|
466
|
+
const next = frame.env.clone();
|
|
467
|
+
for (let i = 0; i < frame.names.length; i++) next.bind(frame.names[i], frame.values[i]);
|
|
468
|
+
if (next._variableConstraints != null && !next.validateVariableConstraints()) continue;
|
|
469
|
+
stack.push({
|
|
470
|
+
kind: 'goals',
|
|
471
|
+
goals: frame.goals,
|
|
472
|
+
env: next,
|
|
473
|
+
depth: frame.depth,
|
|
474
|
+
active: frame.active,
|
|
475
|
+
});
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
440
478
|
if (frame.kind === 'userClause') {
|
|
441
479
|
if (this.solutionsSeen >= this.solutionLimit) continue;
|
|
442
480
|
const { clause, goal: clauseGoal, rest: clauseRest, env: clauseEnv, depth: clauseDepth,
|
|
@@ -585,7 +623,7 @@ export class Solver {
|
|
|
585
623
|
this.solutionsSeen++;
|
|
586
624
|
this.stats.completed_goal_lists++;
|
|
587
625
|
this.active = active;
|
|
588
|
-
yield env;
|
|
626
|
+
yield ownsSolverBindings && env._isSolverEnv?.() === true ? env._toPersistentEnv() : env;
|
|
589
627
|
break;
|
|
590
628
|
}
|
|
591
629
|
|
|
@@ -673,7 +711,7 @@ export class Solver {
|
|
|
673
711
|
// pending exactly as they do for a direct call. A fresh active marker
|
|
674
712
|
// keeps cut opaque at the call/N boundary.
|
|
675
713
|
const expanded = def.expandGoal({ solver: this, goal, env });
|
|
676
|
-
const invocation = { goal, env };
|
|
714
|
+
const invocation = { goal, env: env._snapshotForSolverRead() };
|
|
677
715
|
active = [...active, invocation];
|
|
678
716
|
goals = [expanded, { kind: 'releaseActive' }, ...rest];
|
|
679
717
|
depth++;
|
|
@@ -1096,12 +1134,12 @@ export class Solver {
|
|
|
1096
1134
|
// expansion of the current rule body, but it must be released before
|
|
1097
1135
|
// the caller's remaining goals are solved. Keeping the goal active
|
|
1098
1136
|
// through rest goals over-prunes valid transitive/recursive derivations.
|
|
1099
|
-
this.active.push({ goal, env: goalEnv });
|
|
1137
|
+
this.active.push({ goal, env: goalEnv._snapshotForSolverRead() });
|
|
1100
1138
|
for (const bodyEnv of this.solve(body, env, depth + 1)) {
|
|
1101
1139
|
if (this.solutionsSeen > 0) this.solutionsSeen--;
|
|
1102
1140
|
this.active.pop();
|
|
1103
1141
|
yield* this.solve(rest, bodyEnv, depth + 1);
|
|
1104
|
-
this.active.push({ goal, env: goalEnv });
|
|
1142
|
+
this.active.push({ goal, env: goalEnv._snapshotForSolverRead() });
|
|
1105
1143
|
if (this.solutionsSeen >= this.solutionLimit) break;
|
|
1106
1144
|
}
|
|
1107
1145
|
this.active.pop();
|
|
@@ -1231,25 +1269,24 @@ function pushMemoAnswerFrames(stack, entry, goal, rest, env, depth, active, solv
|
|
|
1231
1269
|
selected = { bucket, fallback, length: candidateLength };
|
|
1232
1270
|
}
|
|
1233
1271
|
}
|
|
1234
|
-
const
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
:
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
}
|
|
1245
|
-
if (ok) stack.push({ kind: 'goals', goals: rest, env: next, depth: depth + 1, active });
|
|
1272
|
+
const pushReplay = (answerIndex) => {
|
|
1273
|
+
stack.push({
|
|
1274
|
+
kind: 'unifyArgsBranch',
|
|
1275
|
+
goal,
|
|
1276
|
+
args: entry.answers[answerIndex],
|
|
1277
|
+
freshNonGround: true,
|
|
1278
|
+
goals: rest,
|
|
1279
|
+
env,
|
|
1280
|
+
depth: depth + 1,
|
|
1281
|
+
active,
|
|
1282
|
+
});
|
|
1246
1283
|
};
|
|
1247
1284
|
if (selected != null) {
|
|
1248
|
-
for (let i = selected.fallback.length - 1; i >= 0; i--)
|
|
1249
|
-
for (let i = selected.bucket.length - 1; i >= 0; i--)
|
|
1285
|
+
for (let i = selected.fallback.length - 1; i >= 0; i--) pushReplay(selected.fallback[i]);
|
|
1286
|
+
for (let i = selected.bucket.length - 1; i >= 0; i--) pushReplay(selected.bucket[i]);
|
|
1250
1287
|
return;
|
|
1251
1288
|
}
|
|
1252
|
-
for (let answerIndex = entry.answers.length - 1; answerIndex >= 0; answerIndex--)
|
|
1289
|
+
for (let answerIndex = entry.answers.length - 1; answerIndex >= 0; answerIndex--) pushReplay(answerIndex);
|
|
1253
1290
|
}
|
|
1254
1291
|
|
|
1255
1292
|
function pushUserGoalUncachedFrames(stack, solver, group, goal, rest, env, depth, active) {
|
|
@@ -1258,7 +1295,7 @@ function pushUserGoalUncachedFrames(stack, solver, group, goal, rest, env, depth
|
|
|
1258
1295
|
if (tryPushGroundChainFrames(stack, solver, group, goal, rest, env, depth, active)) return;
|
|
1259
1296
|
const candidates = selectClauseCandidates(group, goal, env);
|
|
1260
1297
|
const frames = [];
|
|
1261
|
-
const invocation = { goal, env };
|
|
1298
|
+
const invocation = { goal, env: env._snapshotForSolverRead() };
|
|
1262
1299
|
// Active frames serve two purposes: they delimit cut and detect variants in
|
|
1263
1300
|
// recursive user predicates. Cut-free, non-recursive library helpers need
|
|
1264
1301
|
// neither. Copying their full active stack at every recursive step made
|
|
@@ -1535,18 +1572,20 @@ function pushWfsAnswerFrames(stack, model, group, goal, rest, env, depth, active
|
|
|
1535
1572
|
if (!relation) return;
|
|
1536
1573
|
for (let rowIndex = relation.rows.length - 1; rowIndex >= 0; rowIndex--) {
|
|
1537
1574
|
const row = relation.rows[rowIndex];
|
|
1538
|
-
const next = env.clone();
|
|
1539
|
-
let ok = true;
|
|
1540
|
-
for (let i = 0; i < goal.arity; i++) {
|
|
1541
|
-
solver.stats.unify_calls++;
|
|
1542
|
-
if (!unify(goal.args[i], row[i], next)) { ok = false; break; }
|
|
1543
|
-
}
|
|
1544
|
-
if (!ok) continue;
|
|
1545
1575
|
if (!lower?.has(row)) {
|
|
1546
1576
|
solver.stats.wfs_undefined_answers++;
|
|
1547
1577
|
continue;
|
|
1548
1578
|
}
|
|
1549
|
-
stack.push({
|
|
1579
|
+
stack.push({
|
|
1580
|
+
kind: 'unifyArgsBranch',
|
|
1581
|
+
goal,
|
|
1582
|
+
args: row,
|
|
1583
|
+
freshNonGround: false,
|
|
1584
|
+
goals: rest,
|
|
1585
|
+
env,
|
|
1586
|
+
depth: depth + 1,
|
|
1587
|
+
active,
|
|
1588
|
+
});
|
|
1550
1589
|
}
|
|
1551
1590
|
}
|
|
1552
1591
|
|
|
@@ -2030,14 +2069,8 @@ function termReferencesResolvedVariable(term, variableName, env) {
|
|
|
2030
2069
|
}
|
|
2031
2070
|
|
|
2032
2071
|
function envHasObservableAliasTo(variableName, env) {
|
|
2033
|
-
for (
|
|
2034
|
-
if (
|
|
2035
|
-
termReferencesResolvedVariable(state.bindingValue, variableName, env)) return true;
|
|
2036
|
-
if (state.bindings) {
|
|
2037
|
-
for (const [name, value] of state.bindings) {
|
|
2038
|
-
if (name !== variableName && termReferencesResolvedVariable(value, variableName, env)) return true;
|
|
2039
|
-
}
|
|
2040
|
-
}
|
|
2072
|
+
for (const [name, value] of env?._bindingEntries?.() ?? []) {
|
|
2073
|
+
if (name !== variableName && termReferencesResolvedVariable(value, variableName, env)) return true;
|
|
2041
2074
|
}
|
|
2042
2075
|
return false;
|
|
2043
2076
|
}
|
|
@@ -2315,10 +2348,15 @@ function tryPushScalarFactRunFrames(stack, solver, goals, env, depth, active) {
|
|
|
2315
2348
|
const state = localStack.pop();
|
|
2316
2349
|
solver.stats.max_depth = Math.max(solver.stats.max_depth, state.depth);
|
|
2317
2350
|
if (state.index === runLength) {
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2351
|
+
frames.push({
|
|
2352
|
+
kind: 'bindBranch',
|
|
2353
|
+
names: state.names,
|
|
2354
|
+
values: state.values,
|
|
2355
|
+
goals: rest,
|
|
2356
|
+
env,
|
|
2357
|
+
depth: state.depth,
|
|
2358
|
+
active,
|
|
2359
|
+
});
|
|
2322
2360
|
if (frames.length > frameLimit) {
|
|
2323
2361
|
// Do not repeat the old bug of abandoning the optimization and then
|
|
2324
2362
|
// re-solving through the generic engine. Restart this rare oversized
|
package/src/term.js
CHANGED
|
@@ -15,6 +15,10 @@ const EMPTY_ARGS = Object.freeze([]);
|
|
|
15
15
|
// propagation uses 256 because its repeated negative lookups dominate sooner.
|
|
16
16
|
const ENV_FLATTEN_DEPTH = 256;
|
|
17
17
|
const ATTRIBUTED_ENV_FLATTEN_DEPTH = 256;
|
|
18
|
+
// Environment states are immutable once published to a branch, so an absent
|
|
19
|
+
// binding is just as safe to cache as a present one. Negative lookups are
|
|
20
|
+
// especially common while tabling and constraint code probes fresh variables.
|
|
21
|
+
const ENV_UNBOUND = Symbol('environment-unbound');
|
|
18
22
|
// Runtime terms are structurally immutable: environments hold bindings beside
|
|
19
23
|
// them rather than rewriting their argument arrays. Cache only the syntactic
|
|
20
24
|
// variable names; binding reachability is still checked against each Env.
|
|
@@ -163,6 +167,15 @@ export class Env {
|
|
|
163
167
|
this._attributeHookRunner = typeof handler === 'function' ? handler : null;
|
|
164
168
|
return this;
|
|
165
169
|
}
|
|
170
|
+
_asSolverEnv() {
|
|
171
|
+
return SolverEnv.from(this);
|
|
172
|
+
}
|
|
173
|
+
_isSolverEnv() {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
_snapshotForSolverRead() {
|
|
177
|
+
return this.clone();
|
|
178
|
+
}
|
|
166
179
|
adopt(other) {
|
|
167
180
|
if (!(other instanceof Env)) throw new TypeError('Env.adopt expects Env');
|
|
168
181
|
this._state = other._state;
|
|
@@ -292,9 +305,11 @@ export class Env {
|
|
|
292
305
|
}
|
|
293
306
|
get(name) {
|
|
294
307
|
const root = this._state;
|
|
295
|
-
if (root.cacheName === name)
|
|
308
|
+
if (root.cacheName === name) {
|
|
309
|
+
return root.cacheValue === ENV_UNBOUND ? undefined : root.cacheValue;
|
|
310
|
+
}
|
|
296
311
|
const cached = root.cache?.get(name);
|
|
297
|
-
if (cached !== undefined) return cached;
|
|
312
|
+
if (cached !== undefined) return cached === ENV_UNBOUND ? undefined : cached;
|
|
298
313
|
for (let state = root; state != null; state = state.parent) {
|
|
299
314
|
let value;
|
|
300
315
|
let found = false;
|
|
@@ -317,8 +332,32 @@ export class Env {
|
|
|
317
332
|
return value;
|
|
318
333
|
}
|
|
319
334
|
}
|
|
335
|
+
if (root.depth >= 4) {
|
|
336
|
+
if (root.cacheName == null) {
|
|
337
|
+
root.cacheName = name;
|
|
338
|
+
root.cacheValue = ENV_UNBOUND;
|
|
339
|
+
} else {
|
|
340
|
+
(root.cache ??= new Map([[root.cacheName, root.cacheValue]])).set(name, ENV_UNBOUND);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
320
343
|
return undefined;
|
|
321
344
|
}
|
|
345
|
+
*_bindingEntries() {
|
|
346
|
+
const seen = new Set();
|
|
347
|
+
for (let state = this._state; state != null; state = state.parent) {
|
|
348
|
+
if (state.bindingName != null && !seen.has(state.bindingName)) {
|
|
349
|
+
seen.add(state.bindingName);
|
|
350
|
+
yield [state.bindingName, state.bindingValue];
|
|
351
|
+
}
|
|
352
|
+
if (state.bindings) {
|
|
353
|
+
for (const [name, value] of state.bindings) {
|
|
354
|
+
if (seen.has(name)) continue;
|
|
355
|
+
seen.add(name);
|
|
356
|
+
yield [name, value];
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
322
361
|
compactForDeepContinuation(segmentLimit = 16) {
|
|
323
362
|
if ((this._state.segmentCount ?? 0) < segmentLimit) return false;
|
|
324
363
|
const flattened = new Map();
|
|
@@ -634,6 +673,227 @@ export class Env {
|
|
|
634
673
|
}
|
|
635
674
|
}
|
|
636
675
|
|
|
676
|
+
|
|
677
|
+
class SolverBindingStore {
|
|
678
|
+
constructor(bindings) {
|
|
679
|
+
this.slots = new Map();
|
|
680
|
+
this.names = [];
|
|
681
|
+
this.values = [];
|
|
682
|
+
this.baseValues = [];
|
|
683
|
+
this.slotLastTrail = [];
|
|
684
|
+
this.freeSlots = [];
|
|
685
|
+
this.trailNames = [];
|
|
686
|
+
this.trailSlots = [];
|
|
687
|
+
this.trailOldValues = [];
|
|
688
|
+
this.trailNewValues = [];
|
|
689
|
+
this.trailPreviousForSlot = [];
|
|
690
|
+
this.trailCreated = [];
|
|
691
|
+
if (bindings != null) {
|
|
692
|
+
for (const [name, value] of bindings) {
|
|
693
|
+
const slot = this.names.length;
|
|
694
|
+
this.slots.set(name, slot);
|
|
695
|
+
this.names.push(name);
|
|
696
|
+
this.values.push(value);
|
|
697
|
+
this.baseValues.push(value);
|
|
698
|
+
this.slotLastTrail.push(-1);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
mark() {
|
|
704
|
+
return this.trailSlots.length;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
rollback(mark) {
|
|
708
|
+
if (mark > this.trailSlots.length) {
|
|
709
|
+
throw new Error('stale solver binding mark');
|
|
710
|
+
}
|
|
711
|
+
while (this.trailSlots.length > mark) {
|
|
712
|
+
const created = this.trailCreated.pop();
|
|
713
|
+
const previousForSlot = this.trailPreviousForSlot.pop();
|
|
714
|
+
this.trailNewValues.pop();
|
|
715
|
+
const oldValue = this.trailOldValues.pop();
|
|
716
|
+
const slot = this.trailSlots.pop();
|
|
717
|
+
const name = this.trailNames.pop();
|
|
718
|
+
this.slotLastTrail[slot] = previousForSlot;
|
|
719
|
+
if (created) {
|
|
720
|
+
this.slots.delete(name);
|
|
721
|
+
this.values[slot] = ENV_UNBOUND;
|
|
722
|
+
this.baseValues[slot] = ENV_UNBOUND;
|
|
723
|
+
this.names[slot] = null;
|
|
724
|
+
this.freeSlots.push(slot);
|
|
725
|
+
} else {
|
|
726
|
+
this.values[slot] = oldValue;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
bind(name, value) {
|
|
732
|
+
let slot = this.slots.get(name);
|
|
733
|
+
const created = slot === undefined;
|
|
734
|
+
if (created) {
|
|
735
|
+
slot = this.freeSlots.length === 0 ? this.values.length : this.freeSlots.pop();
|
|
736
|
+
this.slots.set(name, slot);
|
|
737
|
+
this.names[slot] = name;
|
|
738
|
+
this.values[slot] = ENV_UNBOUND;
|
|
739
|
+
this.baseValues[slot] = ENV_UNBOUND;
|
|
740
|
+
this.slotLastTrail[slot] = -1;
|
|
741
|
+
}
|
|
742
|
+
const entry = this.trailSlots.length;
|
|
743
|
+
this.trailNames.push(name);
|
|
744
|
+
this.trailSlots.push(slot);
|
|
745
|
+
this.trailOldValues.push(this.values[slot]);
|
|
746
|
+
this.trailNewValues.push(value);
|
|
747
|
+
this.trailPreviousForSlot.push(this.slotLastTrail[slot] ?? -1);
|
|
748
|
+
this.trailCreated.push(created);
|
|
749
|
+
this.slotLastTrail[slot] = entry;
|
|
750
|
+
this.values[slot] = value;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
getAtMark(name, mark) {
|
|
754
|
+
if (mark > this.trailSlots.length) throw new Error('stale solver binding mark');
|
|
755
|
+
const slot = this.slots.get(name);
|
|
756
|
+
if (slot === undefined) return undefined;
|
|
757
|
+
let entry = this.slotLastTrail[slot] ?? -1;
|
|
758
|
+
while (entry >= mark) entry = this.trailPreviousForSlot[entry] ?? -1;
|
|
759
|
+
const value = entry >= 0 ? this.trailNewValues[entry] : this.baseValues[slot];
|
|
760
|
+
return value === ENV_UNBOUND ? undefined : value;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function copyEnvBranchState(source, target) {
|
|
765
|
+
target._delays = source._delays;
|
|
766
|
+
target._variableConstraints = source._variableConstraints;
|
|
767
|
+
target._variableAnnotations = source._variableAnnotations;
|
|
768
|
+
target._prologAttributes = source._prologAttributes;
|
|
769
|
+
target._attributeHookRunner = source._attributeHookRunner;
|
|
770
|
+
target._pendingAttributeGoals = source._pendingAttributeGoals;
|
|
771
|
+
target._backtrackableBlackboard = source._backtrackableBlackboard;
|
|
772
|
+
target._occursCheckHandler = source._occursCheckHandler;
|
|
773
|
+
target._localVariables = source._localVariables;
|
|
774
|
+
target._compactVariableOrigins = source._compactVariableOrigins;
|
|
775
|
+
target._compactVariableRisks = source._compactVariableRisks;
|
|
776
|
+
return target;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
class SolverEnvReadView {
|
|
780
|
+
constructor(source) {
|
|
781
|
+
this._solverBindings = source._solverBindings;
|
|
782
|
+
this._solverBindingMark = source._solverBindingMark;
|
|
783
|
+
this._localVariables = source._localVariables;
|
|
784
|
+
}
|
|
785
|
+
get(name) {
|
|
786
|
+
return this._solverBindings.getAtMark(name, this._solverBindingMark);
|
|
787
|
+
}
|
|
788
|
+
has(name) {
|
|
789
|
+
return this.get(name) !== undefined;
|
|
790
|
+
}
|
|
791
|
+
isLocalVariable(name) {
|
|
792
|
+
return this._localVariables?.has(name) === true;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// SolverEnv replaces persistent binding layers with one solver-owned mutable
|
|
797
|
+
// slot store and a linear trail. Clones are just trail marks plus the existing
|
|
798
|
+
// copy-on-write logical side stores. The solver explores these marks in depth-
|
|
799
|
+
// first order, so rollback can discard unreachable branch history immediately.
|
|
800
|
+
// Public Env remains persistent and is used for stable API snapshots.
|
|
801
|
+
class SolverEnv extends Env {
|
|
802
|
+
constructor() {
|
|
803
|
+
super();
|
|
804
|
+
this._solverBindings = new SolverBindingStore();
|
|
805
|
+
this._solverBindingMark = 0;
|
|
806
|
+
this._state = null;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
static from(env) {
|
|
810
|
+
if (env instanceof SolverEnv) return env;
|
|
811
|
+
const solverEnv = Object.create(SolverEnv.prototype);
|
|
812
|
+
solverEnv._state = null;
|
|
813
|
+
solverEnv._solverBindings = new SolverBindingStore(env?._bindingEntries?.() ?? []);
|
|
814
|
+
solverEnv._solverBindingMark = 0;
|
|
815
|
+
copyEnvBranchState(env ?? new Env(), solverEnv);
|
|
816
|
+
return solverEnv;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
_asSolverEnv() {
|
|
820
|
+
return this;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
_isSolverEnv() {
|
|
824
|
+
return true;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
_snapshotForSolverRead() {
|
|
828
|
+
return new SolverEnvReadView(this);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
_activateBindings() {
|
|
832
|
+
if (this._solverBindingMark !== this._solverBindings.trailSlots.length) {
|
|
833
|
+
this._solverBindings.rollback(this._solverBindingMark);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
clone() {
|
|
838
|
+
this._activateBindings();
|
|
839
|
+
const clone = Object.create(SolverEnv.prototype);
|
|
840
|
+
clone._state = null;
|
|
841
|
+
clone._solverBindings = this._solverBindings;
|
|
842
|
+
clone._solverBindingMark = this._solverBindingMark;
|
|
843
|
+
return copyEnvBranchState(this, clone);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
adopt(other) {
|
|
847
|
+
if (!(other instanceof Env)) throw new TypeError('Env.adopt expects Env');
|
|
848
|
+
if (other instanceof SolverEnv && other._solverBindings === this._solverBindings) {
|
|
849
|
+
other._activateBindings();
|
|
850
|
+
this._solverBindingMark = other._solverBindingMark;
|
|
851
|
+
} else {
|
|
852
|
+
const replacement = SolverEnv.from(other);
|
|
853
|
+
this._solverBindings = replacement._solverBindings;
|
|
854
|
+
this._solverBindingMark = replacement._solverBindingMark;
|
|
855
|
+
}
|
|
856
|
+
copyEnvBranchState(other, this);
|
|
857
|
+
return this;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
has(name) {
|
|
861
|
+
return this.get(name) !== undefined;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
get(name) {
|
|
865
|
+
this._activateBindings();
|
|
866
|
+
const slot = this._solverBindings.slots.get(name);
|
|
867
|
+
if (slot === undefined) return undefined;
|
|
868
|
+
const value = this._solverBindings.values[slot];
|
|
869
|
+
return value === ENV_UNBOUND ? undefined : value;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
*_bindingEntries() {
|
|
873
|
+
this._activateBindings();
|
|
874
|
+
for (const [name, slot] of this._solverBindings.slots) {
|
|
875
|
+
const value = this._solverBindings.values[slot];
|
|
876
|
+
if (value !== ENV_UNBOUND) yield [name, value];
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
bind(name, term) {
|
|
881
|
+
this._activateBindings();
|
|
882
|
+
this._solverBindings.bind(name, term);
|
|
883
|
+
this._solverBindingMark = this._solverBindings.mark();
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
compactForDeepContinuation() {
|
|
887
|
+
return false;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
_toPersistentEnv() {
|
|
891
|
+
const stable = new Env(this._bindingEntries());
|
|
892
|
+
copyEnvBranchState(this, stable);
|
|
893
|
+
return stable;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
637
897
|
export function deref(term, env) {
|
|
638
898
|
// Follow variable bindings until a non-variable term is reached. The seen set
|
|
639
899
|
// protects readback from accidental cycles in partially constructed terms.
|
package/test/run-regression.mjs
CHANGED
|
@@ -7224,11 +7224,15 @@ function whiteBoxCases() {
|
|
|
7224
7224
|
assertEqual(env.get('V31').name, '31', 'binding before flatten');
|
|
7225
7225
|
assertEqual(env.get('V39').name, '39', 'latest binding');
|
|
7226
7226
|
assertEqual(env.has('missing'), false, 'missing binding');
|
|
7227
|
+
assertEqual(env.has('missing'), false, 'cached missing binding');
|
|
7227
7228
|
|
|
7228
7229
|
const clone = env.clone();
|
|
7229
7230
|
clone.bind('OnlyClone', atom('yes'));
|
|
7231
|
+
clone.bind('missing', atom('now_present'));
|
|
7230
7232
|
assertEqual(clone.get('OnlyClone').name, 'yes', 'clone write');
|
|
7233
|
+
assertEqual(clone.get('missing').name, 'now_present', 'clone write supersedes cached miss');
|
|
7231
7234
|
assertEqual(env.has('OnlyClone'), false, 'clone remains isolated');
|
|
7235
|
+
assertEqual(env.has('missing'), false, 'parent retains cached miss');
|
|
7232
7236
|
},
|
|
7233
7237
|
},
|
|
7234
7238
|
{
|