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 CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.5.27",
6
+ "version": "1.5.28",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
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) return root.cacheValue;
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) {
@@ -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
  {