eyeprolog 1.5.27 → 1.5.28
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/term.js +16 -2
- package/test/run-regression.mjs +4 -0
package/package.json
CHANGED
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.
|
|
@@ -292,9 +296,11 @@ export class Env {
|
|
|
292
296
|
}
|
|
293
297
|
get(name) {
|
|
294
298
|
const root = this._state;
|
|
295
|
-
if (root.cacheName === name)
|
|
299
|
+
if (root.cacheName === name) {
|
|
300
|
+
return root.cacheValue === ENV_UNBOUND ? undefined : root.cacheValue;
|
|
301
|
+
}
|
|
296
302
|
const cached = root.cache?.get(name);
|
|
297
|
-
if (cached !== undefined) return cached;
|
|
303
|
+
if (cached !== undefined) return cached === ENV_UNBOUND ? undefined : cached;
|
|
298
304
|
for (let state = root; state != null; state = state.parent) {
|
|
299
305
|
let value;
|
|
300
306
|
let found = false;
|
|
@@ -317,6 +323,14 @@ export class Env {
|
|
|
317
323
|
return value;
|
|
318
324
|
}
|
|
319
325
|
}
|
|
326
|
+
if (root.depth >= 4) {
|
|
327
|
+
if (root.cacheName == null) {
|
|
328
|
+
root.cacheName = name;
|
|
329
|
+
root.cacheValue = ENV_UNBOUND;
|
|
330
|
+
} else {
|
|
331
|
+
(root.cache ??= new Map([[root.cacheName, root.cacheValue]])).set(name, ENV_UNBOUND);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
320
334
|
return undefined;
|
|
321
335
|
}
|
|
322
336
|
compactForDeepContinuation(segmentLimit = 16) {
|
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
|
{
|