eyeprolog 1.3.74 → 1.3.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.3.74",
6
+ "version": "1.3.75",
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
@@ -2556,19 +2556,32 @@ function residueVariablesSince(snapshot, env) {
2556
2556
  return names;
2557
2557
  }
2558
2558
 
2559
- function* callResidueVarsBuiltin({ solver, goal, env }) {
2559
+ function callResidueVarsBuiltin({ solver, goal, env }) {
2560
2560
  const invoked = callable(goal.args[0], env);
2561
2561
  if (invoked.module == null) invoked.module = goal.module ?? 'user';
2562
2562
  const snapshot = residueSnapshot(env);
2563
2563
  const child = solver.cloneForInnerGoal();
2564
- try {
2565
- for (const answerEnv of child.solve([invoked], env, 0)) {
2566
- const next = answerEnv.clone();
2567
- if (unify(goal.args[1], listFromItems(residueVariablesSince(snapshot, answerEnv)), next)) yield next;
2564
+ let pending = true;
2565
+ // The wrapper iterator is resumable even for a deterministic Goal. Expose
2566
+ // the child's actual search state so the top level does not print a phantom
2567
+ // choicepoint after the final answer.
2568
+ const iterator = (function* residueSolutions() {
2569
+ try {
2570
+ for (const answerEnv of child.solve([invoked], env, 0)) {
2571
+ pending = child.hasPendingAlternatives();
2572
+ const next = answerEnv.clone();
2573
+ if (unify(goal.args[1], listFromItems(residueVariablesSince(snapshot, answerEnv)), next)) {
2574
+ yield next;
2575
+ if (!pending) return;
2576
+ }
2577
+ }
2578
+ pending = false;
2579
+ } finally {
2580
+ solver.absorbStatsFrom(child);
2568
2581
  }
2569
- } finally {
2570
- solver.absorbStatsFrom(child);
2571
- }
2582
+ })();
2583
+ iterator.hasPendingAlternatives = () => pending;
2584
+ return iterator;
2572
2585
  }
2573
2586
 
2574
2587
  function* freezeBuiltin({ solver, goal, env }) {
@@ -178,6 +178,16 @@ check :- call_residue_vars(X in 1..3, Vs), Vs = [X].
178
178
  `;
179
179
  assertIncludes(run(clpzResidue, { goal: 'check' }).stdout, 'check.', 'call_residue_vars/2 includes CLP(Z) attributed variables');
180
180
 
181
+ const deterministicProgram = Program.parse(':- use_module(library(atts)).');
182
+ const deterministicSolver = new Solver(deterministicProgram);
183
+ const deterministicGoal = parseGoalText('call_residue_vars(true, Xs)');
184
+ const deterministicSolutions = deterministicSolver.solve([deterministicGoal], new Env(), 0);
185
+ const deterministicAnswer = deterministicSolutions.next();
186
+ assertEqual(deterministicAnswer.done, false, 'call_residue_vars/2 deterministic answer');
187
+ assertEqual(deterministicSolver.hasPendingAlternatives(), false,
188
+ 'call_residue_vars/2 deterministic success has no leftover choicepoint');
189
+ deterministicSolutions.return();
190
+
181
191
  const residualFile = path.join(tmp, `atts-residual-${++tmpCounter}.pl`);
182
192
  fs.writeFileSync(residualFile, `:- use_module(library(atts)).
183
193
  :- attribute required/1.