eyeprolog 1.3.100 → 1.4.1
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 +1 -1
- package/conformance-report.md +15 -1
- package/package.json +1 -1
- package/src/write.js +13 -0
- package/test/conformance/ISO-COMPLIANCE.md +3 -3
- package/test/conformance/README.md +3 -3
- package/test/conformance/WG17-SYNTAX-STATUS.md +4 -1
- package/test/conformance/wg17-syntax-cases.json +7 -7
- package/test/run-conformance-report.mjs +79 -12
- package/test/run-regression.mjs +45 -0
- package/test/run-wg17.mjs +43 -0
- package/the-art-of-eyeprolog.md +3 -3
- package/tools/report-wg17-syntax-coverage.mjs +4 -1
package/README.md
CHANGED
|
@@ -68,7 +68,7 @@ printf 'human(socrates).\nmortal(X) :- human(X).\n' |
|
|
|
68
68
|
- [Symbiotic Knowledge Graphs](examples/deck/symbiotic-knowledge-graphs.md) — RDF ↔ Prolog heatwave-response demo for human/AI/KG co-evolution
|
|
69
69
|
- [rdf-prolog-roundtrip](https://github.com/eyereasoner/rdf-prolog-roundtrip) — standalone RDF 1.2 ↔ ISO Prolog bridge used by the RDF examples
|
|
70
70
|
- [ISO conformance audit](test/conformance/ISO-COMPLIANCE.md) — supported Part 1 profile
|
|
71
|
-
- [Conformance report](conformance-report.md) — generated
|
|
71
|
+
- [Conformance report](conformance-report.md) — generated executable conformance status and corpus summary
|
|
72
72
|
- [OpenRuleBench](openrulebench/README.md) — portable benchmark profile
|
|
73
73
|
|
|
74
74
|
## RDF, Prolog, and symbiotic knowledge graphs
|
package/conformance-report.md
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
# EyeProlog conformance report
|
|
2
2
|
|
|
3
|
-
This report
|
|
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.
|
|
6
|
+
|
|
7
|
+
## Executable conformance status
|
|
8
|
+
|
|
9
|
+
| Gate | Passed | Total | Status |
|
|
10
|
+
|---|---:|---:|---|
|
|
11
|
+
| WG17 syntax | 366 | 366 | pass |
|
|
12
|
+
|
|
13
|
+
The WG17 syntax row executes the vendored 366-case conformity-testing matrix
|
|
14
|
+
against EyeProlog's strict ISO reader/writer. A behavior fix such as operator-token
|
|
15
|
+
spelling therefore changes this report even when no corpus file is added or removed.
|
|
16
|
+
|
|
17
|
+
## File-based corpus inventory
|
|
4
18
|
|
|
5
19
|
| Category | Positive | Errors | Warnings | Proofs | Total |
|
|
6
20
|
|---|---:|---:|---:|---:|---:|
|
package/package.json
CHANGED
package/src/write.js
CHANGED
|
@@ -28,6 +28,11 @@ function quotedControlEscape(ch) {
|
|
|
28
28
|
function atomNeedsQuotes(name) {
|
|
29
29
|
if (!name) return true;
|
|
30
30
|
if (name === '[]' || name === '{}') return false;
|
|
31
|
+
// `;` is a solo-character name token in ISO 6.4.2/6.5.3, so unlike most
|
|
32
|
+
// solo characters it is a valid atom without quotes. Keep `|` distinct:
|
|
33
|
+
// Corrigendum 2 makes the bar token equivalent to atom '|' only while it
|
|
34
|
+
// is being used as an operator; as an ordinary atom/functor it is quoted.
|
|
35
|
+
if (name === ';') return false;
|
|
31
36
|
// A lone full stop is the end token, not a graphic atom. Longer
|
|
32
37
|
// graphic tokens may contain dots and are valid unquoted writeq/1 output
|
|
33
38
|
// (WG17 #371-373: ./*, .*, ...*). Only a token beginning with /* would
|
|
@@ -164,6 +169,10 @@ function writeNumberedVariable(index) {
|
|
|
164
169
|
}
|
|
165
170
|
|
|
166
171
|
function operatorName(name) {
|
|
172
|
+
// Corrigendum 2 adds a dedicated bar token to operator syntax. The atom
|
|
173
|
+
// itself still needs quoting in functional notation, but operator-form
|
|
174
|
+
// output must use the unquoted `|` token (WG17 #181/#290).
|
|
175
|
+
if (name === '|') return '|';
|
|
167
176
|
if (name === '.' || name.startsWith('/*')) return quoteAtom(name);
|
|
168
177
|
if (/^[a-z][A-Za-z0-9_]*$/.test(name)) return name;
|
|
169
178
|
if (/^[!#$&*+\-./<=>?@^~\\;:]+$/.test(name)) return name;
|
|
@@ -274,6 +283,10 @@ function format(term, env, options, table, maxPriority = 1200, context = 'term')
|
|
|
274
283
|
if (resolved.type === STRING) return writeString(resolved.name);
|
|
275
284
|
if (resolved.type === ATOM) {
|
|
276
285
|
if (!options.quoted) return resolved.name;
|
|
286
|
+
// Corrigendum 2's bare bar token exists only in operator syntax. It is
|
|
287
|
+
// not an atom token, so an ordinary atom `|` must remain quoted even when
|
|
288
|
+
// `|` is currently declared as an operator (for example as f('|')).
|
|
289
|
+
if (resolved.name === '|') return quoteAtom(resolved.name);
|
|
277
290
|
// Top-level bindings are already delimited by their answer punctuation.
|
|
278
291
|
// Keep valid dotted graphic tokens readable there without weakening the
|
|
279
292
|
// ISO writeq/1 policy tested by WG17 #308.
|
|
@@ -6,7 +6,7 @@ together with Technical Corrigenda 1:2007, 2:2012, and 3:2017. Detailed
|
|
|
6
6
|
row-level evidence lives only where it adds information: the built-in, processor,
|
|
7
7
|
term-semantics, Prolog-text/execution, evaluable-functor, implementation-defined,
|
|
8
8
|
WG17-syntax, and STC draft ledgers in this directory. The generated
|
|
9
|
-
[`conformance-report.md`](../../conformance-report.md) gives current case totals.
|
|
9
|
+
[`conformance-report.md`](../../conformance-report.md) gives the current executable WG17 syntax result together with file-based case totals.
|
|
10
10
|
|
|
11
11
|
The ledger deliberately does **not** claim independent certification. A row
|
|
12
12
|
marked `covered` means EyeProlog has implementation and executable tests for
|
|
@@ -38,13 +38,13 @@ relevant implementation-dependent overlap choice to an explicit audit outcome.
|
|
|
38
38
|
| 7.7 — execution and backtracking | covered | [ISO-PROLOG-TEXT-EXECUTION-MATRIX.md](ISO-PROLOG-TEXT-EXECUTION-MATRIX.md) closes ordinary clause selection/backtracking, source/database order, empty-versus-unknown procedures, logical-update visibility, and strict dispatch. EyeProlog automatic tabling, cycle guards, and recursive numeric shortcuts remain disabled in the strict profile. |
|
|
39
39
|
| 7.8 — control constructs and exceptions | covered | [ISO-PROLOG-TEXT-EXECUTION-MATRIX.md](ISO-PROLOG-TEXT-EXECUTION-MATRIX.md) closes `true/0`, `fail/0`, `call/1`, cut, conjunction, disjunction, if-then(-else), and `catch/3`/`throw/1`, including Corrigendum 2 catchability of errors arising from the protected goal and Corrigendum 3 term-to-body conversion. The 8.15 built-in additions remain independently row-audited. |
|
|
40
40
|
| 7.9 — expression evaluation | covered | [ISO-EVALUABLE-FUNCTOR-MATRIX.md](ISO-EVALUABLE-FUNCTOR-MATRIX.md) closes recursive evaluation, direct-variable precedence, corrected non-evaluable `F/N` errors (including STC #69), operand template/type rules, mixed arithmetic-comparison conversion, exceptional values, and finite-host resource normalization. The audit also fixes float-only `float_integer_part/1` and `float_fractional_part/1` so even huge valid integers reach `type_error(float,...)` before any I->F overflow. |
|
|
41
|
-
| 7.10 — input/output concepts | covered | Higher-level stream semantics are pinned for non-atom ground stream terms, alias lifetime, current/target stream selection and fallback, write truncation, append positioning, contradictory stream options, exact binary round-tripping, flushing, standard-stream close behavior, and repositioned overwrite. The complete 8.11-8.14 built-in mode/error family remains row-audited in [ISO-BUILTIN-MODE-ERROR-MATRIX.md](ISO-BUILTIN-MODE-ERROR-MATRIX.md), while all implementation-defined stream choices are indexed in [ISO-IMPLEMENTATION-DEFINED.md](ISO-IMPLEMENTATION-DEFINED.md). |
|
|
41
|
+
| 7.10 — input/output concepts | covered | Higher-level stream semantics are pinned for non-atom ground stream terms, alias lifetime, current/target stream selection and fallback, write truncation, append positioning, contradictory stream options, exact binary round-tripping, flushing, standard-stream close behavior, and repositioned overwrite. Term-output spelling is also checked lexically where ISO distinguishes token forms: Corrigendum 2 bar operators are written with bare `|`, ordinary atom/functor `|` remains quoted, and the semicolon name token is emitted bare by canonical output. The complete 8.11-8.14 built-in mode/error family remains row-audited in [ISO-BUILTIN-MODE-ERROR-MATRIX.md](ISO-BUILTIN-MODE-ERROR-MATRIX.md), while all implementation-defined stream choices are indexed in [ISO-IMPLEMENTATION-DEFINED.md](ISO-IMPLEMENTATION-DEFINED.md). |
|
|
42
42
|
| 7.11 — flags | covered | The complete Part 1 flag set, selected defaults, standard value domains, changeability, `current_prolog_flag/2`, and `set_prolog_flag/2` error behavior have dedicated strict tests. EyeProlog selects `bounded=false` and `integer_rounding_function=toward_zero`; valid alternative values of those fixed flags reach `permission_error(modify,flag,...)`, while `max_integer` and `min_integer` have no current value. STC #70 is recorded explicitly: EyeProlog has no separate finite procedure-arity ceiling, so the optional `max_procedure_arity` flag is absent while `max_arity` remains `unbounded`. Strict mode excludes the EyeProlog `occurs_check` extension. |
|
|
43
43
|
| 7.12 — errors | covered | The strict error-envelope gate exercises instantiation, type, domain, existence, permission, representation, evaluation, resource, syntax, and system errors through `error(Error, Context)`. Built-in rows provide the individual prescribed conditions, implementation-defined representation/context choices are documented, and simultaneous-error regressions pin deterministic EyeProlog choices without treating textual table order as normative. |
|
|
44
44
|
| 8.2-8.17 — built-in predicates | covered | [ISO-BUILTIN-MODE-ERROR-MATRIX.md](ISO-BUILTIN-MODE-ERROR-MATRIX.md) now records the complete Part 1 + Corrigenda built-in family at one row per prescribed mode, success/failure behavior, individual error condition, and conditional/not-applicable branch. The row audit exposed and fixed source/runtime 7.6.2 body conversion/variable sharing, strict host-string leakage, input-code error timing, `close/2` force handling, and closed-stream `stream_property/2` behavior. Simultaneous-error choices are documented as implementation dependent under 7.12 unless separately constrained. |
|
|
45
45
|
| Clause 9 — evaluable functors | covered | [ISO-EVALUABLE-FUNCTOR-MATRIX.md](ISO-EVALUABLE-FUNCTOR-MATRIX.md) records the published Part 1 + Corrigenda arithmetic templates and exceptional branches across 9.1, 9.3, and 9.4. It pins the unbounded integer model, `rndI`/`rndF`/`resultF` choices, float-only conversion rules, I->F overflow, explicit `exp/1` and power underflow, Corrigendum 2 additions and mixed-type `max/2`/`min/2` implementation-dependent choice, Corrigendum 3 negative-power correction, signed bitwise/shift choices, and resource-error translation. Post-N289 STC #75 remains a separately tracked draft proposal rather than a silent change to the published baseline. |
|
|
46
46
|
| Corrigendum 1 | covered | Double-quoted atom/operator-priority corrections have dedicated cases. |
|
|
47
|
-
| Corrigendum 2 | covered | Added predicates/functors, catch corrections, bar/operator and uninstantiation corrections have dedicated cases. |
|
|
47
|
+
| Corrigendum 2 | covered | Added predicates/functors, catch corrections, bar/operator and uninstantiation corrections have dedicated cases, including the distinct bare `|` operator token required for term output. |
|
|
48
48
|
| Corrigendum 3 | covered | Writer options, `variable_names/1`, canonical list output and negative-power corrections have dedicated cases. |
|
|
49
49
|
|
|
50
50
|
## Conformance corrections and audit closure
|
|
@@ -109,7 +109,7 @@ terminating newline, so stream-sensitive cases such as #270 and #271 exercise
|
|
|
109
109
|
the characters left after `read/1`. Normal `npm test` remains offline and uses
|
|
110
110
|
only the committed upstream snapshot.
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
Regenerate the public conformance report, including the executable WG17 syntax status and the file-based category inventory:
|
|
113
113
|
|
|
114
114
|
```sh
|
|
115
115
|
node test/run-conformance-report.mjs
|
|
@@ -146,8 +146,8 @@ are recorded in [THIRD_PARTY.md](THIRD_PARTY.md).
|
|
|
146
146
|
The corpus has 386 cases in `iso/` and 802 file-based conformance cases in
|
|
147
147
|
total. Of those, 11 cases in `stc/` are explicitly labelled working-draft
|
|
148
148
|
review evidence rather than normative ISO claims. The separate strict-reader WG17 matrix has 366 executable dispositions.
|
|
149
|
-
The generated `conformance-report.md` is the authoritative source for current
|
|
150
|
-
category totals. Together with regression, documentation-sync, API, example,
|
|
149
|
+
The generated `conformance-report.md` is the authoritative source for the current
|
|
150
|
+
executable WG17 syntax result and file-based category totals. Together with regression, documentation-sync, API, example,
|
|
151
151
|
and book-example checks, `npm test` is the release gate.
|
|
152
152
|
|
|
153
153
|
## Updating expected output
|
|
@@ -7,7 +7,10 @@ This ledger counts an upstream case when its WG17 identifier, query, and
|
|
|
7
7
|
expected ISO disposition are stored in the offline executable matrix. Every
|
|
8
8
|
case is executed against the upstream Codex expectation. Reviewed exact
|
|
9
9
|
EyeProlog outcomes are additional regression locks and can never override the
|
|
10
|
-
upstream assertion.
|
|
10
|
+
upstream assertion. Writer expectations also preserve ISO-significant token
|
|
11
|
+
spelling: in particular, a bare bar used as an operator is not treated as
|
|
12
|
+
equivalent to a quoted atom, and a bare semicolon name token is not silently
|
|
13
|
+
accepted in quoted form.
|
|
11
14
|
|
|
12
15
|
## Current standing
|
|
13
16
|
|
|
@@ -3894,7 +3894,7 @@
|
|
|
3894
3894
|
"variables": "[]"
|
|
3895
3895
|
},
|
|
3896
3896
|
{
|
|
3897
|
-
"output": "a-->b,c
|
|
3897
|
+
"output": "a-->b,c|d",
|
|
3898
3898
|
"variables": "[]"
|
|
3899
3899
|
}
|
|
3900
3900
|
]
|
|
@@ -3914,7 +3914,7 @@
|
|
|
3914
3914
|
"variables": "[]"
|
|
3915
3915
|
},
|
|
3916
3916
|
{
|
|
3917
|
-
"output": "'|'(a
|
|
3917
|
+
"output": "'|'(a,;(b,c))",
|
|
3918
3918
|
"variables": "[]"
|
|
3919
3919
|
}
|
|
3920
3920
|
]
|
|
@@ -3934,7 +3934,7 @@
|
|
|
3934
3934
|
"variables": "[]"
|
|
3935
3935
|
},
|
|
3936
3936
|
{
|
|
3937
|
-
"output": "'|'(
|
|
3937
|
+
"output": "'|'(;(a,b),c)",
|
|
3938
3938
|
"variables": "[]"
|
|
3939
3939
|
}
|
|
3940
3940
|
]
|
|
@@ -3965,7 +3965,7 @@
|
|
|
3965
3965
|
"variables": "[]"
|
|
3966
3966
|
},
|
|
3967
3967
|
{
|
|
3968
|
-
"output": "[(a
|
|
3968
|
+
"output": "[(a|b)]",
|
|
3969
3969
|
"variables": "[]"
|
|
3970
3970
|
}
|
|
3971
3971
|
]
|
|
@@ -5173,7 +5173,7 @@
|
|
|
5173
5173
|
"type": "success",
|
|
5174
5174
|
"stages": [
|
|
5175
5175
|
{
|
|
5176
|
-
"output": "
|
|
5176
|
+
"output": ";(a)",
|
|
5177
5177
|
"variables": "[]"
|
|
5178
5178
|
}
|
|
5179
5179
|
]
|
|
@@ -5189,7 +5189,7 @@
|
|
|
5189
5189
|
"type": "success",
|
|
5190
5190
|
"stages": [
|
|
5191
5191
|
{
|
|
5192
|
-
"output": "
|
|
5192
|
+
"output": ";(a)",
|
|
5193
5193
|
"variables": "[]"
|
|
5194
5194
|
}
|
|
5195
5195
|
]
|
|
@@ -5205,7 +5205,7 @@
|
|
|
5205
5205
|
"type": "success",
|
|
5206
5206
|
"stages": [
|
|
5207
5207
|
{
|
|
5208
|
-
"output": "
|
|
5208
|
+
"output": ";(a,b)",
|
|
5209
5209
|
"variables": "[]"
|
|
5210
5210
|
}
|
|
5211
5211
|
]
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
2
|
+
// Executable conformance status plus static corpus inventory.
|
|
3
|
+
// The WG17 row is run when this report is generated, so a syntax-conformance
|
|
4
|
+
// regression changes the public report even when the fixture inventory itself
|
|
5
|
+
// has not changed.
|
|
5
6
|
import fs from 'node:fs';
|
|
6
7
|
import path from 'node:path';
|
|
7
8
|
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { runWg17 } from './run-wg17.mjs';
|
|
8
10
|
import { listPrologFiles } from './test-support.mjs';
|
|
9
11
|
|
|
10
12
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)));
|
|
@@ -18,9 +20,9 @@ const KINDS = [
|
|
|
18
20
|
{ kind: 'proofs', expectedKind: 'expected-proofs', expectedExt: '.pl', column: 'proofs' },
|
|
19
21
|
];
|
|
20
22
|
|
|
21
|
-
export function buildConformanceReport() {
|
|
23
|
+
export function buildConformanceReport({ wg17Suite = runWg17 } = {}) {
|
|
22
24
|
const categories = new Map();
|
|
23
|
-
const
|
|
25
|
+
const corpusIssues = [];
|
|
24
26
|
|
|
25
27
|
for (const { kind, expectedKind, expectedExt, column } of KINDS) {
|
|
26
28
|
const base = path.join(conformanceRoot, kind);
|
|
@@ -33,10 +35,10 @@ export function buildConformanceReport() {
|
|
|
33
35
|
|
|
34
36
|
const stem = file.slice(0, -3);
|
|
35
37
|
const expected = path.join(conformanceRoot, expectedKind, `${stem}${expectedExt}`);
|
|
36
|
-
if (!fs.existsSync(expected))
|
|
38
|
+
if (!fs.existsSync(expected)) corpusIssues.push(`missing ${expectedKind}/${stem}${expectedExt}`);
|
|
37
39
|
if (kind === 'warnings') {
|
|
38
40
|
const expectedStderr = path.join(conformanceRoot, expectedKind, `${stem}.txt`);
|
|
39
|
-
if (!fs.existsSync(expectedStderr))
|
|
41
|
+
if (!fs.existsSync(expectedStderr)) corpusIssues.push(`missing ${expectedKind}/${stem}.txt`);
|
|
40
42
|
}
|
|
41
43
|
}
|
|
42
44
|
}
|
|
@@ -52,32 +54,97 @@ export function buildConformanceReport() {
|
|
|
52
54
|
total: acc.total + row.total,
|
|
53
55
|
}), { positive: 0, errors: 0, warnings: 0, proofs: 0, total: 0 });
|
|
54
56
|
|
|
55
|
-
|
|
57
|
+
const executable = [executeGate('WG17 syntax', wg17Suite)];
|
|
58
|
+
const executionIssues = executable.flatMap((gate) => gate.failures.map((failure) =>
|
|
59
|
+
`${gate.name}: ${failure.name}: ${failure.message}`));
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
rows,
|
|
63
|
+
total,
|
|
64
|
+
executable,
|
|
65
|
+
corpusIssues: corpusIssues.sort(),
|
|
66
|
+
executionIssues,
|
|
67
|
+
issues: [...corpusIssues.sort(), ...executionIssues],
|
|
68
|
+
};
|
|
56
69
|
}
|
|
57
70
|
|
|
58
71
|
export function formatConformanceReport(report = buildConformanceReport()) {
|
|
59
72
|
const lines = [
|
|
60
73
|
'# EyeProlog conformance report',
|
|
61
74
|
'',
|
|
62
|
-
'This report
|
|
75
|
+
'This report combines an executable external conformance gate with the file-based',
|
|
76
|
+
'conformance corpus under `test/conformance/`. The executable result is measured',
|
|
77
|
+
'when this report is generated; it is not inferred from fixture counts.',
|
|
78
|
+
'',
|
|
79
|
+
'## Executable conformance status',
|
|
80
|
+
'',
|
|
81
|
+
'| Gate | Passed | Total | Status |',
|
|
82
|
+
'|---|---:|---:|---|',
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
for (const gate of report.executable) {
|
|
86
|
+
const status = gate.passed === gate.total ? 'pass' : 'fail';
|
|
87
|
+
lines.push(`| ${gate.name} | ${gate.passed} | ${gate.total} | ${status} |`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
lines.push(
|
|
91
|
+
'',
|
|
92
|
+
'The WG17 syntax row executes the vendored 366-case conformity-testing matrix',
|
|
93
|
+
'against EyeProlog\'s strict ISO reader/writer. A behavior fix such as operator-token',
|
|
94
|
+
'spelling therefore changes this report even when no corpus file is added or removed.',
|
|
95
|
+
'',
|
|
96
|
+
'## File-based corpus inventory',
|
|
63
97
|
'',
|
|
64
98
|
'| Category | Positive | Errors | Warnings | Proofs | Total |',
|
|
65
99
|
'|---|---:|---:|---:|---:|---:|',
|
|
66
|
-
|
|
100
|
+
);
|
|
67
101
|
|
|
68
102
|
for (const row of report.rows) {
|
|
69
103
|
lines.push(`| ${row.category} | ${row.positive} | ${row.errors} | ${row.warnings} | ${row.proofs} | ${row.total} |`);
|
|
70
104
|
}
|
|
71
105
|
lines.push(`| **Total** | **${report.total.positive}** | **${report.total.errors}** | **${report.total.warnings}** | **${report.total.proofs}** | **${report.total.total}** |`);
|
|
72
106
|
|
|
73
|
-
if (report.
|
|
107
|
+
if (report.corpusIssues.length > 0) {
|
|
74
108
|
lines.push('', '## Corpus issues', '');
|
|
75
|
-
for (const issue of report.
|
|
109
|
+
for (const issue of report.corpusIssues) lines.push(`- ${issue}`);
|
|
110
|
+
}
|
|
111
|
+
if (report.executionIssues.length > 0) {
|
|
112
|
+
lines.push('', '## Executable conformance failures', '');
|
|
113
|
+
for (const issue of report.executionIssues) lines.push(`- ${issue}`);
|
|
76
114
|
}
|
|
77
115
|
|
|
78
116
|
return `${lines.join('\n')}\n`;
|
|
79
117
|
}
|
|
80
118
|
|
|
119
|
+
function executeGate(name, runSuite) {
|
|
120
|
+
const failures = [];
|
|
121
|
+
const reporter = {
|
|
122
|
+
passed: 0,
|
|
123
|
+
total: 0,
|
|
124
|
+
section() {},
|
|
125
|
+
sectionTotal() {},
|
|
126
|
+
test(testName, run) {
|
|
127
|
+
this.total++;
|
|
128
|
+
try {
|
|
129
|
+
run();
|
|
130
|
+
this.passed++;
|
|
131
|
+
} catch (error) {
|
|
132
|
+
failures.push({
|
|
133
|
+
name: testName,
|
|
134
|
+
message: String(error?.message ?? error).split('\n', 1)[0],
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
runSuite(reporter);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
failures.push({ name: 'suite setup', message: String(error?.message ?? error).split('\n', 1)[0] });
|
|
144
|
+
}
|
|
145
|
+
return { name, passed: reporter.passed, total: reporter.total, failures };
|
|
146
|
+
}
|
|
147
|
+
|
|
81
148
|
function categoryOf(file) {
|
|
82
149
|
const parts = file.split('/');
|
|
83
150
|
return parts.length > 1 ? parts[0] : 'legacy-numbered';
|
package/test/run-regression.mjs
CHANGED
|
@@ -4698,6 +4698,34 @@ function documentationSyncCases() {
|
|
|
4698
4698
|
true,
|
|
4699
4699
|
'non-semantic internal operator spacing remains flexible',
|
|
4700
4700
|
);
|
|
4701
|
+
const barWriter = {
|
|
4702
|
+
id: 181,
|
|
4703
|
+
query: "writeq((a-->b,c|d)).",
|
|
4704
|
+
input: "op(1105,xfy,'|').\nwriteq((a-->b,c|d)).",
|
|
4705
|
+
};
|
|
4706
|
+
assertEqual(
|
|
4707
|
+
matchesUpstreamExpectation(
|
|
4708
|
+
'a-->b,c|d',
|
|
4709
|
+
{ type: 'success', stages: [{ output: "a-->b,c'|'d", variables: '[]' }] },
|
|
4710
|
+
barWriter,
|
|
4711
|
+
),
|
|
4712
|
+
false,
|
|
4713
|
+
'quoted bar is not accepted in operator-form writer output',
|
|
4714
|
+
);
|
|
4715
|
+
const semicolonWriter = {
|
|
4716
|
+
id: 331,
|
|
4717
|
+
query: 'write_canonical(;(a,b)).',
|
|
4718
|
+
input: 'write_canonical(;(a,b)).',
|
|
4719
|
+
};
|
|
4720
|
+
assertEqual(
|
|
4721
|
+
matchesUpstreamExpectation(
|
|
4722
|
+
';(a,b)',
|
|
4723
|
+
{ type: 'success', stages: [{ output: "';'(a,b)", variables: '[]' }] },
|
|
4724
|
+
semicolonWriter,
|
|
4725
|
+
),
|
|
4726
|
+
false,
|
|
4727
|
+
'quoted semicolon is not accepted when the ISO name token is bare',
|
|
4728
|
+
);
|
|
4701
4729
|
const repeated = {
|
|
4702
4730
|
id: 227, input: 'write_canonical(B+B).',
|
|
4703
4731
|
outcome: { type: 'success', stages: [{ output: '+(_A,_A)', variables: "['B' = B]" }] },
|
|
@@ -5001,10 +5029,27 @@ function documentationSyncCases() {
|
|
|
5001
5029
|
assertEqual(report.total.total >= 475, true, 'conformance case count');
|
|
5002
5030
|
assertEqual(report.total.positive + report.total.errors + report.total.warnings + report.total.proofs, report.total.total, 'conformance total');
|
|
5003
5031
|
assertEqual(report.rows.some((row) => row.category === 'legacy-numbered'), false, 'legacy-numbered category');
|
|
5032
|
+
const wg17 = report.executable.find((gate) => gate.name === 'WG17 syntax');
|
|
5033
|
+
assertEqual(wg17?.total, 366, 'WG17 executable total');
|
|
5034
|
+
assertEqual(wg17?.passed, 366, 'WG17 executable passed');
|
|
5035
|
+
assertArrayEqual(wg17?.failures ?? [], [], 'WG17 executable failures');
|
|
5004
5036
|
const text = formatConformanceReport(report);
|
|
5037
|
+
assertIncludes(text, '| WG17 syntax | 366 | 366 | pass |', 'report');
|
|
5005
5038
|
assertIncludes(text, '| variables |', 'report');
|
|
5006
5039
|
assertIncludes(text, '| Proofs |', 'report');
|
|
5007
5040
|
assertIncludes(text, '| **Total** |', 'report');
|
|
5041
|
+
|
|
5042
|
+
const failing = buildConformanceReport({
|
|
5043
|
+
wg17Suite: (reporter) => {
|
|
5044
|
+
reporter.section('WG17 syntax');
|
|
5045
|
+
reporter.test('synthetic syntax failure', () => { throw new Error('synthetic mismatch'); });
|
|
5046
|
+
reporter.sectionTotal('WG17 syntax');
|
|
5047
|
+
},
|
|
5048
|
+
});
|
|
5049
|
+
assertEqual(failing.executable[0].passed, 0, 'failing WG17 executable passed');
|
|
5050
|
+
assertEqual(failing.executable[0].total, 1, 'failing WG17 executable total');
|
|
5051
|
+
assertEqual(failing.executionIssues.length, 1, 'failing WG17 report issue count');
|
|
5052
|
+
assertIncludes(formatConformanceReport(failing), '| WG17 syntax | 0 | 1 | fail |', 'failing report');
|
|
5008
5053
|
},
|
|
5009
5054
|
},
|
|
5010
5055
|
|
package/test/run-wg17.mjs
CHANGED
|
@@ -252,6 +252,48 @@ function termEquivalent(expected, actual, item) {
|
|
|
252
252
|
}
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
+
function soloTokenQuoteSignature(text) {
|
|
256
|
+
const signature = [];
|
|
257
|
+
let quote = null;
|
|
258
|
+
const source = presentationText(text);
|
|
259
|
+
for (let index = 0; index < source.length; index++) {
|
|
260
|
+
const ch = source[index];
|
|
261
|
+
if (quote != null) {
|
|
262
|
+
if (ch === '\\') {
|
|
263
|
+
index++;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
if (ch === quote) {
|
|
267
|
+
if (source[index + 1] === quote) {
|
|
268
|
+
index++;
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
quote = null;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (ch === '|' || ch === ';' || ch === ',') signature.push(`${ch}:quoted`);
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
if (ch === "'") {
|
|
278
|
+
quote = ch;
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
if (ch === '|' || ch === ';' || ch === ',') signature.push(`${ch}:bare`);
|
|
282
|
+
}
|
|
283
|
+
return signature;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function preservesWriterSoloTokenSpelling(expected, actual, item) {
|
|
287
|
+
// For writer conformity cases, semantic term equivalence is deliberately
|
|
288
|
+
// not enough: ISO distinguishes the bare bar operator token from atom '|',
|
|
289
|
+
// and semicolon is itself a valid unquoted name token. This prevents the
|
|
290
|
+
// fallback parser comparison from accepting regressions such as `a'|'b` or
|
|
291
|
+
// canonical `';'(a,b)` when the WG17 expected spelling is `a|b` / `;(a,b)`.
|
|
292
|
+
if (!/\bwrite(?:q|_canonical|_term)?\s*\(/.test(item?.query ?? '')) return true;
|
|
293
|
+
return JSON.stringify(soloTokenQuoteSignature(expected)) ===
|
|
294
|
+
JSON.stringify(soloTokenQuoteSignature(actual));
|
|
295
|
+
}
|
|
296
|
+
|
|
255
297
|
function abbreviatedErrorMatches(expected, actual) {
|
|
256
298
|
if (actual.type !== 'error') return false;
|
|
257
299
|
const formal = actual.formal ?? '';
|
|
@@ -284,6 +326,7 @@ function textExpectationMatches(expectedText, actual, item, example = false) {
|
|
|
284
326
|
// boundary, notably Cor.3 prefix-operator cases such as `- (1^2)`. Do not
|
|
285
327
|
// erase that boundary before the semantic/layout-tolerant fallbacks below.
|
|
286
328
|
if (!preservesLeadingOperatorParenLayout(left, right)) continue;
|
|
329
|
+
if (!preservesWriterSoloTokenSpelling(left, right, item)) continue;
|
|
287
330
|
if (stripLayoutOutsideQuotes(left) === stripLayoutOutsideQuotes(right)) return true;
|
|
288
331
|
if (termEquivalent(left, right, item)) return true;
|
|
289
332
|
if (example) {
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -7784,14 +7784,14 @@ their declared goals are executed. The eight-case
|
|
|
7784
7784
|
playground contract suite imports the production worker, sends real reasoning
|
|
7785
7785
|
requests through its message protocol, and crawls the served module graph for
|
|
7786
7786
|
missing assets, bad MIME types, and static Node-only imports. The generated
|
|
7787
|
-
`conformance-report.md` is the authoritative source for current
|
|
7788
|
-
category totals.
|
|
7787
|
+
`conformance-report.md` is the authoritative source for the current executable
|
|
7788
|
+
WG17 syntax result and file-based conformance category totals.
|
|
7789
7789
|
|
|
7790
7790
|
### Generated and checked repository material
|
|
7791
7791
|
|
|
7792
7792
|
Repository artifacts have distinct roles:
|
|
7793
7793
|
|
|
7794
|
-
- `conformance-report.md`
|
|
7794
|
+
- `conformance-report.md` executes the vendored WG17 syntax gate and also inventories the file-based conformance corpus;
|
|
7795
7795
|
- `examples/book/` is extracted from executable code blocks in this book and
|
|
7796
7796
|
should be rebuilt with `npm run generate` rather than edited directly;
|
|
7797
7797
|
- `examples/output/` and `examples/proof/` contain reviewed exact-output
|
|
@@ -95,7 +95,10 @@ This ledger counts an upstream case when its WG17 identifier, query, and
|
|
|
95
95
|
expected ISO disposition are stored in the offline executable matrix. Every
|
|
96
96
|
case is executed against the upstream Codex expectation. Reviewed exact
|
|
97
97
|
EyeProlog outcomes are additional regression locks and can never override the
|
|
98
|
-
upstream assertion.
|
|
98
|
+
upstream assertion. Writer expectations also preserve ISO-significant token
|
|
99
|
+
spelling: in particular, a bare bar used as an operator is not treated as
|
|
100
|
+
equivalent to a quoted atom, and a bare semicolon name token is not silently
|
|
101
|
+
accepted in quoted form.
|
|
99
102
|
|
|
100
103
|
## Current standing
|
|
101
104
|
|