eyeprolog 1.5.73 → 1.5.75

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.73",
6
+ "version": "1.5.75",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
package/src/repl.js CHANGED
@@ -773,6 +773,18 @@ function formatAnswer(engine, state, variables, env) {
773
773
  doubleBar: !state.strictIso,
774
774
  };
775
775
  let generated = 0;
776
+ // Names generated for otherwise-anonymous variables (`_A`, `_B`, ...) must
777
+ // never collide with a name the query itself is already using for a
778
+ // *different* variable (e.g. a query literally naming `_A`) — printing an
779
+ // internal fresh variable as `_A` right next to the user's own `_A` binding
780
+ // falsely suggests they are the same variable (issue #108).
781
+ const usedDisplayNames = new Set(names.values());
782
+ const nextGeneratedName = () => {
783
+ let candidate;
784
+ do { candidate = `_${letterName(generated++)}`; } while (usedDisplayNames.has(candidate));
785
+ usedDisplayNames.add(candidate);
786
+ return candidate;
787
+ };
776
788
 
777
789
  // Meta-predicate wrappers can make a query variable alias an internal fresh
778
790
  // variable. Keep residual constraints tied to the query's visible name.
@@ -780,7 +792,7 @@ function formatAnswer(engine, state, variables, env) {
780
792
  const root = engine.deref(variable, env);
781
793
  if (root.type === 'var' && !names.has(root.name)) names.set(root.name, variable.name);
782
794
  }
783
- for (const variable of variables) collectUnboundVariables(engine, variable, env, names, () => `_${letterName(generated++)}`);
795
+ for (const variable of variables) collectUnboundVariables(engine, variable, env, names, nextGeneratedName);
784
796
  for (const variable of variables) {
785
797
  const value = engine.deref(variable, env);
786
798
  if (value.type === 'var' &&
@@ -796,7 +808,7 @@ function formatAnswer(engine, state, variables, env) {
796
808
  for (const constraint of env.variableConstraints?.() ?? []) {
797
809
  const residual = constraint.residualGoal?.(env);
798
810
  if (residual == null) continue;
799
- collectUnboundVariables(engine, residual, env, names, () => `_${letterName(generated++)}`);
811
+ collectUnboundVariables(engine, residual, env, names, nextGeneratedName);
800
812
  bindings.push(engine.formatTermForWrite(residual, env, answerWriteOptions));
801
813
  }
802
814
  // Residual attributes are part of the answer even when the attributed
@@ -821,9 +833,9 @@ function formatAnswer(engine, state, variables, env) {
821
833
  for (const root of attributeRoots) {
822
834
  if (projectedAttributeRoots.has(root.name) || !env.hasPrologAttributes?.(root.name)) continue;
823
835
  projectedAttributeRoots.add(root.name);
824
- if (!names.has(root.name)) names.set(root.name, `_${letterName(generated++)}`);
836
+ if (!names.has(root.name)) names.set(root.name, nextGeneratedName());
825
837
  for (const residual of state.solver.attributeResidualGoals(root, env)) {
826
- collectUnboundVariables(engine, residual, env, names, () => `_${letterName(generated++)}`);
838
+ collectUnboundVariables(engine, residual, env, names, nextGeneratedName);
827
839
  bindings.push(engine.formatTermForWrite(residual, env, answerWriteOptions));
828
840
  }
829
841
  }
@@ -28,6 +28,18 @@ import {
28
28
 
29
29
  export function regressionCases() {
30
30
  return [
31
+ {
32
+ name: 'top level never mints a generated variable name that collides with a query variable\'s own name (issue #108)',
33
+ run: () => {
34
+ const result = runCli([], { input: 'length(L,1),_A=99.\nhalt.\n' });
35
+ assertEqual(result.status, 0, result.stderr);
36
+ // `_A` is a real query variable here, not the top level's own first
37
+ // generated name. Printing the anonymous variable inside L as `_A` as
38
+ // well would misleadingly suggest it is the same variable as the
39
+ // query's own `_A`, which is bound to 99 and unrelated to L.
40
+ assertIncludes(result.stdout, '?- L = [_B], _A = 99.\n', 'generated name skips the query\'s own _A');
41
+ },
42
+ },
31
43
  {
32
44
  name: 'REPL current_input/1 and current_output/1 fail on closed stream handles (issue #107)',
33
45
  run: () => {