eyeprolog 1.6.6 → 1.6.7

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.
@@ -39,3 +39,4 @@ step(xor_row(row(1, 0, 1)),
39
39
  step(xor_circuit(1, 0, 1), rule(1), ['X' = 1, 'Y' = 0, 'Z' = 1], [sat(1 =:= 1 * ~ 0 + ~ 1 * 0)]).
40
40
  step(sat(1 =:= 1 * ~ 0 + ~ 1 * 0), builtin, [], []).
41
41
  step(labeling([1, 0, 1]), builtin, [], []).
42
+ step(xor_circuit_verified(1), unproven, [], []).
@@ -1,3 +1,7 @@
1
1
  review_quorum(selection(alice(0), bob(0), carol(1), dan(1))).
2
2
  review_quorum(selection(alice(0), bob(1), carol(1), dan(0))).
3
3
  review_quorum_count(2).
4
+
5
+ step(review_quorum(selection(alice(0), bob(0), carol(1), dan(1))), unproven, [], []).
6
+ step(review_quorum(selection(alice(0), bob(1), carol(1), dan(0))), unproven, [], []).
7
+ step(review_quorum_count(2), unproven, [], []).
@@ -52,3 +52,4 @@ step(feature_constraints(1, 0, 1, 1),
52
52
  [sat((1 # 0) * (1 =< 1) * (0 =< 1))]).
53
53
  step(sat((1 # 0) * (1 =< 1) * (0 =< 1)), builtin, [], []).
54
54
  step(labeling([1, 0, 1, 1]), builtin, [], []).
55
+ step(feature_plan_count(4), unproven, [], []).
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.6.6",
6
+ "version": "1.6.7",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
package/src/cli.js CHANGED
@@ -317,10 +317,23 @@ async function runDefault(engine, program, options) {
317
317
  });
318
318
  if (options.proof && !options.quiet) {
319
319
  const explanation = await loadExplanation();
320
- const roots = claimed
321
- .map((fact) => explanation.proofNodeFor(program, fact, { registry, proofDetail: options?.proofDetail ?? 'abstract' }))
322
- .filter(Boolean);
320
+ const detail = options?.proofDetail ?? 'abstract';
321
+ const roots = [];
322
+ const unexplained = [];
323
+ for (const fact of claimed) {
324
+ const node = explanation.proofNodeFor(program, fact, { registry, proofDetail: detail });
325
+ if (node) roots.push(node);
326
+ else unexplained.push(fact);
327
+ }
323
328
  const { clauses, steps } = explanation.flattenProof(roots, program);
329
+ // An answer the explanation replay cannot reproduce is recorded as
330
+ // `unproven` rather than left without a step: a document containing
331
+ // one is not a valid proof, and saying so is the point.
332
+ const concluded = new Set(steps.map((step) => engine.termToString(step.conclusion, new engine.Env(), true)));
333
+ for (const fact of unexplained) {
334
+ if (concluded.has(engine.termToString(fact, new engine.Env(), true))) continue;
335
+ steps.push({ conclusion: fact, by: engine.atom('unproven'), bindings: [], uses: [] });
336
+ }
324
337
  process.stdout.write(engine.proofBlocks(program, clauses, steps));
325
338
  }
326
339
  if (haltCode != null) process.exitCode = haltCode;
package/src/index.js CHANGED
@@ -40,6 +40,7 @@ import { getStrictIsoRegistry } from './iso.js';
40
40
  import { getEyePrologRegistry } from './standard-library.js';
41
41
  import { executeForwardRules, executeGoals, hasForwardRules, normalizeGoals } from './execute.js';
42
42
  import { proofBlocks } from './result-format.js';
43
+ import { Env, atom, termToString } from './term.js';
43
44
 
44
45
  // The public API is an entry point above the solver/registry layers, so it can
45
46
  // install pruning-aware iterator disposal without introducing an import cycle.
@@ -110,9 +111,25 @@ export function run(source, options = {}) {
110
111
  // The `clause/3` and `step/4` blocks explaining the facts a run claimed.
111
112
  // One walk across every claim, so a conclusion several of them rest on is
112
113
  // explained once.
114
+ //
115
+ // An answer the solver found but the explanation replay cannot reproduce --
116
+ // a CLP(B) answer decided by propagation rather than by resolution -- is
117
+ // recorded as `unproven` rather than quietly left without a step. A document
118
+ // containing one is not a valid proof, and saying so is the point.
113
119
  function proofBlocksFor(program, claimed, registry, proofDetail = 'abstract') {
114
- const roots = claimed.map((fact) => proofNodeFor(program, fact, { registry, proofDetail })).filter(Boolean);
120
+ const roots = [];
121
+ const unexplained = [];
122
+ for (const fact of claimed) {
123
+ const node = proofNodeFor(program, fact, { registry, proofDetail });
124
+ if (node) roots.push(node);
125
+ else unexplained.push(fact);
126
+ }
115
127
  const { clauses, steps } = flattenProof(roots, program);
128
+ const concluded = new Set(steps.map((step) => termToString(step.conclusion, new Env(), true)));
129
+ for (const fact of unexplained) {
130
+ if (concluded.has(termToString(fact, new Env(), true))) continue;
131
+ steps.push({ conclusion: fact, by: atom('unproven'), bindings: [], uses: [] });
132
+ }
116
133
  return proofBlocks(program, clauses, steps);
117
134
  }
118
135
 
@@ -29,12 +29,21 @@ const KNOWN_GAPS = new Map([
29
29
  ['clpb-feature-model.pl', 'a CLP(B) answer decided by propagation, not by resolution'],
30
30
  ]);
31
31
 
32
+ const totals = { steps: 0, verified: 0, trusted: 0 };
33
+
32
34
  export function runProofChecking(reporter = new TestReporter()) {
33
35
  reporter.section('Proof checking');
36
+ totals.steps = 0;
37
+ totals.verified = 0;
38
+ totals.trusted = 0;
34
39
  for (const name of [...proofExamples].sort()) {
35
40
  reporter.test(name, () => checkPackagedProof(name));
36
41
  }
37
42
  reporter.sectionTotal('proof checking');
43
+ // What the corpus establishes, in the terms the specification uses: a
44
+ // verified step was re-performed, a trusted one was recorded because
45
+ // re-deciding it would mean running the program.
46
+ reporter.section(`${totals.steps} steps, ${totals.verified} verified, ${totals.trusted} trusted`);
38
47
  }
39
48
 
40
49
  function checkPackagedProof(name) {
@@ -42,6 +51,9 @@ function checkPackagedProof(name) {
42
51
  const proof = fs.readFileSync(path.join(examplesDir, 'proof', name), 'utf8');
43
52
  const program = Program.parseSources([{ text: source, filename: name }], { sourceMetadata: true });
44
53
  const report = checkProofDocument(program, proof);
54
+ totals.steps += report.steps;
55
+ totals.verified += report.verified;
56
+ totals.trusted += report.trusted.length;
45
57
  const gap = KNOWN_GAPS.get(name);
46
58
  if (gap) {
47
59
  assertEqual(report.valid, false, `${name} now checks; remove it from KNOWN_GAPS (${gap})`);