eyeprolog 1.3.30 → 1.3.32
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/README.md +4 -2
- package/package.json +5 -1
- package/src/cli.js +10 -42
- package/src/execute.js +56 -0
- package/src/index.js +9 -53
- package/src/solver.js +1 -1
- package/src/standard-library.js +1 -1
- package/test/conformance/ISO-COMPLIANCE.md +4 -4
- package/test/conformance/ISO-IMPLEMENTATION-DEFINED.md +146 -0
- package/test/conformance/ISO-MATRIX.md +7 -6
- package/test/run-all.mjs +3 -9
- package/test/run-book-examples.mjs +2 -8
- package/test/run-conformance-report.mjs +2 -14
- package/test/run-conformance.mjs +4 -38
- package/test/run-examples.mjs +2 -8
- package/test/run-iso-strict.mjs +2 -8
- package/test/run-openrulebench.mjs +14 -44
- package/test/run-playground.mjs +9 -26
- package/test/run-regression.mjs +62 -44
- package/test/run-wg17.mjs +2 -9
- package/test/test-style.mjs +37 -0
- package/test/test-support.mjs +36 -0
- package/the-art-of-eyeprolog.md +8 -4
package/README.md
CHANGED
|
@@ -182,6 +182,8 @@ modules, DCGs, quads, libraries, proofs, and the other documented extensions.
|
|
|
182
182
|
|
|
183
183
|
The auditable processor-requirement checklist lives in
|
|
184
184
|
[`test/conformance/ISO-COMPLIANCE.md`](test/conformance/ISO-COMPLIANCE.md).
|
|
185
|
+
The ISO 5.4 implementation-defined/implementation-specific decision index is
|
|
186
|
+
[`test/conformance/ISO-IMPLEMENTATION-DEFINED.md`](test/conformance/ISO-IMPLEMENTATION-DEFINED.md).
|
|
185
187
|
The separate [WG17 syntax ledger](test/conformance/WG17-SYNTAX-STATUS.md)
|
|
186
188
|
records executable dispositions for the vendored active upstream WG17 syntax
|
|
187
189
|
cases and runs as part of `npm test`. Reviewed cases can pin exact outcomes;
|
|
@@ -211,7 +213,7 @@ left-associative operators, so state is handed repeatedly from one nonterminal
|
|
|
211
213
|
to the next rather than hidden in host code.
|
|
212
214
|
|
|
213
215
|
Deep finite DCG traversal is kept relational but does not have to consume one
|
|
214
|
-
general solver frame per token. In particular, the interoperable
|
|
216
|
+
general solver frame per token. In particular, the interoperable `... //0`
|
|
215
217
|
helper from `library(iso_ext)` can scan a finite compact list iteratively, and a
|
|
216
218
|
following grammar that is statically known to leave the DCG state unchanged can
|
|
217
219
|
be continued without rebuilding a full clause-resolution frame for every
|
|
@@ -242,7 +244,7 @@ headroom, so an exhausted finite heap is reported as a catchable
|
|
|
242
244
|
`resource_error(memory)` instead of degenerating into quadratic list checks.
|
|
243
245
|
|
|
244
246
|
`library(iso_ext)` is also accepted as a common interop module name.
|
|
245
|
-
EyeProlog exports `call_nth/2`, `time/1`, and the DCG helper
|
|
247
|
+
EyeProlog exports `call_nth/2`, `time/1`, and the DCG helper `... //0` there.
|
|
246
248
|
The latter describes an arbitrary number of input elements and supports the
|
|
247
249
|
nonterminal hand-off benchmark discussed in issue #49. These common predicates
|
|
248
250
|
may be imported explicitly, while source/CLI/API dependency loading can resolve
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.3.
|
|
6
|
+
"version": "1.3.32",
|
|
7
7
|
"description": "EyeProlog turns facts and rules into answers and proofs.",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"main": "./index.js",
|
|
@@ -55,6 +55,10 @@
|
|
|
55
55
|
"test:wg17": "node test/run-wg17.mjs",
|
|
56
56
|
"test:examples": "node test/run-examples.mjs",
|
|
57
57
|
"test:regression": "node test/run-regression.mjs",
|
|
58
|
+
"test:regression:api": "node test/run-regression.mjs api",
|
|
59
|
+
"test:regression:core": "node test/run-regression.mjs regression",
|
|
60
|
+
"test:regression:docs": "node test/run-regression.mjs docs",
|
|
61
|
+
"test:regression:white-box": "node test/run-regression.mjs white-box",
|
|
58
62
|
"test:playground": "node test/run-playground.mjs",
|
|
59
63
|
"wg17:upgrade": "node tools/upgrade-wg17.mjs",
|
|
60
64
|
"report:wg17": "node tools/report-wg17-syntax-coverage.mjs",
|
package/src/cli.js
CHANGED
|
@@ -173,7 +173,7 @@ export async function main(argv) {
|
|
|
173
173
|
|
|
174
174
|
async function loadEngine() {
|
|
175
175
|
if (engineModule == null) {
|
|
176
|
-
const [term, parser, program, solver, iso, library, write, quads] = await Promise.all([
|
|
176
|
+
const [term, parser, program, solver, iso, library, write, quads, execute] = await Promise.all([
|
|
177
177
|
import('./term.js'),
|
|
178
178
|
import('./parser.js'),
|
|
179
179
|
import('./program.js'),
|
|
@@ -182,8 +182,9 @@ async function loadEngine() {
|
|
|
182
182
|
import('./standard-library.js'),
|
|
183
183
|
import('./write.js'),
|
|
184
184
|
import('./quads.js'),
|
|
185
|
+
import('./execute.js'),
|
|
185
186
|
]);
|
|
186
|
-
engineModule = { ...term, ...parser, ...program, ...solver, ...iso, ...library, ...write, ...quads };
|
|
187
|
+
engineModule = { ...term, ...parser, ...program, ...solver, ...iso, ...library, ...write, ...quads, ...execute };
|
|
187
188
|
}
|
|
188
189
|
return engineModule;
|
|
189
190
|
}
|
|
@@ -201,49 +202,16 @@ async function runDefault(engine, program, options) {
|
|
|
201
202
|
ioOptions: { write: (text) => process.stdout.write(String(text)) },
|
|
202
203
|
});
|
|
203
204
|
program = solver.program;
|
|
204
|
-
const goals = options.goals
|
|
205
|
-
const goal = engine.parseGoalText(text, {
|
|
206
|
-
doubleQuotes: solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
|
|
207
|
-
operatorDefinitions: [...program.operators.values()],
|
|
208
|
-
isoStrict: options.isoStrict,
|
|
209
|
-
});
|
|
210
|
-
if (goal.type === 'var') throw new engine.PrologError('instantiation_error');
|
|
211
|
-
if (goal.type !== 'atom' && goal.type !== 'compound') throw new engine.PrologError('type_error(callable)', goal);
|
|
212
|
-
return goal;
|
|
213
|
-
});
|
|
214
|
-
const queriedKeys = new Set(goals.map((goal) => `${goal.name}/${goal.arity}`));
|
|
215
|
-
const writeOptions = {
|
|
216
|
-
doubleQuotes: solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
|
|
217
|
-
operators: [...program.operators.values()],
|
|
218
|
-
quoted: true,
|
|
219
|
-
};
|
|
220
|
-
const facts = program.sourceFactLines(queriedKeys, writeOptions);
|
|
221
|
-
const lines = new Set();
|
|
205
|
+
const goals = engine.normalizeGoals(options.goals, solver);
|
|
222
206
|
const explanation = options.proof ? await loadExplanation() : null;
|
|
223
207
|
try {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
solver.solutionsSeen = 0;
|
|
227
|
-
for (const env of solver.solve([goal], new engine.Env(), 0)) {
|
|
228
|
-
if (!engine.termIsGround(goal, env)) continue;
|
|
229
|
-
|
|
230
|
-
const currentWriteOptions = {
|
|
231
|
-
doubleQuotes: solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
|
|
232
|
-
operators: [...program.operators.values()],
|
|
233
|
-
quoted: true,
|
|
234
|
-
};
|
|
235
|
-
const line = `${engine.formatTermForWrite(goal, env, currentWriteOptions)}.\n`;
|
|
236
|
-
if (facts.has(line) || lines.has(line)) continue;
|
|
237
|
-
|
|
238
|
-
lines.add(line);
|
|
239
|
-
|
|
208
|
+
const { haltCode } = engine.executeGoals(program, solver, goals, {
|
|
209
|
+
onAnswer: (line, resolved) => {
|
|
240
210
|
process.stdout.write(line);
|
|
241
|
-
if (options.proof) writeExplanation(explanation, program,
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if (error?.name !== 'HaltSignal') throw error;
|
|
246
|
-
process.exitCode = error.code;
|
|
211
|
+
if (options.proof) writeExplanation(explanation, program, resolved, registry);
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
if (haltCode != null) process.exitCode = haltCode;
|
|
247
215
|
} finally {
|
|
248
216
|
if (options.stats) printStats(solver.stats);
|
|
249
217
|
}
|
package/src/execute.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Shared goal preparation and execution for the CLI and embedding API.
|
|
2
|
+
import { ATOM, COMPOUND, VAR, Env, copyResolved, termIsGround } from './term.js';
|
|
3
|
+
import { parseGoalText } from './parser.js';
|
|
4
|
+
import { HaltSignal, PrologError } from './iso.js';
|
|
5
|
+
import { formatTermForWrite } from './write.js';
|
|
6
|
+
|
|
7
|
+
export function normalizeGoals(requestedGoals, solver) {
|
|
8
|
+
return requestedGoals.map((requestedGoal) => {
|
|
9
|
+
const goal = typeof requestedGoal === 'string'
|
|
10
|
+
? parseGoalText(requestedGoal, {
|
|
11
|
+
doubleQuotes: solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
|
|
12
|
+
operatorDefinitions: [...solver.program.operators.values()],
|
|
13
|
+
isoStrict: solver.isoStrict,
|
|
14
|
+
})
|
|
15
|
+
: requestedGoal;
|
|
16
|
+
if (goal.type === VAR) throw new PrologError('instantiation_error');
|
|
17
|
+
if (goal.type !== ATOM && goal.type !== COMPOUND) throw new PrologError('type_error(callable)', goal);
|
|
18
|
+
return goal;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function executeGoals(program, solver, goals, { onAnswer = () => {} } = {}) {
|
|
23
|
+
const initialWriteOptions = currentWriteOptions(program, solver);
|
|
24
|
+
const queriedKeys = new Set(goals.map((goal) => `${goal.name}/${goal.arity}`));
|
|
25
|
+
const facts = program.sourceFactLines(queriedKeys, initialWriteOptions);
|
|
26
|
+
const seen = new Set();
|
|
27
|
+
let haltCode = null;
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
solver.runInitializations();
|
|
31
|
+
for (const goal of goals) {
|
|
32
|
+
solver.solutionsSeen = 0;
|
|
33
|
+
for (const env of solver.solve([goal], new Env(), 0)) {
|
|
34
|
+
if (!termIsGround(goal, env)) continue;
|
|
35
|
+
const resolved = copyResolved(goal, env);
|
|
36
|
+
const line = `${formatTermForWrite(resolved, new Env(), currentWriteOptions(program, solver))}.\n`;
|
|
37
|
+
if (facts.has(line) || seen.has(line)) continue;
|
|
38
|
+
seen.add(line);
|
|
39
|
+
onAnswer(line, resolved);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if (!(error instanceof HaltSignal)) throw error;
|
|
44
|
+
haltCode = error.code;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return { haltCode };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function currentWriteOptions(program, solver) {
|
|
51
|
+
return {
|
|
52
|
+
doubleQuotes: solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
|
|
53
|
+
operators: [...program.operators.values()],
|
|
54
|
+
quoted: true,
|
|
55
|
+
};
|
|
56
|
+
}
|
package/src/index.js
CHANGED
|
@@ -27,14 +27,12 @@ export {
|
|
|
27
27
|
export { StreamManager } from './io.js';
|
|
28
28
|
export { runQuads } from './quads.js';
|
|
29
29
|
|
|
30
|
-
import { ATOM, COMPOUND, VAR, Env, copyResolved, termIsGround } from './term.js';
|
|
31
30
|
import { Program, autoloadProgramGoals } from './program.js';
|
|
32
31
|
import { Solver } from './solver.js';
|
|
33
32
|
import { whyNoProof, whyProof } from './explain.js';
|
|
34
|
-
import {
|
|
33
|
+
import { getStrictIsoRegistry } from './iso.js';
|
|
35
34
|
import { getEyePrologRegistry } from './standard-library.js';
|
|
36
|
-
import {
|
|
37
|
-
import { formatTermForWrite } from './write.js';
|
|
35
|
+
import { executeGoals, normalizeGoals } from './execute.js';
|
|
38
36
|
|
|
39
37
|
export function run(source, options = {}) {
|
|
40
38
|
const includeWhy = options.proof === true || options.why === true || options.explain === true;
|
|
@@ -67,56 +65,14 @@ export function run(source, options = {}) {
|
|
|
67
65
|
},
|
|
68
66
|
});
|
|
69
67
|
program = solver.program;
|
|
70
|
-
const goals = normalizeGoals(
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const queriedKeys = new Set(goals.map((goal) => `${goal.name}/${goal.arity}`));
|
|
77
|
-
const facts = program.sourceFactLines(queriedKeys, writeOptions);
|
|
78
|
-
const seen = new Set();
|
|
79
|
-
let haltCode = null;
|
|
80
|
-
try {
|
|
81
|
-
solver.runInitializations();
|
|
82
|
-
for (const goal of goals) {
|
|
83
|
-
solver.solutionsSeen = 0;
|
|
84
|
-
for (const env of solver.solve([goal], new Env(), 0)) {
|
|
85
|
-
const resolved = copyResolved(goal, env);
|
|
86
|
-
if (!termIsGround(resolved)) continue;
|
|
87
|
-
const currentWriteOptions = {
|
|
88
|
-
doubleQuotes: solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
|
|
89
|
-
operators: [...program.operators.values()],
|
|
90
|
-
quoted: true,
|
|
91
|
-
};
|
|
92
|
-
const line = `${formatTermForWrite(resolved, new Env(), currentWriteOptions)}.\n`;
|
|
93
|
-
if (facts.has(line) || seen.has(line)) continue;
|
|
94
|
-
seen.add(line);
|
|
95
|
-
output.push(line);
|
|
96
|
-
if (includeWhy) appendExplanation(output, program, resolved, runOptions.registry);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
} catch (error) {
|
|
100
|
-
if (!(error instanceof HaltSignal)) throw error;
|
|
101
|
-
haltCode = error.code;
|
|
102
|
-
}
|
|
103
|
-
return { stdout: output.join(''), stats: solver.stats, haltCode };
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function normalizeGoals(options, solver) {
|
|
107
|
-
const requested = options.goals ?? (options.goal == null ? [] : [options.goal]);
|
|
108
|
-
return requested.map((requestedGoal) => {
|
|
109
|
-
const goal = typeof requestedGoal === 'string'
|
|
110
|
-
? parseGoalText(requestedGoal, {
|
|
111
|
-
doubleQuotes: solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
|
|
112
|
-
operatorDefinitions: [...solver.program.operators.values()],
|
|
113
|
-
isoStrict: solver.isoStrict,
|
|
114
|
-
})
|
|
115
|
-
: requestedGoal;
|
|
116
|
-
if (goal.type === VAR) throw new PrologError('instantiation_error');
|
|
117
|
-
if (goal.type !== ATOM && goal.type !== COMPOUND) throw new PrologError('type_error(callable)', goal);
|
|
118
|
-
return goal;
|
|
68
|
+
const goals = normalizeGoals(requestedGoals, solver);
|
|
69
|
+
const { haltCode } = executeGoals(program, solver, goals, {
|
|
70
|
+
onAnswer: (line, resolved) => {
|
|
71
|
+
output.push(line);
|
|
72
|
+
if (includeWhy) appendExplanation(output, program, resolved, runOptions.registry);
|
|
73
|
+
},
|
|
119
74
|
});
|
|
75
|
+
return { stdout: output.join(''), stats: solver.stats, haltCode };
|
|
120
76
|
}
|
|
121
77
|
|
|
122
78
|
function appendExplanation(output, program, resolved, registry) {
|
package/src/solver.js
CHANGED
|
@@ -1333,7 +1333,7 @@ function bundledEllipsisPlan(solver, group, goal, rest, env) {
|
|
|
1333
1333
|
}
|
|
1334
1334
|
|
|
1335
1335
|
// length/2 and other native constructors can leave a known finite list as a
|
|
1336
|
-
// compact spine. The ordinary
|
|
1336
|
+
// compact spine. The ordinary ... //0 relation simply enumerates every
|
|
1337
1337
|
// suffix of such a list; doing that directly avoids clause freshening and
|
|
1338
1338
|
// recursive solver depth for every consumed element. Non-compact and open
|
|
1339
1339
|
// list cases retain the ordinary Prolog definition.
|
package/src/standard-library.js
CHANGED
|
@@ -118,7 +118,7 @@ export const eyePrologInteropAutoload = Object.freeze({
|
|
|
118
118
|
// allowing Trealla-style unqualified source to use the same autoload entry.
|
|
119
119
|
'call_nth/2': 'iso_ext',
|
|
120
120
|
// Trealla exposes time/1 as a meta timing predicate and library(iso_ext)
|
|
121
|
-
// supplies
|
|
121
|
+
// supplies ... //0. Autoload both so UWN's DCG hand-off benchmark runs
|
|
122
122
|
// unchanged while their implementations remain outside the ISO core.
|
|
123
123
|
'time/1': 'iso_ext',
|
|
124
124
|
'.../2': 'iso_ext',
|
|
@@ -21,9 +21,9 @@ error-ordering alternative to an individual executable assertion.
|
|
|
21
21
|
| 5.1(a) prepare conforming Prolog text | audit | Clause 6 parser/tokenizer coverage, directive coverage, syntax-error corpus, and the [complete vendored WG17 syntax matrix](WG17-SYNTAX-STATUS.md). Wider shall-by-shall text-processing audit remains open. |
|
|
22
22
|
| 5.1(b) execute conforming Prolog goals | audit | Clause 7-9 conformance corpus plus regression/API/example gates. A normative goal-semantics ledger is still being expanded. |
|
|
23
23
|
| 5.1(c) reject nonconforming text/read-terms | audit | Dedicated syntax-error cases and strict-core extension rejection. Exhaustive lexical rejection coverage remains open. |
|
|
24
|
-
| 5.1(d) document permitted variations |
|
|
24
|
+
| 5.1(d) document permitted variations | covered | The clause-by-clause [ISO 5.4 decision index](ISO-IMPLEMENTATION-DEFINED.md) records every explicit implementation-defined decision found in the Part 1 + Corrigenda baseline and separately inventories implementation-specific extension families. Rows marked `audit gap` remain conformance work, but the variation is no longer undocumented. |
|
|
25
25
|
| 5.1(e) offer a strictly conforming mode | covered | `--iso-strict` and API option `isoStrict: true` restrict the processor to the Part 1 + Corrigenda 1-3 core language surface, remove EyeProlog-only registry/flag/operator features, and disable automatic tabling/recursion guards. |
|
|
26
|
-
| 5.4 accompanying documentation |
|
|
26
|
+
| 5.4 accompanying documentation | covered | *The Art of EyeProlog* remains the implementation reference; [ISO-IMPLEMENTATION-DEFINED.md](ISO-IMPLEMENTATION-DEFINED.md) is the closed clause-by-clause 5.4 decision index and points each decision to implementation evidence or an explicit audit gap. |
|
|
27
27
|
| 5.5 extensions preserve standard text | covered | Default mode retains EyeProlog extensions; strict core mode removes their language/runtime interpretation. Regression tests ensure the default profile remains unchanged. |
|
|
28
28
|
|
|
29
29
|
## Normative language families
|
|
@@ -95,6 +95,6 @@ ISO/IEC 13211-1 processor” until all of the following are true:
|
|
|
95
95
|
difference explained or fixed;
|
|
96
96
|
4. prescribed modes, errors, side effects, and relevant error precedence for
|
|
97
97
|
every Part 1 built-in have executable coverage;
|
|
98
|
-
5. every
|
|
99
|
-
|
|
98
|
+
5. every `audit gap` recorded in the ISO 5.4 decision index is either fixed or
|
|
99
|
+
explicitly excluded from the strict-conformance claim; and
|
|
100
100
|
6. an external conformance run has found no unexplained deviations.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# ISO/IEC 13211-1 implementation-defined and implementation-specific profile
|
|
2
|
+
|
|
3
|
+
This is the clause-by-clause ISO 5.4 decision index for EyeProlog. The
|
|
4
|
+
normative baseline used for this table is **ISO/IEC 13211-1:1995** together
|
|
5
|
+
with **Technical Corrigenda 1:2007, 2:2012, and 3:2017**. The current WG17/STC
|
|
6
|
+
pages at complang.tuwien.ac.at are useful review input, but draft proposals are
|
|
7
|
+
not silently treated as normative changes to that licensed baseline.
|
|
8
|
+
|
|
9
|
+
[*The Art of EyeProlog*](../../the-art-of-eyeprolog.md) remains the single
|
|
10
|
+
implementation reference. This file is an audit index: it identifies each
|
|
11
|
+
explicitly implementation-defined decision in Part 1, states the EyeProlog
|
|
12
|
+
choice, and points to the implementation boundary that realizes it. Repeated
|
|
13
|
+
references to the same decision are folded into one row. Requirements that the
|
|
14
|
+
standard calls **implementation dependent** are listed separately because ISO
|
|
15
|
+
5.4 does not require their documentation in the same way.
|
|
16
|
+
|
|
17
|
+
Status values are:
|
|
18
|
+
|
|
19
|
+
- **defined** — the current behavior is implemented and stated here;
|
|
20
|
+
- **not applicable** — the standard decision is conditional and the condition
|
|
21
|
+
is false for EyeProlog's selected profile;
|
|
22
|
+
- **audit gap** — the current code behavior is stated, but strict-mode
|
|
23
|
+
conformance still needs a correction or a narrower profile before this row
|
|
24
|
+
can support a full conformance claim.
|
|
25
|
+
|
|
26
|
+
## Explicit implementation-defined decisions
|
|
27
|
+
|
|
28
|
+
| Clause | Decision completed by ISO 5.4 documentation | EyeProlog choice | Status / implementation evidence |
|
|
29
|
+
| --- | --- | --- | --- |
|
|
30
|
+
| 5.5.11 | Reserved atoms and the effect of instantiating a variable to one | EyeProlog reserves no Prolog atom under 5.5.11. Atoms with implementation-looking names remain ordinary terms unless a particular predicate interprets them. | **defined** — term representation and built-ins in `src/term.js`, `src/iso.js`. |
|
|
31
|
+
| 6.5 | Processor character set (PCS) | The unquoted ISO lexical classes are the Part 1 ASCII characters implemented by `src/parser.js`. Quoted character data additionally accepts Unicode scalar values. | **audit gap** — the Unicode quoted-character extension is currently also accepted by `--iso-strict`; strict extension rejection still needs a narrower PCS rule or a documented conforming classification. |
|
|
32
|
+
| 6.5 | Classification of additional/extended PCS characters | Non-ASCII scalar values are not accepted as unquoted small-letter, capital-letter, graphic, solo, layout, or meta characters; they are accepted only inside quoted character data. | **audit gap** — same strict-mode boundary as the preceding row. |
|
|
33
|
+
| 6.6 | Collating-sequence integers | Character codes are Unicode scalar values. Atom comparison uses ECMAScript string lexicographic order; on the ISO ASCII repertoire this is code-point order and satisfies the required monotonic ranges. | **defined** — `src/term.js` (`compareTerms`), `src/iso.js` character-code predicates. |
|
|
34
|
+
| 6.6 | Collating values of control escapes and extended characters | Control escapes and character-code predicates use Unicode scalar values. Atom ordering is ECMAScript string order; for non-BMP one-char atoms that order is based on UTF-16 code units rather than scalar values. | **audit gap** — the ISO ASCII repertoire is conforming, but the extended-character collating rule still needs one coherent documented integer/order mapping in strict mode. |
|
|
35
|
+
| 7.1.2.2 | Mapping between a character code and bytes | Text file streams decode and encode UTF-8. Binary streams expose bytes 0..255 directly. | **defined** — `src/io.js`. |
|
|
36
|
+
| 7.1.4.1 | Set `C` of characters represented by one-char atoms | Character predicates accept Unicode scalar values U+0000..U+10FFFF excluding surrogate code points. | **defined** — `src/iso.js` character-code validation. |
|
|
37
|
+
| 7.4.2.4 | Whether `op/3` directives affect other Prolog texts or execution | An `op/3` directive changes parsing of subsequent text loaded into the same `Program`; the resulting operator table is also used by execution-time term I/O. Separately created `Program` objects are independent. | **defined** — `src/parser.js`, `src/program.js`, `src/iso.js`. |
|
|
38
|
+
| 7.4.2.5 | Whether directive-created `Convc` affects other text/execution | Directive mappings are copied into the solver's execution-time `charConversions` map. The source tokenizer itself does not currently apply `char_conversion/2` to later unquoted source characters. | **audit gap** — `src/program.js`, `src/solver.js`; source-preparation conversion remains to be aligned with 7.11.2.1. |
|
|
39
|
+
| 7.4.2.6 | Order of `initialization/1` goals | Initialization goals run once, in source/inclusion order, before requested goals; each must obtain a first solution. | **defined** — `Program.initializations`, `Solver.runInitializations()`. |
|
|
40
|
+
| 7.4.2.7 | Ground term designating a Prolog text for `include/1` | A source designation is an atom interpreted as a file path; relative paths resolve from the including file's directory (or the current working directory for unanchored input). | **defined** — `src/program.js` (`readIncludedSource`). |
|
|
41
|
+
| 7.4.2.8 | Ground term designating a Prolog text for `ensure_loaded/1` | Same atom/file-path designation as `include/1`. | **defined** — `src/program.js`. |
|
|
42
|
+
| 7.4.2.8 | Position at which an ensured text is included | The first `ensure_loaded/1` expands the text in place at the directive; later requests for the same resolved path are ignored. | **defined** — `src/program.js` (`ensured` set and in-place builder loading). |
|
|
43
|
+
| 7.4.2.9 | Whether `set_prolog_flag/2` directives affect other texts/execution | Parser-relevant `double_quotes` changes following source text in the same parse; recorded flag directives initialize the solver used for execution. Separately created programs/solvers are independent. | **defined** — `src/parser.js`, `src/program.js`, `src/solver.js`. |
|
|
44
|
+
| 7.5.1 | Means by which the processor is asked to prepare Prolog text | Text is prepared through the CLI/REPL, the JavaScript `Program.parse`/`run` API, source files/URLs, and `include/1`/`ensure_loaded/1`. | **defined** — Chapter 40 and `src/cli.js`, `src/index.js`, `src/program.js`. |
|
|
45
|
+
| 7.5.1 | Effects of directive-driven reordering, addition, or removal of clauses during preparation | Includes are expanded in place; declarations annotate the affected procedure; clauses otherwise retain source order. No directive silently reorders ordinary clauses. | **defined** — streaming builder in `src/program.js`. |
|
|
46
|
+
| 7.7.1 | External form of a negative answer | The interactive top level prints `false.`. Non-interactive `run()`/CLI corpus execution emits no answer line for a failed goal. | **defined** — Chapter 40, `src/repl.js`, `src/execute.js`. |
|
|
47
|
+
| 7.7.1 | External form of a positive answer | The REPL prints `true.` for a solution without visible bindings or `Name = Term` bindings; the file/API runner emits each resolved ground goal as a Prolog term followed by `.`. | **defined** — Chapter 40, `src/repl.js`, `src/execute.js`. |
|
|
48
|
+
| 7.7.3 | Method by which a user delivers a goal | REPL queries, CLI `--goal`, `%% goal:` host comments, and the JavaScript `goal` option are supported host interfaces. | **defined** — Chapter 40. |
|
|
49
|
+
| 7.10.1 | Additional source/sink possibilities | Node runtimes support local files plus the predefined standard streams; embedding supplies `user_input` text and a `user_output` callback. URL loading is a source-loading facility, not an ISO stream opened by `open/4`. | **defined** — `src/io.js`, `src/cli.js`, `src/index.js`. |
|
|
50
|
+
| 7.10.1 | Ground term designating a source/sink to `open/4` | An atom is interpreted as a local file-system path. | **defined** — `src/iso.js`, `src/io.js`. |
|
|
51
|
+
| 7.10.2.6 | Record-based/non-record-based text stream support | EyeProlog exposes non-record-based text streams. | **defined** — `src/io.js`. |
|
|
52
|
+
| 7.10.2.6 | Alteration of spaces at line ends | None. EyeProlog preserves text-stream characters; it does not trim or pad line endings. | **defined** — `src/io.js`. |
|
|
53
|
+
| 7.10.2.6 | Whether the last line is followed by newline / close adds one | Closing a text output stream writes exactly the accumulated content and does not synthesize a final newline. | **defined** — `StreamManager.close()`. |
|
|
54
|
+
| 7.10.2.6 | Effect of outputting control characters | The Unicode character is appended unchanged to a text stream (subject to the host sink's ordinary string handling). | **defined** — `StreamManager.writeUnit()`. |
|
|
55
|
+
| 7.10.2.7 | Number of zero bytes that may be appended on binary re-input | Zero. Binary file output is written and read byte-for-byte. | **defined** — `src/io.js`. |
|
|
56
|
+
| 7.10.2.8 | Whether a source/sink can be arbitrarily repositioned | File streams are repositionable only when opened with `reposition(true)`; standard streams are not repositionable. | **defined** — `src/io.js`, `set_stream_position/2` in `src/iso.js`. |
|
|
57
|
+
| 7.10.2.9 | Terms denoting end/past-end stream positions when repositionable | Stream positions are non-negative integer offsets; `position(N)` is also accepted by `set_stream_position/2`. End/past-end state is separately exposed by `end_of_stream/1`. | **defined** — `streamProperties()` and `setStreamPositionBuiltin()` in `src/iso.js`. |
|
|
58
|
+
| 7.10.2.11 | Default `eof_action` | Newly opened file streams default to `error`. The predefined standard streams use `reset`. | **defined** — `src/io.js`. |
|
|
59
|
+
| 7.10.2.13 | `eof_action(Action)` property when the stream uses its default action | Reports the same selected action: `error` for ordinary opened files and `reset` for the predefined standard streams. | **defined** — `streamProperties()` in `src/iso.js`, defaults in `src/io.js`. |
|
|
60
|
+
| 7.10.2.11 | Whether `reposition(false)` streams may nevertheless be repositioned | No. `set_stream_position/2` raises a permission error unless the stream was created with `reposition(true)`. | **defined** — `src/iso.js`. |
|
|
61
|
+
| 7.11.1.1 | Default `bounded` flag | `false`: EyeProlog's Prolog integer model uses arbitrary-precision `BigInt`, subject to host resources. | **defined** — `defaultPrologFlags()` in `src/solver.js`. |
|
|
62
|
+
| 7.11.1.2 | Default `max_integer` when `bounded=true` | The bounded-profile default is not applicable because EyeProlog selects `bounded=false`. However, EyeProlog currently exposes the sentinel atom `unbounded` through `current_prolog_flag/2` instead of making the `max_integer` query fail as 7.11.1.1 prescribes for an unbounded processor. | **audit gap** — `src/solver.js`, `current_prolog_flag/2`. |
|
|
63
|
+
| 7.11.1.3 | Default `min_integer` when `bounded=true` | The bounded-profile default is not applicable because EyeProlog selects `bounded=false`. However, EyeProlog currently exposes the sentinel atom `unbounded` through `current_prolog_flag/2` instead of making the `min_integer` query fail as 7.11.1.1 prescribes for an unbounded processor. | **audit gap** — `src/solver.js`, `current_prolog_flag/2`. |
|
|
64
|
+
| 7.11.1.4 | Default `integer_rounding_function` flag | `toward_zero`; `//` uses truncation toward zero. `div` is provided separately with downward/floor division semantics. | **defined** — `src/solver.js`, `src/iso.js`. |
|
|
65
|
+
| 9.1.3.1 | Integer division rounding function `rndI` | Truncation toward zero, matching the `integer_rounding_function=toward_zero` flag. | **defined** — BigInt division for `//` in `src/iso.js`. |
|
|
66
|
+
| 7.11.2.1 | Whether preparation-time `Convc` affects execution-time `Convc` | Yes: mappings recorded by `char_conversion/2` directives initialize the solver map. Source-token conversion itself has the audit gap noted at 7.4.2.5. | **defined / audit note** — `src/solver.js`. |
|
|
67
|
+
| 7.11.2.2 | Effect when `debug=on` | The flag is accepted and stored; it does not change goal semantics or enable a debugger. | **defined** — `src/solver.js`; no semantic branch depends on `debug`. |
|
|
68
|
+
| 7.11.2.3 | Default `max_arity` | `unbounded` in the Prolog model, subject to host memory and practical JavaScript array/index limits. | **defined** — `src/solver.js`; relevant guards report representation/resource errors. |
|
|
69
|
+
| 7.11.2.5 | Default `double_quotes` | `chars`. | **defined** — `src/solver.js`, parser flag state. |
|
|
70
|
+
| 7.12.1 | Second argument of `error/2` | The default context term is the atom `eyeprolog`. A few implementation-specific diagnostics may deliberately supply a more specific context term. | **defined** — `formalErrorTerm()` in `src/iso.js`. |
|
|
71
|
+
| 7.12.2(f) | Implementation-defined representation limits | Character and character-code operations use Unicode scalar limits; arity/integer values are modeled as unbounded but may hit host/resource limits. Float input overflow uses the implementation-specific `max_float`/`min_float` representation names documented by the STC-oriented tests. | **defined** — parser/ISO numeric and character guards. |
|
|
72
|
+
| 8.17.1 | Implementation-defined flag value ranges | Strict mode exposes only Part 1 core flags and their standard value sets. Normal mode additionally exposes EyeProlog's `occurs_check` flag; `max_integer`/`min_integer` use the `unbounded` sentinel described above. | **defined** — strict registry/flag filtering in `src/solver.js`. |
|
|
73
|
+
| 8.17.3 | Other effects of `halt/0` | Terminates EyeProlog execution and returns host/process status `0`; it produces no Prolog solution. | **defined** — `HaltSignal`, `haltBuiltin()`, CLI/runner handling. |
|
|
74
|
+
| 8.17.4 | Meaning/effects of `halt(Status)` | Integer `Status` is converted to the host process/runner halt code; it produces no Prolog solution. | **defined** — `haltBuiltin()`, `src/execute.js`, `src/cli.js`. |
|
|
75
|
+
| 9.1.4.1 | Floating-point rounding function `rndF` | Floating values and operations use ECMAScript `Number` (IEEE-754 binary64) and the host's specified binary64 arithmetic/conversions. | **defined** — `src/iso.js`, `src/number-value.js`. |
|
|
76
|
+
| 9.1.4.2 | Floating-point result function, including tiny non-zero arithmetic results | For arithmetic operation results that underflow to zero from non-zero operands, EyeProlog chooses the exceptional value `underflow`, exposed as `evaluation_error(underflow)`. Float token/`number_chars/2` input is a separate conversion path and finite input underflow rounds to `0.0`. | **defined** — `evaluateOperation()` and parser/number conversion; executable issue-56 regression below. |
|
|
77
|
+
| 9.1.4.3 | Approximate-addition function | ECMAScript binary64 addition is used; subtraction is implemented through the corresponding host operation and all finite results remain binary64 values. | **defined** — `evaluateOperation()` in `src/iso.js`. |
|
|
78
|
+
| 9.4 | Representation of negative integers for bitwise operations | BigInt's unbounded signed binary semantics are used, equivalent to an infinite two's-complement sign extension for bitwise operations. | **defined** — `src/iso.js` BigInt bitwise operators. |
|
|
79
|
+
| 9.4.1 | Right shift of negative integers and unusual shift counts | `>>` is arithmetic/sign-propagating. A negative count reverses direction according to JavaScript BigInt shift semantics; there is no finite integer bit-size ceiling in the Prolog model. | **defined** — `a >> b` in `src/iso.js`. |
|
|
80
|
+
| 9.4.2 | Left shift of negative integers and unusual shift counts | `<<` uses BigInt signed shift semantics; a negative count reverses direction. Resource exhaustion remains possible for very large results. | **defined** — `a << b` in `src/iso.js`. |
|
|
81
|
+
| 9.4.3 | Bitwise AND with negative operands | BigInt infinite-two's-complement semantics. | **defined** — `a & b`. |
|
|
82
|
+
| 9.4.4 | Bitwise OR with negative operands | BigInt infinite-two's-complement semantics. | **defined** — `a \| b`. |
|
|
83
|
+
| 9.4.5 | Bitwise complement | BigInt complement, i.e. `~N = -N-1`. | **defined** — `~a`. |
|
|
84
|
+
| Cor.2 9.4.6 | `xor/2` with negative operands | BigInt infinite-two's-complement semantics. | **defined** — `a ^ b`. |
|
|
85
|
+
|
|
86
|
+
### Why issue #56's two float results differ
|
|
87
|
+
|
|
88
|
+
The arithmetic expression and the float token go through different specified
|
|
89
|
+
layers. Clause 9.1.4 defines arithmetic operations in terms of the
|
|
90
|
+
implementation-defined floating rounding/result functions, and EyeProlog's
|
|
91
|
+
chosen `resultF` policy reports underflow for a non-zero arithmetic result that
|
|
92
|
+
falls below the normal range and rounds to zero. By contrast, reading the token
|
|
93
|
+
`0.1e-999` is an input conversion; the 1995 text does not precisely define the
|
|
94
|
+
rounding of an inexact float token, a gap also called out in the later
|
|
95
|
+
[WG17/STC item #40](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/stc#40).
|
|
96
|
+
EyeProlog's documented input policy is therefore to accept finite token
|
|
97
|
+
underflow as `0.0` while arithmetic underflow remains an evaluation error.
|
|
98
|
+
|
|
99
|
+
The regression suite contains an issue-56 case that checks both observations,
|
|
100
|
+
and the existing `stc/float_underflow_input` conformance case covers the related
|
|
101
|
+
input-conversion policy.
|
|
102
|
+
|
|
103
|
+
## Implementation-specific features required to be documented by 5.4
|
|
104
|
+
|
|
105
|
+
Part 1 clause 5.5 permits extensions only as implementation-specific features.
|
|
106
|
+
Corrigendum 3 additionally makes extra option names explicit implementation-
|
|
107
|
+
specific features. EyeProlog's normal profile provides the following extension
|
|
108
|
+
families; `--iso-strict` is intended to remove their Part 1 interpretation.
|
|
109
|
+
|
|
110
|
+
| Part 1 extension hook | EyeProlog normal-profile feature | Strict-core disposition |
|
|
111
|
+
| --- | --- | --- |
|
|
112
|
+
| 5.5.1 Syntax | Part 2 modules, Part 3 grammar-rule expansion, and embedded quad syntax | Module directives are rejected; grammar rules remain ordinary `-->/2` terms rather than being expanded; quad syntax is rejected. Unicode quoted-character handling is the open PCS audit item above. |
|
|
113
|
+
| 5.5.2 Predefined operators | Part 3 `|` and EyeProlog's labelable infix `(?-)/2`; CLP(Z) operators when that library is imported | Only the Part 1 operator table is predefined; a conforming `op/3` may still add permitted operators. |
|
|
114
|
+
| 5.5.3 Character-conversion mapping | No non-identity initial `Convc` extension | Identity initial mapping. |
|
|
115
|
+
| 5.5.4 Types | No additional runtime Prolog term type is exposed by the core solver | Only variable, integer, float, atom, and compound term ordering participates in strict mode. |
|
|
116
|
+
| 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. |
|
|
117
|
+
| 5.5.6 Side effects | Host output/statistics, library-specific state, proof/statistics collection | The strict registry excludes library adapters; ordinary Part 1 I/O/database/flag/operator side effects remain. |
|
|
118
|
+
| 5.5.7 Control constructs | `tnot/1` and normal-profile execution optimizations | `tnot/1` is absent; automatic tabling/recursion guards are disabled. |
|
|
119
|
+
| 5.5.8 Flags | `occurs_check` | Absent in strict mode. |
|
|
120
|
+
| 5.5.9 Built-in predicates | EyeProlog libraries, CLP(Z), statistics, Part 3 `phrase/2-3`, and interop autoloaded predicates | Strict registry contains only the Part 1 + Corrigenda core registry. |
|
|
121
|
+
| 5.5.10 Evaluable functors | Library/runtime extensions when registered outside the strict core | Strict arithmetic registry is the Part 1 + Corrigenda set. |
|
|
122
|
+
| 5.5.11 Reserved atoms | None | None. |
|
|
123
|
+
| Cor.3 5.5.12 Options | Extra library/host options may exist outside core option lists | Strict core accepts the standard option names implemented for Part 1; invalid options follow Corrigendum 3 validation. |
|
|
124
|
+
|
|
125
|
+
Part 2 and Part 3 are separately standardized profiles when EyeProlog is run in
|
|
126
|
+
its normal mode; the table above describes them as extensions only relative to
|
|
127
|
+
the Part 1 strict-core boundary.
|
|
128
|
+
|
|
129
|
+
## Important implementation-dependent behavior (not the 5.4 mandatory table)
|
|
130
|
+
|
|
131
|
+
For portability work, EyeProlog also documents these choices even though the
|
|
132
|
+
1995 text labels them implementation **dependent**, not implementation defined:
|
|
133
|
+
|
|
134
|
+
- distinct variables are ordered by first encounter within the operation that
|
|
135
|
+
needs a stable ordering;
|
|
136
|
+
- expression arguments are evaluated left-to-right by the JavaScript evaluator;
|
|
137
|
+
- generated variable names use EyeProlog's stable `_A`, `_B`, ... style within
|
|
138
|
+
a write operation/top-level query;
|
|
139
|
+
- resource and syntax-error detail atoms are EyeProlog implementation details;
|
|
140
|
+
- the order of `current_prolog_flag/2` solutions is the insertion order of the
|
|
141
|
+
flag registry.
|
|
142
|
+
|
|
143
|
+
These rows are intentionally separate so that the ISO 5.4 checklist does not
|
|
144
|
+
blur the standard's distinction between *implementation defined* (documented),
|
|
145
|
+
*implementation dependent* (need not be documented), and *implementation
|
|
146
|
+
specific* (an extension that must be documented for a conforming processor).
|
|
@@ -28,12 +28,13 @@ uninstantiation errors, and corrected `catch/3` behavior (Cor.2); and option
|
|
|
28
28
|
validation, variable-name traversal/output, canonical list output, and negative
|
|
29
29
|
integer powers (Cor.3).
|
|
30
30
|
|
|
31
|
-
Implementation-defined choices are
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
Implementation-defined and implementation-specific choices are indexed
|
|
32
|
+
clause-by-clause in [ISO-IMPLEMENTATION-DEFINED.md](ISO-IMPLEMENTATION-DEFINED.md),
|
|
33
|
+
with *The Art of EyeProlog* remaining the implementation reference. In
|
|
34
|
+
particular, the index records the unbounded integer model, `double_quotes=chars`,
|
|
35
|
+
`//` rounding toward zero, the ECMAScript binary64 float policy (including the
|
|
36
|
+
9.1.4.2 arithmetic-underflow choice), stream/character decisions, and the
|
|
37
|
+
normal-profile extension boundary.
|
|
37
38
|
|
|
38
39
|
This is an executable conformance matrix, not a certification issued by an
|
|
39
40
|
independent standards body. Release gating runs the ISO cases and the dedicated
|
package/test/run-all.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Unified test runner used by `npm test`.
|
|
3
3
|
// Running all suites in one process keeps the numbering continuous and avoids
|
|
4
4
|
// npm's intermediate script banners between conformance, regression, and examples.
|
|
5
|
-
import {
|
|
5
|
+
import { runStandalone } from './test-style.mjs';
|
|
6
6
|
import { runConformance } from './run-conformance.mjs';
|
|
7
7
|
import { runRegression } from './run-regression.mjs';
|
|
8
8
|
import { runIsoStrict } from './run-iso-strict.mjs';
|
|
@@ -12,9 +12,7 @@ import { runBookExamples } from './run-book-examples.mjs';
|
|
|
12
12
|
import { runWg17 } from './run-wg17.mjs';
|
|
13
13
|
import { runOpenRuleBenchChecks } from './run-openrulebench.mjs';
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
try {
|
|
15
|
+
await runStandalone(async (reporter) => {
|
|
18
16
|
runConformance(reporter);
|
|
19
17
|
runIsoStrict(reporter);
|
|
20
18
|
runWg17(reporter);
|
|
@@ -23,8 +21,4 @@ try {
|
|
|
23
21
|
await runPlayground(reporter);
|
|
24
22
|
runExamples(reporter);
|
|
25
23
|
runBookExamples(reporter);
|
|
26
|
-
|
|
27
|
-
process.exit(0);
|
|
28
|
-
} catch (_) {
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
24
|
+
});
|
|
@@ -4,7 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
|
|
6
6
|
import { Program, run } from '../src/index.js';
|
|
7
|
-
import { TestReporter, isMainModule } from './test-style.mjs';
|
|
7
|
+
import { TestReporter, isMainModule, runStandalone } from './test-style.mjs';
|
|
8
8
|
import { goalsFromSource } from './goal-metadata.mjs';
|
|
9
9
|
|
|
10
10
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
@@ -39,11 +39,5 @@ function listPrograms(directory) {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
if (isMainModule(import.meta.url)) {
|
|
42
|
-
|
|
43
|
-
try {
|
|
44
|
-
runBookExamples(reporter);
|
|
45
|
-
reporter.totalLine();
|
|
46
|
-
} catch (_) {
|
|
47
|
-
process.exit(1);
|
|
48
|
-
}
|
|
42
|
+
await runStandalone(runBookExamples);
|
|
49
43
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import fs from 'node:fs';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { listPrologFiles } from './test-support.mjs';
|
|
8
9
|
|
|
9
10
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)));
|
|
10
11
|
const packageRoot = path.resolve(root, '..');
|
|
@@ -24,7 +25,7 @@ export function buildConformanceReport() {
|
|
|
24
25
|
for (const { kind, expectedKind, expectedExt, column } of KINDS) {
|
|
25
26
|
const base = path.join(conformanceRoot, kind);
|
|
26
27
|
if (!fs.existsSync(base)) continue;
|
|
27
|
-
for (const file of
|
|
28
|
+
for (const file of listPrologFiles(base)) {
|
|
28
29
|
const category = categoryOf(file);
|
|
29
30
|
const counts = ensureCategory(categories, category);
|
|
30
31
|
counts[column]++;
|
|
@@ -77,19 +78,6 @@ export function formatConformanceReport(report = buildConformanceReport()) {
|
|
|
77
78
|
return `${lines.join('\n')}\n`;
|
|
78
79
|
}
|
|
79
80
|
|
|
80
|
-
function listEyePrologFiles(base, dir = base) {
|
|
81
|
-
const files = [];
|
|
82
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
83
|
-
const full = path.join(dir, entry.name);
|
|
84
|
-
if (entry.isDirectory()) {
|
|
85
|
-
files.push(...listEyePrologFiles(base, full));
|
|
86
|
-
} else if (entry.isFile() && entry.name.endsWith('.pl')) {
|
|
87
|
-
files.push(path.relative(base, full).split(path.sep).join('/'));
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return files.sort();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
81
|
function categoryOf(file) {
|
|
94
82
|
const parts = file.split('/');
|
|
95
83
|
return parts.length > 1 ? parts[0] : 'legacy-numbered';
|
package/test/run-conformance.mjs
CHANGED
|
@@ -6,27 +6,12 @@ import path from 'node:path';
|
|
|
6
6
|
import { spawnSync } from 'node:child_process';
|
|
7
7
|
import { Program, createDefaultRegistry, run } from '../src/index.js';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
|
-
import { TestReporter, isMainModule } from './test-style.mjs';
|
|
9
|
+
import { TestReporter, isMainModule, runStandalone } from './test-style.mjs';
|
|
10
10
|
import { goalsFromSource } from './goal-metadata.mjs';
|
|
11
|
+
import { listPrologFiles, withStandardModules } from './test-support.mjs';
|
|
11
12
|
|
|
12
13
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)));
|
|
13
14
|
const filterArg = process.argv[2] ?? null;
|
|
14
|
-
const libraryCall = /\b(?:uuid|difference|maplist|lt|gt|le|ge|between|smallest_divisor_from|random|matches|split|replace|lowercase|uppercase|trim|number_string|atom_string|term_string|append|string_concat|contains|join|substring|member|select|last|nth0|nth1|set_nth0|take|drop|slice|reverse|length|sum_list|min_list|max_list|list_to_set|countall|sumall|aggregate_min|aggregate_max)\s*\(/;
|
|
15
|
-
|
|
16
|
-
function withStandardModules(text) {
|
|
17
|
-
if (!libraryCall.test(text) || text.includes('use_module(library(')) return text;
|
|
18
|
-
return `:- use_module(library(aggregate)).
|
|
19
|
-
:- use_module(library(comparison)).
|
|
20
|
-
:- use_module(library(dates)).
|
|
21
|
-
:- use_module(library(iso_ext)).
|
|
22
|
-
:- use_module(library(lists)).
|
|
23
|
-
:- use_module(library(primes)).
|
|
24
|
-
:- use_module(library(prologue), [between/3]).
|
|
25
|
-
:- use_module(library(random)).
|
|
26
|
-
:- use_module(library(strings)).
|
|
27
|
-
:- use_module(library(uuid)).
|
|
28
|
-
${text}`;
|
|
29
|
-
}
|
|
30
15
|
|
|
31
16
|
export function runConformance(reporter = new TestReporter(), requestedFilter = null) {
|
|
32
17
|
const filter = requestedFilter ?? filterArg;
|
|
@@ -42,7 +27,7 @@ export function runConformance(reporter = new TestReporter(), requestedFilter =
|
|
|
42
27
|
function listCaseFiles(kind, filter = null) {
|
|
43
28
|
const base = path.join(root, 'conformance', kind);
|
|
44
29
|
if (!fs.existsSync(base)) return [];
|
|
45
|
-
return
|
|
30
|
+
return listPrologFiles(base)
|
|
46
31
|
.filter((name) => matchesFilter(kind, name, filter))
|
|
47
32
|
.sort();
|
|
48
33
|
}
|
|
@@ -58,19 +43,6 @@ function matchesFilter(kind, name, filter) {
|
|
|
58
43
|
|| `${label}/${stem}`.includes(filter);
|
|
59
44
|
}
|
|
60
45
|
|
|
61
|
-
function listEyePrologFiles(base, dir = base) {
|
|
62
|
-
const files = [];
|
|
63
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
64
|
-
const full = path.join(dir, entry.name);
|
|
65
|
-
if (entry.isDirectory()) {
|
|
66
|
-
files.push(...listEyePrologFiles(base, full));
|
|
67
|
-
} else if (entry.isFile() && entry.name.endsWith('.pl')) {
|
|
68
|
-
files.push(path.relative(base, full).split(path.sep).join('/'));
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return files;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
46
|
function runCaseFile(reporter, file) {
|
|
75
47
|
const name = file.slice(0, -3);
|
|
76
48
|
reporter.test(name, () => runCase(name, file));
|
|
@@ -194,11 +166,5 @@ function diffText(expected, actualText) {
|
|
|
194
166
|
}
|
|
195
167
|
|
|
196
168
|
if (isMainModule(import.meta.url)) {
|
|
197
|
-
|
|
198
|
-
try {
|
|
199
|
-
runConformance(reporter);
|
|
200
|
-
reporter.totalLine();
|
|
201
|
-
} catch (_) {
|
|
202
|
-
process.exit(1);
|
|
203
|
-
}
|
|
169
|
+
await runStandalone(runConformance);
|
|
204
170
|
}
|
package/test/run-examples.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import path from 'node:path';
|
|
|
6
6
|
import { spawnSync } from 'node:child_process';
|
|
7
7
|
import { Program, run } from '../src/index.js';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
|
-
import { TestReporter, isMainModule } from './test-style.mjs';
|
|
9
|
+
import { TestReporter, isMainModule, runStandalone } from './test-style.mjs';
|
|
10
10
|
import { goalsInProgramOrder } from './goal-metadata.mjs';
|
|
11
11
|
|
|
12
12
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)));
|
|
@@ -156,11 +156,5 @@ function diffText(expected, actualText) {
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
if (isMainModule(import.meta.url)) {
|
|
159
|
-
|
|
160
|
-
try {
|
|
161
|
-
runExamples(reporter);
|
|
162
|
-
reporter.totalLine();
|
|
163
|
-
} catch (_) {
|
|
164
|
-
process.exit(1);
|
|
165
|
-
}
|
|
159
|
+
await runStandalone(runExamples);
|
|
166
160
|
}
|
package/test/run-iso-strict.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
parseGoalText,
|
|
11
11
|
run,
|
|
12
12
|
} from '../src/index.js';
|
|
13
|
-
import { TestReporter, isMainModule } from './test-style.mjs';
|
|
13
|
+
import { TestReporter, isMainModule, runStandalone } from './test-style.mjs';
|
|
14
14
|
|
|
15
15
|
export function runIsoStrict(reporter = new TestReporter()) {
|
|
16
16
|
reporter.section('Strict ISO core');
|
|
@@ -103,11 +103,5 @@ function includes(actual, expected, label) {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
if (isMainModule(import.meta.url)) {
|
|
106
|
-
|
|
107
|
-
try {
|
|
108
|
-
runIsoStrict(reporter);
|
|
109
|
-
reporter.totalLine();
|
|
110
|
-
} catch (_) {
|
|
111
|
-
process.exit(1);
|
|
112
|
-
}
|
|
106
|
+
await runStandalone(runIsoStrict);
|
|
113
107
|
}
|
|
@@ -2,57 +2,27 @@
|
|
|
2
2
|
// Fast structural checks for the generated multi-engine OpenRuleBench corpus.
|
|
3
3
|
// Full benchmark execution remains separate because it requires external
|
|
4
4
|
// Prolog implementations and is intentionally performance-oriented.
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import { spawnSync } from 'node:child_process';
|
|
8
|
-
import { fileURLToPath } from 'node:url';
|
|
9
|
-
import { TestReporter, isMainModule } from './test-style.mjs';
|
|
10
|
-
|
|
11
|
-
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
5
|
+
import { validateOpenRuleBench } from '../openrulebench/tools/check.mjs';
|
|
6
|
+
import { TestReporter, isMainModule, runStandalone } from './test-style.mjs';
|
|
12
7
|
|
|
13
8
|
export function runOpenRuleBenchChecks(reporter = new TestReporter()) {
|
|
9
|
+
let report = null;
|
|
14
10
|
reporter.section('OpenRuleBench source integrity');
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
'engine variants preserve table and WFS adaptations',
|
|
24
|
-
'openrulebench/tools/check_multiengine.mjs',
|
|
25
|
-
'OK: 14 benchmarks x 4 engines; table/WFS adaptations verified.',
|
|
26
|
-
);
|
|
11
|
+
reporter.test('generated sources pass lexical checks', () => {
|
|
12
|
+
report = validateOpenRuleBench();
|
|
13
|
+
assertNoErrors(report.lexicalErrors);
|
|
14
|
+
});
|
|
15
|
+
reporter.test('engine variants preserve table and WFS adaptations', () => {
|
|
16
|
+
report ??= validateOpenRuleBench();
|
|
17
|
+
assertNoErrors(report.adaptationErrors);
|
|
18
|
+
});
|
|
27
19
|
reporter.sectionTotal('OpenRuleBench source-integrity');
|
|
28
20
|
}
|
|
29
21
|
|
|
30
|
-
function
|
|
31
|
-
|
|
32
|
-
const result = spawnSync(process.execPath, [relativeScript], {
|
|
33
|
-
cwd: packageRoot,
|
|
34
|
-
encoding: 'utf8',
|
|
35
|
-
timeout: 30000,
|
|
36
|
-
});
|
|
37
|
-
if (result.error) throw result.error;
|
|
38
|
-
if (result.status !== 0) {
|
|
39
|
-
throw new Error(
|
|
40
|
-
`${relativeScript} exited with ${result.status}\n` +
|
|
41
|
-
`${result.stdout ?? ''}${result.stderr ?? ''}`.trimEnd(),
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
if (!String(result.stdout).includes(expectedOutput)) {
|
|
45
|
-
throw new Error(`${relativeScript} did not report its expected summary\n${result.stdout ?? ''}`.trimEnd());
|
|
46
|
-
}
|
|
47
|
-
});
|
|
22
|
+
function assertNoErrors(errors) {
|
|
23
|
+
if (errors.length > 0) throw new Error(errors.join('\n'));
|
|
48
24
|
}
|
|
49
25
|
|
|
50
26
|
if (isMainModule(import.meta.url)) {
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
runOpenRuleBenchChecks(reporter);
|
|
54
|
-
reporter.totalLine();
|
|
55
|
-
} catch (_) {
|
|
56
|
-
process.exit(1);
|
|
57
|
-
}
|
|
27
|
+
await runStandalone(runOpenRuleBenchChecks);
|
|
58
28
|
}
|
package/test/run-playground.mjs
CHANGED
|
@@ -10,7 +10,14 @@ import {
|
|
|
10
10
|
executePlaygroundRequest,
|
|
11
11
|
installPlaygroundWorker,
|
|
12
12
|
} from '../src/playground-worker.js';
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
TestReporter,
|
|
15
|
+
assertEqual,
|
|
16
|
+
assertIncludes,
|
|
17
|
+
assertNotIncludes,
|
|
18
|
+
isMainModule,
|
|
19
|
+
runStandalone,
|
|
20
|
+
} from './test-style.mjs';
|
|
14
21
|
|
|
15
22
|
const testRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)));
|
|
16
23
|
const packageRoot = path.resolve(testRoot, '..');
|
|
@@ -220,30 +227,6 @@ function assert(condition, message) {
|
|
|
220
227
|
if (!condition) throw new Error(message);
|
|
221
228
|
}
|
|
222
229
|
|
|
223
|
-
function assertEqual(actual, expected, label) {
|
|
224
|
-
if (actual !== expected) {
|
|
225
|
-
throw new Error(`${label} mismatch\nexpected: ${JSON.stringify(expected)}\nactual: ${JSON.stringify(actual)}`);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function assertIncludes(actual, expected, label) {
|
|
230
|
-
if (!String(actual).includes(expected)) {
|
|
231
|
-
throw new Error(`${label} did not include ${JSON.stringify(expected)}\nactual: ${JSON.stringify(actual)}`);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
function assertNotIncludes(actual, expected, label) {
|
|
236
|
-
if (String(actual).includes(expected)) {
|
|
237
|
-
throw new Error(`${label} unexpectedly included ${JSON.stringify(expected)}`);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
230
|
if (isMainModule(import.meta.url)) {
|
|
242
|
-
|
|
243
|
-
try {
|
|
244
|
-
await runPlayground(reporter);
|
|
245
|
-
reporter.totalLine();
|
|
246
|
-
} catch (_) {
|
|
247
|
-
process.exit(1);
|
|
248
|
-
}
|
|
231
|
+
await runStandalone(runPlayground);
|
|
249
232
|
}
|
package/test/run-regression.mjs
CHANGED
|
@@ -48,13 +48,21 @@ import { PrologError, formalErrorTerm } from '../src/iso.js';
|
|
|
48
48
|
import { compareTerms } from '../src/term.js';
|
|
49
49
|
import { formatTermForWrite } from '../src/write.js';
|
|
50
50
|
import { selectClauseCandidates } from '../src/program.js';
|
|
51
|
-
import {
|
|
51
|
+
import {
|
|
52
|
+
TestReporter,
|
|
53
|
+
assertEqual,
|
|
54
|
+
assertIncludes,
|
|
55
|
+
assertNotIncludes,
|
|
56
|
+
isMainModule,
|
|
57
|
+
runStandalone,
|
|
58
|
+
} from './test-style.mjs';
|
|
52
59
|
import { buildConformanceReport, formatConformanceReport } from './run-conformance-report.mjs';
|
|
53
60
|
import { proofExamples } from './run-examples.mjs';
|
|
54
61
|
import { goalsFromSource } from './goal-metadata.mjs';
|
|
55
62
|
import { renderWg17SyntaxStatus } from '../tools/report-wg17-syntax-coverage.mjs';
|
|
56
63
|
import { parseWg17SyntaxTable } from '../tools/upgrade-wg17.mjs';
|
|
57
64
|
import { executeWg17Item, matchesUpstreamExpectation, readWg17SyntaxFixture } from './run-wg17.mjs';
|
|
65
|
+
import { withStandardModules } from './test-support.mjs';
|
|
58
66
|
|
|
59
67
|
const testRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)));
|
|
60
68
|
const packageRoot = path.resolve(testRoot, '..');
|
|
@@ -62,22 +70,6 @@ const bin = path.join(packageRoot, 'bin', 'eyeprolog.js');
|
|
|
62
70
|
const pkg = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
|
|
63
71
|
let tmp = null;
|
|
64
72
|
let tmpCounter = 0;
|
|
65
|
-
const libraryCall = /\b(?:uuid|difference|maplist|foldl|call_nth|lt|gt|le|ge|between|smallest_divisor_from|random|matches|split|replace|lowercase|uppercase|trim|number_string|atom_string|term_string|append|string_concat|contains|join|substring|member|select|last|nth0|nth1|set_nth0|take|drop|slice|reverse|length|sum_list|min_list|max_list|list_to_set|countall|sumall|aggregate_min|aggregate_max)\s*\(/;
|
|
66
|
-
|
|
67
|
-
function withStandardModules(source) {
|
|
68
|
-
if (!libraryCall.test(source) || source.includes('use_module(library(') || source.includes(':- module(')) return source;
|
|
69
|
-
return `:- use_module(library(aggregate)).
|
|
70
|
-
:- use_module(library(comparison)).
|
|
71
|
-
:- use_module(library(dates)).
|
|
72
|
-
:- use_module(library(iso_ext)).
|
|
73
|
-
:- use_module(library(lists)).
|
|
74
|
-
:- use_module(library(primes)).
|
|
75
|
-
:- use_module(library(prologue), [between/3]).
|
|
76
|
-
:- use_module(library(random)).
|
|
77
|
-
:- use_module(library(strings)).
|
|
78
|
-
:- use_module(library(uuid)).
|
|
79
|
-
${source}`;
|
|
80
|
-
}
|
|
81
73
|
|
|
82
74
|
function run(source, options = {}) {
|
|
83
75
|
const programSource = Array.isArray(source) ? source.join('\n') : source;
|
|
@@ -92,15 +84,24 @@ function sourceAtom(value) {
|
|
|
92
84
|
return `'${String(value).replaceAll('\\', '\\\\').replaceAll("'", "''")}'`;
|
|
93
85
|
}
|
|
94
86
|
|
|
95
|
-
export function runRegression(reporter = new TestReporter()) {
|
|
87
|
+
export function runRegression(reporter = new TestReporter(), requestedSection = null) {
|
|
88
|
+
const sections = [
|
|
89
|
+
{ key: 'regression', name: 'Regression', cases: regressionCases },
|
|
90
|
+
{ key: 'docs', name: 'Documentation sync', cases: documentationSyncCases },
|
|
91
|
+
{ key: 'api', name: 'API', cases: apiCases },
|
|
92
|
+
{ key: 'white-box', name: 'White-box', cases: whiteBoxCases },
|
|
93
|
+
];
|
|
94
|
+
const selected = requestedSection == null
|
|
95
|
+
? sections
|
|
96
|
+
: sections.filter((section) => section.key === requestedSection);
|
|
97
|
+
if (selected.length === 0) {
|
|
98
|
+
throw new Error(`unknown regression section: ${requestedSection}; expected ${sections.map(({ key }) => key).join(', ')}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
96
101
|
tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'eyeprolog-regression.'));
|
|
97
102
|
tmpCounter = 0;
|
|
98
|
-
|
|
99
103
|
try {
|
|
100
|
-
runSection(reporter,
|
|
101
|
-
runSection(reporter, 'Documentation sync', documentationSyncCases());
|
|
102
|
-
runSection(reporter, 'API', apiCases());
|
|
103
|
-
runSection(reporter, 'White-box', whiteBoxCases());
|
|
104
|
+
for (const section of selected) runSection(reporter, section.name, section.cases());
|
|
104
105
|
} finally {
|
|
105
106
|
fs.rmSync(tmp, { recursive: true, force: true });
|
|
106
107
|
tmp = null;
|
|
@@ -738,6 +739,14 @@ c4 ?- call((!;1)).
|
|
|
738
739
|
assertEqual(result.stderr, '', 'stderr');
|
|
739
740
|
},
|
|
740
741
|
},
|
|
742
|
+
{
|
|
743
|
+
name: 'arithmetic underflow choice differs from float-token input underflow (issue #56)',
|
|
744
|
+
run: () => {
|
|
745
|
+
const result = run('', { goal: 'catch((N is 0.1*10** -999), error(evaluation_error(underflow), _), N = underflow)' });
|
|
746
|
+
assertEqual(result.stdout, 'catch(underflow is 0.1 * 10 ** -999, error(evaluation_error(underflow), eyeprolog), underflow = underflow).\n', 'operation underflow');
|
|
747
|
+
assertEqual(run('', { goal: 'N = 0.1e-999' }).stdout, '0.0 = 0.0.\n', 'float-token input underflow');
|
|
748
|
+
},
|
|
749
|
+
},
|
|
741
750
|
{
|
|
742
751
|
name: 'float literals reject overflow and normalize underflow (issue #54)',
|
|
743
752
|
run: () => {
|
|
@@ -1519,7 +1528,7 @@ c4 ?- call((!;1)).
|
|
|
1519
1528
|
},
|
|
1520
1529
|
},
|
|
1521
1530
|
{
|
|
1522
|
-
name: 'Trealla-style DCG hand-off autoloads time/1 and
|
|
1531
|
+
name: 'Trealla-style DCG hand-off autoloads time/1 and ... //0 without quadratic occurs checks (issue #49)',
|
|
1523
1532
|
run: () => {
|
|
1524
1533
|
const engineUrl = new URL('../src/index.js', import.meta.url).href;
|
|
1525
1534
|
const script = `
|
|
@@ -1592,7 +1601,7 @@ c4 ?- call((!;1)).
|
|
|
1592
1601
|
},
|
|
1593
1602
|
},
|
|
1594
1603
|
{
|
|
1595
|
-
name: 'REPL applies conservative autoloading to interactive time/1 and consulted
|
|
1604
|
+
name: 'REPL applies conservative autoloading to interactive time/1 and consulted ... //0',
|
|
1596
1605
|
run: () => {
|
|
1597
1606
|
const filename = path.join(tmp, `issue49-handoff-${tmpCounter++}.pl`);
|
|
1598
1607
|
fs.writeFileSync(filename, 'a --> ..., epsilon.\nepsilon --> [].\n');
|
|
@@ -3399,6 +3408,33 @@ function documentationSyncCases() {
|
|
|
3399
3408
|
name: 'documentation uses EyeProlog source style',
|
|
3400
3409
|
run: () => assertArrayEqual(documentationSourceStyleIssues(), [], 'documentation source style'),
|
|
3401
3410
|
},
|
|
3411
|
+
{
|
|
3412
|
+
name: 'DCG nonterminal indicator prose uses valid ... //0 spacing (issue #49)',
|
|
3413
|
+
run: () => {
|
|
3414
|
+
for (const filename of ['README.md', 'the-art-of-eyeprolog.md', 'src/standard-library.js', 'src/solver.js']) {
|
|
3415
|
+
const text = fs.readFileSync(path.join(packageRoot, filename), 'utf8');
|
|
3416
|
+
assertNotIncludes(text, '...' + '//0', `${filename} invalid compact nonterminal indicator`);
|
|
3417
|
+
}
|
|
3418
|
+
},
|
|
3419
|
+
},
|
|
3420
|
+
{
|
|
3421
|
+
name: 'ISO 5.4 decision index inventories implementation-defined choices (issue #56)',
|
|
3422
|
+
run: () => {
|
|
3423
|
+
const filename = path.join(testRoot, 'conformance', 'ISO-IMPLEMENTATION-DEFINED.md');
|
|
3424
|
+
const text = fs.readFileSync(filename, 'utf8');
|
|
3425
|
+
for (const clause of [
|
|
3426
|
+
'5.5.11', '6.5', '6.6', '7.1.2.2', '7.1.4.1', '7.4.2.4', '7.4.2.5',
|
|
3427
|
+
'7.4.2.6', '7.4.2.7', '7.4.2.8', '7.4.2.9', '7.5.1', '7.7.1', '7.7.3',
|
|
3428
|
+
'7.10.1', '7.10.2.6', '7.10.2.7', '7.10.2.8', '7.10.2.9', '7.10.2.11',
|
|
3429
|
+
'7.10.2.13', '7.11.1.1', '7.11.1.2', '7.11.1.3', '7.11.1.4', '7.11.2.1',
|
|
3430
|
+
'7.11.2.2', '7.11.2.3', '7.11.2.5', '7.12.1', '7.12.2(f)', '8.17.1',
|
|
3431
|
+
'8.17.3', '8.17.4', '9.1.3.1', '9.1.4.1', '9.1.4.2', '9.1.4.3', '9.4',
|
|
3432
|
+
'9.4.1', '9.4.2', '9.4.3', '9.4.4', '9.4.5', 'Cor.2 9.4.6',
|
|
3433
|
+
]) assertIncludes(text, `| ${clause} |`, `ISO 5.4 clause ${clause}`);
|
|
3434
|
+
assertIncludes(text, 'Why issue #56', 'issue #56 explanation');
|
|
3435
|
+
assertIncludes(text, 'Implementation-specific features required to be documented by 5.4', '5.5 extension inventory');
|
|
3436
|
+
},
|
|
3437
|
+
},
|
|
3402
3438
|
{
|
|
3403
3439
|
name: 'book is the single implementation reference',
|
|
3404
3440
|
run: () => assertArrayEqual(bookReferenceDocumentationIssues(), [], 'book reference documentation'),
|
|
@@ -5739,18 +5775,6 @@ function runCli(args, options = {}) {
|
|
|
5739
5775
|
});
|
|
5740
5776
|
}
|
|
5741
5777
|
|
|
5742
|
-
function assertEqual(actual, expected, label) {
|
|
5743
|
-
if (actual !== expected) throw new Error(`${label} mismatch\nexpected: ${format(expected)}\nactual: ${format(actual)}`);
|
|
5744
|
-
}
|
|
5745
|
-
|
|
5746
|
-
function assertIncludes(actual, expected, label) {
|
|
5747
|
-
if (!actual.includes(expected)) throw new Error(`${label} did not include ${format(expected)}\nactual: ${format(actual)}`);
|
|
5748
|
-
}
|
|
5749
|
-
|
|
5750
|
-
function assertNotIncludes(actual, expected, label) {
|
|
5751
|
-
if (String(actual).includes(expected)) throw new Error(`${label} unexpectedly included ${format(expected)}\nactual: ${format(actual)}`);
|
|
5752
|
-
}
|
|
5753
|
-
|
|
5754
5778
|
function arrayDiffMessages(actual, expected, label) {
|
|
5755
5779
|
const messages = [];
|
|
5756
5780
|
const actualSet = new Set(actual);
|
|
@@ -5776,11 +5800,5 @@ function format(value) {
|
|
|
5776
5800
|
}
|
|
5777
5801
|
|
|
5778
5802
|
if (isMainModule(import.meta.url)) {
|
|
5779
|
-
|
|
5780
|
-
try {
|
|
5781
|
-
runRegression(reporter);
|
|
5782
|
-
reporter.totalLine();
|
|
5783
|
-
} catch (_) {
|
|
5784
|
-
process.exit(1);
|
|
5785
|
-
}
|
|
5803
|
+
await runStandalone((reporter) => runRegression(reporter, process.argv[2] ?? null));
|
|
5786
5804
|
}
|
package/test/run-wg17.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from '../src/index.js';
|
|
12
12
|
import { parseTermText } from '../src/parser.js';
|
|
13
13
|
import { variantTerms } from '../src/term.js';
|
|
14
|
-
import { TestReporter, isMainModule } from './test-style.mjs';
|
|
14
|
+
import { TestReporter, isMainModule, runStandalone } from './test-style.mjs';
|
|
15
15
|
|
|
16
16
|
const testRoot = path.dirname(fileURLToPath(import.meta.url));
|
|
17
17
|
const fixturePath = path.join(testRoot, 'conformance', 'wg17-syntax-cases.json');
|
|
@@ -377,12 +377,5 @@ export function runWg17(reporter = new TestReporter()) {
|
|
|
377
377
|
}
|
|
378
378
|
|
|
379
379
|
if (isMainModule(import.meta.url)) {
|
|
380
|
-
|
|
381
|
-
try {
|
|
382
|
-
runWg17(reporter);
|
|
383
|
-
reporter.totalLine();
|
|
384
|
-
} catch (error) {
|
|
385
|
-
process.stderr.write(`${error?.stack ?? error}\n`);
|
|
386
|
-
process.exitCode = 1;
|
|
387
|
-
}
|
|
380
|
+
await runStandalone(runWg17);
|
|
388
381
|
}
|
package/test/test-style.mjs
CHANGED
|
@@ -102,6 +102,43 @@ export function isMainModule(metaUrl) {
|
|
|
102
102
|
return process.argv[1] != null && path.resolve(process.argv[1]) === fileURLToPath(metaUrl);
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
export async function runStandalone(runSuite) {
|
|
106
|
+
const reporter = new TestReporter();
|
|
107
|
+
try {
|
|
108
|
+
await runSuite(reporter);
|
|
109
|
+
reporter.totalLine();
|
|
110
|
+
} catch (error) {
|
|
111
|
+
// TestReporter already prints failures raised inside a test. Preserve a
|
|
112
|
+
// diagnostic for setup/teardown failures that occur outside reporter.test.
|
|
113
|
+
if (reporter.ok === reporter.total) {
|
|
114
|
+
reporter.stderr.write(`${error?.stack ?? String(error)}\n`);
|
|
115
|
+
}
|
|
116
|
+
process.exitCode = 1;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function assertEqual(actual, expected, label) {
|
|
121
|
+
if (actual !== expected) {
|
|
122
|
+
throw new Error(`${label} mismatch\nexpected: ${formatValue(expected)}\nactual: ${formatValue(actual)}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function assertIncludes(actual, expected, label) {
|
|
127
|
+
if (!String(actual).includes(expected)) {
|
|
128
|
+
throw new Error(`${label} did not include ${formatValue(expected)}\nactual: ${formatValue(actual)}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function assertNotIncludes(actual, expected, label) {
|
|
133
|
+
if (String(actual).includes(expected)) {
|
|
134
|
+
throw new Error(`${label} unexpectedly included ${formatValue(expected)}\nactual: ${formatValue(actual)}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function formatValue(value) {
|
|
139
|
+
return typeof value === 'string' ? JSON.stringify(value) : String(value);
|
|
140
|
+
}
|
|
141
|
+
|
|
105
142
|
function defaultSectionLabel(name) {
|
|
106
143
|
return String(name)
|
|
107
144
|
.replace(/^Conformance\s+/, 'conformance ')
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Shared fixtures and filesystem helpers used across test suites.
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
const libraryCall = /\b(?:uuid|difference|maplist|foldl|call_nth|lt|gt|le|ge|between|smallest_divisor_from|random|matches|split|replace|lowercase|uppercase|trim|number_string|atom_string|term_string|append|string_concat|contains|join|substring|member|select|last|nth0|nth1|set_nth0|take|drop|slice|reverse|length|sum_list|min_list|max_list|list_to_set|countall|sumall|aggregate_min|aggregate_max)\s*\(/;
|
|
6
|
+
|
|
7
|
+
const standardModulePrelude = `:- use_module(library(aggregate)).
|
|
8
|
+
:- use_module(library(comparison)).
|
|
9
|
+
:- use_module(library(dates)).
|
|
10
|
+
:- use_module(library(iso_ext)).
|
|
11
|
+
:- use_module(library(lists)).
|
|
12
|
+
:- use_module(library(primes)).
|
|
13
|
+
:- use_module(library(prologue), [between/3]).
|
|
14
|
+
:- use_module(library(random)).
|
|
15
|
+
:- use_module(library(strings)).
|
|
16
|
+
:- use_module(library(uuid)).
|
|
17
|
+
`;
|
|
18
|
+
|
|
19
|
+
export function withStandardModules(source) {
|
|
20
|
+
const text = String(source);
|
|
21
|
+
if (!libraryCall.test(text) || text.includes('use_module(library(') || text.includes(':- module(')) return text;
|
|
22
|
+
return `${standardModulePrelude}${text}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function listPrologFiles(base, dir = base) {
|
|
26
|
+
const files = [];
|
|
27
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
28
|
+
const full = path.join(dir, entry.name);
|
|
29
|
+
if (entry.isDirectory()) {
|
|
30
|
+
files.push(...listPrologFiles(base, full));
|
|
31
|
+
} else if (entry.isFile() && entry.name.endsWith('.pl')) {
|
|
32
|
+
files.push(path.relative(base, full).split(path.sep).join('/'));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return files.sort();
|
|
36
|
+
}
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -5668,7 +5668,7 @@ round-tripping. The checked answers are in
|
|
|
5668
5668
|
|
|
5669
5669
|
#### Deep sequence hand-off
|
|
5670
5670
|
|
|
5671
|
-
`library(iso_ext)` provides the common
|
|
5671
|
+
`library(iso_ext)` provides the common `... //0` helper, which describes an
|
|
5672
5672
|
arbitrary number of input elements. It is not part of ISO Part 3, but it is a
|
|
5673
5673
|
useful interoperability and stress-test relation. A compact hand-off test is:
|
|
5674
5674
|
|
|
@@ -5677,7 +5677,7 @@ a --> ..., epsilon.
|
|
|
5677
5677
|
epsilon --> [].
|
|
5678
5678
|
```
|
|
5679
5679
|
|
|
5680
|
-
Here the remaining sequence is repeatedly passed from
|
|
5680
|
+
Here the remaining sequence is repeatedly passed from `... //0` to another
|
|
5681
5681
|
nonterminal. For a finite compact list, EyeProlog can scan the arbitrary
|
|
5682
5682
|
sequence iteratively instead of consuming one ordinary solver depth level per
|
|
5683
5683
|
list cell. If the continuation is structurally proven to be a zero-width
|
|
@@ -6207,7 +6207,7 @@ The current interoperability profile recognizes these library roles:
|
|
|
6207
6207
|
| Library | Role in the interoperability profile |
|
|
6208
6208
|
| --- | --- |
|
|
6209
6209
|
| `library(lists)` | Common list module. A conservative subset of its exports is in the shared predicate profile. |
|
|
6210
|
-
| `library(iso_ext)` | Common extension-module name. `call_nth/2`, `time/1`, and the
|
|
6210
|
+
| `library(iso_ext)` | Common extension-module name. `call_nth/2`, `time/1`, and the `... //0` arbitrary-sequence helper are conservatively autoloaded for cross-engine source. |
|
|
6211
6211
|
| `library(lambda)` | Scryer-aligned higher-order notation. It is imported explicitly because loading it also installs the `+\` operator. |
|
|
6212
6212
|
| `library(prologue)` | EyeProlog compatibility module, not a common interop library name. `between/3` is nevertheless autoloaded from it so portable source need not name this EyeProlog-specific provider. |
|
|
6213
6213
|
|
|
@@ -6234,7 +6234,7 @@ recovery headroom so finite-heap exhaustion remains a catchable
|
|
|
6234
6234
|
EyeProlog API belongs to the shared profile. `call_nth/2`, `time/1`, and
|
|
6235
6235
|
`.../2` are mapped there. `time/1` measures each solution of a meta-call and
|
|
6236
6236
|
prints elapsed time, EyeProlog inference count, and MLips in Trealla-style form,
|
|
6237
|
-
for example `% Time elapsed 0.832s, 65551 Inferences, 0.079 MLips`;
|
|
6237
|
+
for example `% Time elapsed 0.832s, 65551 Inferences, 0.079 MLips`; `... //0`
|
|
6238
6238
|
describes an arbitrary number of input elements. Together they let the
|
|
6239
6239
|
Trealla/Scryer DCG hand-off benchmark run in EyeProlog without source changes
|
|
6240
6240
|
(assuming the usual list library is already imported in an interactive
|
|
@@ -7416,6 +7416,10 @@ Part 1 conformance audit. It distinguishes implemented/tested families from
|
|
|
7416
7416
|
requirements whose normative `shall` clauses, option combinations, or error
|
|
7417
7417
|
precedence still need one-by-one closure. `test/conformance/ISO-MATRIX.md`
|
|
7418
7418
|
maps language families to representative executable cases.
|
|
7419
|
+
`test/conformance/ISO-IMPLEMENTATION-DEFINED.md` is the ISO 5.4 decision
|
|
7420
|
+
index: it enumerates the Part 1 implementation-defined decisions, the
|
|
7421
|
+
implementation-specific extension families, and any remaining strict-mode audit
|
|
7422
|
+
gaps without turning draft WG17/STC proposals into the licensed baseline.
|
|
7419
7423
|
`test/conformance/WG17-SYNTAX-STATUS.md` separately traces the vendored active
|
|
7420
7424
|
upstream syntax cases. Reviewed cases can pin exact strict-reader outcomes, while
|
|
7421
7425
|
newly upgraded cases execute directly against the upstream Codex expectation.
|