eyeprolog 1.5.82 → 1.5.84

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 CHANGED
@@ -95,11 +95,10 @@ npm test
95
95
  ```
96
96
  The npm command list is deliberately small:
97
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.
98
+ - `npm test` (or `npm run test`): run the release gate, including live upstream conformity checks (WG17 syntax among them).
100
99
  - `npm run generate`: rebuild generated library and book files.
101
100
  - `npm run benchmark`: run the wall-clock benchmarks.
102
101
 
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).
102
+ Use `npm test -- --offline` for a network-free local pass (this also skips the live-discovered WG17 syntax check, since it has no offline snapshot). Focused checks remain available directly, for example `node test/run-regression.mjs docs`; 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
103
 
105
104
  EyeProlog is released under the [MIT License](LICENSE.md).
@@ -1,25 +1,17 @@
1
1
  # EyeProlog conformance report
2
2
 
3
- This report combines an executable external conformance gate with the file-based
4
- conformance corpus under `test/conformance/`. The executable result is measured
5
- when this report is generated; it is not inferred from fixture counts.
3
+ This report combines a live external conformance gate with the file-based
4
+ conformance corpus under `test/conformance/`. The file-based corpus is
5
+ measured when this report is generated; it is not inferred from fixture counts.
6
6
 
7
7
  ## Latest Neumerkel evidence
8
8
 
9
- See the tracked [latest Neumerkel conformity report](test/conformance/NEUMERKEL-LATEST.md).
10
- `npm test` fetches all eight TU Wien sources once and executes the discovered inventory.
11
- The release workflow then synchronizes this tracked report from those exact successful
12
- cached source bytes, avoiding a second live fetch and its race window.
13
-
14
- ## Executable conformance status
15
-
16
- | Gate | Passed | Total | Status |
17
- |---|---:|---:|---|
18
- | WG17 syntax | 379 | 379 | pass |
19
-
20
- The WG17 syntax row executes the vendored 379-case conformity-testing matrix
21
- against EyeProlog's strict ISO reader/writer. A behavior fix such as operator-token
22
- spelling therefore changes this report even when no corpus file is added or removed.
9
+ See the tracked [latest Neumerkel conformity report](test/conformance/NEUMERKEL-LATEST.md)
10
+ for the executable external gate, including WG17 syntax conformance: `npm test`
11
+ fetches all eight TU Wien sources (syntax discovered and executed live, not a
12
+ vendored fixture) and executes the discovered inventory. The release workflow
13
+ then synchronizes this tracked report from those exact successful cached source
14
+ bytes, avoiding a second live fetch and its race window.
23
15
 
24
16
  ## File-based corpus inventory
25
17
 
@@ -32,7 +24,7 @@ spelling therefore changes this report even when no corpus file is added or remo
32
24
  | context | 11 | 0 | 0 | 0 | 11 |
33
25
  | control | 15 | 0 | 0 | 0 | 15 |
34
26
  | explicit-tabling | 6 | 0 | 0 | 0 | 6 |
35
- | iso | 174 | 219 | 0 | 0 | 393 |
27
+ | iso | 176 | 219 | 0 | 0 | 395 |
36
28
  | lists | 52 | 3 | 0 | 0 | 55 |
37
29
  | modules | 2 | 0 | 0 | 0 | 2 |
38
30
  | negation | 8 | 0 | 19 | 0 | 27 |
@@ -45,7 +37,7 @@ spelling therefore changes this report even when no corpus file is added or remo
45
37
  | terms | 26 | 3 | 0 | 0 | 29 |
46
38
  | unification | 18 | 0 | 0 | 0 | 18 |
47
39
  | variables | 16 | 7 | 0 | 0 | 23 |
48
- | **Total** | **496** | **274** | **19** | **21** | **810** |
40
+ | **Total** | **498** | **274** | **19** | **21** | **812** |
49
41
 
50
42
  ## DCG conformance clarification
51
43
 
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.5.82",
6
+ "version": "1.5.84",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
@@ -46,7 +46,6 @@
46
46
  },
47
47
  "scripts": {
48
48
  "test": "node test/run-all.mjs",
49
- "conformance:update:wg17": "node tools/upgrade-wg17.mjs",
50
49
  "benchmark": "node test/benchmark.mjs",
51
50
  "generate": "node tools/generate-library-autoload-index.mjs && node tools/generate-predicate-reference.mjs && node tools/extract-book-examples.mjs",
52
51
  "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",
package/test/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # Test runners
2
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.
3
+ Normally, run `npm test`. Use `npm test -- --offline` to skip live upstream fetching
4
+ (this also skips the WG17 syntax check, which is discovered live and has no
5
+ offline snapshot).
5
6
 
6
7
  For development, run a focused check directly from the repository root:
7
8
 
@@ -9,7 +10,6 @@ For development, run a focused check directly from the repository root:
9
10
  node test/run-regression.mjs # all regression sections
10
11
  node test/run-regression.mjs docs # documentation checks only
11
12
  node test/run-conformance-all.mjs # conformance layers; accepts --offline
12
- node test/run-wg17.mjs # vendored WG17 syntax
13
13
  node test/run-iso-strict.mjs # strict ISO core
14
14
  node test/run-iso-part2-amendment.mjs
15
15
  node test/run-neumerkel.mjs # live upstream; --cached reproduces last fetch
@@ -23,7 +23,7 @@ relevant implementation-dependent overlap choice to an explicit review outcome.
23
23
 
24
24
  | Requirement | Status | EyeProlog evidence / remaining work |
25
25
  | --- | --- | --- |
26
- | 5.1(a) prepare conforming Prolog text | covered | Clause 6 token/term production and rejection families are mapped by the strict production gate and the complete 379-case vendored WG17 syntax matrix; 7.4 preparation/directive behavior is separately closed. |
26
+ | 5.1(a) prepare conforming Prolog text | covered | Clause 6 token/term production and rejection families are mapped by the strict production gate and the complete WG17 syntax matrix, discovered dynamically and executed live as part of the Neumerkel conformity gate; 7.4 preparation/directive behavior is separately closed. |
27
27
  | 5.1(b) execute conforming Prolog goals | covered | Clause 7 term/control/execution semantics, the higher-level 7.10 stream model, 7.11 flags, the 7.12 error envelope, all 8.2-8.17 built-in rows, and Clause 9 arithmetic have explicit executable dispositions. |
28
28
  | 5.1(c) reject nonconforming text/read-terms | covered | WG17 negative syntax cases, focused malformed-production/escape/comment/operator cases, read-term syntax errors, and strict rejection of implementation-specific syntax provide explicit rejection evidence across the Clause 6 families. |
29
29
  | 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. The index retains an `gap` category for future discoveries; no current release-facing row depends on one. |
@@ -35,7 +35,7 @@ relevant implementation-dependent overlap choice to an explicit review outcome.
35
35
 
36
36
  | Standard area | Status | Current evidence |
37
37
  | --- | --- | --- |
38
- | Clause 6 — tokens, terms, lists, operators, quoted text | covered | The strict Clause 6 gate maps atomic/variable/compound/operator/list/curly/double-quoted term forms, layout/comments, quoted escapes, integer bases/character codes, floating tokens, and solo/meta token boundaries, with malformed counterparts rejected. The complete 379-case WG17 syntax matrix provides externally sourced production-level expectations, and a cross-profile gate verifies that normal syntax extensions preserve every strict-accepted standard observation. The implementation-defined 6.5/6.6 PCS/collation choices are documented separately. |
38
+ | Clause 6 — tokens, terms, lists, operators, quoted text | covered | The strict Clause 6 gate maps atomic/variable/compound/operator/list/curly/double-quoted term forms, layout/comments, quoted escapes, integer bases/character codes, floating tokens, and solo/meta token boundaries, with malformed counterparts rejected. The complete WG17 syntax matrix, discovered dynamically at run time, provides externally sourced production-level expectations, and a cross-profile gate verifies that normal syntax extensions preserve every strict-accepted standard observation. The implementation-defined 6.5/6.6 PCS/collation choices are documented separately. |
39
39
  | 7.1-7.3 — term types, term order, unification | covered | [ISO-TERM-SEMANTICS-MATRIX.md](ISO-TERM-SEMANTICS-MATRIX.md) records the five mutually exclusive strict term types, derived variable/compound/list notions, the complete standard-order classes and implementation-dependent variable-order choice, NSTO MGU/failure behavior, and EyeProlog's permitted consistent occurs-check failure for Part 1's undefined STO ordinary-unification cases. The normal JavaScript API string term is documented as a 5.5.4 extension and rejected at strict program/goal entry. |
40
40
  | 7.4 — Prolog text and directives | covered | [ISO-PROLOG-TEXT-EXECUTION-MATRIX.md](ISO-PROLOG-TEXT-EXECUTION-MATRIX.md) closes the preparation/directive rows: declaration semantics, source clauses, cross-text operators/character conversion/flags, initialization order/lifetime, `include/1`, and one-time `ensure_loaded/1`, with implementation-defined cross-text choices indexed under 5.4. |
41
41
  | 7.5-7.6 — database and term/clause conversion | covered | [ISO-PROLOG-TEXT-EXECUTION-MATRIX.md](ISO-PROLOG-TEXT-EXECUTION-MATRIX.md) closes static/dynamic and private/public procedure semantics, clause order, logical-update visibility, empty/unknown lifetime, and term/body/clause conversion. Source and runtime assertion conversion recurse through `,/2`, `;/2`, and `->/2`, preserve variable identity, and protect standardized static/control procedures. |
@@ -278,10 +278,11 @@ A release intended to advance ISO conformance uses one canonical command:
278
278
  npm test
279
279
  ```
280
280
 
281
- That command first fetches and runs the **latest seven Neumerkel conformity
282
- sources**, then executes the deterministic local conformance, strict ISO,
283
- vendored WG17 regression, regression/API, examples, documentation and
284
- architecture gates. The upstream case counts are discovered dynamically.
281
+ That command first fetches and runs the **latest eight Neumerkel conformity
282
+ sources** (WG17 syntax among them, discovered live, not vendored), then
283
+ executes the deterministic local conformance, strict ISO,
284
+ regression/API, examples, documentation and architecture gates. The upstream
285
+ case counts are discovered dynamically.
285
286
 
286
287
  Useful focused commands are:
287
288
 
@@ -289,7 +290,6 @@ Useful focused commands are:
289
290
  node test/run-conformance-all.mjs
290
291
  node test/run-neumerkel.mjs
291
292
  node test/run-iso-strict.mjs
292
- node test/run-wg17.mjs
293
293
  npm test -- --offline
294
294
  ```
295
295
 
@@ -307,7 +307,7 @@ post-N289 STC drafts remain review input until standardized.
307
307
  | Criterion | Status | Evidence |
308
308
  | --- | --- | --- |
309
309
  | Clause 5 processor obligations have explicit dispositions | covered | `ISO-PROCESSOR-REQUIREMENTS.md` gives explicit outcomes for 5.1-5.5, including the strict/normal extension boundary. |
310
- | Clause 6 lexical/syntactic requirements have explicit dispositions | covered | the strict Clause 6 production/rejection gate and the 379-case vendored WG17 matrix cover the standard families; cross-profile preservation verifies that normal extensions do not reinterpret strict-accepted text. |
310
+ | Clause 6 lexical/syntactic requirements have explicit dispositions | covered | the strict Clause 6 production/rejection gate and the live-discovered WG17 matrix cover the standard families; cross-profile preservation verifies that normal extensions do not reinterpret strict-accepted text. |
311
311
  | Clause 7 semantic requirements have explicit dispositions | covered | 7.1-7.12 now have explicit semantic, implementation-defined, or error-envelope dispositions backed by strict tests and the specialized matrices. |
312
312
  | Clause 8 built-in modes/errors have explicit dispositions | covered | `ISO-BUILTIN-MODE-ERROR-MATRIX.md` accounts for 8.2-8.17 condition by condition, using grouped rows only where every grouped condition and its evidence are named |
313
313
  | Clause 9 evaluable-functor requirements have explicit dispositions | covered | `ISO-EVALUABLE-FUNCTOR-MATRIX.md` plus the strict arithmetic regression closes the published Part 1 + Corrigenda arithmetic rows |
@@ -315,7 +315,7 @@ post-N289 STC drafts remain review input until standardized.
315
315
  | Implementation-specific strict/normal boundary is documented and tested | covered | all 5.5 hooks have explicit dispositions; the WG17 cross-profile gate verifies syntax-preservation for standard text accepted by the strict reader. |
316
316
  | Published Corrigenda 1-3 are incorporated | covered | `ISO-CORRIGENDA-MATRIX.md` inventories every published amendment cluster with executable, editorial, or superseded disposition |
317
317
  | Current post-N289 draft is tracked without silently changing the published baseline | covered | `STC-DRAFT-STATUS.md` tracks reviewed draft items separately from normative requirements |
318
- | Latest Neumerkel conformity is a live release gate | covered | `npm test` fetches and executes the eight current TU Wien conformity sources with dynamic inventories; the vendored WG17 matrix remains an offline reviewed-outcome regression layer |
318
+ | Latest Neumerkel conformity is a live release gate | covered | `npm test` fetches and executes the eight current TU Wien conformity sources with dynamic inventories, including WG17 syntax; a small offline corpus of reviewed cases (`test/conformance/wg17-syntax-cases.json`) supplies an additional exact-outcome regression lock, cross-referenced by id against the live-discovered cases |
319
319
  | Third-party standard-core regression provenance is retained | covered | adapted Logtalk, Scryer, Trealla, and SWI-Prolog cases retain source identifiers and licenses in `THIRD_PARTY.md` |
320
320
  | No unexplained deviation remains in the release-facing ledger | covered | the release-facing ledger contains no remaining `review` rows; documented variation points are implementation-defined/specific or draft-only rather than unexplained deviations. |
321
321
 
@@ -1,6 +1,6 @@
1
1
  # EyeProlog — latest Neumerkel conformity
2
2
 
3
- Status: **PASS** — **719/719** discovered upstream cases passed.
3
+ Status: **PASS — 1 divergence to be addressed** — **718/719** discovered upstream cases passed.
4
4
 
5
5
  This tracked report records the latest upstream inventory successfully checked by EyeProlog.
6
6
  `npm test` fetches the eight TU Wien sources again and executes the discovered cases.
@@ -15,9 +15,13 @@ Counts are output from upstream, not hard-coded test constants.
15
15
  | dif/2 | 26 | 26 |
16
16
  | length/2 | 37 | 37 |
17
17
  | phrase/2,3 | 58 | 58 |
18
- | Prologue draft | 33 | 33 |
18
+ | Prologue draft | 32 | 33 |
19
19
  | setup_call_cleanup/3 | 25 | 25 |
20
- | **Total** | **719** | **719** |
20
+ | **Total** | **718** | **719** |
21
+
22
+ ## Known divergences
23
+
24
+ - **Prologue draft** (line 118): bounded=false: EyeProlog reports no max_integer value at all (ISO 7.11.1.1), so this quad's evaluation_error(int_overflow) | Max = unbounded pair never applies
21
25
 
22
26
  ## Upstream sources
23
27
 
@@ -19,9 +19,12 @@ The Prologue corpus carries one permanent, documented divergence (its
19
19
  `max_integer` quad — see `KNOWN_QUAD_DIVERGENCES` in `test/neumerkel.mjs`):
20
20
  EyeProlog's `bounded=false` reports no `max_integer` value at all, which
21
21
  neither of the quad's two anticipated answers describes. That quad is still
22
- executed and verified every run; it is reported as a passing, documented
23
- divergence for as long as it keeps reproducing, and would fail loudly if it
24
- ever stopped.
22
+ executed every run and counted honestly (it does not report as passing), but
23
+ it does not abort the run the way an unexplained failure would; the tracked
24
+ report and console summary both say "N divergence(s) to be addressed" rather
25
+ than a bare pass/fail count. See `NEUMERKEL-LATEST.md`'s own "Known
26
+ divergences" section for the explanation. If the divergence ever stopped
27
+ reproducing, the counts would simply climb back to a plain, unqualified PASS.
25
28
 
26
29
  The runner discovers the inventory at run time. A new upstream row is therefore
27
30
  executed automatically and a removed row disappears automatically. The syntax
@@ -22,8 +22,10 @@ implements of Part 2 (modules) and Part 3 (definite clause grammar rules),
22
22
  including the 2013 module-amendment evidence and the known gaps and deviations.
23
23
  Neither part carries a conformance claim. Built-in rows may group closely related conditions only when the
24
24
  row names every grouped condition and its executable evidence.
25
- The exit checklist is embedded in [ISO-COMPLIANCE.md](ISO-COMPLIANCE.md). [WG17-SYNTAX-STATUS.md](WG17-SYNTAX-STATUS.md) records the
26
- complete one-to-one trace for the vendored active upstream WG17 syntax cases.
25
+ The exit checklist is embedded in [ISO-COMPLIANCE.md](ISO-COMPLIANCE.md). WG17
26
+ syntax cases are discovered live and executed as part of the Neumerkel
27
+ conformity gate (see [NEUMERKEL-LIVE.md](NEUMERKEL-LIVE.md)); there is no
28
+ separate offline coverage ledger to keep in sync.
27
29
  [STC-DRAFT-STATUS.md](STC-DRAFT-STATUS.md) separately tracks executable
28
30
  implementation questions from the post-N289 working draft (reviewed through the 2026-08-23 items #73-#76); those cases are
29
31
  review evidence, not normative ISO claims.
@@ -96,24 +98,19 @@ node test/run-neumerkel.mjs --cached # exact last fetched bytes; reproductio
96
98
  node test/run-neumerkel.mjs --cached --verify-report # verify tracked report against last successful live snapshot
97
99
  node test/run-iso-strict.mjs # Part 1 + Corrigenda strict-core processor gate
98
100
  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
101
  ```
101
102
 
102
103
  `node test/run-neumerkel.mjs` always fetches the current TU Wien sources. It does not skip a
103
104
  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
105
 
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
-
107
- ```sh
108
- npm run conformance:update:wg17
109
- node test/run-neumerkel.mjs --update-report
110
- node test/run-neumerkel.mjs --cached --update-report
111
- node test/run-wg17.mjs
112
- ```
113
-
114
- Every vendored syntax case is still checked against its upstream Codex
115
- expectation; reviewed exact outcomes are an additional regression lock rather
116
- than an alternative acceptance rule.
106
+ WG17 syntax cases are discovered live, the same way the other seven TU Wien
107
+ sources are: there is no separate vendored snapshot or update step. A small
108
+ offline corpus, `test/conformance/wg17-syntax-cases.json`, additionally pins
109
+ the exact reviewed EyeProlog outcome for most cases, cross-referenced by id
110
+ against the live-discovered cases in `test/neumerkel.mjs`. Every case, live or
111
+ reviewed, is still checked against its upstream Codex expectation
112
+ (`matchesUpstreamExpectation` in `test/run-wg17.mjs`); a reviewed exact outcome
113
+ is an additional regression lock, never an alternative acceptance rule.
117
114
 
118
115
  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
116
 
@@ -141,7 +138,7 @@ Selected cases are adapted from the ISO and standard-core suites of Logtalk,
141
138
  Scryer Prolog, Trealla Prolog, and SWI-Prolog. Their upstream identifiers and licenses
142
139
  are recorded in [THIRD_PARTY.md](THIRD_PARTY.md).
143
140
 
144
- The corpus has 393 cases in `iso/` and 810 file-based conformance cases in total. Of those, 11 cases in `stc/` are explicitly labelled working-draft review evidence rather than normative ISO claims. The separate vendored strict-reader WG17 matrix is a deterministic regression snapshot; the live Neumerkel gate discovers the current upstream inventory at run time; release/report checks separately verify the tracked `NEUMERKEL-LATEST.md`. The generated `conformance-report.md` records local corpus totals and links to that live evidence. Together with regression, documentation-sync, API, example, and book-example checks, `npm test` is the release gate.
141
+ The corpus has 395 cases in `iso/` and 812 file-based conformance cases in total. Of those, 11 cases in `stc/` are explicitly labelled working-draft review evidence rather than normative ISO claims. The strict-reader WG17 syntax matrix is discovered dynamically and executed live by the Neumerkel gate, the same as its other seven TU Wien sources; release/report checks separately verify the tracked `NEUMERKEL-LATEST.md`. The generated `conformance-report.md` records local corpus totals and links to that live evidence. Together with regression, documentation-sync, API, example, and book-example checks, `npm test` is the release gate.
145
142
 
146
143
  ## Updating expected output
147
144
 
@@ -0,0 +1,23 @@
1
+ % Adapted from Trealla Prolog tests/tests/test0027.pl, test0030.pl, and test0031.pl.
2
+ % See test/conformance/THIRD_PARTY.md.
3
+ foo(a, b, cc).
4
+ foo(a, b, dd).
5
+ foo(b, c, ee).
6
+ foo(b, c, ff).
7
+ foo(c, c, gg).
8
+ foo(d, e, gg).
9
+
10
+ %% goal: bagof_grouped_by_two_free_variables(X0)
11
+
12
+ bagof_grouped_by_two_free_variables(Cs) :-
13
+ bagof(C, foo(_, _, C), Cs).
14
+
15
+ %% goal: bagof_two_free_variables_quantified(X0)
16
+
17
+ bagof_two_free_variables_quantified(Cs) :-
18
+ bagof(C, A^B^foo(A, B, C), Cs).
19
+
20
+ %% goal: setof_two_free_variables_quantified(X0)
21
+
22
+ setof_two_free_variables_quantified(Cs) :-
23
+ setof(C, A^B^foo(A, B, C), Cs).
@@ -0,0 +1,32 @@
1
+ % Adapted from Trealla Prolog tests/tests/test0058.pl.
2
+ % See test/conformance/THIRD_PARTY.md.
3
+
4
+ %% goal: sub_atom_all(X0, X1, X2, X3)
5
+
6
+ sub_atom_all(B, L, A, S) :-
7
+ sub_atom(abc, B, L, A, S).
8
+
9
+ %% goal: sub_atom_length_2(X0, X1, X2)
10
+
11
+ sub_atom_length_2(B, A, S) :-
12
+ sub_atom(abc, B, 2, A, S).
13
+
14
+ %% goal: sub_atom_before_0_length_2(X0, X1)
15
+
16
+ sub_atom_before_0_length_2(A, S) :-
17
+ sub_atom(abc, 0, 2, A, S).
18
+
19
+ %% goal: sub_atom_before_0(X0, X1, X2)
20
+
21
+ sub_atom_before_0(L, A, S) :-
22
+ sub_atom(abc, 0, L, A, S).
23
+
24
+ %% goal: sub_atom_after_0(X0, X1, X2)
25
+
26
+ sub_atom_after_0(B, L, S) :-
27
+ sub_atom(abc, B, L, 0, S).
28
+
29
+ %% goal: sub_atom_length_2_after_0(X0, X1)
30
+
31
+ sub_atom_length_2_after_0(B, S) :-
32
+ sub_atom(abc, B, 2, 0, S).
@@ -0,0 +1,5 @@
1
+ bagof_grouped_by_two_free_variables([cc, dd]).
2
+ bagof_grouped_by_two_free_variables([ee, ff]).
3
+ bagof_grouped_by_two_free_variables([gg]).
4
+ bagof_two_free_variables_quantified([cc, dd, ee, ff, gg, gg]).
5
+ setof_two_free_variables_quantified([cc, dd, ee, ff, gg]).
@@ -0,0 +1,22 @@
1
+ sub_atom_all(0, 0, 3, '').
2
+ sub_atom_all(0, 1, 2, a).
3
+ sub_atom_all(0, 2, 1, ab).
4
+ sub_atom_all(0, 3, 0, abc).
5
+ sub_atom_all(1, 0, 2, '').
6
+ sub_atom_all(1, 1, 1, b).
7
+ sub_atom_all(1, 2, 0, bc).
8
+ sub_atom_all(2, 0, 1, '').
9
+ sub_atom_all(2, 1, 0, c).
10
+ sub_atom_all(3, 0, 0, '').
11
+ sub_atom_length_2(0, 1, ab).
12
+ sub_atom_length_2(1, 0, bc).
13
+ sub_atom_before_0_length_2(1, ab).
14
+ sub_atom_before_0(0, 3, '').
15
+ sub_atom_before_0(1, 2, a).
16
+ sub_atom_before_0(2, 1, ab).
17
+ sub_atom_before_0(3, 0, abc).
18
+ sub_atom_after_0(0, 3, abc).
19
+ sub_atom_after_0(1, 2, bc).
20
+ sub_atom_after_0(2, 1, c).
21
+ sub_atom_after_0(3, 0, '').
22
+ sub_atom_length_2_after_0(1, bc).
@@ -11,10 +11,11 @@ import {
11
11
  htmlCellText,
12
12
  parseWg17SyntaxTable,
13
13
  setupInput,
14
- } from '../tools/upgrade-wg17.mjs';
14
+ } from '../tools/wg17-syntax.mjs';
15
15
  import {
16
16
  executeWg17Item,
17
17
  matchesUpstreamExpectation,
18
+ readWg17SyntaxOutcomes,
18
19
  wg17TestDescription,
19
20
  } from './run-wg17.mjs';
20
21
 
@@ -308,10 +309,11 @@ function expectedMatches(expected, actual) {
308
309
 
309
310
  // A permanent, deliberate divergence from an upstream quad: EyeProlog's own
310
311
  // implementation choice, not a bug to chase. Keyed by corpus key, then by the
311
- // answer description's source line. Reported as its own test either way, so a
312
- // future upstream change that makes the divergence stop reproducing (as
313
- // happened with the length corpus's occurs-check quad) is caught immediately
314
- // instead of silently masked.
312
+ // answer description's source line. It is still reported and counted as an
313
+ // ordinary failing test (so the totals stay honest and it self-corrects the
314
+ // moment it stops reproducing, exactly as happened with the length corpus's
315
+ // occurs-check quad); it just does not abort the run the way an unexplained
316
+ // failure would.
315
317
  const KNOWN_QUAD_DIVERGENCES = {
316
318
  prologue: new Map([
317
319
  [118, 'bounded=false: EyeProlog reports no max_integer value at all (ISO 7.11.1.1), ' +
@@ -358,17 +360,9 @@ function runQuadCorpus(reporter, label, item, knownDivergences = new Map()) {
358
360
  const divergence = knownDivergences.get(description.line);
359
361
  const name = divergence == null
360
362
  ? `${label} ${locator} (line ${description.line ?? '?'}) ?- ${formatQuadTerm(program, description.query)}`
361
- : `${label} ${locator} (line ${description.line ?? '?'}, documented divergence) ?- ${formatQuadTerm(program, description.query)}`;
363
+ : `${label} ${locator} (line ${description.line ?? '?'}, documented divergence: ${divergence}) ?- ${formatQuadTerm(program, description.query)}`;
362
364
  try {
363
365
  reporter.test(name, () => {
364
- if (divergence != null) {
365
- // Verify the divergence itself, not just tolerate it: if it stops
366
- // reproducing, that is exactly the kind of change worth noticing.
367
- if (description.ok) {
368
- throw new Error(`documented divergence (${divergence}) no longer reproduces; remove it from KNOWN_QUAD_DIVERGENCES`);
369
- }
370
- return;
371
- }
372
366
  if (!description.ok) {
373
367
  const reason = description.reason ? `: ${description.reason}` : '';
374
368
  throw new Error(`${description.kind ?? 'failed'}${reason}`);
@@ -376,10 +370,12 @@ function runQuadCorpus(reporter, label, item, knownDivergences = new Map()) {
376
370
  });
377
371
  passed++;
378
372
  } catch (error) {
379
- // Upstream changes often arrive in small clusters. Keep running the
380
- // corpus so one live test run exposes every new mismatch instead of
381
- // forcing a fix/rerun cycle for each row.
382
- failures.push(error);
373
+ // A documented divergence is expected to keep failing; note it without
374
+ // aborting the run over it. Upstream changes otherwise often arrive in
375
+ // small clusters -- keep running the corpus so one live test run
376
+ // exposes every new mismatch instead of forcing a fix/rerun cycle for
377
+ // each row.
378
+ if (divergence == null) failures.push(error);
383
379
  }
384
380
  });
385
381
  return { total: result.results.length, passed, failures };
@@ -392,6 +388,7 @@ export async function executeNeumerkel({ reporter, mode = 'live', cacheDir = def
392
388
  reporter.section(`Neumerkel conformity (${mode === 'live' && sourceDir == null ? 'live upstream' : sourceDir != null ? 'source fixtures' : 'cached'})`);
393
389
 
394
390
  const syntaxCases = materializeSyntaxCases(sources.get('syntax').text);
391
+ const syntaxOutcomes = readWg17SyntaxOutcomes();
395
392
  const syntaxFailures = [];
396
393
  let syntaxPassed = 0;
397
394
  for (const item of syntaxCases) {
@@ -401,6 +398,16 @@ export async function executeNeumerkel({ reporter, mode = 'live', cacheDir = def
401
398
  if (!matchesUpstreamExpectation(item.expected, actual, item)) {
402
399
  throw new Error(`syntax #${item.id} expected ${item.expected}; actual ${JSON.stringify(actual)}`);
403
400
  }
401
+ // A case can also carry a reviewed exact-behavior lock (see
402
+ // wg17-syntax-outcomes.json): a stronger regression check than the
403
+ // upstream assertion alone, catching a change that still happens to
404
+ // satisfy the loose Codex expectation.
405
+ const reviewed = syntaxOutcomes.get(item.id);
406
+ if (reviewed != null && JSON.stringify(actual) !== JSON.stringify(reviewed)) {
407
+ throw new Error(
408
+ `syntax #${item.id} reviewed outcome changed; expected ${JSON.stringify(reviewed)}; actual ${JSON.stringify(actual)}`,
409
+ );
410
+ }
404
411
  });
405
412
  syntaxPassed++;
406
413
  } catch (error) {
@@ -514,10 +521,21 @@ export function formatNeumerkelMarkdown({ summary }) {
514
521
  const rows = labels.map(([key, label]) => ({ key, label, ...summary[key] }));
515
522
  const passed = rows.reduce((sum, row) => sum + row.passed, 0);
516
523
  const total = rows.reduce((sum, row) => sum + row.total, 0);
524
+ const divergenceCount = Object.values(KNOWN_QUAD_DIVERGENCES)
525
+ .reduce((sum, byLine) => sum + byLine.size, 0);
526
+ const shortfall = total - passed;
527
+ // A shortfall fully accounted for by known, documented divergences (see
528
+ // KNOWN_QUAD_DIVERGENCES and the list below) is not a build failure; any
529
+ // other shortfall is.
530
+ const status = shortfall === 0
531
+ ? 'PASS'
532
+ : shortfall === divergenceCount
533
+ ? `PASS — ${shortfall} divergence${shortfall === 1 ? '' : 's'} to be addressed`
534
+ : 'FAIL';
517
535
  const lines = [
518
536
  '# EyeProlog — latest Neumerkel conformity',
519
537
  '',
520
- `Status: **${passed === total ? 'PASS' : 'FAIL'}** — **${passed}/${total}** discovered upstream cases passed.`,
538
+ `Status: **${status}** — **${passed}/${total}** discovered upstream cases passed.`,
521
539
  '',
522
540
  'This tracked report records the latest upstream inventory successfully checked by EyeProlog.',
523
541
  '`npm test` fetches the eight TU Wien sources again and executes the discovered cases.',
@@ -530,6 +548,14 @@ export function formatNeumerkelMarkdown({ summary }) {
530
548
  for (const row of rows) lines.push(`| ${row.label} | ${row.passed} | ${row.total} |`);
531
549
  lines.push(`| **Total** | **${passed}** | **${total}** |`);
532
550
 
551
+ if (divergenceCount > 0) {
552
+ lines.push('', '## Known divergences', '');
553
+ for (const [key, byLine] of Object.entries(KNOWN_QUAD_DIVERGENCES)) {
554
+ const label = labels.find(([labelKey]) => labelKey === key)?.[1] ?? key;
555
+ for (const [line, reason] of byLine) lines.push(`- **${label}** (line ${line}): ${reason}`);
556
+ }
557
+ }
558
+
533
559
  lines.push(
534
560
  '',
535
561
  '## Upstream sources',
@@ -7,8 +7,7 @@ import * as publicApi from '../../src/index.js';
7
7
  import { createDefaultRegistry, eyePrologLibraryIndicators, eyePrologNativeLibraryIndicators, eyePrologPortableLibraryIndicators, standardLibrarySources } from '../../src/index.js';
8
8
  import { assertEqual, assertIncludes, assertNotIncludes } from '../test-style.mjs';
9
9
  import { buildConformanceReport, formatConformanceReport } from '../run-conformance-report.mjs';
10
- import { renderWg17SyntaxStatus } from '../../tools/report-wg17-syntax-coverage.mjs';
11
- import { parseWg17SyntaxTable, updateWg17InventoryReferences } from '../../tools/upgrade-wg17.mjs';
10
+ import { parseWg17SyntaxTable } from '../../tools/wg17-syntax.mjs';
12
11
  import { executeWg17Item, matchesUpstreamExpectation, readWg17SyntaxFixture } from '../run-wg17.mjs';
13
12
  import {
14
13
  assertArrayEqual,
@@ -163,29 +162,10 @@ ${profile}`;
163
162
  },
164
163
  },
165
164
  {
166
- name: 'WG17 syntax status matches its executable-coverage manifest',
167
- run: () => {
168
- const filename = path.join(testRoot, 'conformance', 'WG17-SYNTAX-STATUS.md');
169
- assertEqual(fs.readFileSync(filename, 'utf8'), renderWg17SyntaxStatus(), 'WG17 syntax status');
170
- },
171
- },
172
- {
173
- name: 'WG17 inventory documentation accepts omitted counts and repairs partial updates',
174
- run: () => {
175
- const countFree = 'The vendored WG17 syntax snapshot is intentionally secondary.';
176
- assertEqual(updateWg17InventoryReferences(countFree, 379), countFree, 'no count required');
177
- const stale = 'The 366-case vendored WG17 matrix and 365-case WG17 syntax matrix; WG17 matrix has 366 executable cases. An unrelated 366-case suite stays unchanged.';
178
- const expected = 'The 379-case vendored WG17 matrix and 379-case WG17 syntax matrix; WG17 matrix has 379 executable cases. An unrelated 366-case suite stays unchanged.';
179
- const updated = updateWg17InventoryReferences(stale, 379);
180
- assertEqual(updated, expected, 'all stale WG17 counts repaired independently of fixture state');
181
- assertEqual(updateWg17InventoryReferences(updated, 379), updated, 'retry is idempotent');
182
- },
183
- },
184
- {
185
- name: 'WG17 upgrader accepts omitted HTML table end tags',
165
+ name: 'WG17 syntax parser accepts omitted HTML table end tags',
186
166
  run: () => {
187
167
  // HTML permits </td> and </tr> to be omitted. TU Wien uses this
188
- // compact form, so the upgrader must not depend on explicit closes.
168
+ // compact form, so the parser must not depend on explicit closes.
189
169
  const rows = Array.from({ length: 120 }, (_, index) =>
190
170
  `<tr><td>${index + 1}<td><code>write(${index + 1}).</code><td>ok`).join('\n');
191
171
  const html = `<table><tr><th>#<th>Query<th>Codex${rows}</table>`;
@@ -197,7 +177,7 @@ ${profile}`;
197
177
  },
198
178
  },
199
179
  {
200
- name: 'WG17 upgrader normalizes presentation non-breaking spaces',
180
+ name: 'WG17 syntax parser normalizes presentation non-breaking spaces',
201
181
  run: () => {
202
182
  const rows = Array.from({ length: 120 }, (_, index) =>
203
183
  `<tr><td>${index + 1}<td>set_prolog_flag(&nbsp;double_quotes,chars).<td>succeeds`).join('\n');
@@ -207,7 +187,7 @@ ${profile}`;
207
187
  },
208
188
  },
209
189
  {
210
- name: 'WG17 upgrader removes presentation footnote markers from Codex text',
190
+ name: 'WG17 syntax parser removes presentation footnote markers from Codex text',
211
191
  run: () => {
212
192
  const rows = Array.from({ length: 120 }, (_, index) =>
213
193
  `<tr><td>${index + 1}<td>writeq(-(1^2)).<td>- (1^2)&sup3;`).join('\n');
@@ -649,7 +629,7 @@ ${profile}`;
649
629
  const publishIndex = publishWorkflow.indexOf('run: npm publish');
650
630
  assertEqual(testIndex >= 0 && testIndex < publishIndex, true, 'publish workflow test gate');
651
631
  assertEqual(packIndex >= 0 && packIndex < publishIndex, true, 'publish workflow package gate');
652
- assertArrayEqual(Object.keys(pkg.scripts).sort(), ['benchmark', 'conformance:update:wg17', 'generate', 'postversion', 'preversion', 'test'], 'small npm command surface');
632
+ assertArrayEqual(Object.keys(pkg.scripts).sort(), ['benchmark', 'generate', 'postversion', 'preversion', 'test'], 'small npm command surface');
653
633
  assertEqual(pkg.scripts.test, 'node test/run-all.mjs', 'full release gate');
654
634
  const runner = fs.readFileSync(path.join(packageRoot, 'test', 'run-all.mjs'), 'utf8');
655
635
  assertIncludes(runner, 'runOpenRuleBenchChecks(reporter)', 'OpenRuleBench remains in release gate');
@@ -667,28 +647,11 @@ ${profile}`;
667
647
  assertEqual(report.total.total >= 475, true, 'conformance case count');
668
648
  assertEqual(report.total.positive + report.total.errors + report.total.warnings + report.total.proofs, report.total.total, 'conformance total');
669
649
  assertEqual(report.rows.some((row) => row.category === 'legacy-numbered'), false, 'legacy-numbered category');
670
- const wg17 = report.executable.find((gate) => gate.name === 'WG17 syntax');
671
- const wg17FixtureTotal = readWg17SyntaxFixture().cases.length;
672
- assertEqual(wg17?.total, wg17FixtureTotal, 'WG17 executable total');
673
- assertEqual(wg17?.passed, wg17FixtureTotal, 'WG17 executable passed');
674
- assertArrayEqual(wg17?.failures ?? [], [], 'WG17 executable failures');
675
650
  const text = formatConformanceReport(report);
676
- assertIncludes(text, `| WG17 syntax | ${wg17FixtureTotal} | ${wg17FixtureTotal} | pass |`, 'report');
651
+ assertIncludes(text, 'latest Neumerkel conformity report', 'report links live evidence');
677
652
  assertIncludes(text, '| variables |', 'report');
678
653
  assertIncludes(text, '| Proofs |', 'report');
679
654
  assertIncludes(text, '| **Total** |', 'report');
680
-
681
- const failing = buildConformanceReport({
682
- wg17Suite: (reporter) => {
683
- reporter.section('WG17 syntax');
684
- reporter.test('synthetic syntax failure', () => { throw new Error('synthetic mismatch'); });
685
- reporter.sectionTotal('WG17 syntax');
686
- },
687
- });
688
- assertEqual(failing.executable[0].passed, 0, 'failing WG17 executable passed');
689
- assertEqual(failing.executable[0].total, 1, 'failing WG17 executable total');
690
- assertEqual(failing.executionIssues.length, 1, 'failing WG17 report issue count');
691
- assertIncludes(formatConformanceReport(failing), '| WG17 syntax | 0 | 1 | fail |', 'failing report');
692
655
  },
693
656
  },
694
657
 
@@ -512,7 +512,6 @@ export function documentedConformanceMetricIssues() {
512
512
  const report = buildConformanceReport();
513
513
  const iso = report.rows.find((row) => row.category === 'iso')?.total;
514
514
  const total = report.total.total;
515
- const wg17 = report.executable.find((gate) => gate.name === 'WG17 syntax')?.total;
516
515
  const checks = [
517
516
  {
518
517
  file: path.join(packageRoot, 'the-art-of-eyeprolog.md'),
@@ -542,6 +541,9 @@ export function documentedConformanceMetricIssues() {
542
541
  }
543
542
  }
544
543
  }
544
+ // WG17 syntax cases are discovered live (test/neumerkel.mjs), never
545
+ // vendored, so no document should hard-code a specific count for them: it
546
+ // would just go stale the next time upstream adds or removes a case.
545
547
  for (const file of [
546
548
  path.join(packageRoot, 'test', 'conformance', 'README.md'),
547
549
  path.join(packageRoot, 'test', 'conformance', 'ISO-COMPLIANCE.md'),
@@ -552,13 +554,9 @@ export function documentedConformanceMetricIssues() {
552
554
  ...[...text.matchAll(/\b(\d+)-case[^\n|]*WG17/g)].map((match) => Number(match[1])),
553
555
  ...[...text.matchAll(/WG17[^\n|]*?\b(\d+) executable/g)].map((match) => Number(match[1])),
554
556
  ];
555
- if (claims.length === 0) {
556
- const dynamicPolicy = /live Neumerkel|current upstream inventory|discovered dynamically/i.test(text);
557
- if (!dynamicPolicy) issues.push(`${relative}: WG17 total or dynamic-upstream policy not found`);
558
- }
559
- for (const claim of claims) {
560
- if (claim !== wg17) issues.push(`${relative}: WG17 count ${claim} != ${wg17}`);
561
- }
557
+ const dynamicPolicy = /live Neumerkel|current upstream inventory|discovered dynamically/i.test(text);
558
+ if (!dynamicPolicy) issues.push(`${relative}: WG17 dynamic-upstream policy not found`);
559
+ for (const claim of claims) issues.push(`${relative}: WG17 count ${claim} is hard-coded, not dynamic`);
562
560
  }
563
561
  return issues.sort();
564
562
  }