eyeprolog 1.5.24 → 1.5.26
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/examples/odrl-policy-reasoning.pl +2 -5
- package/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/cli.js +8 -4
- package/src/program.js +10 -1
- package/src/solver.js +34 -12
- package/src/standard-library.js +8 -0
- package/test/conformance/ISO-IMPLEMENTATION-DEFINED.md +1 -1
- package/test/conformance/ISO-PROCESSOR-REQUIREMENTS.md +1 -1
- package/test/conformance/README.md +2 -2
- package/test/run-regression.mjs +80 -4
- package/the-art-of-eyeprolog.md +24 -9
|
@@ -345,11 +345,8 @@ profile_prohibition(Request) :-
|
|
|
345
345
|
cycle_request(Request),
|
|
346
346
|
tnot(profile_permission(Request)).
|
|
347
347
|
|
|
348
|
-
%
|
|
349
|
-
%
|
|
350
|
-
wfs_truth(Claim, true) :- call(Claim), \+ tnot(Claim).
|
|
351
|
-
wfs_truth(Claim, false) :- tnot(Claim), \+ call(Claim).
|
|
352
|
-
wfs_truth(Claim, undefined) :- call(Claim), tnot(Claim).
|
|
348
|
+
% The normal-profile wfs_truth/2 predicate reports the three-valued result of
|
|
349
|
+
% each ground claim without treating an undefined claim as ordinary success.
|
|
353
350
|
|
|
354
351
|
% --- Curated questions -----------------------------------------------------
|
|
355
352
|
|
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
|
|
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
package/src/cli.js
CHANGED
|
@@ -28,6 +28,7 @@ export async function main(argv) {
|
|
|
28
28
|
proofDetail: 'abstract',
|
|
29
29
|
verifyProof: null,
|
|
30
30
|
quads: false,
|
|
31
|
+
quiet: false,
|
|
31
32
|
stats: false,
|
|
32
33
|
isoStrict: false,
|
|
33
34
|
portable: false,
|
|
@@ -60,6 +61,8 @@ export async function main(argv) {
|
|
|
60
61
|
options.verifyProof = file;
|
|
61
62
|
} else if (!endOptions && (arg === '--quads' || arg === '-q')) {
|
|
62
63
|
options.quads = true;
|
|
64
|
+
} else if (!endOptions && arg === '--quiet') {
|
|
65
|
+
options.quiet = true;
|
|
63
66
|
} else if (!endOptions && (arg === '--stats' || arg === '-s')) {
|
|
64
67
|
options.stats = true;
|
|
65
68
|
} else if (!endOptions && arg === '--iso-strict') {
|
|
@@ -116,7 +119,7 @@ export async function main(argv) {
|
|
|
116
119
|
}
|
|
117
120
|
|
|
118
121
|
if (options.isoStrict && options.files.length === 0 && options.goals.length === 0 &&
|
|
119
|
-
options.verifyProof == null && !options.proof && !options.stats && !options.warnings) {
|
|
122
|
+
options.verifyProof == null && !options.proof && !options.quiet && !options.stats && !options.warnings) {
|
|
120
123
|
const engine = await loadEngine();
|
|
121
124
|
const { runRepl } = await import('./repl.js');
|
|
122
125
|
const exitCode = await runRepl(engine, {
|
|
@@ -251,8 +254,8 @@ async function runForwardDefault(engine, program, options) {
|
|
|
251
254
|
});
|
|
252
255
|
try {
|
|
253
256
|
const result = engine.executeForwardRules(program, solver, {
|
|
254
|
-
onAnswer: (line) => process.stdout.write(line),
|
|
255
|
-
onFuse: (line) => process.stdout.write(line),
|
|
257
|
+
onAnswer: (line) => { if (!options.quiet) process.stdout.write(line); },
|
|
258
|
+
onFuse: (line) => { if (!options.quiet) process.stdout.write(line); },
|
|
256
259
|
onDiagnostic: (line) => process.stderr.write(line),
|
|
257
260
|
});
|
|
258
261
|
if (result.haltCode != null) process.exitCode = result.haltCode;
|
|
@@ -277,7 +280,7 @@ async function runDefault(engine, program, options) {
|
|
|
277
280
|
try {
|
|
278
281
|
const { haltCode } = engine.executeGoals(program, solver, goals, {
|
|
279
282
|
onAnswer: (line, resolved) => {
|
|
280
|
-
process.stdout.write(line);
|
|
283
|
+
if (!options.quiet) process.stdout.write(line);
|
|
281
284
|
if (options.proof) writeExplanation(explanation, program, resolved, registry, options);
|
|
282
285
|
},
|
|
283
286
|
});
|
|
@@ -313,6 +316,7 @@ Options:
|
|
|
313
316
|
--proof-detail mode Use abstract or expanded proof detail (implies --proof).
|
|
314
317
|
--verify-proof file Verify why/2 proof certificates against the input program.
|
|
315
318
|
-q, --quads Run embedded quad tests and fail if any do not hold.
|
|
319
|
+
--quiet Suppress answer terms while preserving Prolog output.
|
|
316
320
|
-s, --stats Print solver and memory statistics to stderr after execution.
|
|
317
321
|
--iso-strict Use ISO/IEC 13211-1 core + Corrigenda 1-3 only;
|
|
318
322
|
reject EyeProlog language extensions.
|
package/src/program.js
CHANGED
|
@@ -57,6 +57,10 @@ function collectMultiplicitySensitiveDependencies(goal, out = []) {
|
|
|
57
57
|
collectMultiplicitySensitiveDependencies(goal.args[0], out);
|
|
58
58
|
return out;
|
|
59
59
|
}
|
|
60
|
+
if (goal.name === 'wfs_truth' && goal.arity === 2) {
|
|
61
|
+
collectMultiplicitySensitiveDependencies(goal.args[0], out);
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
60
64
|
if (goal.name === 'forall' && goal.arity === 2) {
|
|
61
65
|
collectMultiplicitySensitiveDependencies(goal.args[0], out);
|
|
62
66
|
collectMultiplicitySensitiveDependencies(goal.args[1], out);
|
|
@@ -1134,6 +1138,10 @@ function collectAutoloadGoalDependencies(goal, out = []) {
|
|
|
1134
1138
|
collectAutoloadGoalDependencies(goal.args[0], out);
|
|
1135
1139
|
return out;
|
|
1136
1140
|
}
|
|
1141
|
+
if (goal.name === 'wfs_truth' && goal.arity === 2) {
|
|
1142
|
+
collectAutoloadGoalDependencies(goal.args[0], out);
|
|
1143
|
+
return out;
|
|
1144
|
+
}
|
|
1137
1145
|
|
|
1138
1146
|
out.push({ key: `${goal.name}/${goal.arity}`, name: goal.name, arity: goal.arity, module: goal.module });
|
|
1139
1147
|
|
|
@@ -1687,7 +1695,8 @@ function annotateGoalModule(term, module) {
|
|
|
1687
1695
|
if (!term || (term.type !== ATOM && term.type !== COMPOUND)) return term;
|
|
1688
1696
|
term.module = module;
|
|
1689
1697
|
const callableArguments = (term.name === ',' || term.name === ';' || term.name === '->') ? term.args
|
|
1690
|
-
: (
|
|
1698
|
+
: (term.name === 'wfs_truth' && term.arity === 2) ? [term.args[0]]
|
|
1699
|
+
: (['call', 'once', '\\+', 'not', 'tnot', 'catch', 'call_cleanup', 'setup_call_cleanup',
|
|
1691
1700
|
'forall', 'findall', 'bagof', 'setof', 'countall', 'sumall',
|
|
1692
1701
|
'aggregate_min', 'aggregate_max', 'maplist', 'phrase'].includes(term.name)
|
|
1693
1702
|
? term.args
|
package/src/solver.js
CHANGED
|
@@ -909,14 +909,37 @@ 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))
|
|
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
|
}
|
|
916
919
|
}
|
|
917
920
|
|
|
918
921
|
*solveTabledNegation(argument, env) {
|
|
919
|
-
const
|
|
922
|
+
const truth = this.groundGoalTruth(argument, env);
|
|
923
|
+
if (truth === 'true') return;
|
|
924
|
+
if (truth === 'undefined') {
|
|
925
|
+
this.stats.wfs_undefined_answers++;
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
yield env;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
groundGoalTruth(argument, env) {
|
|
932
|
+
let invoked = copyResolved(argument, env);
|
|
933
|
+
while (invoked.type === COMPOUND && invoked.name === ':' && invoked.arity === 2) {
|
|
934
|
+
const module = invoked.args[0];
|
|
935
|
+
if (module.type === VAR) throw new PrologError('instantiation_error');
|
|
936
|
+
if (module.type !== 'atom') throw new PrologError('type_error(atom)', module);
|
|
937
|
+
invoked = invoked.args[1];
|
|
938
|
+
if (invoked.type !== COMPOUND && invoked.type !== 'atom') {
|
|
939
|
+
throw new PrologError('type_error(callable)', invoked);
|
|
940
|
+
}
|
|
941
|
+
qualifyTerm(invoked, module.name);
|
|
942
|
+
}
|
|
920
943
|
if (invoked.type === VAR) throw new PrologError('instantiation_error');
|
|
921
944
|
if (invoked.type !== COMPOUND && invoked.type !== 'atom') {
|
|
922
945
|
throw new PrologError('type_error(callable)', invoked);
|
|
@@ -925,22 +948,18 @@ export class Solver {
|
|
|
925
948
|
const group = this.program.findGroup(invoked.name, invoked.arity, invoked.module ?? 'user');
|
|
926
949
|
if (group?.wfsDatalog === true) {
|
|
927
950
|
const model = this.wfsModelFor(group);
|
|
928
|
-
|
|
929
|
-
if (truth === 'true') return;
|
|
930
|
-
if (truth === 'undefined') this.stats.wfs_undefined_answers++;
|
|
931
|
-
yield env;
|
|
932
|
-
return;
|
|
951
|
+
return truthOfGroundGoal(model, invoked);
|
|
933
952
|
}
|
|
934
953
|
|
|
935
|
-
// Outside an unstratified WFS component, a ground
|
|
936
|
-
//
|
|
954
|
+
// Outside an unstratified WFS component, a ground goal has the ordinary
|
|
955
|
+
// two-valued result after the positive goal is evaluated.
|
|
937
956
|
const child = this.cloneForInnerGoal(1);
|
|
938
957
|
for (const _ of child.solve([invoked], env.clone(), 0)) {
|
|
939
958
|
this.absorbStatsFrom(child);
|
|
940
|
-
return;
|
|
959
|
+
return 'true';
|
|
941
960
|
}
|
|
942
961
|
this.absorbStatsFrom(child);
|
|
943
|
-
|
|
962
|
+
return 'false';
|
|
944
963
|
}
|
|
945
964
|
|
|
946
965
|
activeVariant(goal, env) {
|
|
@@ -1523,7 +1542,10 @@ function pushWfsAnswerFrames(stack, model, group, goal, rest, env, depth, active
|
|
|
1523
1542
|
if (!unify(goal.args[i], row[i], next)) { ok = false; break; }
|
|
1524
1543
|
}
|
|
1525
1544
|
if (!ok) continue;
|
|
1526
|
-
if (!lower?.has(row))
|
|
1545
|
+
if (!lower?.has(row)) {
|
|
1546
|
+
solver.stats.wfs_undefined_answers++;
|
|
1547
|
+
continue;
|
|
1548
|
+
}
|
|
1527
1549
|
stack.push({ kind: 'goals', goals: rest, env: next, depth: depth + 1, active });
|
|
1528
1550
|
}
|
|
1529
1551
|
}
|
package/src/standard-library.js
CHANGED
|
@@ -310,6 +310,13 @@ function* tabledNegationBuiltin({ solver, goal, env }) {
|
|
|
310
310
|
yield* solver.solveTabledNegation(goal.args[0], env);
|
|
311
311
|
}
|
|
312
312
|
|
|
313
|
+
function* wfsTruthBuiltin({ solver, goal, env }) {
|
|
314
|
+
const truth = solver.groundGoalTruth(goal.args[0], env);
|
|
315
|
+
if (truth === 'undefined') solver.stats.wfs_undefined_answers++;
|
|
316
|
+
const next = env.clone();
|
|
317
|
+
if (unify(goal.args[1], atom(truth), next)) yield next;
|
|
318
|
+
}
|
|
319
|
+
|
|
313
320
|
function runtimeStatistics(solver) {
|
|
314
321
|
return { ...solver.stats, ...memoryStatistics() };
|
|
315
322
|
}
|
|
@@ -361,6 +368,7 @@ export function createEyePrologRegistry() {
|
|
|
361
368
|
registry.add('statistics', 0, statisticsBuiltin, { deterministic: true });
|
|
362
369
|
registry.add('statistics', 2, statisticsValueBuiltin);
|
|
363
370
|
registry.add('tnot', 1, tabledNegationBuiltin, { deterministic: true });
|
|
371
|
+
registry.add('wfs_truth', 2, wfsTruthBuiltin, { deterministic: true });
|
|
364
372
|
attsHostBuiltins.register(registry);
|
|
365
373
|
expansionBuiltins.register(registry);
|
|
366
374
|
arithmeticHostBuiltins.register(registry);
|
|
@@ -119,7 +119,7 @@ families; `--iso-strict` is intended to remove their Part 1 interpretation.
|
|
|
119
119
|
| 5.5.4 Types | The normal JavaScript API exposes an implementation-specific string term `stringTerm(Text)`. It is disjoint from the five Part 1 term types; normal term order places it after atoms and before compound terms, and normal `atomic/1` treats it as atomic. It has no Prolog source token syntax (double-quoted source still follows `double_quotes`), is non-callable for term-to-clause conversion, is not evaluable as an arithmetic expression, and writes as a double-quoted host string. | Strict mode rejects a programmatic string term at program/goal entry with `representation_error(term)`, so the additional type cannot enter the Part 1 execution domain. **defined** — `src/term.js`, `src/program.js`, `src/solver.js`; strict API-boundary regression. |
|
|
120
120
|
| 5.5.5 Directives | `module/2`, `use_module/1-2`, `meta_predicate/1` and normal-profile library behavior | Rejected as implementation-specific Part 1 directives. |
|
|
121
121
|
| 5.5.6 Side effects | Normal mode adds `statistics/0-2`, cleanup/library state, and optional proof/statistics host instrumentation. | The strict registry excludes Prolog-visible statistics/cleanup/library adapters; ordinary Part 1 I/O/database/flag/operator side effects remain. Host proof/statistics collection observes execution through the embedding API rather than adding a strict Prolog goal effect. **covered** — strict registry/execution regression. |
|
|
122
|
-
| 5.5.7 Control constructs | `tnot/1` and normal-profile execution optimizations | `tnot/1` and the normal-profile explicit `table` declaration are absent. |
|
|
122
|
+
| 5.5.7 Control constructs | `tnot/1`, `wfs_truth/2`, and normal-profile execution optimizations | `tnot/1`, `wfs_truth/2`, and the normal-profile explicit `table` declaration are absent. |
|
|
123
123
|
| 5.5.8 Flags | `occurs_check` | Absent in strict mode. |
|
|
124
124
|
| 5.5.9 Built-in predicates | EyeProlog libraries, CLP(Z), statistics, Part 3 `phrase/2-3`, and bundled-library autoloaded predicates | Strict registry contains only the Part 1 + Corrigenda core registry. |
|
|
125
125
|
| 5.5.10 Evaluable functors | Normal mode additionally accepts the EyeProlog evaluable atom `e`; the remaining arithmetic functors accepted by strict mode are the Part 1 + Corrigenda set. | Strict mode rejects `e/0` as non-evaluable and retains the Corrigendum arithmetic additions. **covered** — strict extension-boundary regression plus `src/iso-arithmetic.js`. |
|
|
@@ -40,7 +40,7 @@ certification claim.
|
|
|
40
40
|
| 5.5.4 additional term types | covered | the normal JavaScript API's `stringTerm(Text)` is documented as an implementation-specific sixth term type, including disjointness, ordering, clause conversion, lack of source token syntax, expression behavior, and writing; strict program/goal entry rejects that type with `representation_error(term)` |
|
|
41
41
|
| 5.5.5 additional directives | covered | normal module/library directives are documented implementation-specific features and are rejected by strict mode |
|
|
42
42
|
| 5.5.6 additional side effects | covered | normal-profile `statistics/0-2`, cleanup/library state, proof/statistics host instrumentation, and other extension effects are documented outside the Part 1 core; strict registry tests exclude the Prolog-visible statistics/cleanup extensions. Host instrumentation is an embedding observation rather than an extra strict Prolog goal effect |
|
|
43
|
-
| 5.5.7 additional control constructs | covered | `tnot/1` and implementation-specific execution optimizations are absent/disabled in strict mode; standard control constructs remain separately audited |
|
|
43
|
+
| 5.5.7 additional control constructs | covered | `tnot/1`, `wfs_truth/2`, and implementation-specific execution optimizations are absent/disabled in strict mode; standard control constructs remain separately audited |
|
|
44
44
|
| 5.5.8 additional flags | covered | normal `occurs_check` extension is absent from strict mode; the Part 1 flag family is fully audited |
|
|
45
45
|
| 5.5.9 additional built-in predicates and error forms | covered | strict registry excludes normal-profile library/native additions; `ISO-BUILTIN-MODE-ERROR-MATRIX.md` closes the complete 8.2-8.17 family row-by-row; simultaneous-error choices are documented per 7.12 rather than treated as a global table-order mandate |
|
|
46
46
|
| 5.5.10 additional evaluable functors/types | covered | strict mode rejects the normal-profile evaluable `e` extension while retaining the full Part 1 + Corrigenda arithmetic set. `ISO-EVALUABLE-FUNCTOR-MATRIX.md` now closes the Clause 9 semantic/error audit and pins the 9.1.4.2 `resultF`, mixed-type `max/2`/`min/2`, and signed bitwise/shift choices. Post-N289 STC #75 remains separate from the published baseline |
|
|
@@ -31,9 +31,9 @@ Clause 9 evaluable functors. Public comparison material remains supporting revie
|
|
|
31
31
|
evidence rather than a duplicated vendored corpus. Cases under `iso/`
|
|
32
32
|
identify standards-derived behavior; other directories cover EyeProlog host
|
|
33
33
|
contracts and extensions. EyeProlog-only execution features such as explicit
|
|
34
|
-
tabling
|
|
34
|
+
tabling, `tnot/1` well-founded negation, and `wfs_truth/2` inspection are outside the Part 1 strict-core
|
|
35
35
|
claim. Their focused semantic coverage lives primarily in regression tests;
|
|
36
|
-
`tnot/1`
|
|
36
|
+
`tnot/1` and `wfs_truth/2` are absent from the strict ISO registry. The processor character set is documented as the Unicode scalar repertoire with
|
|
37
37
|
scalar-value collation in both normal and strict profiles; `--iso-strict`
|
|
38
38
|
therefore changes only implementation-specific language facilities, not this
|
|
39
39
|
implementation-defined processor choice.
|
package/test/run-regression.mjs
CHANGED
|
@@ -2404,6 +2404,7 @@ c4 ?- call((!;1)).
|
|
|
2404
2404
|
assertIncludes(result.stdout, '-g, --goal goal', 'stdout');
|
|
2405
2405
|
assertIncludes(result.stdout, '-p, --proof', 'stdout');
|
|
2406
2406
|
assertIncludes(result.stdout, '-q, --quads', 'stdout');
|
|
2407
|
+
assertIncludes(result.stdout, '--quiet', 'stdout');
|
|
2407
2408
|
assertIncludes(result.stdout, '-s, --stats', 'stdout');
|
|
2408
2409
|
assertIncludes(result.stdout, '--portable', 'stdout');
|
|
2409
2410
|
assertIncludes(result.stdout, '--no-autoload', 'stdout');
|
|
@@ -3821,6 +3822,19 @@ child.stdin.write(\`consult(${consultedAtom}).\\n\`);
|
|
|
3821
3822
|
assertEqual(result.stderr, '', 'stderr');
|
|
3822
3823
|
},
|
|
3823
3824
|
},
|
|
3825
|
+
{
|
|
3826
|
+
name: '--quiet preserves goal side effects without printing answer terms',
|
|
3827
|
+
run: () => {
|
|
3828
|
+
const input = 'emit :- write(side_effect), nl.\n';
|
|
3829
|
+
const quiet = runCli(['--quiet', '--goal', 'emit', '-'], { input });
|
|
3830
|
+
assertEqual(quiet.status, 0, 'quiet exit status');
|
|
3831
|
+
assertEqual(quiet.stdout, 'side_effect\n', 'quiet stdout');
|
|
3832
|
+
assertEqual(quiet.stderr, '', 'quiet stderr');
|
|
3833
|
+
|
|
3834
|
+
const ordinary = runCli(['--goal', 'emit', '-'], { input });
|
|
3835
|
+
assertEqual(ordinary.stdout, 'side_effect\nemit.\n', 'ordinary answer output remains unchanged');
|
|
3836
|
+
},
|
|
3837
|
+
},
|
|
3824
3838
|
{
|
|
3825
3839
|
name: '-g requires a goal argument',
|
|
3826
3840
|
run: () => {
|
|
@@ -6726,7 +6740,7 @@ answer(ok) :-
|
|
|
6726
6740
|
assertEqual(Boolean(registry.get('is', 2)), true, 'ISO is/2 exists');
|
|
6727
6741
|
assertEqual(Boolean(registry.get('append', 3)), false, 'append/3 is not ISO core');
|
|
6728
6742
|
assertEqual(library.eyePrologLibrary, true, 'complete registry marker');
|
|
6729
|
-
assertEqual(library.defs.size,
|
|
6743
|
+
assertEqual(library.defs.size, 222, 'EyeProlog registry contains ISO definitions, cleanup controls, observability extensions, WFS predicates, and generic library adapters');
|
|
6730
6744
|
assertEqual(registry.get('eyeprolog__dynify', 1), null, 'Eyelet dynify adapter is absent from the ISO registry');
|
|
6731
6745
|
assertEqual(Boolean(library.get('eyeprolog__dynify', 1)), true, 'Eyelet dynify adapter is an internal EyeProlog library primitive');
|
|
6732
6746
|
assertEqual(registry.get('eyeprolog__eyelet_emit', 2), null, 'Eyelet event adapter is absent from the ISO registry');
|
|
@@ -6743,6 +6757,8 @@ answer(ok) :-
|
|
|
6743
6757
|
assertEqual(Boolean(library.get('statistics', 2)), true, 'statistics/2 is an EyeProlog observability extension');
|
|
6744
6758
|
assertEqual(registry.get('tnot', 1), null, 'tnot/1 is absent from the ISO registry');
|
|
6745
6759
|
assertEqual(Boolean(library.get('tnot', 1)), true, 'tnot/1 is an EyeProlog WFS extension');
|
|
6760
|
+
assertEqual(registry.get('wfs_truth', 2), null, 'wfs_truth/2 is absent from the ISO registry');
|
|
6761
|
+
assertEqual(Boolean(library.get('wfs_truth', 2)), true, 'wfs_truth/2 is an EyeProlog WFS extension');
|
|
6746
6762
|
assertEqual(registry.get('time', 1), null, 'time/1 is absent from the ISO registry');
|
|
6747
6763
|
assertEqual(Boolean(library.get('time', 1)), true, 'time/1 is an EyeProlog timing extension');
|
|
6748
6764
|
assertEqual(registry.get('dif', 2), null, 'dif/2 is absent from the strict ISO core registry');
|
|
@@ -7782,9 +7798,9 @@ win(X) :- move(X, Y), tnot(win(Y)).
|
|
|
7782
7798
|
assertEqual(program.findGroup('win', 1).wfsDatalog, true, 'win/1 uses finite WFS evaluation');
|
|
7783
7799
|
assertEqual(program.findGroup('win', 1).tabled, false, 'win/1 is not positive least-model tabled');
|
|
7784
7800
|
const result = run(program, { goal: 'win(X)' });
|
|
7785
|
-
assertEqual(result.stdout
|
|
7801
|
+
assertEqual(result.stdout, '', 'negative cycle does not expose undefined atoms as successful answers');
|
|
7786
7802
|
assertEqual(result.stats.wfs_fixpoint_rounds, 2, 'alternating fixed point converges in two rounds');
|
|
7787
|
-
assertEqual(result.stats.wfs_undefined_answers, 4, 'cycle
|
|
7803
|
+
assertEqual(result.stats.wfs_undefined_answers, 4, 'cycle atoms are observed as undefined rather than unconditional truths');
|
|
7788
7804
|
|
|
7789
7805
|
const nafProgram = Program.parse('p(X) :- \\+ q(X).\nq(X) :- p(X).\n');
|
|
7790
7806
|
assertEqual(nafProgram.findGroup('p', 1).wfsDatalog, false, '\\+/1 remains ordinary negation as failure');
|
|
@@ -7804,7 +7820,67 @@ undefined_case :- tnot(win(c)).
|
|
|
7804
7820
|
`;
|
|
7805
7821
|
assertEqual(run(source, { goal: 'true_case' }).stdout, 'true_case.\n', 'win(a) is true because b is false');
|
|
7806
7822
|
assertEqual(run(source, { goal: 'false_case' }).stdout, '', 'tnot(win(a)) fails for a true atom');
|
|
7807
|
-
assertEqual(run(source, { goal: 'undefined_case' }).stdout, '
|
|
7823
|
+
assertEqual(run(source, { goal: 'undefined_case' }).stdout, '', 'tnot of an undefined WFS atom is not a successful answer');
|
|
7824
|
+
},
|
|
7825
|
+
},
|
|
7826
|
+
{
|
|
7827
|
+
name: 'wfs_truth exposes three-valued results without executing undefined goals',
|
|
7828
|
+
run: () => {
|
|
7829
|
+
const source = `
|
|
7830
|
+
:- use_module(library(tabling)).
|
|
7831
|
+
:- table(win/1).
|
|
7832
|
+
move(a, b).
|
|
7833
|
+
move(c, d).
|
|
7834
|
+
move(d, c).
|
|
7835
|
+
win(X) :- move(X, Y), tnot(win(Y)).
|
|
7836
|
+
`;
|
|
7837
|
+
assertEqual(run(source, { goal: 'wfs_truth(win(a), Truth)' }).stdout,
|
|
7838
|
+
'wfs_truth(win(a), true).\n', 'true WFS atom');
|
|
7839
|
+
assertEqual(run(source, { goal: 'wfs_truth(win(b), Truth)' }).stdout,
|
|
7840
|
+
'wfs_truth(win(b), false).\n', 'false WFS atom');
|
|
7841
|
+
const undefinedResult = run(source, { goal: 'wfs_truth(win(c), Truth)' });
|
|
7842
|
+
assertEqual(undefinedResult.stdout, 'wfs_truth(win(c), undefined).\n', 'undefined WFS atom');
|
|
7843
|
+
assertEqual(undefinedResult.stats.wfs_undefined_answers, 1, 'undefined truth inspection is counted');
|
|
7844
|
+
assertEqual(run(source, { goal: 'wfs_truth(win(c), true)' }).stdout, '', 'truth filter can reject undefined');
|
|
7845
|
+
let nongroundError = null;
|
|
7846
|
+
try {
|
|
7847
|
+
run(source, { goal: 'wfs_truth(win(X), Truth)' });
|
|
7848
|
+
} catch (error) {
|
|
7849
|
+
nongroundError = error;
|
|
7850
|
+
}
|
|
7851
|
+
assertEqual(nongroundError?.formal, 'instantiation_error', 'inspected goal must be ground');
|
|
7852
|
+
|
|
7853
|
+
const moduleSource = `
|
|
7854
|
+
:- module(cycle, [status/1]).
|
|
7855
|
+
:- use_module(library(tabling)).
|
|
7856
|
+
:- table(a/0).
|
|
7857
|
+
:- table(b/0).
|
|
7858
|
+
a :- tnot(b).
|
|
7859
|
+
b :- tnot(a).
|
|
7860
|
+
status(Truth) :- wfs_truth(a, Truth).
|
|
7861
|
+
`;
|
|
7862
|
+
assertEqual(run(moduleSource, { goal: 'cycle:status(Truth)' }).stdout,
|
|
7863
|
+
'cycle:status(undefined).\n', 'lexical module qualification');
|
|
7864
|
+
assertEqual(run(moduleSource, { goal: 'wfs_truth(cycle:a, Truth)' }).stdout,
|
|
7865
|
+
'wfs_truth(cycle:a, undefined).\n', 'explicit module qualification');
|
|
7866
|
+
assertEqual(run(moduleSource, { goal: 'tnot(cycle:a)' }).stdout, '',
|
|
7867
|
+
'qualified tnot preserves undefined failure');
|
|
7868
|
+
},
|
|
7869
|
+
},
|
|
7870
|
+
{
|
|
7871
|
+
name: 'undefined WFS goals do not run continuations or print CLI answers',
|
|
7872
|
+
run: () => {
|
|
7873
|
+
const source = `
|
|
7874
|
+
:- use_module(library(tabling)).
|
|
7875
|
+
:- table(a/0).
|
|
7876
|
+
:- table(b/0).
|
|
7877
|
+
a :- tnot(b).
|
|
7878
|
+
b :- tnot(a).
|
|
7879
|
+
`;
|
|
7880
|
+
const result = run(source, { goal: 'a, write(true), nl; halt' });
|
|
7881
|
+
assertEqual(result.stdout, '', 'undefined first branch neither writes nor produces a ground answer');
|
|
7882
|
+
assertEqual(result.haltCode, 0, 'fallback halt branch runs');
|
|
7883
|
+
assertEqual(result.stats.wfs_undefined_answers, 1, 'undefined atom remains observable in statistics');
|
|
7808
7884
|
},
|
|
7809
7885
|
},
|
|
7810
7886
|
{
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -1144,8 +1144,19 @@ 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
|
|
1148
|
-
|
|
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.
|
|
1150
|
+
Use `wfs_truth/2` when the truth state itself is data:
|
|
1151
|
+
|
|
1152
|
+
```text
|
|
1153
|
+
?- wfs_truth(win(a), Truth).
|
|
1154
|
+
Truth = undefined.
|
|
1155
|
+
```
|
|
1156
|
+
|
|
1157
|
+
The inspected goal must be ground. The predicate reports `true`, `false`, or
|
|
1158
|
+
`undefined`; reporting `undefined` is a successful result of `wfs_truth/2`, not
|
|
1159
|
+
a successful execution of the inspected goal.
|
|
1149
1160
|
Direct calls to `tnot/1` must be ground. In WFS rules, variables occurring in
|
|
1150
1161
|
the head or a negated literal must be range-restricted by positive body
|
|
1151
1162
|
literals. Ordinary `\+/1` is unchanged, and strict ISO mode does not provide
|
|
@@ -1664,8 +1675,8 @@ The reported counters include completed goal lists, calls to the goal solver
|
|
|
1664
1675
|
and single-goal solver, unification attempts, maximum depth and goal-list size,
|
|
1665
1676
|
deterministic built-in successes and failures, and table fixed-point rounds.
|
|
1666
1677
|
WFS execution additionally reports `wfs_fixpoint_rounds` and
|
|
1667
|
-
`wfs_undefined_answers`. The latter counts undefined
|
|
1668
|
-
|
|
1678
|
+
`wfs_undefined_answers`. The latter counts undefined atoms encountered while
|
|
1679
|
+
resolving query goals; it is an execution statistic, not a declaration
|
|
1669
1680
|
that those atoms are true. All statistics describe work performed, not logical
|
|
1670
1681
|
truth. Compare counters only across equivalent queries and the same
|
|
1671
1682
|
implementation version.
|
|
@@ -5950,8 +5961,9 @@ declaration; they never opt an undeclared predicate into tabling.
|
|
|
5950
5961
|
|
|
5951
5962
|
Ordinary `\+/1` remains ISO-style negation as failure. The separate `tnot/1`
|
|
5952
5963
|
extension explicitly requests well-founded evaluation for eligible finite,
|
|
5953
|
-
range-restricted Datalog dependencies.
|
|
5954
|
-
|
|
5964
|
+
range-restricted Datalog dependencies. `wfs_truth/2` reports the three-valued
|
|
5965
|
+
state of a ground callable as `true`, `false`, or `undefined`. Strict ISO mode
|
|
5966
|
+
exposes none of `table`, `tnot/1`, or `wfs_truth/2`.
|
|
5955
5967
|
|
|
5956
5968
|
#### Query execution
|
|
5957
5969
|
|
|
@@ -6358,9 +6370,9 @@ or observability. The ISO and library catalogs therefore cover **523 distinct pr
|
|
|
6358
6370
|
re-exported by a compatibility module is counted once in this library surface:
|
|
6359
6371
|
for example `call_cleanup/2` and `setup_call_cleanup/3` are exported by
|
|
6360
6372
|
`library(iso_ext)`, while `time/1` and `statistics/2` are available from
|
|
6361
|
-
`library(time)`. `statistics/0` and `
|
|
6362
|
-
outside the library catalog. These additions are
|
|
6363
|
-
registry.
|
|
6373
|
+
`library(time)`. `statistics/0`, `tnot/1`, and `wfs_truth/2` remain
|
|
6374
|
+
normal-runtime extensions outside the library catalog. These additions are
|
|
6375
|
+
absent from the strict ISO registry.
|
|
6364
6376
|
|
|
6365
6377
|
The sources are `src/lib/aggregate.pl`, `src/lib/arithmetic.pl`,
|
|
6366
6378
|
`src/lib/assoc.pl`, `src/lib/atts.pl`, `src/lib/between.pl`,
|
|
@@ -9374,6 +9386,8 @@ eyeprolog --goal 'ancestor(ada, Who)' examples/ancestor.pl
|
|
|
9374
9386
|
|
|
9375
9387
|
Repeat `-g` or `--goal` to request several result relations in one run. EyeProlog prints
|
|
9376
9388
|
their ground answers in the order the goals were supplied.
|
|
9389
|
+
Use `--quiet` for command-style goals: Prolog output such as `write/1` remains
|
|
9390
|
+
visible, while the resolved answer terms are suppressed.
|
|
9377
9391
|
|
|
9378
9392
|
For a self-running example, place the host goal in an ordinary comment:
|
|
9379
9393
|
|
|
@@ -9395,6 +9409,7 @@ make the observed question explicit.
|
|
|
9395
9409
|
| `--proof-detail abstract|expanded` | Select library abstraction for proof output; implies `--proof` |
|
|
9396
9410
|
| `--verify-proof File` | Verify saved `why/2` proof certificates against the input program without proof search |
|
|
9397
9411
|
| `-q`, `--quads` | Run embedded quad tests and fail if any do not hold |
|
|
9412
|
+
| `--quiet` | Suppress resolved answer terms while preserving Prolog output and diagnostics |
|
|
9398
9413
|
| `--iso-strict` | Restrict parsing and execution to ISO/IEC 13211-1:1995 + Corrigenda 1–3; reject EyeProlog language extensions (including `table` and `:+`) and disable bundled-library autoloading |
|
|
9399
9414
|
| `--portable` | Enforce the conservative EyeProlog/Trealla/Scryer interoperability profile |
|
|
9400
9415
|
| `--no-autoload` | Disable bundled-library predicate autoloading |
|