eyeprolog 1.5.24 → 1.5.25

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.
@@ -345,11 +345,11 @@ profile_prohibition(Request) :-
345
345
  cycle_request(Request),
346
346
  tnot(profile_permission(Request)).
347
347
 
348
- % For a ground WFS claim, both Claim and tnot(Claim) conditionally succeed when
349
- % the truth value is undefined. That lets this example expose all three states.
348
+ % For a ground WFS claim, true and false are successful positive and negative
349
+ % probes. If neither probe succeeds, the WFS truth value is undefined.
350
350
  wfs_truth(Claim, true) :- call(Claim), \+ tnot(Claim).
351
351
  wfs_truth(Claim, false) :- tnot(Claim), \+ call(Claim).
352
- wfs_truth(Claim, undefined) :- call(Claim), tnot(Claim).
352
+ wfs_truth(Claim, undefined) :- \+ call(Claim), \+ tnot(Claim).
353
353
 
354
354
  % --- Curated questions -----------------------------------------------------
355
355
 
package/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export interface EyePrologStats {
2
2
  /** Alternating-fixed-point rounds used to build WFS models. */
3
3
  wfs_fixpoint_rounds: number;
4
- /** Undefined WFS answers observed while producing query results. */
4
+ /** Undefined WFS atoms encountered while resolving query goals. */
5
5
  wfs_undefined_answers: number;
6
6
  [key: string]: number;
7
7
  }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.5.24",
6
+ "version": "1.5.25",
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
@@ -909,7 +909,10 @@ export class Solver {
909
909
  if (!unify(goal.args[i], row[i], next)) { ok = false; break; }
910
910
  }
911
911
  if (!ok) continue;
912
- if (!lower?.has(row)) this.stats.wfs_undefined_answers++;
912
+ if (!lower?.has(row)) {
913
+ this.stats.wfs_undefined_answers++;
914
+ continue;
915
+ }
913
916
  yield* this.solve(rest, next, depth + 1);
914
917
  if (this.solutionsSeen >= this.solutionLimit) return;
915
918
  }
@@ -927,7 +930,10 @@ export class Solver {
927
930
  const model = this.wfsModelFor(group);
928
931
  const truth = truthOfGroundGoal(model, invoked);
929
932
  if (truth === 'true') return;
930
- if (truth === 'undefined') this.stats.wfs_undefined_answers++;
933
+ if (truth === 'undefined') {
934
+ this.stats.wfs_undefined_answers++;
935
+ return;
936
+ }
931
937
  yield env;
932
938
  return;
933
939
  }
@@ -1523,7 +1529,10 @@ function pushWfsAnswerFrames(stack, model, group, goal, rest, env, depth, active
1523
1529
  if (!unify(goal.args[i], row[i], next)) { ok = false; break; }
1524
1530
  }
1525
1531
  if (!ok) continue;
1526
- if (!lower?.has(row)) solver.stats.wfs_undefined_answers++;
1532
+ if (!lower?.has(row)) {
1533
+ solver.stats.wfs_undefined_answers++;
1534
+ continue;
1535
+ }
1527
1536
  stack.push({ kind: 'goals', goals: rest, env: next, depth: depth + 1, active });
1528
1537
  }
1529
1538
  }
@@ -7782,9 +7782,9 @@ win(X) :- move(X, Y), tnot(win(Y)).
7782
7782
  assertEqual(program.findGroup('win', 1).wfsDatalog, true, 'win/1 uses finite WFS evaluation');
7783
7783
  assertEqual(program.findGroup('win', 1).tabled, false, 'win/1 is not positive least-model tabled');
7784
7784
  const result = run(program, { goal: 'win(X)' });
7785
- assertEqual(result.stdout.trim().split('\n').length, 4, 'negative cycle yields four conditional WFS answers');
7785
+ assertEqual(result.stdout, '', 'negative cycle does not expose undefined atoms as successful answers');
7786
7786
  assertEqual(result.stats.wfs_fixpoint_rounds, 2, 'alternating fixed point converges in two rounds');
7787
- assertEqual(result.stats.wfs_undefined_answers, 4, 'cycle answers are undefined rather than unconditional truths');
7787
+ assertEqual(result.stats.wfs_undefined_answers, 4, 'cycle atoms are observed as undefined rather than unconditional truths');
7788
7788
 
7789
7789
  const nafProgram = Program.parse('p(X) :- \\+ q(X).\nq(X) :- p(X).\n');
7790
7790
  assertEqual(nafProgram.findGroup('p', 1).wfsDatalog, false, '\\+/1 remains ordinary negation as failure');
@@ -7804,7 +7804,23 @@ undefined_case :- tnot(win(c)).
7804
7804
  `;
7805
7805
  assertEqual(run(source, { goal: 'true_case' }).stdout, 'true_case.\n', 'win(a) is true because b is false');
7806
7806
  assertEqual(run(source, { goal: 'false_case' }).stdout, '', 'tnot(win(a)) fails for a true atom');
7807
- assertEqual(run(source, { goal: 'undefined_case' }).stdout, 'undefined_case.\n', 'tnot of an undefined WFS atom is a conditional success');
7807
+ assertEqual(run(source, { goal: 'undefined_case' }).stdout, '', 'tnot of an undefined WFS atom is not a successful answer');
7808
+ },
7809
+ },
7810
+ {
7811
+ name: 'undefined WFS goals do not run continuations or print CLI answers',
7812
+ run: () => {
7813
+ const source = `
7814
+ :- use_module(library(tabling)).
7815
+ :- table(a/0).
7816
+ :- table(b/0).
7817
+ a :- tnot(b).
7818
+ b :- tnot(a).
7819
+ `;
7820
+ const result = run(source, { goal: 'a, write(true), nl; halt' });
7821
+ assertEqual(result.stdout, '', 'undefined first branch neither writes nor produces a ground answer');
7822
+ assertEqual(result.haltCode, 0, 'fallback halt branch runs');
7823
+ assertEqual(result.stats.wfs_undefined_answers, 1, 'undefined atom remains observable in statistics');
7808
7824
  },
7809
7825
  },
7810
7826
  {
@@ -1144,8 +1144,9 @@ win(X) :- move(X, Y), tnot(win(Y)).
1144
1144
  ```
1145
1145
 
1146
1146
  Here neither `win(a)` nor `win(b)` is unconditionally established; both belong
1147
- to the undefined part of the well-founded model. EyeProlog exposes undefined
1148
- WFS answers as conditional successes, including inside finite collectors.
1147
+ to the undefined part of the well-founded model. EyeProlog retains that state
1148
+ internally but does not expose undefined atoms as successful query answers or
1149
+ run the remaining goals of a conjunction through them.
1149
1150
  Direct calls to `tnot/1` must be ground. In WFS rules, variables occurring in
1150
1151
  the head or a negated literal must be range-restricted by positive body
1151
1152
  literals. Ordinary `\+/1` is unchanged, and strict ISO mode does not provide
@@ -1664,8 +1665,8 @@ The reported counters include completed goal lists, calls to the goal solver
1664
1665
  and single-goal solver, unification attempts, maximum depth and goal-list size,
1665
1666
  deterministic built-in successes and failures, and table fixed-point rounds.
1666
1667
  WFS execution additionally reports `wfs_fixpoint_rounds` and
1667
- `wfs_undefined_answers`. The latter counts undefined-answer observations made
1668
- while producing query results; it is an execution statistic, not a declaration
1668
+ `wfs_undefined_answers`. The latter counts undefined atoms encountered while
1669
+ resolving query goals; it is an execution statistic, not a declaration
1669
1670
  that those atoms are true. All statistics describe work performed, not logical
1670
1671
  truth. Compare counters only across equivalent queries and the same
1671
1672
  implementation version.