eyeprolog 1.3.93 → 1.3.94

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.93",
6
+ "version": "1.3.94",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
package/src/solver.js CHANGED
@@ -524,14 +524,21 @@ export class Solver {
524
524
  if (this.solutionsSeen >= this.solutionLimit) break;
525
525
 
526
526
  const pendingAttributeGoals = env.takePendingAttributeGoals?.() ?? [];
527
- if (pendingAttributeGoals.length > 0) goals = [...pendingAttributeGoals, ...goals];
527
+ if (pendingAttributeGoals.length > 0) {
528
+ // Goals produced by verify_attributes/3 are invoked by the attribute
529
+ // mechanism, not textually spliced into the caller. Give every
530
+ // awakened goal the same opaque cut boundary as call/1 so a delayed
531
+ // cut cannot prune choices that were made before the suspension.
532
+ const awakened = pendingAttributeGoals.map((pending) => compound('call', [pending]));
533
+ goals = [...awakened, ...goals];
534
+ }
528
535
 
529
536
  const readyDelays = env.takeReadyDelays();
530
537
  if (readyDelays.length > 0) {
531
538
  const awakened = readyDelays.map(({ goal, module }) => {
532
539
  const delayed = copyResolved(goal, env);
533
540
  qualifyTerm(delayed, module);
534
- return delayed;
541
+ return compound('call', [delayed]);
535
542
  });
536
543
  goals = [...awakened, ...goals];
537
544
  }
@@ -1990,6 +1990,22 @@ c4 ?- call((!;1)).
1990
1990
  assertEqual(result.stdout, 'wake(ready, awake).\n', 'freeze answer');
1991
1991
  },
1992
1992
  },
1993
+ {
1994
+ name: 'Prologue freeze keeps awakened cuts inside the delayed call (issue #81)',
1995
+ run: () => {
1996
+ const result = publicApi.runQuads(`:- use_module(library(prologue)).
1997
+ 38
1998
+ ?- call( ( (Y=1;Y=2), freeze(X,!), X = c ) ); Y = 3.
1999
+ Y = 1, X = c
2000
+ ; Y = 2, X = c
2001
+ ; Y = 3.
2002
+ `);
2003
+ assertEqual(result.total, 1, 'quad total');
2004
+ assertEqual(result.passed, 1, 'quad passed');
2005
+ assertEqual(result.failed, 0, 'quad failed');
2006
+ assertEqual(result.stdout, 'quads: 1 run, 1 passed, 0 failed.\n', 'quad report');
2007
+ },
2008
+ },
1993
2009
  {
1994
2010
  name: 'runQuads covers the remaining finite Prologue examples and arities',
1995
2011
  run: () => {
@@ -6283,7 +6283,11 @@ private helpers and same-named predicates in different modules separate.
6283
6283
  `freeze(?Term,:Goal)` runs `Goal` immediately when `Term` is already nonvariable;
6284
6284
  otherwise it delays the goal until `Term` becomes nonvariable. Suspensions are
6285
6285
  kept in the logical environment, so bindings and backtracking remain isolated
6286
- between solution branches.
6286
+ between solution branches. When a suspension wakes, its goal is meta-invoked
6287
+ with its own cut scope: a `!` inside the delayed goal may commit choices made by
6288
+ that invocation, but it cannot prune alternatives that were created before the
6289
+ `freeze/2` call. Thus `call(((Y=1;Y=2),freeze(X,!),X=c));Y=3` retains the three
6290
+ answers `Y=1`, `Y=2`, and `Y=3`.
6287
6291
 
6288
6292
  `dif(?Left,?Right)` posts a delayed finite-tree disequality when its arguments
6289
6293
  can still unify. Its residual store is normalized by logical implication: