eyeprolog 1.5.44 → 1.5.46
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/ACKNOWLEDGEMENTS.md +36 -0
- package/README.md +2 -1
- package/conformance-report.md +28 -3
- package/package.json +2 -1
- package/src/cli.js +16 -1
- package/src/iso.js +7 -1
- package/src/program-analysis.js +14 -0
- package/src/program.js +18 -1
- package/test/conformance/README.md +1 -1
- package/test/conformance/cases/aggregation/existential_qualifier_autoload.pl +12 -0
- package/test/conformance/cases/iso/part3_grammar_body_constructs.pl +35 -0
- package/test/conformance/cases/iso/part3_phrase_examples.pl +42 -0
- package/test/conformance/expected/aggregation/existential_qualifier_autoload.pl +5 -0
- package/test/conformance/expected/iso/part3_grammar_body_constructs.pl +13 -0
- package/test/conformance/expected/iso/part3_phrase_examples.pl +6 -0
- package/test/run-conformance-report.mjs +35 -0
- package/test/run-iso-strict.mjs +33 -0
- package/test/run-regression.mjs +34 -0
- package/the-art-of-eyeprolog.md +1 -1
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Acknowledgements
|
|
2
|
+
|
|
3
|
+
This work was carried out at [IDLab](https://idlab.ugent.be/), Ghent University
|
|
4
|
+
– imec, within the [KNoWS](https://knows.idlab.ugent.be/) (Knowledge on Web
|
|
5
|
+
Scale) research team, as part of the **Koreografeye** project.
|
|
6
|
+
|
|
7
|
+
## Standards
|
|
8
|
+
|
|
9
|
+
Conformance work on this repository is checked against the following documents,
|
|
10
|
+
which are consulted under a single-user licence and are not redistributed here:
|
|
11
|
+
|
|
12
|
+
- ISO/IEC 13211-1:1995, *Information technology — Programming languages —
|
|
13
|
+
Prolog — Part 1: General core*
|
|
14
|
+
- ISO/IEC 13211-1:1995/Cor.1:2007
|
|
15
|
+
- ISO/IEC 13211-1:1995/Cor.2:2012
|
|
16
|
+
- ISO/IEC 13211-1:1995/Cor.3:2017
|
|
17
|
+
- ISO/IEC 13211-2:2000, *Part 2: Modules*, and its 2013 amendment
|
|
18
|
+
- ISO/IEC TS 13211-3:2023, *Part 3: Definite clause grammar rules*
|
|
19
|
+
|
|
20
|
+
## External conformance corpora
|
|
21
|
+
|
|
22
|
+
The test suite executes third-party material that is vendored unmodified and
|
|
23
|
+
credited in place:
|
|
24
|
+
|
|
25
|
+
- Ulrich Neumerkel's ISO Prolog conformity corpora (TU Wien), including the
|
|
26
|
+
`phrase_quad.pl` and `variable_names_quad.pl` snapshots under
|
|
27
|
+
`test/fixtures/`.
|
|
28
|
+
- The WG17 syntax conformity-testing matrix.
|
|
29
|
+
- Test cases adapted from the Logtalk ISO conformance suite, marked as such in
|
|
30
|
+
the corpus files that use them.
|
|
31
|
+
|
|
32
|
+
## Citation
|
|
33
|
+
|
|
34
|
+
When referring to this work in a publication, please acknowledge the project as
|
|
35
|
+
described above. A suggested sentence is available in the paper template used by
|
|
36
|
+
the KNoWS team.
|
package/README.md
CHANGED
|
@@ -72,7 +72,8 @@ printf 'human(socrates).\nmortal(X) :- human(X).\n' |
|
|
|
72
72
|
- [rdf-prolog-roundtrip](https://github.com/eyereasoner/rdf-prolog-roundtrip) — standalone RDF 1.2 ↔ ISO Prolog bridge used by the RDF examples
|
|
73
73
|
- [ISO conformance audit](test/conformance/ISO-COMPLIANCE.md) — supported Part 1 profile
|
|
74
74
|
- [Latest Neumerkel conformity](test/conformance/NEUMERKEL-LATEST.md) — tracked result from the current live upstream inventory
|
|
75
|
-
- [Conformance report](conformance-report.md) — generated executable conformance status
|
|
75
|
+
- [Conformance report](conformance-report.md) — generated executable conformance status, local corpus summary, and known deviations
|
|
76
|
+
- [Acknowledgements](ACKNOWLEDGEMENTS.md) — funding, standards consulted, and credits for vendored conformance corpora
|
|
76
77
|
- [OpenRuleBench](openrulebench/README.md) — portable benchmark profile
|
|
77
78
|
## RDF, Prolog, and symbiotic knowledge graphs
|
|
78
79
|
|
package/conformance-report.md
CHANGED
|
@@ -25,14 +25,14 @@ spelling therefore changes this report even when no corpus file is added or remo
|
|
|
25
25
|
|
|
26
26
|
| Category | Positive | Errors | Warnings | Proofs | Total |
|
|
27
27
|
|---|---:|---:|---:|---:|---:|
|
|
28
|
-
| aggregation |
|
|
28
|
+
| aggregation | 18 | 0 | 0 | 0 | 18 |
|
|
29
29
|
| arithmetic | 38 | 0 | 0 | 0 | 38 |
|
|
30
30
|
| atoms | 23 | 8 | 0 | 0 | 31 |
|
|
31
31
|
| builtins | 11 | 0 | 0 | 0 | 11 |
|
|
32
32
|
| context | 11 | 0 | 0 | 0 | 11 |
|
|
33
33
|
| control | 15 | 0 | 0 | 0 | 15 |
|
|
34
34
|
| explicit-tabling | 6 | 0 | 0 | 0 | 6 |
|
|
35
|
-
| iso |
|
|
35
|
+
| iso | 171 | 217 | 0 | 0 | 388 |
|
|
36
36
|
| lists | 52 | 3 | 0 | 0 | 55 |
|
|
37
37
|
| modules | 2 | 0 | 0 | 0 | 2 |
|
|
38
38
|
| negation | 8 | 0 | 19 | 0 | 27 |
|
|
@@ -45,4 +45,29 @@ spelling therefore changes this report even when no corpus file is added or remo
|
|
|
45
45
|
| terms | 26 | 3 | 0 | 0 | 29 |
|
|
46
46
|
| unification | 18 | 0 | 0 | 0 | 18 |
|
|
47
47
|
| variables | 16 | 7 | 0 | 0 | 23 |
|
|
48
|
-
| **Total** | **
|
|
48
|
+
| **Total** | **493** | **272** | **19** | **21** | **805** |
|
|
49
|
+
|
|
50
|
+
## Known deviations
|
|
51
|
+
|
|
52
|
+
These are conscious departures from a standards clause, not open defects.
|
|
53
|
+
|
|
54
|
+
### phrase/2-3 terminal-sequence type errors
|
|
55
|
+
|
|
56
|
+
ISO/IEC TS 13211-3:2023, 8.18.1.4 c and d specify that a processor which
|
|
57
|
+
checks the terminal-sequence arguments of `phrase/2-3` shall report
|
|
58
|
+
`type_error(terminal_sequence, S)`. Error clause c is required for
|
|
59
|
+
`phrase/2` and implementation defined for `phrase/3`.
|
|
60
|
+
|
|
61
|
+
EyeProlog checks both arguments but reports `type_error(list, S)`.
|
|
62
|
+
Ulrich Neumerkel's `phrase_quad.pl` corpus, which the release gate fetches
|
|
63
|
+
live from TU Wien, accepts only `false` or `type_error(list, S)` for quads
|
|
64
|
+
41 and 42, so the published TS and that corpus cannot both be satisfied.
|
|
65
|
+
The corpus predates the TS. Changing the error term is a one-line edit in
|
|
66
|
+
`phraseSolutions` (`src/iso.js`); it additionally requires relaxing quads
|
|
67
|
+
41-42 of the Neumerkel gate.
|
|
68
|
+
|
|
69
|
+
Note that TS 13211-3, 7.14.7.2 gives `phrase({true}, nonlist, S)` as an
|
|
70
|
+
example which succeeds with `S = nonlist`, which is consistent with the TS
|
|
71
|
+
permitting `phrase/3` to omit the check entirely. EyeProlog prefers the
|
|
72
|
+
stricter diagnostic that the TS also explicitly allows.
|
|
73
|
+
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.5.
|
|
6
|
+
"version": "1.5.46",
|
|
7
7
|
"description": "EyeProlog turns facts and rules into answers and proofs.",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"main": "./index.js",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"files": [
|
|
29
29
|
"LICENSE.md",
|
|
30
30
|
"README.md",
|
|
31
|
+
"ACKNOWLEDGEMENTS.md",
|
|
31
32
|
"index.js",
|
|
32
33
|
"index.d.ts",
|
|
33
34
|
"playground.html",
|
package/src/cli.js
CHANGED
|
@@ -9,6 +9,15 @@ import { memoryStatistics } from './platform.js';
|
|
|
9
9
|
let engineModule = null;
|
|
10
10
|
let explanationModule = null;
|
|
11
11
|
|
|
12
|
+
// The usage text documents the input file as optional, and `--goal` is
|
|
13
|
+
// meaningful on its own. Only fall back to stdin when it is actually
|
|
14
|
+
// redirected: on an interactive terminal there is nothing to read, and
|
|
15
|
+
// blocking on it makes `eyeprolog --goal G` look like it does nothing.
|
|
16
|
+
// An explicit `-` argument still selects stdin in either case.
|
|
17
|
+
export function defaultsToStdin(fileCount, stdinIsTty) {
|
|
18
|
+
return fileCount === 0 && !stdinIsTty;
|
|
19
|
+
}
|
|
20
|
+
|
|
12
21
|
export async function main(argv) {
|
|
13
22
|
if (argv.length === 0) {
|
|
14
23
|
const engine = await loadEngine();
|
|
@@ -132,7 +141,7 @@ export async function main(argv) {
|
|
|
132
141
|
return;
|
|
133
142
|
}
|
|
134
143
|
|
|
135
|
-
if (options.files.length
|
|
144
|
+
if (defaultsToStdin(options.files.length, process.stdin.isTTY)) {
|
|
136
145
|
options.files.push('-');
|
|
137
146
|
}
|
|
138
147
|
|
|
@@ -157,6 +166,12 @@ export async function main(argv) {
|
|
|
157
166
|
}
|
|
158
167
|
}
|
|
159
168
|
|
|
169
|
+
if (sourceParts.length === 0) {
|
|
170
|
+
// No file and no redirected stdin: run the requested goals against an
|
|
171
|
+
// empty database rather than against nothing at all.
|
|
172
|
+
sourceParts.push({ text: '', filename: '<empty>' });
|
|
173
|
+
}
|
|
174
|
+
|
|
160
175
|
if (options.goals.length === 0 && !options.quads && options.verifyProof == null) {
|
|
161
176
|
for (const source of sourceParts) options.goals.push(...goalsFromSource(source.text));
|
|
162
177
|
}
|
package/src/iso.js
CHANGED
|
@@ -2688,7 +2688,7 @@ function validateControlCallable(term, culprit, env) {
|
|
|
2688
2688
|
// eagerly and reports the whole control term as the culprit.
|
|
2689
2689
|
if (argument.type === VAR) continue;
|
|
2690
2690
|
if (argument.type !== ATOM && argument.type !== COMPOUND) {
|
|
2691
|
-
throw new PrologError('type_error(callable)', culprit);
|
|
2691
|
+
throw new PrologError('type_error(callable)', copyResolved(culprit, env));
|
|
2692
2692
|
}
|
|
2693
2693
|
pending.push(argument);
|
|
2694
2694
|
}
|
|
@@ -3022,6 +3022,12 @@ function* phraseSolutions({ solver, goal, env }, state) {
|
|
|
3022
3022
|
const input = goal.args[1];
|
|
3023
3023
|
const requestedOutput = goal.arity === 2 ? emptyTerminalSequence() : goal.args[2];
|
|
3024
3024
|
validateDcgEmbeddedGoals(grammarBody, input, requestedOutput);
|
|
3025
|
+
// ISO/IEC TS 13211-3:2023, 8.18.1.4 c and d require
|
|
3026
|
+
// type_error(terminal_sequence, S) here. EyeProlog still reports
|
|
3027
|
+
// type_error(list, S): Ulrich Neumerkel's phrase_quad corpus, which the
|
|
3028
|
+
// release gate fetches live, accepts only `false` or type_error(list, S)
|
|
3029
|
+
// for quads 41-42, so the two cannot currently both be satisfied. See
|
|
3030
|
+
// conformance-report.md, "Known deviations".
|
|
3025
3031
|
if (!isListOrPartialList(input, env)) {
|
|
3026
3032
|
throw new PrologError('type_error(list)', deref(input, env));
|
|
3027
3033
|
}
|
package/src/program-analysis.js
CHANGED
|
@@ -445,6 +445,15 @@ export function directGoalDependencyKey(goal) {
|
|
|
445
445
|
return `${goal.name}/${goal.arity}`;
|
|
446
446
|
}
|
|
447
447
|
|
|
448
|
+
// ISO 13211-1, 7.1.6.3: `V1^V2^...^Goal` has the iterated goal Goal.
|
|
449
|
+
export function iteratedGoal(goal) {
|
|
450
|
+
let current = goal;
|
|
451
|
+
while (current?.type === COMPOUND && current.name === '^' && current.arity === 2) {
|
|
452
|
+
current = current.args[1];
|
|
453
|
+
}
|
|
454
|
+
return current;
|
|
455
|
+
}
|
|
456
|
+
|
|
448
457
|
export function collectGoalDependencies(goal, negated, traverseConditionals = false, wfs = false) {
|
|
449
458
|
if (goal.type === ATOM) return [{ key: `${goal.name}/0`, name: goal.name, arity: 0, module: goal.module, negative: negated, wfs }];
|
|
450
459
|
if (goal.type !== COMPOUND) return [];
|
|
@@ -478,6 +487,11 @@ export function collectGoalDependencies(goal, negated, traverseConditionals = fa
|
|
|
478
487
|
if ((goal.name === 'findall' || goal.name === 'sumall') && goal.arity === 3) {
|
|
479
488
|
return collectGoalDependencies(goal.args[1], negated, traverseConditionals, wfs);
|
|
480
489
|
}
|
|
490
|
+
if ((goal.name === 'bagof' || goal.name === 'setof') && goal.arity === 3) {
|
|
491
|
+
// The goal argument is an iterated-goal term (ISO 13211-1, 7.1.6.3), so
|
|
492
|
+
// strip its `^` qualifiers before recording the call it really performs.
|
|
493
|
+
return collectGoalDependencies(iteratedGoal(goal.args[1]), negated, traverseConditionals, wfs);
|
|
494
|
+
}
|
|
481
495
|
if (goal.name === 'countall' && goal.arity === 2) {
|
|
482
496
|
return collectGoalDependencies(goal.args[0], negated, traverseConditionals, wfs);
|
|
483
497
|
}
|
package/src/program.js
CHANGED
|
@@ -1136,6 +1136,18 @@ function collectForwardRulePremiseDependencies(term, collect, out) {
|
|
|
1136
1136
|
// intentionally looks through those wrappers; the autoloader must see both the
|
|
1137
1137
|
// wrapper and any statically visible nested calls so a bundled library can be
|
|
1138
1138
|
// loaded before execution reaches it.
|
|
1139
|
+
// ISO 13211-1, 7.1.6.3: the iterated goal of `V1^V2^...^Goal` is Goal. The
|
|
1140
|
+
// qualifiers only mark witness variables as existentially quantified, so every
|
|
1141
|
+
// static analysis of a bagof/3 or setof/3 goal argument has to look through
|
|
1142
|
+
// them before it reaches the callable term.
|
|
1143
|
+
function iteratedGoalTerm(goal) {
|
|
1144
|
+
let current = goal;
|
|
1145
|
+
while (current?.type === COMPOUND && current.name === '^' && current.arity === 2) {
|
|
1146
|
+
current = current.args[1];
|
|
1147
|
+
}
|
|
1148
|
+
return current;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1139
1151
|
function collectAutoloadGoalDependencies(goal, out = []) {
|
|
1140
1152
|
if (goal?.type === ATOM) {
|
|
1141
1153
|
out.push({ key: `${goal.name}/0`, name: goal.name, arity: 0, module: goal.module });
|
|
@@ -1167,7 +1179,12 @@ function collectAutoloadGoalDependencies(goal, out = []) {
|
|
|
1167
1179
|
collectAutoloadGoalDependencies(goal.args[0], out);
|
|
1168
1180
|
collectAutoloadGoalDependencies(goal.args[1], out);
|
|
1169
1181
|
} else if ((goal.name === 'findall' || goal.name === 'bagof' || goal.name === 'setof' || goal.name === 'sumall') && goal.arity === 3) {
|
|
1170
|
-
|
|
1182
|
+
// bagof/3 and setof/3 take an iterated-goal term (ISO 13211-1, 7.1.6.3):
|
|
1183
|
+
// any number of `Witness^Goal` qualifiers wrap the goal that is actually
|
|
1184
|
+
// executed. Autoloading has to see through them, otherwise
|
|
1185
|
+
// setof(X, Y^member(X,L), S) raises existence_error(member/2) while the
|
|
1186
|
+
// unqualified setof(X, member(X,L), S) resolves normally.
|
|
1187
|
+
collectAutoloadGoalDependencies(iteratedGoalTerm(goal.args[1]), out);
|
|
1171
1188
|
} else if (goal.name === 'countall' && goal.arity === 2) {
|
|
1172
1189
|
collectAutoloadGoalDependencies(goal.args[0], out);
|
|
1173
1190
|
} else if ((goal.name === 'aggregate_min' || goal.name === 'aggregate_max') && goal.arity === 5) {
|
|
@@ -140,7 +140,7 @@ Selected cases are adapted from the ISO and standard-core suites of Logtalk,
|
|
|
140
140
|
Scryer Prolog, Trealla Prolog, and SWI-Prolog. Their upstream identifiers and licenses
|
|
141
141
|
are recorded in [THIRD_PARTY.md](THIRD_PARTY.md).
|
|
142
142
|
|
|
143
|
-
The corpus has
|
|
143
|
+
The corpus has 388 cases in `iso/` and 805 file-based conformance cases in total. Of those, 11 cases in `stc/` are explicitly labelled working-draft review evidence rather than normative ISO claims. The separate vendored strict-reader WG17 matrix is a deterministic regression snapshot; the live Neumerkel gate discovers the current upstream inventory at run time; release/report checks separately verify the tracked `NEUMERKEL-LATEST.md`. The generated `conformance-report.md` records local corpus totals and links to that live evidence. Together with regression, documentation-sync, API, example, and book-example checks, `npm test` is the release gate.
|
|
144
144
|
|
|
145
145
|
## Updating expected output
|
|
146
146
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
% ISO/IEC 13211-1:1995, 7.1.6.3 (iterated-goal term) with 8.10.2/8.10.3.
|
|
2
|
+
% The goal argument of bagof/3 and setof/3 is `V1^...^Goal`, so every static
|
|
3
|
+
% analysis has to look through the `^` qualifiers to reach the callable term.
|
|
4
|
+
% Regression: the library autoloader used to stop at `^/2`, which made
|
|
5
|
+
% setof(X, Y^member(X,L), S) raise existence_error(member/2) while the
|
|
6
|
+
% unqualified setof(X, member(X,L), S) resolved normally.
|
|
7
|
+
answer(plain, S) :- setof(X, member(X, [b,a,b]), S).
|
|
8
|
+
answer(qualified, S) :- setof(X, Y^member(X, [b,a,b]), S).
|
|
9
|
+
answer(qualified_bag, S) :- bagof(X, Y^member(X, [b,a,b]), S).
|
|
10
|
+
answer(nested, S) :- setof(X, Y^Z^member(X-Y-Z, [1-a-p, 2-b-q]), S).
|
|
11
|
+
answer(witness_kept, S) :- setof(X-Y, Y^member(X-Y, [2-b, 1-a]), S).
|
|
12
|
+
%% goal: answer(X0, X1)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
% ISO/IEC TS 13211-3:2023, 7.14 - the semantics of each grammar body element,
|
|
2
|
+
% expressed as the phrase/3 identities the TS states for them.
|
|
3
|
+
digit(D) --> [D], { member(D, [a,b]) }.
|
|
4
|
+
|
|
5
|
+
% 7.14.1 phrase([], S0, S) is true iff S0 = S.
|
|
6
|
+
answer(empty_sequence, S) :- phrase([], [a,b], S).
|
|
7
|
+
|
|
8
|
+
% 7.14.3 phrase((A,B), S0, S) iff phrase(A, S0, S1), phrase(B, S1, S).
|
|
9
|
+
answer(concatenation, S) :- phrase(([a], [b]), [a,b,c], S).
|
|
10
|
+
|
|
11
|
+
% 7.14.4 phrase((A;B), S0, S) iff ( phrase(A,S0,S) ; phrase(B,S0,S) ).
|
|
12
|
+
answer(alternative, S) :- phrase(([a] ; [b]), [b,c], S).
|
|
13
|
+
|
|
14
|
+
% 7.14.5.3 the if-then-else examples given verbatim in the TS.
|
|
15
|
+
answer(ite_13, yes) :- phrase((("1" | "2") -> "3" ; "4"), "13").
|
|
16
|
+
answer(ite_23, yes) :- phrase((("1" | "2") -> "3" ; "4"), "23").
|
|
17
|
+
answer(ite_4, yes) :- phrase((("1" | "2") -> "3" ; "4"), "4").
|
|
18
|
+
answer(ite_single, no) :- \+ phrase((("1" | "2") -> "3" ; "4"), [_]).
|
|
19
|
+
|
|
20
|
+
% 7.14.7 phrase({G}, S0, S) is true iff ( G, S0 = S ).
|
|
21
|
+
answer(grammar_body_goal, S) :- phrase({true}, [a], S).
|
|
22
|
+
|
|
23
|
+
% 7.14.9 phrase(phrase(NT), S0, S) iff phrase(NT, S0, S).
|
|
24
|
+
answer(phrase_nonterminal, S) :- phrase(phrase([a]), [a,b], S).
|
|
25
|
+
|
|
26
|
+
% 7.14.10 phrase(!, S0, S) is true iff S0 = S.
|
|
27
|
+
answer(cut_identity, S) :- phrase(!, [a,b], S).
|
|
28
|
+
|
|
29
|
+
% 7.14.11 phrase(\+ GRBody, S0, S) iff ( \+ phrase(GRBody, S0, _), S0 = S ).
|
|
30
|
+
answer(negation_identity, S) :- phrase(\+ [a], [b,c], S).
|
|
31
|
+
answer(negation_blocks, no) :- \+ phrase(\+ [a], [a], _).
|
|
32
|
+
|
|
33
|
+
% 7.14.2 ('.')//2 separates a terminal from the rest of the sequence.
|
|
34
|
+
answer(terminal_split, D) :- phrase(digit(D), [b]).
|
|
35
|
+
%% goal: answer(X0, X1)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
% ISO/IEC TS 13211-3:2023, 8.18.1.5 - the worked phrase/2 and phrase/3
|
|
2
|
+
% examples, using the grammar given verbatim in that subclause.
|
|
3
|
+
determiner --> [the].
|
|
4
|
+
determiner --> [a].
|
|
5
|
+
|
|
6
|
+
noun --> [boy].
|
|
7
|
+
noun --> [girl].
|
|
8
|
+
|
|
9
|
+
verb --> [likes].
|
|
10
|
+
verb --> [scares].
|
|
11
|
+
|
|
12
|
+
noun_phrase --> determiner, noun.
|
|
13
|
+
noun_phrase --> noun.
|
|
14
|
+
|
|
15
|
+
verb_phrase --> verb.
|
|
16
|
+
verb_phrase --> verb, noun_phrase.
|
|
17
|
+
|
|
18
|
+
sentence --> noun_phrase, verb_phrase.
|
|
19
|
+
|
|
20
|
+
% ?- phrase([the], [the]). true.
|
|
21
|
+
answer(terminal_only, yes) :- phrase([the], [the]).
|
|
22
|
+
|
|
23
|
+
% ?- phrase(sentence, [the, girl, likes, the, boy]). true ; false.
|
|
24
|
+
answer(full_sentence, yes) :- phrase(sentence, [the, girl, likes, the, boy]).
|
|
25
|
+
|
|
26
|
+
% ?- phrase(sentence, [the, girl, likes, the, boy, today]). false.
|
|
27
|
+
answer(trailing_word, no) :- \+ phrase(sentence, [the, girl, likes, the, boy, today]).
|
|
28
|
+
|
|
29
|
+
% ?- phrase(sentence, [the, girl, likes]). true ; false.
|
|
30
|
+
answer(intransitive, yes) :- phrase(sentence, [the, girl, likes]).
|
|
31
|
+
|
|
32
|
+
% ?- phrase(noun_phrase, [the, girl, scares, the, boy], Rest).
|
|
33
|
+
% Rest = [scares, the, boy] ; false.
|
|
34
|
+
answer(remainder, Rest) :- phrase(noun_phrase, [the, girl, scares, the, boy], Rest).
|
|
35
|
+
|
|
36
|
+
% ?- phrase(sentence, Sentence). Sentence = [the, boy, likes] ; ... .
|
|
37
|
+
answer(first_generated, Sentence) :-
|
|
38
|
+
findall(S, phrase(sentence, S), All),
|
|
39
|
+
first_four(All, Sentence).
|
|
40
|
+
|
|
41
|
+
first_four([A,B,C,D|_], [A,B,C,D]).
|
|
42
|
+
%% goal: answer(X0, X1)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
answer(empty_sequence, "ab").
|
|
2
|
+
answer(concatenation, "c").
|
|
3
|
+
answer(alternative, "c").
|
|
4
|
+
answer(ite_13, yes).
|
|
5
|
+
answer(ite_23, yes).
|
|
6
|
+
answer(ite_4, yes).
|
|
7
|
+
answer(ite_single, no).
|
|
8
|
+
answer(grammar_body_goal, "a").
|
|
9
|
+
answer(phrase_nonterminal, "b").
|
|
10
|
+
answer(cut_identity, "ab").
|
|
11
|
+
answer(negation_identity, "bc").
|
|
12
|
+
answer(negation_blocks, no).
|
|
13
|
+
answer(terminal_split, b).
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
answer(terminal_only, yes).
|
|
2
|
+
answer(full_sentence, yes).
|
|
3
|
+
answer(trailing_word, no).
|
|
4
|
+
answer(intransitive, yes).
|
|
5
|
+
answer(remainder, [scares, the, boy]).
|
|
6
|
+
answer(first_generated, [[the, boy, likes], [the, boy, scares], [the, boy, likes, the, boy], [the, boy, likes, the, girl]]).
|
|
@@ -119,6 +119,8 @@ export function formatConformanceReport(report = buildConformanceReport()) {
|
|
|
119
119
|
}
|
|
120
120
|
lines.push(`| **Total** | **${report.total.positive}** | **${report.total.errors}** | **${report.total.warnings}** | **${report.total.proofs}** | **${report.total.total}** |`);
|
|
121
121
|
|
|
122
|
+
lines.push(...knownDeviationSection());
|
|
123
|
+
|
|
122
124
|
if (report.corpusIssues.length > 0) {
|
|
123
125
|
lines.push('', '## Corpus issues', '');
|
|
124
126
|
for (const issue of report.corpusIssues) lines.push(`- ${issue}`);
|
|
@@ -131,6 +133,39 @@ export function formatConformanceReport(report = buildConformanceReport()) {
|
|
|
131
133
|
return `${lines.join('\n')}\n`;
|
|
132
134
|
}
|
|
133
135
|
|
|
136
|
+
// Deviations that are known, deliberate, and traceable to a specific clause.
|
|
137
|
+
// Keeping them in the generated report means they survive regeneration instead
|
|
138
|
+
// of living only in a commit message.
|
|
139
|
+
function knownDeviationSection() {
|
|
140
|
+
return [
|
|
141
|
+
'',
|
|
142
|
+
'## Known deviations',
|
|
143
|
+
'',
|
|
144
|
+
'These are conscious departures from a standards clause, not open defects.',
|
|
145
|
+
'',
|
|
146
|
+
'### phrase/2-3 terminal-sequence type errors',
|
|
147
|
+
'',
|
|
148
|
+
'ISO/IEC TS 13211-3:2023, 8.18.1.4 c and d specify that a processor which',
|
|
149
|
+
'checks the terminal-sequence arguments of `phrase/2-3` shall report',
|
|
150
|
+
'`type_error(terminal_sequence, S)`. Error clause c is required for',
|
|
151
|
+
'`phrase/2` and implementation defined for `phrase/3`.',
|
|
152
|
+
'',
|
|
153
|
+
'EyeProlog checks both arguments but reports `type_error(list, S)`.',
|
|
154
|
+
'Ulrich Neumerkel\'s `phrase_quad.pl` corpus, which the release gate fetches',
|
|
155
|
+
'live from TU Wien, accepts only `false` or `type_error(list, S)` for quads',
|
|
156
|
+
'41 and 42, so the published TS and that corpus cannot both be satisfied.',
|
|
157
|
+
'The corpus predates the TS. Changing the error term is a one-line edit in',
|
|
158
|
+
'`phraseSolutions` (`src/iso.js`); it additionally requires relaxing quads',
|
|
159
|
+
'41-42 of the Neumerkel gate.',
|
|
160
|
+
'',
|
|
161
|
+
'Note that TS 13211-3, 7.14.7.2 gives `phrase({true}, nonlist, S)` as an',
|
|
162
|
+
'example which succeeds with `S = nonlist`, which is consistent with the TS',
|
|
163
|
+
'permitting `phrase/3` to omit the check entirely. EyeProlog prefers the',
|
|
164
|
+
'stricter diagnostic that the TS also explicitly allows.',
|
|
165
|
+
'',
|
|
166
|
+
];
|
|
167
|
+
}
|
|
168
|
+
|
|
134
169
|
function executeGate(name, runSuite) {
|
|
135
170
|
const failures = [];
|
|
136
171
|
const reporter = {
|
package/test/run-iso-strict.mjs
CHANGED
|
@@ -72,6 +72,29 @@ export function runIsoStrict(reporter = new TestReporter()) {
|
|
|
72
72
|
'type_error(float)', 'negative integer exponent correction');
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
+
reporter.test('round-trips ISO floats through number_chars/2 and quoted write_term/2', () => {
|
|
76
|
+
for (const literal of ['1.0000000000000001', '0.10000000000000002']) {
|
|
77
|
+
const converted = run('', {
|
|
78
|
+
isoStrict: true,
|
|
79
|
+
goal: `N=${literal}, number_chars(N,Chars), number_chars(M,Chars), M == N`,
|
|
80
|
+
});
|
|
81
|
+
equal(converted.stats.completed_goal_lists, 1, `number_chars/2 float round-trip ${literal}`);
|
|
82
|
+
|
|
83
|
+
let written = '';
|
|
84
|
+
run('', {
|
|
85
|
+
isoStrict: true,
|
|
86
|
+
goal: `write_term(${literal},[quoted(true)])`,
|
|
87
|
+
ioOptions: { write: (text) => { written += text; } },
|
|
88
|
+
});
|
|
89
|
+
const reread = run('', {
|
|
90
|
+
isoStrict: true,
|
|
91
|
+
goal: `read_term(X,[]), X == ${literal}`,
|
|
92
|
+
ioOptions: { input: `${written}.` },
|
|
93
|
+
});
|
|
94
|
+
equal(reread.stats.completed_goal_lists, 1, `quoted write_term/2 float round-trip ${literal}`);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
75
98
|
reporter.test('keeps Corrigendum 2 core predicates and excludes Part 3 phrase', () => {
|
|
76
99
|
const registry = createStrictIsoRegistry();
|
|
77
100
|
equal(Boolean(registry.get('subsumes_term', 2)), true, 'subsumes_term/2');
|
|
@@ -1250,6 +1273,16 @@ export function runIsoStrict(reporter = new TestReporter()) {
|
|
|
1250
1273
|
'if-then-else commits after a successful condition');
|
|
1251
1274
|
equal(capture(() => run('', { isoStrict: true, goal: 'call((write(a),3))' })).formal,
|
|
1252
1275
|
'type_error(callable)', 'nested non-callable control term is rejected');
|
|
1276
|
+
let issue93Output = '';
|
|
1277
|
+
const issue93Error = capture(() => run('', {
|
|
1278
|
+
isoStrict: true,
|
|
1279
|
+
goal: 'X=3,Y=(write(X),X),call(Y)',
|
|
1280
|
+
ioOptions: { write: (chunk) => { issue93Output += chunk; } },
|
|
1281
|
+
}));
|
|
1282
|
+
equal(issue93Error.formal, 'type_error(callable)', 'issue #93 raises the ISO callability error');
|
|
1283
|
+
equal(issue93Error.message, 'error(type_error(callable), (write(3), 3))',
|
|
1284
|
+
'issue #93 reports the fully instantiated control-term culprit');
|
|
1285
|
+
equal(issue93Output, '', 'issue #93 validates the complete body before executing write/1');
|
|
1253
1286
|
equal(run('', {
|
|
1254
1287
|
isoStrict: true,
|
|
1255
1288
|
goal: 'catch(X,error(instantiation_error,_),true)',
|
package/test/run-regression.mjs
CHANGED
|
@@ -54,6 +54,7 @@ import {
|
|
|
54
54
|
import { ISO_OPERATOR_DEFINITIONS, parseGoalText, parseNumberTokenText, tryParseClausesFastInto } from '../src/parser.js';
|
|
55
55
|
import { PrologError, formalErrorTerm } from '../src/iso.js';
|
|
56
56
|
import { compareTerms } from '../src/term.js';
|
|
57
|
+
import { defaultsToStdin } from '../src/cli.js';
|
|
57
58
|
import { formatTermForWrite } from '../src/write.js';
|
|
58
59
|
import { selectClauseCandidates } from '../src/program.js';
|
|
59
60
|
import {
|
|
@@ -1596,6 +1597,25 @@ c4 ?- call((!;1)).
|
|
|
1596
1597
|
assertEqual(result.stdout, '250000', 'number_chars/read cross-check count');
|
|
1597
1598
|
},
|
|
1598
1599
|
},
|
|
1600
|
+
{
|
|
1601
|
+
name: '--goal without an input file does not block on an interactive stdin',
|
|
1602
|
+
run: () => {
|
|
1603
|
+
// Usage documents the input file as optional. The CLI used to append
|
|
1604
|
+
// '-' unconditionally, so `eyeprolog --goal G` on a terminal waited for
|
|
1605
|
+
// an EOF that never came and appeared to do nothing. stdin is only a
|
|
1606
|
+
// default source when it is actually redirected.
|
|
1607
|
+
assertEqual(defaultsToStdin(0, true), false, 'tty stdin, no file');
|
|
1608
|
+
assertEqual(defaultsToStdin(0, false), true, 'redirected stdin, no file');
|
|
1609
|
+
assertEqual(defaultsToStdin(0, undefined), true, 'non-tty stdin reported as undefined');
|
|
1610
|
+
assertEqual(defaultsToStdin(1, true), false, 'tty stdin, one file');
|
|
1611
|
+
assertEqual(defaultsToStdin(1, false), false, 'redirected stdin, one file');
|
|
1612
|
+
|
|
1613
|
+
// An empty database is still a usable program for --goal.
|
|
1614
|
+
const result = runCli(['--goal', 'X = 1, write(X), nl'], { input: '' });
|
|
1615
|
+
assertEqual(result.status, 0, 'exit status');
|
|
1616
|
+
assertIncludes(result.stdout, '1\n', 'goal output without an input file');
|
|
1617
|
+
},
|
|
1618
|
+
},
|
|
1599
1619
|
{
|
|
1600
1620
|
name: 'float literals canonicalize to their represented value and mix with generated floats (issue #92)',
|
|
1601
1621
|
run: () => {
|
|
@@ -1618,6 +1638,20 @@ c4 ?- call((!;1)).
|
|
|
1618
1638
|
assertEqual(result.stderr, '', 'stderr');
|
|
1619
1639
|
},
|
|
1620
1640
|
},
|
|
1641
|
+
{
|
|
1642
|
+
name: 'call/1 reports the instantiated complete control-term culprit (issue #93)',
|
|
1643
|
+
run: () => {
|
|
1644
|
+
const result = runCli([], {
|
|
1645
|
+
input: 'X = 3, Y = (write(X), X), call(Y).\nhalt.\n',
|
|
1646
|
+
});
|
|
1647
|
+
assertEqual(result.status, 0, 'exit status');
|
|
1648
|
+
assertEqual(result.stdout,
|
|
1649
|
+
'?- error(type_error(callable, (write(3), 3)), eyeprolog).\n' +
|
|
1650
|
+
'?- ',
|
|
1651
|
+
'fully instantiated call/1 culprit');
|
|
1652
|
+
assertEqual(result.stderr, '', 'stderr');
|
|
1653
|
+
},
|
|
1654
|
+
},
|
|
1621
1655
|
{
|
|
1622
1656
|
name: 'number_chars and number_codes keep exponent floats syntactically readable (issue #50)',
|
|
1623
1657
|
run: () => {
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -10353,7 +10353,7 @@ must preserve the same observable outcome. Additional normal-mode syntax may
|
|
|
10353
10353
|
accept texts outside the strict grammar, but it may not reinterpret an accepted
|
|
10354
10354
|
standard case.
|
|
10355
10355
|
|
|
10356
|
-
The file-based conformance corpus contains
|
|
10356
|
+
The file-based conformance corpus contains 805 cases, including 388 focused ISO cases derived from the success, failure, mode, and error behavior in ISO/IEC 13211-1 clauses 7 and 8, Part 2 modules, and Part 3 grammar rules.
|
|
10357
10357
|
Separate exact-output suites check 210 normal examples and 61 proof examples; all executable chapter programs are parsed and their declared goals are executed. The eight-case
|
|
10358
10358
|
playground contract suite imports the production worker, sends real reasoning
|
|
10359
10359
|
requests through its message protocol, and crawls the served module graph for
|