eyeprolog 1.5.72 → 1.5.75
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 +10 -2
- package/examples/deck/symbiotic-knowledge-graphs.md +1 -1
- package/package.json +4 -30
- package/src/repl.js +16 -4
- package/test/README.md +33 -0
- package/test/benchmark.mjs +3 -3
- package/test/conformance/ISO-COMPLIANCE.md +6 -6
- package/test/conformance/NEUMERKEL-LATEST.md +3 -3
- package/test/conformance/NEUMERKEL-LIVE.md +6 -6
- package/test/conformance/README.md +16 -16
- package/test/conformance/WG17-SYNTAX-STATUS.md +2 -2
- package/test/neumerkel.mjs +6 -6
- package/test/regression/cases-documentation-sync.mjs +4 -1
- package/test/regression/cases-regression.mjs +12 -0
- package/test/run-neumerkel-tests.mjs +5 -9
- package/test/run-neumerkel.mjs +2 -2
- package/the-art-of-eyeprolog.md +9 -9
- package/tools/generate-library-autoload-index.mjs +1 -1
- package/tools/generate-predicate-reference.mjs +1 -1
- package/tools/report-wg17-syntax-coverage.mjs +3 -3
- package/tools/upgrade-wg17.mjs +3 -3
package/README.md
CHANGED
|
@@ -84,7 +84,7 @@ The checked [Symbiotic Knowledge Graphs example](examples/symbiotic-knowledge-gr
|
|
|
84
84
|
The same RDF → Prolog → RDF boundary is exercised by five additional checked scenarios: [cross-organization data sharing](https://eyereasoner.github.io/eyeprolog/examples/deck/cross-organization-data-sharing), [explainable EV-depot configuration](https://eyereasoner.github.io/eyeprolog/examples/deck/explainable-ev-depot-configuration), [operational incident response](https://eyereasoner.github.io/eyeprolog/examples/deck/operational-incident-response), [software supply-chain vulnerability response](https://eyereasoner.github.io/eyeprolog/examples/deck/sbom-vulnerability-response), and a [scientific evidence graph](https://eyereasoner.github.io/eyeprolog/examples/deck/scientific-evidence-graph). Together they cover policy decisions, reversible configuration reasoning, dependency-graph diagnosis, transitive SBOM exposure, and evidence aggregation with explicit disagreement.
|
|
85
85
|
|
|
86
86
|
## Benchmarks
|
|
87
|
-
EyeProlog has 19 checksum-protected wall-clock benchmarks spanning recursion/indexing, constraints, tabling/WFS, DCGs, Eyelet, search, term I/O, attributes, rewriting, and the classic Prolog naive-reverse workload. Short workloads are adaptively batched before timing so millisecond-scale noise is not mistaken for a regression. Run `npm run benchmark`; create a machine-local comparison point with `npm run benchmark
|
|
87
|
+
EyeProlog has 19 checksum-protected wall-clock benchmarks spanning recursion/indexing, constraints, tabling/WFS, DCGs, Eyelet, search, term I/O, attributes, rewriting, and the classic Prolog naive-reverse workload. Short workloads are adaptively batched before timing so millisecond-scale noise is not mistaken for a regression. Run `npm run benchmark`; create a machine-local comparison point with `npm run benchmark -- --save .benchmarks/baseline.json`; use `node test/run-benchmark-tests.mjs` for harness checks. The checked [`examples/bench.pl`](examples/bench.pl) preserves the classic Quintus 1984 `nrev/2` workload on a 30-element list. For a comparable LIPS number, run `node test/lips-benchmark.mjs`: it executes the classic failure-driven `dobench/1` and `dodummy/1` loops in Prolog, subtracts dummy-loop CPU time, and applies the historical 496 procedure calls per reversal. The generic benchmark table still shows a quick wall-clock LIPS estimate for `classic-nrev`, but `node test/lips-benchmark.mjs` is the canonical engine-speed measurement. LIPS is a historical basic-engine-speed indicator, not a whole-system performance score. Details are in [*The Art of EyeProlog*](the-art-of-eyeprolog.md).
|
|
88
88
|
For the project policy on post-ISO-standard and WG17 compatibility features such as digit separators, see [ISO/WG17 compatibility extensions](test/conformance/ISO-WG17-EXTENSIONS.md).
|
|
89
89
|
## Development
|
|
90
90
|
```sh
|
|
@@ -93,5 +93,13 @@ cd eyeprolog
|
|
|
93
93
|
npm install
|
|
94
94
|
npm test
|
|
95
95
|
```
|
|
96
|
-
|
|
96
|
+
The npm command list is deliberately small:
|
|
97
|
+
|
|
98
|
+
- `npm test` (or `npm run test`): run the release gate, including live upstream conformity checks.
|
|
99
|
+
- `npm run conformance:update:wg17`: update the vendored WG17 syntax cases and their inventory documentation.
|
|
100
|
+
- `npm run generate`: rebuild generated library and book files.
|
|
101
|
+
- `npm run benchmark`: run the wall-clock benchmarks.
|
|
102
|
+
|
|
103
|
+
Use `npm test -- --offline` for a network-free local pass. Focused checks remain available directly, for example `node test/run-regression.mjs docs` or `node test/run-wg17.mjs`; see [test runners](test/README.md). The automatic version hooks still run the release gate, refresh and stage conformance reports, and push the release. Detailed upstream report maintenance is documented in the [conformance guide](test/conformance/README.md).
|
|
104
|
+
|
|
97
105
|
EyeProlog is released under the [MIT License](LICENSE.md).
|
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.75",
|
|
7
7
|
"description": "EyeProlog turns facts and rules into answers and proofs.",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"main": "./index.js",
|
|
@@ -46,36 +46,10 @@
|
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"test": "node test/run-all.mjs",
|
|
49
|
-
"
|
|
50
|
-
"test:conformance": "node test/run-conformance-all.mjs",
|
|
51
|
-
"test:conformance:offline": "node test/run-conformance-all.mjs --offline",
|
|
52
|
-
"test:neumerkel": "node test/run-neumerkel.mjs",
|
|
53
|
-
"test:neumerkel:cached": "node test/run-neumerkel.mjs --cached",
|
|
54
|
-
"test:neumerkel:harness": "node test/run-neumerkel-tests.mjs",
|
|
55
|
-
"test:iso": "node test/run-iso-strict.mjs",
|
|
56
|
-
"test:wg17": "node test/run-wg17.mjs",
|
|
57
|
-
"test:regression": "node test/run-regression.mjs",
|
|
58
|
-
"test:examples": "node test/run-examples.mjs",
|
|
59
|
-
"test:interop": "node test/run-interop.mjs",
|
|
60
|
-
"test:playground": "node test/run-playground.mjs",
|
|
61
|
-
"test:architecture": "node test/run-architecture.mjs",
|
|
62
|
-
"test:openrulebench": "node test/run-openrulebench.mjs",
|
|
63
|
-
"test:benchmark": "node test/run-benchmark-tests.mjs",
|
|
49
|
+
"conformance:update:wg17": "node tools/upgrade-wg17.mjs",
|
|
64
50
|
"benchmark": "node test/benchmark.mjs",
|
|
65
|
-
"benchmark:lips": "node test/lips-benchmark.mjs",
|
|
66
|
-
"benchmark:baseline": "node test/benchmark.mjs --save .benchmarks/baseline.json",
|
|
67
|
-
"conformance:update": "npm run conformance:update:wg17 && npm run conformance:update:neumerkel",
|
|
68
|
-
"conformance:report": "node test/run-conformance-report.mjs conformance-report.md",
|
|
69
51
|
"generate": "node tools/generate-library-autoload-index.mjs && node tools/generate-predicate-reference.mjs && node tools/extract-book-examples.mjs",
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
"preversion": "npm test && npm run conformance:sync:neumerkel && npm run conformance:report && git add test/conformance/NEUMERKEL-LATEST.md conformance-report.md",
|
|
73
|
-
"postversion": "git push origin HEAD --follow-tags",
|
|
74
|
-
"conformance:update:wg17": "node tools/upgrade-wg17.mjs",
|
|
75
|
-
"conformance:update:neumerkel": "node test/run-neumerkel.mjs --update-report",
|
|
76
|
-
"conformance:check:neumerkel": "node test/run-neumerkel.mjs --cached --verify-report",
|
|
77
|
-
"conformance:sync:neumerkel": "node test/run-neumerkel.mjs --cached --update-report",
|
|
78
|
-
"test:http-json": "node test/run-http-json.mjs",
|
|
79
|
-
"test:iso-part2-amendment": "node test/run-iso-part2-amendment.mjs"
|
|
52
|
+
"preversion": "npm test && node test/run-neumerkel.mjs --cached --update-report && node test/run-conformance-report.mjs conformance-report.md && git add test/conformance/NEUMERKEL-LATEST.md conformance-report.md",
|
|
53
|
+
"postversion": "git push origin HEAD --follow-tags"
|
|
80
54
|
}
|
|
81
55
|
}
|
package/src/repl.js
CHANGED
|
@@ -773,6 +773,18 @@ function formatAnswer(engine, state, variables, env) {
|
|
|
773
773
|
doubleBar: !state.strictIso,
|
|
774
774
|
};
|
|
775
775
|
let generated = 0;
|
|
776
|
+
// Names generated for otherwise-anonymous variables (`_A`, `_B`, ...) must
|
|
777
|
+
// never collide with a name the query itself is already using for a
|
|
778
|
+
// *different* variable (e.g. a query literally naming `_A`) — printing an
|
|
779
|
+
// internal fresh variable as `_A` right next to the user's own `_A` binding
|
|
780
|
+
// falsely suggests they are the same variable (issue #108).
|
|
781
|
+
const usedDisplayNames = new Set(names.values());
|
|
782
|
+
const nextGeneratedName = () => {
|
|
783
|
+
let candidate;
|
|
784
|
+
do { candidate = `_${letterName(generated++)}`; } while (usedDisplayNames.has(candidate));
|
|
785
|
+
usedDisplayNames.add(candidate);
|
|
786
|
+
return candidate;
|
|
787
|
+
};
|
|
776
788
|
|
|
777
789
|
// Meta-predicate wrappers can make a query variable alias an internal fresh
|
|
778
790
|
// variable. Keep residual constraints tied to the query's visible name.
|
|
@@ -780,7 +792,7 @@ function formatAnswer(engine, state, variables, env) {
|
|
|
780
792
|
const root = engine.deref(variable, env);
|
|
781
793
|
if (root.type === 'var' && !names.has(root.name)) names.set(root.name, variable.name);
|
|
782
794
|
}
|
|
783
|
-
for (const variable of variables) collectUnboundVariables(engine, variable, env, names,
|
|
795
|
+
for (const variable of variables) collectUnboundVariables(engine, variable, env, names, nextGeneratedName);
|
|
784
796
|
for (const variable of variables) {
|
|
785
797
|
const value = engine.deref(variable, env);
|
|
786
798
|
if (value.type === 'var' &&
|
|
@@ -796,7 +808,7 @@ function formatAnswer(engine, state, variables, env) {
|
|
|
796
808
|
for (const constraint of env.variableConstraints?.() ?? []) {
|
|
797
809
|
const residual = constraint.residualGoal?.(env);
|
|
798
810
|
if (residual == null) continue;
|
|
799
|
-
collectUnboundVariables(engine, residual, env, names,
|
|
811
|
+
collectUnboundVariables(engine, residual, env, names, nextGeneratedName);
|
|
800
812
|
bindings.push(engine.formatTermForWrite(residual, env, answerWriteOptions));
|
|
801
813
|
}
|
|
802
814
|
// Residual attributes are part of the answer even when the attributed
|
|
@@ -821,9 +833,9 @@ function formatAnswer(engine, state, variables, env) {
|
|
|
821
833
|
for (const root of attributeRoots) {
|
|
822
834
|
if (projectedAttributeRoots.has(root.name) || !env.hasPrologAttributes?.(root.name)) continue;
|
|
823
835
|
projectedAttributeRoots.add(root.name);
|
|
824
|
-
if (!names.has(root.name)) names.set(root.name,
|
|
836
|
+
if (!names.has(root.name)) names.set(root.name, nextGeneratedName());
|
|
825
837
|
for (const residual of state.solver.attributeResidualGoals(root, env)) {
|
|
826
|
-
collectUnboundVariables(engine, residual, env, names,
|
|
838
|
+
collectUnboundVariables(engine, residual, env, names, nextGeneratedName);
|
|
827
839
|
bindings.push(engine.formatTermForWrite(residual, env, answerWriteOptions));
|
|
828
840
|
}
|
|
829
841
|
}
|
package/test/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Test runners
|
|
2
|
+
|
|
3
|
+
Normally, run `npm test`. Use `npm test -- --offline` to skip live upstream fetching.
|
|
4
|
+
`npm run conformance:update:wg17` refreshes the vendored WG17 snapshot.
|
|
5
|
+
|
|
6
|
+
For development, run a focused check directly from the repository root:
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
node test/run-regression.mjs # all regression sections
|
|
10
|
+
node test/run-regression.mjs docs # documentation checks only
|
|
11
|
+
node test/run-conformance-all.mjs # conformance layers; accepts --offline
|
|
12
|
+
node test/run-wg17.mjs # vendored WG17 syntax
|
|
13
|
+
node test/run-iso-strict.mjs # strict ISO core
|
|
14
|
+
node test/run-iso-part2-amendment.mjs
|
|
15
|
+
node test/run-neumerkel.mjs # live upstream; --cached reproduces last fetch
|
|
16
|
+
node test/run-neumerkel-tests.mjs # upstream-fetch harness
|
|
17
|
+
node test/run-examples.mjs
|
|
18
|
+
node test/run-playground.mjs
|
|
19
|
+
node test/run-architecture.mjs
|
|
20
|
+
node test/run-openrulebench.mjs
|
|
21
|
+
node test/run-http-json.mjs
|
|
22
|
+
node test/run-interop.mjs # requires the comparison engines
|
|
23
|
+
node test/run-benchmark-tests.mjs # benchmark harness
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
These runners retain their existing options; there is no separate npm alias for
|
|
27
|
+
each one. Focused checks do not replace the full release gate.
|
|
28
|
+
|
|
29
|
+
For performance measurements, use `npm run benchmark`. Save a local baseline with
|
|
30
|
+
`npm run benchmark -- --save .benchmarks/baseline.json`, or run
|
|
31
|
+
`node test/lips-benchmark.mjs` for the classic LIPS measurement.
|
|
32
|
+
|
|
33
|
+
See the [conformance guide](conformance/README.md) for report maintenance.
|
package/test/benchmark.mjs
CHANGED
|
@@ -102,7 +102,7 @@ if (baselinePath != null) {
|
|
|
102
102
|
const loaded = JSON.parse(await fs.readFile(baselinePath, 'utf8'));
|
|
103
103
|
if (loaded.format === 2) baseline = loaded;
|
|
104
104
|
else {
|
|
105
|
-
baselineWarning = `Ignoring legacy timing baseline format ${loaded.format ?? 'unknown'}; regenerate it with npm run benchmark
|
|
105
|
+
baselineWarning = `Ignoring legacy timing baseline format ${loaded.format ?? 'unknown'}; regenerate it with npm run benchmark -- --save .benchmarks/baseline.json.`;
|
|
106
106
|
baselinePath = null;
|
|
107
107
|
}
|
|
108
108
|
}
|
|
@@ -296,10 +296,10 @@ if (options.json) {
|
|
|
296
296
|
|
|
297
297
|
process.stdout.write(`\n${results.length} benchmarks; ${options.runs} measured batch${options.runs === 1 ? '' : 'es'} each after ${options.warmup} warm-up batch${options.warmup === 1 ? '' : 'es'}, calibrated after one priming execution toward ${options.targetMs} ms per batch.\n`);
|
|
298
298
|
if (baselineWarning) process.stdout.write(`${baselineWarning}\n`);
|
|
299
|
-
if (baselinePath == null && !baselineWarning) process.stdout.write('No timing baseline found; run npm run benchmark
|
|
299
|
+
if (baselinePath == null && !baselineWarning) process.stdout.write('No timing baseline found; run npm run benchmark -- --save .benchmarks/baseline.json to create .benchmarks/baseline.json.\n');
|
|
300
300
|
if (options.save != null) process.stdout.write(`Saved timing baseline: ${path.relative(root, options.save)}\n`);
|
|
301
301
|
process.stdout.write('Change compares the current median/op directly with the saved baseline median/op; the measured range is shown separately.\n');
|
|
302
|
-
process.stdout.write('The classic-nrev LIPS column is a quick wall-clock estimate. Use
|
|
302
|
+
process.stdout.write('The classic-nrev LIPS column is a quick wall-clock estimate. Use node test/lips-benchmark.mjs for the Quintus-style dummy-subtracted CPU measurement.\n');
|
|
303
303
|
if (summary.comparable > 0) {
|
|
304
304
|
process.stdout.write('Suite score is the geometric mean of current/baseline ratios, so every benchmark has equal relative weight; Time-weighted total compares summed medians and is dominated by longer workloads.\n');
|
|
305
305
|
}
|
|
@@ -286,14 +286,14 @@ architecture gates. The upstream case counts are discovered dynamically.
|
|
|
286
286
|
Useful focused commands are:
|
|
287
287
|
|
|
288
288
|
```sh
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
npm
|
|
289
|
+
node test/run-conformance-all.mjs
|
|
290
|
+
node test/run-neumerkel.mjs
|
|
291
|
+
node test/run-iso-strict.mjs
|
|
292
|
+
node test/run-wg17.mjs
|
|
293
|
+
npm test -- --offline
|
|
294
294
|
```
|
|
295
295
|
|
|
296
|
-
`test
|
|
296
|
+
`npm test -- --offline` is a development/reproduction aid; it is not sufficient for a
|
|
297
297
|
release claim that says EyeProlog passes the latest Neumerkel suites. Expected
|
|
298
298
|
conformance outputs are never auto-accepted.
|
|
299
299
|
|
|
@@ -31,8 +31,8 @@ Counts are output from upstream, not hard-coded test constants.
|
|
|
31
31
|
Exact fetched bytes, SHA-256 hashes, fetch timestamps, and HTTP validators remain under
|
|
32
32
|
Git-ignored `.cache/neumerkel/` for local inspection/reproduction and are intentionally not committed.
|
|
33
33
|
A normal test run warns when this tracked report is stale. Refresh directly from live
|
|
34
|
-
upstream with `
|
|
35
|
-
snapshot already fetched by `npm test` with `
|
|
36
|
-
`
|
|
34
|
+
upstream with `node test/run-neumerkel.mjs --update-report`, or sync the exact successful
|
|
35
|
+
snapshot already fetched by `npm test` with `node test/run-neumerkel.mjs --cached --update-report`.
|
|
36
|
+
`node test/run-neumerkel.mjs --cached --verify-report` verifies the tracked report against that last
|
|
37
37
|
successful live snapshot without fetching upstream a second time.
|
|
38
38
|
|
|
@@ -4,7 +4,7 @@ EyeProlog treats Ulrich Neumerkel's current ISO/WG17 conformity material as a
|
|
|
4
4
|
moving upstream release gate, not as a frozen snapshot with permanent case
|
|
5
5
|
counts.
|
|
6
6
|
|
|
7
|
-
`
|
|
7
|
+
`node test/run-neumerkel.mjs` fetches these seven TU Wien sources on every live run:
|
|
8
8
|
|
|
9
9
|
1. `conformity_testing` — Part 1 syntax/reader/writer matrix;
|
|
10
10
|
2. `number_chars_cont_quad.pl` — `number_chars/2` continuation corpus;
|
|
@@ -32,13 +32,13 @@ Markdown no longer matches, the test still reflects engine conformance and print
|
|
|
32
32
|
warning with the refresh command:
|
|
33
33
|
|
|
34
34
|
```sh
|
|
35
|
-
|
|
35
|
+
node test/run-neumerkel.mjs --update-report
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
Commit the resulting `test/conformance/NEUMERKEL-LATEST.md` after reviewing the
|
|
39
|
-
change. After a successful `npm test`, `
|
|
39
|
+
change. After a successful `npm test`, `node test/run-neumerkel.mjs --cached --update-report` writes
|
|
40
40
|
the tracked report from the exact cached source bytes that just passed, avoiding a
|
|
41
|
-
second network fetch. `
|
|
41
|
+
second network fetch. `node test/run-neumerkel.mjs --cached --verify-report` verifies the tracked
|
|
42
42
|
report against that same last successful snapshot. The npm version lifecycle
|
|
43
43
|
uses this race-free sync path and stages the generated reports into the release
|
|
44
44
|
commit. The tracked report intentionally omits fetch timestamps and HTTP validators,
|
|
@@ -53,14 +53,14 @@ Git-ignored: it is an inspection/reproduction cache, not published project evide
|
|
|
53
53
|
Use:
|
|
54
54
|
|
|
55
55
|
```sh
|
|
56
|
-
|
|
56
|
+
node test/run-neumerkel.mjs
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
for the canonical live check. For offline reproduction of the exact last live
|
|
60
60
|
fetch, use:
|
|
61
61
|
|
|
62
62
|
```sh
|
|
63
|
-
|
|
63
|
+
node test/run-neumerkel.mjs --cached
|
|
64
64
|
```
|
|
65
65
|
|
|
66
66
|
The cached command never claims to check the latest upstream suites by itself.
|
|
@@ -83,39 +83,39 @@ npm test
|
|
|
83
83
|
For an offline-only development pass:
|
|
84
84
|
|
|
85
85
|
```sh
|
|
86
|
-
npm
|
|
86
|
+
npm test -- --offline
|
|
87
87
|
```
|
|
88
88
|
|
|
89
89
|
The conformance commands are:
|
|
90
90
|
|
|
91
91
|
```sh
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
92
|
+
node test/run-conformance-all.mjs # live Neumerkel + local ISO/conformance layers
|
|
93
|
+
node test/run-conformance-all.mjs --offline # same local layers, no network
|
|
94
|
+
node test/run-neumerkel.mjs # the seven live upstream suites only
|
|
95
|
+
node test/run-neumerkel.mjs --cached # exact last fetched bytes; reproduction only
|
|
96
|
+
node test/run-neumerkel.mjs --cached --verify-report # verify tracked report against last successful live snapshot
|
|
97
|
+
node test/run-iso-strict.mjs # Part 1 + Corrigenda strict-core processor gate
|
|
98
|
+
node test/run-iso-part2-amendment.mjs # 2013 Part 2 amendment module requirements
|
|
99
|
+
node test/run-wg17.mjs # vendored reviewed WG17 syntax regression
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
-
`test
|
|
103
|
-
fetch because a cache exists. The runner discovers the number of active tests from those sources and fails on any newly introduced case EyeProlog does not pass. If the stable, tracked [NEUMERKEL-LATEST.md](NEUMERKEL-LATEST.md) is stale, normal tests warn rather than turning a passing engine run into a failure. `
|
|
102
|
+
`node test/run-neumerkel.mjs` always fetches the current TU Wien sources. It does not skip a
|
|
103
|
+
fetch because a cache exists. The runner discovers the number of active tests from those sources and fails on any newly introduced case EyeProlog does not pass. If the stable, tracked [NEUMERKEL-LATEST.md](NEUMERKEL-LATEST.md) is stale, normal tests warn rather than turning a passing engine run into a failure. `node test/run-neumerkel.mjs --update-report` performs a fresh live run and refreshes the report; after `npm test`, `node test/run-neumerkel.mjs --cached --update-report` refreshes it from the exact successful cached snapshot; and `node test/run-neumerkel.mjs --cached --verify-report` verifies that snapshot without a second network fetch. Exact bytes, SHA-256 hashes, timestamps, and HTTP validators stay under Git-ignored `.cache/neumerkel/` for inspection/reproduction only. See [NEUMERKEL-LIVE.md](NEUMERKEL-LIVE.md).
|
|
104
104
|
|
|
105
|
-
The vendored WG17 syntax snapshot is intentionally secondary. Update
|
|
105
|
+
The vendored WG17 syntax snapshot is intentionally secondary. Update it with `npm run conformance:update:wg17`. For focused upstream report maintenance and verification:
|
|
106
106
|
|
|
107
107
|
```sh
|
|
108
108
|
npm run conformance:update:wg17
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
node test/run-neumerkel.mjs --update-report
|
|
110
|
+
node test/run-neumerkel.mjs --cached --update-report
|
|
111
|
+
node test/run-wg17.mjs
|
|
112
112
|
```
|
|
113
113
|
|
|
114
114
|
Every vendored syntax case is still checked against its upstream Codex
|
|
115
115
|
expectation; reviewed exact outcomes are an additional regression lock rather
|
|
116
116
|
than an alternative acceptance rule.
|
|
117
117
|
|
|
118
|
-
Regenerate the top-level local-corpus report with `
|
|
118
|
+
Regenerate the top-level local-corpus report with `node test/run-conformance-report.mjs conformance-report.md`. It links to [NEUMERKEL-LATEST.md](NEUMERKEL-LATEST.md) rather than duplicating live evidence. Counts in the tracked Neumerkel report are generated evidence, not permanent constants in project policy.
|
|
119
119
|
|
|
120
120
|
Run a matching local file-based conformance subset directly with:
|
|
121
121
|
|
|
@@ -46,6 +46,6 @@ None.
|
|
|
46
46
|
|
|
47
47
|
## Maintenance
|
|
48
48
|
|
|
49
|
-
1. Run `npm run conformance:update` to reconcile the dated fixture with upstream.
|
|
49
|
+
1. Run `npm run conformance:update:wg17` to reconcile the dated fixture with upstream.
|
|
50
50
|
2. Review every new or changed ISO expectation before adding its expected outcome.
|
|
51
|
-
3. Run `
|
|
51
|
+
3. Run `node test/run-wg17.mjs` and keep this generated status page synchronized.
|
package/test/neumerkel.mjs
CHANGED
|
@@ -59,8 +59,8 @@ async function fetchOne(source) {
|
|
|
59
59
|
} catch (error) {
|
|
60
60
|
throw new Error(
|
|
61
61
|
`Neumerkel live fetch failed for ${source.key} (${source.url}). ` +
|
|
62
|
-
'The canonical conformance gate requires network access; use npm
|
|
63
|
-
'for a network-free local run or
|
|
62
|
+
'The canonical conformance gate requires network access; use npm test -- --offline ' +
|
|
63
|
+
'for a network-free local run or node test/run-neumerkel.mjs --cached to reproduce the last live fetch.',
|
|
64
64
|
{ cause: error },
|
|
65
65
|
);
|
|
66
66
|
}
|
|
@@ -88,7 +88,7 @@ async function fetchOne(source) {
|
|
|
88
88
|
function readCachedOne(source, cacheDir) {
|
|
89
89
|
const filename = path.join(cacheDir, source.filename);
|
|
90
90
|
if (!fs.existsSync(filename)) {
|
|
91
|
-
throw new Error(`Neumerkel cache is incomplete: missing ${path.relative(packageRoot, filename)}; run
|
|
91
|
+
throw new Error(`Neumerkel cache is incomplete: missing ${path.relative(packageRoot, filename)}; run node test/run-neumerkel.mjs online first`);
|
|
92
92
|
}
|
|
93
93
|
const bytes = new Uint8Array(fs.readFileSync(filename));
|
|
94
94
|
return {
|
|
@@ -476,9 +476,9 @@ export function formatNeumerkelMarkdown({ summary }) {
|
|
|
476
476
|
'Exact fetched bytes, SHA-256 hashes, fetch timestamps, and HTTP validators remain under',
|
|
477
477
|
'Git-ignored `.cache/neumerkel/` for local inspection/reproduction and are intentionally not committed.',
|
|
478
478
|
'A normal test run warns when this tracked report is stale. Refresh directly from live',
|
|
479
|
-
'upstream with `
|
|
480
|
-
'snapshot already fetched by `npm test` with `
|
|
481
|
-
'`
|
|
479
|
+
'upstream with `node test/run-neumerkel.mjs --update-report`, or sync the exact successful',
|
|
480
|
+
'snapshot already fetched by `npm test` with `node test/run-neumerkel.mjs --cached --update-report`.',
|
|
481
|
+
'`node test/run-neumerkel.mjs --cached --verify-report` verifies the tracked report against that last',
|
|
482
482
|
'successful live snapshot without fetching upstream a second time.',
|
|
483
483
|
'',
|
|
484
484
|
);
|
|
@@ -649,7 +649,10 @@ ${profile}`;
|
|
|
649
649
|
const publishIndex = publishWorkflow.indexOf('run: npm publish');
|
|
650
650
|
assertEqual(testIndex >= 0 && testIndex < publishIndex, true, 'publish workflow test gate');
|
|
651
651
|
assertEqual(packIndex >= 0 && packIndex < publishIndex, true, 'publish workflow package gate');
|
|
652
|
-
|
|
652
|
+
assertArrayEqual(Object.keys(pkg.scripts).sort(), ['benchmark', 'conformance:update:wg17', 'generate', 'postversion', 'preversion', 'test'], 'small npm command surface');
|
|
653
|
+
assertEqual(pkg.scripts.test, 'node test/run-all.mjs', 'full release gate');
|
|
654
|
+
const runner = fs.readFileSync(path.join(packageRoot, 'test', 'run-all.mjs'), 'utf8');
|
|
655
|
+
assertIncludes(runner, 'runOpenRuleBenchChecks(reporter)', 'OpenRuleBench remains in release gate');
|
|
653
656
|
},
|
|
654
657
|
},
|
|
655
658
|
{
|
|
@@ -28,6 +28,18 @@ import {
|
|
|
28
28
|
|
|
29
29
|
export function regressionCases() {
|
|
30
30
|
return [
|
|
31
|
+
{
|
|
32
|
+
name: 'top level never mints a generated variable name that collides with a query variable\'s own name (issue #108)',
|
|
33
|
+
run: () => {
|
|
34
|
+
const result = runCli([], { input: 'length(L,1),_A=99.\nhalt.\n' });
|
|
35
|
+
assertEqual(result.status, 0, result.stderr);
|
|
36
|
+
// `_A` is a real query variable here, not the top level's own first
|
|
37
|
+
// generated name. Printing the anonymous variable inside L as `_A` as
|
|
38
|
+
// well would misleadingly suggest it is the same variable as the
|
|
39
|
+
// query's own `_A`, which is bound to 99 and unrelated to L.
|
|
40
|
+
assertIncludes(result.stdout, '?- L = [_B], _A = 99.\n', 'generated name skips the query\'s own _A');
|
|
41
|
+
},
|
|
42
|
+
},
|
|
31
43
|
{
|
|
32
44
|
name: 'REPL current_input/1 and current_output/1 fail on closed stream handles (issue #107)',
|
|
33
45
|
run: () => {
|
|
@@ -123,17 +123,13 @@ export function runNeumerkelHarnessTests(reporter = new TestReporter()) {
|
|
|
123
123
|
reporter.test('release workflow reuses the successful live snapshot instead of refetching', () => {
|
|
124
124
|
const pkg = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
|
|
125
125
|
const scripts = pkg.scripts ?? {};
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
if (scripts['conformance:check:neumerkel'] !== 'node test/run-neumerkel.mjs --cached --verify-report') {
|
|
130
|
-
throw new Error('Neumerkel report check must not refetch live upstream');
|
|
131
|
-
}
|
|
132
|
-
if (!String(scripts.preversion ?? '').includes('conformance:sync:neumerkel')) {
|
|
126
|
+
const releaseSteps = String(scripts.preversion ?? '').split(' && ');
|
|
127
|
+
if (releaseSteps[0] !== 'npm test' || releaseSteps[1] !== 'node test/run-neumerkel.mjs --cached --update-report') {
|
|
133
128
|
throw new Error('preversion must synchronize the tracked report from the successful npm test snapshot');
|
|
134
129
|
}
|
|
135
|
-
if (
|
|
136
|
-
|
|
130
|
+
if (releaseSteps[2] !== 'node test/run-conformance-report.mjs conformance-report.md' ||
|
|
131
|
+
releaseSteps[3] !== 'git add test/conformance/NEUMERKEL-LATEST.md conformance-report.md' || releaseSteps.length !== 4) {
|
|
132
|
+
throw new Error('preversion must generate and stage both reports without another fetch');
|
|
137
133
|
}
|
|
138
134
|
});
|
|
139
135
|
|
package/test/run-neumerkel.mjs
CHANGED
|
@@ -29,7 +29,7 @@ function parseArgs(argv) {
|
|
|
29
29
|
|
|
30
30
|
function printHelp() {
|
|
31
31
|
process.stdout.write(
|
|
32
|
-
'Usage:
|
|
32
|
+
'Usage: node test/run-neumerkel.mjs [--cached] [--source-dir DIR] [--verify-report] [--update-report]\n\n' +
|
|
33
33
|
'Default: fetch all seven current Neumerkel conformity sources live and run\n' +
|
|
34
34
|
'every discovered case. A stale tracked report is reported as a warning, not\n' +
|
|
35
35
|
'an engine-test failure. --verify-report makes report freshness mandatory;\n' +
|
|
@@ -54,7 +54,7 @@ export async function runNeumerkel(reporter, options = {}) {
|
|
|
54
54
|
if (committed !== result.reportText) {
|
|
55
55
|
const message =
|
|
56
56
|
`tracked Neumerkel report is stale: ${relativeReportPath}\n` +
|
|
57
|
-
'Run
|
|
57
|
+
'Run node test/run-neumerkel.mjs --update-report and commit the updated report.';
|
|
58
58
|
if (effective.verifyReport) throw new Error(message);
|
|
59
59
|
reporter.stdout.write(`WARN ${message}\n`);
|
|
60
60
|
} else {
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -7034,7 +7034,7 @@ library dependency should be explicit. `--iso-strict` always disables EyeProlog
|
|
|
7034
7034
|
library autoloading, so strict ISO execution never gains procedures from this
|
|
7035
7035
|
implementation convenience.
|
|
7036
7036
|
|
|
7037
|
-
`-w` / `--warnings` reports explicit dependencies on non-profile libraries and calls to non-profile predicates from otherwise common modules. `--portable` turns those diagnostics into a failing run, making the conservative profile suitable for continuous integration. Cross-engine portability can be exercised with `
|
|
7037
|
+
`-w` / `--warnings` reports explicit dependencies on non-profile libraries and calls to non-profile predicates from otherwise common modules. `--portable` turns those diagnostics into a failing run, making the conservative profile suitable for continuous integration. Cross-engine portability can be exercised with `node test/run-interop.mjs` when EyeProlog, Trealla, and Scryer are installed.
|
|
7038
7038
|
|
|
7039
7039
|
### Specialized library implementation notes
|
|
7040
7040
|
|
|
@@ -10339,7 +10339,7 @@ hand.
|
|
|
10339
10339
|
Run all 210 normal answer goldens and the 61 selected proof goldens with:
|
|
10340
10340
|
|
|
10341
10341
|
```sh
|
|
10342
|
-
|
|
10342
|
+
node test/run-examples.mjs
|
|
10343
10343
|
```
|
|
10344
10344
|
|
|
10345
10345
|
Run the complete conformance, regression, example, and proof corpus
|
|
@@ -10354,8 +10354,8 @@ and fast:
|
|
|
10354
10354
|
|
|
10355
10355
|
```sh
|
|
10356
10356
|
npm run benchmark
|
|
10357
|
-
npm run benchmark
|
|
10358
|
-
|
|
10357
|
+
npm run benchmark -- --save .benchmarks/baseline.json
|
|
10358
|
+
node test/lips-benchmark.mjs
|
|
10359
10359
|
```
|
|
10360
10360
|
|
|
10361
10361
|
The benchmark suite contains 19 representative workloads and stores their
|
|
@@ -10368,7 +10368,7 @@ warm-up batch, five measured batches are reported as milliseconds per workload
|
|
|
10368
10368
|
execution. Naturally long workloads keep a batch size of one.
|
|
10369
10369
|
|
|
10370
10370
|
The classic `examples/bench.pl` workload also has a dedicated LIPS harness.
|
|
10371
|
-
`
|
|
10371
|
+
`node test/lips-benchmark.mjs` follows the 1984 Quintus method more closely than the
|
|
10372
10372
|
generic runner: `dobench/1` and `dodummy/1` execute failure-driven loops inside
|
|
10373
10373
|
Prolog, the dummy CPU time is subtracted, and the remaining time is converted
|
|
10374
10374
|
using 496 procedure calls for one reversal of the 30-element list. Node's
|
|
@@ -10409,9 +10409,9 @@ lists, terms, atoms, variables, negation, queries, rules, and
|
|
|
10409
10409
|
syntax. Separate corpora cover expected errors, warnings, and proofs:
|
|
10410
10410
|
|
|
10411
10411
|
```sh
|
|
10412
|
-
|
|
10413
|
-
|
|
10414
|
-
|
|
10412
|
+
node test/run-conformance-all.mjs
|
|
10413
|
+
node test/run-iso-strict.mjs
|
|
10414
|
+
node test/run-wg17.mjs
|
|
10415
10415
|
node test/run-conformance-report.mjs
|
|
10416
10416
|
```
|
|
10417
10417
|
|
|
@@ -10452,7 +10452,7 @@ The repository exposes several forms of executable evidence:
|
|
|
10452
10452
|
Run the browser contract independently with:
|
|
10453
10453
|
|
|
10454
10454
|
```sh
|
|
10455
|
-
|
|
10455
|
+
node test/run-playground.mjs
|
|
10456
10456
|
```
|
|
10457
10457
|
|
|
10458
10458
|
### Supported ISO Prolog implementation
|
|
@@ -97,7 +97,7 @@ const text = `// Generated by tools/generate-library-autoload-index.mjs.\n` +
|
|
|
97
97
|
if (process.argv.includes('--check')) {
|
|
98
98
|
const current = fs.existsSync(outputFile) ? fs.readFileSync(outputFile, 'utf8') : '';
|
|
99
99
|
if (current !== text) {
|
|
100
|
-
console.error(`${path.relative(root, outputFile)} is stale; run npm run generate
|
|
100
|
+
console.error(`${path.relative(root, outputFile)} is stale; run npm run generate`);
|
|
101
101
|
process.exit(1);
|
|
102
102
|
}
|
|
103
103
|
console.log(`${path.relative(root, outputFile)} is up to date`);
|
|
@@ -209,7 +209,7 @@ const wanted = expectedBook(book, generated);
|
|
|
209
209
|
|
|
210
210
|
if (process.argv.includes('--check')) {
|
|
211
211
|
if (wanted !== book) {
|
|
212
|
-
console.error(`${path.relative(root, bookFile)} predicate reference is stale; run npm run generate
|
|
212
|
+
console.error(`${path.relative(root, bookFile)} predicate reference is stale; run npm run generate`);
|
|
213
213
|
process.exit(1);
|
|
214
214
|
}
|
|
215
215
|
console.log(`${path.relative(root, bookFile)} predicate reference is up to date (${surface.indicators.length} predicates)`);
|
|
@@ -134,9 +134,9 @@ ${untracedIds.length === 0 ? 'None.' : `${formatRanges(untracedIds)}.`}
|
|
|
134
134
|
|
|
135
135
|
## Maintenance
|
|
136
136
|
|
|
137
|
-
1. Run \`npm run conformance:update\` to reconcile the dated fixture with upstream.
|
|
137
|
+
1. Run \`npm run conformance:update:wg17\` to reconcile the dated fixture with upstream.
|
|
138
138
|
2. Review every new or changed ISO expectation before adding its expected outcome.
|
|
139
|
-
3. Run \`
|
|
139
|
+
3. Run \`node test/run-wg17.mjs\` and keep this generated status page synchronized.
|
|
140
140
|
`;
|
|
141
141
|
}
|
|
142
142
|
|
|
@@ -158,7 +158,7 @@ if (process.argv[1] != null && path.resolve(process.argv[1]) === fileURLToPath(i
|
|
|
158
158
|
if (process.argv.includes('--check')) {
|
|
159
159
|
const current = fs.readFileSync(statusPath, 'utf8');
|
|
160
160
|
if (current !== rendered) {
|
|
161
|
-
process.stderr.write('WG17 syntax status is stale; run npm run conformance:update and update the file.\n');
|
|
161
|
+
process.stderr.write('WG17 syntax status is stale; run npm run conformance:update:wg17 and update the file.\n');
|
|
162
162
|
process.exitCode = 1;
|
|
163
163
|
}
|
|
164
164
|
} else {
|
package/tools/upgrade-wg17.mjs
CHANGED
|
@@ -457,7 +457,7 @@ function parseArgs(argv) {
|
|
|
457
457
|
}
|
|
458
458
|
|
|
459
459
|
function printHelp() {
|
|
460
|
-
process.stdout.write(`Usage: npm run conformance:update -- [--check] [--source URL_OR_FILE]\n\n` +
|
|
460
|
+
process.stdout.write(`Usage: npm run conformance:update:wg17 -- [--check] [--source URL_OR_FILE]\n\n` +
|
|
461
461
|
`Refreshes the vendored WG17 conformity tests from the TU Wien table.\n` +
|
|
462
462
|
`New or changed rows are executable immediately against the upstream\n` +
|
|
463
463
|
`Codex expectation; existing reviewed exact outcomes remain pinned only as additional regression checks.\n`);
|
|
@@ -477,7 +477,7 @@ export async function upgradeWg17({ check = false, source = syntaxSource } = {})
|
|
|
477
477
|
|
|
478
478
|
if (check) {
|
|
479
479
|
if (semanticChanges > 0) {
|
|
480
|
-
process.stderr.write('WG17 snapshot is stale; run npm run conformance:update.\n');
|
|
480
|
+
process.stderr.write('WG17 snapshot is stale; run npm run conformance:update:wg17.\n');
|
|
481
481
|
process.exitCode = 1;
|
|
482
482
|
return { changed: true, ...reconciliation };
|
|
483
483
|
}
|
|
@@ -524,7 +524,7 @@ export async function upgradeWg17({ check = false, source = syntaxSource } = {})
|
|
|
524
524
|
process.stdout.write(`Updated WG17 snapshot (${fixture.cases.length} cases).\n`);
|
|
525
525
|
if (upstreamAssertions.length > 0) {
|
|
526
526
|
process.stdout.write(
|
|
527
|
-
`Direct upstream assertions used by
|
|
527
|
+
`Direct upstream assertions used by the WG17 runner: ${formatIdList(upstreamAssertions)}\n`,
|
|
528
528
|
);
|
|
529
529
|
}
|
|
530
530
|
return { changed: semanticChanges > 0, upstreamAssertions, ...reconciliation };
|