eyeprolog 1.3.22 → 1.3.23

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.3.22",
6
+ "version": "1.3.23",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
package/src/iso.js CHANGED
@@ -1371,6 +1371,7 @@ function writeBuiltin(mode) {
1371
1371
  solver.io.writeUnit(stream, formatTermForWrite(goal.args[goal.arity - 1], env, {
1372
1372
  ...options,
1373
1373
  generateVariableNames: true,
1374
+ variableNameState: solver.writeVariableState,
1374
1375
  operators: solver.program.operators.values(),
1375
1376
  }));
1376
1377
  yield env;
@@ -1388,6 +1389,7 @@ function* writeTermBuiltin({ solver, goal, env }) {
1388
1389
  solver.io.writeUnit(stream, formatTermForWrite(goal.args[goal.arity - 2], env, {
1389
1390
  ...options,
1390
1391
  generateVariableNames: true,
1392
+ variableNameState: solver.writeVariableState,
1391
1393
  operators: solver.program.operators.values(),
1392
1394
  }));
1393
1395
  yield env;
package/src/solver.js CHANGED
@@ -116,6 +116,11 @@ export class Solver {
116
116
  }
117
117
  }
118
118
  this.io = options.io ?? new StreamManager(options.ioOptions);
119
+ // Keep generated write-variable names stable for the lifetime of one
120
+ // top-level query. Inner/meta-call solvers share this state so separate
121
+ // write/1, writeq/1, write_canonical/1, and write_term/2-3 calls can refer
122
+ // to the same logical variable by the same printed name.
123
+ this.writeVariableState = options.writeVariableState ?? { depth: 0, names: new Map(), next: 0 };
119
124
  this.solveStacks = [];
120
125
  this.active = [];
121
126
  this.cutEpoch = 0;
@@ -163,6 +168,7 @@ export class Solver {
163
168
  io: this.io,
164
169
  innerTableScopes: this.innerTableScopes,
165
170
  inferenceObservation: this.inferenceObservation,
171
+ writeVariableState: this.writeVariableState,
166
172
  skipListTailTabling: options.skipListTailTabling ?? this.skipListTailTabling,
167
173
  });
168
174
  if (options.tableScope != null) {
@@ -279,6 +285,13 @@ export class Solver {
279
285
  if (!Array.isArray(goals)) goals = [goals];
280
286
  env.setOccursCheckHandler(this.occursCheckHandler);
281
287
 
288
+ const writeVariableState = this.writeVariableState;
289
+ if (writeVariableState.depth === 0) {
290
+ writeVariableState.names.clear();
291
+ writeVariableState.next = 0;
292
+ }
293
+ writeVariableState.depth++;
294
+
282
295
  const savedActive = this.active;
283
296
  let registeredStack = null;
284
297
  try {
@@ -651,6 +664,11 @@ export class Solver {
651
664
  const stackIndex = this.solveStacks.indexOf(registeredStack);
652
665
  if (stackIndex >= 0) this.solveStacks.splice(stackIndex, 1);
653
666
  this.active = savedActive;
667
+ writeVariableState.depth = Math.max(0, writeVariableState.depth - 1);
668
+ if (writeVariableState.depth === 0) {
669
+ writeVariableState.names.clear();
670
+ writeVariableState.next = 0;
671
+ }
654
672
  }
655
673
  }
656
674
 
package/src/write.js CHANGED
@@ -180,13 +180,14 @@ function generatedVariableName(index) {
180
180
  return suffix === 0 ? `_${letter}` : `_${letter}${suffix}`;
181
181
  }
182
182
 
183
- function printableGeneratedVariableNames(term, env, explicit) {
183
+ function printableGeneratedVariableNames(term, env, explicit, state = null) {
184
184
  const names = new Map(explicit);
185
- const used = new Set(names.values());
185
+ const sharedNames = state?.names instanceof Map ? state.names : new Map();
186
+ const used = new Set([...sharedNames.values(), ...names.values()]);
186
187
  const seenVariables = new Set();
187
188
  const seenTerms = new Set();
188
189
  const stack = [term];
189
- let generated = 0;
190
+ let generated = Number.isSafeInteger(state?.next) ? state.next : 0;
190
191
 
191
192
  while (stack.length) {
192
193
  const current = deref(stack.pop(), env);
@@ -194,8 +195,14 @@ function printableGeneratedVariableNames(term, env, explicit) {
194
195
  if (seenVariables.has(current.name)) continue;
195
196
  seenVariables.add(current.name);
196
197
  if (names.has(current.name)) continue;
198
+ const shared = sharedNames.get(current.name);
199
+ if (shared != null) {
200
+ names.set(current.name, shared);
201
+ continue;
202
+ }
197
203
  let candidate;
198
204
  do candidate = generatedVariableName(generated++); while (used.has(candidate));
205
+ sharedNames.set(current.name, candidate);
199
206
  names.set(current.name, candidate);
200
207
  used.add(candidate);
201
208
  continue;
@@ -205,6 +212,7 @@ function printableGeneratedVariableNames(term, env, explicit) {
205
212
  for (let i = current.arity - 1; i >= 0; i--) stack.push(current.args[i]);
206
213
  }
207
214
 
215
+ if (state != null) state.next = generated;
208
216
  return names;
209
217
  }
210
218
 
@@ -317,7 +325,7 @@ export function formatTermForWrite(term, env = new Env(), options = {}) {
317
325
  numbervars: options.numbervars !== false,
318
326
  doubleQuotes: options.doubleQuotes,
319
327
  variableNames: options.generateVariableNames === true
320
- ? printableGeneratedVariableNames(term, env, explicitVariableNames)
328
+ ? printableGeneratedVariableNames(term, env, explicitVariableNames, options.variableNameState)
321
329
  : printableReadVariableNames(term, env, explicitVariableNames),
322
330
  compact: options.compact === true,
323
331
  operatorAtomsAsArgs: options.operatorAtomsAsArgs === true,
@@ -2989,6 +2989,20 @@ child.stdin.write(\`consult(${consultedAtom}).\\n\`);
2989
2989
  assertEqual(fresh.stdout, '_A\nemit.\n', 'fresh clause variable hides its internal suffix');
2990
2990
  },
2991
2991
  },
2992
+ {
2993
+ name: 'write predicates keep generated variable names stable across calls (issue #53 comment 5356861151)',
2994
+ run: () => {
2995
+ const result = run([
2996
+ "emit :- write_term(pair(A,B), []), write(' / '), write_term(user_output,B,[]), write(' / '), writeq(A), write(' / '), write_canonical(B), nl.",
2997
+ "again :- write_canonical(B+B), nl.",
2998
+ ].join('\n'), { goals: ['emit', 'again'] });
2999
+ assertEqual(
3000
+ result.stdout,
3001
+ 'pair(_A,_B) / _B / _A / _B\nemit.\n+(_A,_A)\nagain.\n',
3002
+ 'stable names across calls and reset at the next top-level query',
3003
+ );
3004
+ },
3005
+ },
2992
3006
  {
2993
3007
  name: 'write predicates and write_term options select distinct formats',
2994
3008
  run: () => {