eyeprolog 1.5.22 → 1.5.24
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/conformance-report.md +2 -2
- package/package.json +1 -1
- package/src/iso.js +5 -5
- package/src/parser.js +64 -13
- package/test/benchmark.mjs +61 -0
- package/test/conformance/ISO-COMPLIANCE.md +4 -4
- package/test/conformance/ISO-IMPLEMENTATION-DEFINED.md +1 -1
- package/test/conformance/ISO-PROCESSOR-REQUIREMENTS.md +1 -1
- package/test/conformance/README.md +1 -1
- package/test/conformance/WG17-SYNTAX-STATUS.md +9 -9
- package/test/conformance/wg17-syntax-cases.json +4 -103
- package/test/conformance/wg17-syntax-coverage.json +3 -3
- package/test/run-regression.mjs +68 -3
- package/the-art-of-eyeprolog.md +10 -3
- package/tools/upgrade-wg17.mjs +26 -0
package/conformance-report.md
CHANGED
|
@@ -8,9 +8,9 @@ when this report is generated; it is not inferred from fixture counts.
|
|
|
8
8
|
|
|
9
9
|
| Gate | Passed | Total | Status |
|
|
10
10
|
|---|---:|---:|---|
|
|
11
|
-
| WG17 syntax |
|
|
11
|
+
| WG17 syntax | 366 | 366 | pass |
|
|
12
12
|
|
|
13
|
-
The WG17 syntax row executes the vendored
|
|
13
|
+
The WG17 syntax row executes the vendored 366-case conformity-testing matrix
|
|
14
14
|
against EyeProlog's strict ISO reader/writer. A behavior fix such as operator-token
|
|
15
15
|
spelling therefore changes this report even when no corpus file is added or removed.
|
|
16
16
|
|
package/package.json
CHANGED
package/src/iso.js
CHANGED
|
@@ -1762,7 +1762,7 @@ function parseReadTermText(text, solver) {
|
|
|
1762
1762
|
const numericText = converted.slice(0, -1).replace(/[\u0009-\u000d\u0020]+$/, '');
|
|
1763
1763
|
let numericTerm;
|
|
1764
1764
|
try {
|
|
1765
|
-
numericTerm = parseIsoNumber(numericText);
|
|
1765
|
+
numericTerm = parseIsoNumber(numericText, { isoStrict: solver.isoStrict });
|
|
1766
1766
|
} catch (error) {
|
|
1767
1767
|
// parseIsoNumber/1 exposes Prolog errors to number_chars/2. The stream
|
|
1768
1768
|
// reader's candidate loop instead recognizes parser representation errors
|
|
@@ -2338,7 +2338,7 @@ function quotedNumberSign(text, start) {
|
|
|
2338
2338
|
return null;
|
|
2339
2339
|
}
|
|
2340
2340
|
|
|
2341
|
-
function parseIsoNumber(text) {
|
|
2341
|
+
function parseIsoNumber(text, options = {}) {
|
|
2342
2342
|
if (text.length === 0) return null;
|
|
2343
2343
|
let position = skipNumberLayout(text, 0);
|
|
2344
2344
|
let sign = '';
|
|
@@ -2375,7 +2375,7 @@ function parseIsoNumber(text) {
|
|
|
2375
2375
|
// ISO floating-point syntax requires a decimal fraction before an exponent.
|
|
2376
2376
|
if (/^-?\d+[eE][+-]?\d+$/.test(numericText)) return null;
|
|
2377
2377
|
try {
|
|
2378
|
-
const value = parseNumberTokenText(numericText);
|
|
2378
|
+
const value = parseNumberTokenText(numericText, options);
|
|
2379
2379
|
if (isDecimalInteger(value.name)) return numberTerm(BigInt(value.name).toString());
|
|
2380
2380
|
const finite = Number(value.name);
|
|
2381
2381
|
if (!Number.isFinite(finite)) return null;
|
|
@@ -2447,7 +2447,7 @@ function numberListBuiltin(kind) {
|
|
|
2447
2447
|
const text = numberListText(list, env, kind, value.type === NUMBER, solver);
|
|
2448
2448
|
if (value.type === NUMBER) {
|
|
2449
2449
|
if (text != null) {
|
|
2450
|
-
const parsed = parseIsoNumber(text);
|
|
2450
|
+
const parsed = parseIsoNumber(text, { isoStrict: solver?.isoStrict === true });
|
|
2451
2451
|
if (parsed == null) throw numberSyntaxError;
|
|
2452
2452
|
if (sameNumber(value, parsed)) yield env.clone();
|
|
2453
2453
|
return;
|
|
@@ -2458,7 +2458,7 @@ function numberListBuiltin(kind) {
|
|
|
2458
2458
|
if (unify(goal.args[1], listFromItems(items), next)) yield next;
|
|
2459
2459
|
return;
|
|
2460
2460
|
}
|
|
2461
|
-
const parsed = parseIsoNumber(text);
|
|
2461
|
+
const parsed = parseIsoNumber(text, { isoStrict: solver?.isoStrict === true });
|
|
2462
2462
|
if (parsed == null) throw numberSyntaxError;
|
|
2463
2463
|
const next = env.clone();
|
|
2464
2464
|
if (unify(goal.args[0], parsed, next)) yield next;
|
package/src/parser.js
CHANGED
|
@@ -439,6 +439,21 @@ class Parser {
|
|
|
439
439
|
break;
|
|
440
440
|
}
|
|
441
441
|
}
|
|
442
|
+
integerDigits(digitPattern, line) {
|
|
443
|
+
let digits = '';
|
|
444
|
+
let separated = false;
|
|
445
|
+
while (digitPattern.test(this.peek())) {
|
|
446
|
+
digits += this.take();
|
|
447
|
+
if (this.strictIso || this.peek() !== '_') continue;
|
|
448
|
+
separated = true;
|
|
449
|
+
this.take();
|
|
450
|
+
this.skipWhitespaceAndComments();
|
|
451
|
+
if (!digitPattern.test(this.peek())) {
|
|
452
|
+
throw new Error(`parse line ${line}: bad digit separator`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return { digits, separated };
|
|
456
|
+
}
|
|
442
457
|
readEscape(line, options = {}) {
|
|
443
458
|
const takeChar = () => options.raw ? this.rawTake() : this.take();
|
|
444
459
|
const peekChar = () => options.raw ? this.rawPeek() : this.peek();
|
|
@@ -646,17 +661,16 @@ class Parser {
|
|
|
646
661
|
const kind = this.take();
|
|
647
662
|
const radix = kind === 'b' ? 2 : kind === 'o' ? 8 : 16;
|
|
648
663
|
const digitPattern = radix === 2 ? /^[01]$/ : radix === 8 ? /^[0-7]$/ : /^[0-9A-Fa-f]$/;
|
|
649
|
-
|
|
650
|
-
while (digitPattern.test(this.peek())) digits += this.take();
|
|
664
|
+
const { digits } = this.integerDigits(digitPattern, line);
|
|
651
665
|
if (!digits) throw new Error(`parse line ${line}: bad radix integer`);
|
|
652
666
|
let integer = 0n;
|
|
653
667
|
for (const digit of digits) integer = integer * BigInt(radix) + BigInt(Number.parseInt(digit, radix));
|
|
654
668
|
if (negative) integer = -integer;
|
|
655
669
|
return { type: TOK.NUMBER, text: integer.toString(), line };
|
|
656
670
|
}
|
|
657
|
-
|
|
671
|
+
const { digits, separated } = this.integerDigits(/^[0-9]$/, line);
|
|
658
672
|
let hasFraction = false;
|
|
659
|
-
if (this.peek() === '.' && isDigitCode(this.peek(1).charCodeAt(0))) {
|
|
673
|
+
if (!separated && this.peek() === '.' && isDigitCode(this.peek(1).charCodeAt(0))) {
|
|
660
674
|
hasFraction = true;
|
|
661
675
|
this.take();
|
|
662
676
|
while (isDigitCode(this.peek().charCodeAt(0))) this.take();
|
|
@@ -674,7 +688,7 @@ class Parser {
|
|
|
674
688
|
}
|
|
675
689
|
}
|
|
676
690
|
let text = this.convertedSlice(start, this.pos);
|
|
677
|
-
if (!hasFraction) text = BigInt(
|
|
691
|
+
if (!hasFraction) text = BigInt(`${negative ? '-' : ''}${digits}`).toString();
|
|
678
692
|
else text = finiteFloatTokenText(text);
|
|
679
693
|
return { type: TOK.NUMBER, text, line };
|
|
680
694
|
}
|
|
@@ -1704,8 +1718,43 @@ export function parseProgramText(source, options = {}) {
|
|
|
1704
1718
|
|
|
1705
1719
|
const invalidNumberTokenError = new Error('not exactly one number token');
|
|
1706
1720
|
|
|
1707
|
-
|
|
1721
|
+
function skipDigitSeparatorLayout(source, start) {
|
|
1722
|
+
let position = start;
|
|
1723
|
+
while (true) {
|
|
1724
|
+
while (isWhitespaceCharacter(source[position] ?? '')) position++;
|
|
1725
|
+
if (source[position] === '%') {
|
|
1726
|
+
const newline = source.indexOf('\n', position + 1);
|
|
1727
|
+
if (newline < 0) return source.length;
|
|
1728
|
+
position = newline + 1;
|
|
1729
|
+
continue;
|
|
1730
|
+
}
|
|
1731
|
+
if (source.startsWith('/*', position)) {
|
|
1732
|
+
const end = source.indexOf('*/', position + 2);
|
|
1733
|
+
if (end < 0) return source.length;
|
|
1734
|
+
position = end + 2;
|
|
1735
|
+
continue;
|
|
1736
|
+
}
|
|
1737
|
+
return position;
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
function separatedIntegerDigits(source, start, digitPattern, enabled) {
|
|
1742
|
+
let position = start;
|
|
1743
|
+
let digits = '';
|
|
1744
|
+
let separated = false;
|
|
1745
|
+
while (digitPattern.test(source[position] ?? '')) {
|
|
1746
|
+
digits += source[position++];
|
|
1747
|
+
if (!enabled || source[position] !== '_') continue;
|
|
1748
|
+
separated = true;
|
|
1749
|
+
position = skipDigitSeparatorLayout(source, position + 1);
|
|
1750
|
+
if (!digitPattern.test(source[position] ?? '')) throw invalidNumberTokenError;
|
|
1751
|
+
}
|
|
1752
|
+
return { digits, position, separated };
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
export function parseNumberTokenText(text, options = {}) {
|
|
1708
1756
|
const source = String(text ?? '');
|
|
1757
|
+
const digitSeparators = options.isoStrict !== true;
|
|
1709
1758
|
let position = 0;
|
|
1710
1759
|
let negative = false;
|
|
1711
1760
|
if (source[position] === '-') {
|
|
@@ -1771,8 +1820,9 @@ export function parseNumberTokenText(text) {
|
|
|
1771
1820
|
const radix = kind === 'b' ? 2 : kind === 'o' ? 8 : 16;
|
|
1772
1821
|
const digitPattern = radix === 2 ? /^[01]$/ : radix === 8 ? /^[0-7]$/ : /^[0-9A-Fa-f]$/;
|
|
1773
1822
|
position += 2;
|
|
1774
|
-
|
|
1775
|
-
|
|
1823
|
+
const scanned = separatedIntegerDigits(source, position, digitPattern, digitSeparators);
|
|
1824
|
+
const { digits } = scanned;
|
|
1825
|
+
position = scanned.position;
|
|
1776
1826
|
if (!digits || position !== source.length) throw invalidNumberTokenError;
|
|
1777
1827
|
let integer = 0n;
|
|
1778
1828
|
for (const digit of digits) integer = integer * BigInt(radix) + BigInt(Number.parseInt(digit, radix));
|
|
@@ -1780,11 +1830,12 @@ export function parseNumberTokenText(text) {
|
|
|
1780
1830
|
return numberTerm(integer.toString());
|
|
1781
1831
|
}
|
|
1782
1832
|
|
|
1783
|
-
const
|
|
1784
|
-
|
|
1785
|
-
|
|
1833
|
+
const scanned = separatedIntegerDigits(source, position, /^[0-9]$/, digitSeparators);
|
|
1834
|
+
const { digits, separated } = scanned;
|
|
1835
|
+
position = scanned.position;
|
|
1836
|
+
if (!digits) throw invalidNumberTokenError;
|
|
1786
1837
|
let hasFraction = false;
|
|
1787
|
-
if (source[position] === '.' && isDigitCode(source.charCodeAt(position + 1))) {
|
|
1838
|
+
if (!separated && source[position] === '.' && isDigitCode(source.charCodeAt(position + 1))) {
|
|
1788
1839
|
hasFraction = true;
|
|
1789
1840
|
position++;
|
|
1790
1841
|
while (isDigitCode(source.charCodeAt(position))) position++;
|
|
@@ -1797,7 +1848,7 @@ export function parseNumberTokenText(text) {
|
|
|
1797
1848
|
if (position === exponentStart) throw invalidNumberTokenError;
|
|
1798
1849
|
}
|
|
1799
1850
|
if (position !== source.length) throw invalidNumberTokenError;
|
|
1800
|
-
if (
|
|
1851
|
+
if (!hasFraction) return numberTerm(BigInt(`${negative ? '-' : ''}${digits}`).toString());
|
|
1801
1852
|
return numberTerm(finiteFloatTokenText(source));
|
|
1802
1853
|
}
|
|
1803
1854
|
|
package/test/benchmark.mjs
CHANGED
|
@@ -128,6 +128,48 @@ function changeText(item) {
|
|
|
128
128
|
return `${change < 0 ? '↓' : '↑'} ${value}`;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
function summarizeResults(results) {
|
|
132
|
+
const comparable = results.filter((item) =>
|
|
133
|
+
Number.isFinite(item.medianMs) &&
|
|
134
|
+
item.medianMs > 0 &&
|
|
135
|
+
Number.isFinite(item.baselineMs) &&
|
|
136
|
+
item.baselineMs > 0);
|
|
137
|
+
|
|
138
|
+
if (comparable.length === 0) {
|
|
139
|
+
return {
|
|
140
|
+
comparable: 0,
|
|
141
|
+
total: results.length,
|
|
142
|
+
ratio: null,
|
|
143
|
+
changePercent: null,
|
|
144
|
+
currentTotalMs: null,
|
|
145
|
+
baselineTotalMs: null,
|
|
146
|
+
totalRatio: null,
|
|
147
|
+
totalChangePercent: null,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const ratio = Math.exp(
|
|
152
|
+
comparable.reduce(
|
|
153
|
+
(sum, item) => sum + Math.log(item.medianMs / item.baselineMs),
|
|
154
|
+
0,
|
|
155
|
+
) / comparable.length,
|
|
156
|
+
);
|
|
157
|
+
const currentTotalMs = comparable.reduce((sum, item) => sum + item.medianMs, 0);
|
|
158
|
+
const baselineTotalMs = comparable.reduce((sum, item) => sum + item.baselineMs, 0);
|
|
159
|
+
const totalRatio = currentTotalMs / baselineTotalMs;
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
comparable: comparable.length,
|
|
163
|
+
total: results.length,
|
|
164
|
+
ratio,
|
|
165
|
+
changePercent: (ratio - 1) * 100,
|
|
166
|
+
currentTotalMs,
|
|
167
|
+
baselineTotalMs,
|
|
168
|
+
totalRatio,
|
|
169
|
+
totalChangePercent: (totalRatio - 1) * 100,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
131
173
|
function runWorker(item) {
|
|
132
174
|
const child = spawnSync(process.execPath, [
|
|
133
175
|
path.join(root, 'test', 'benchmark-worker.mjs'),
|
|
@@ -176,6 +218,8 @@ for (const item of selected) {
|
|
|
176
218
|
});
|
|
177
219
|
}
|
|
178
220
|
|
|
221
|
+
const summary = summarizeResults(results);
|
|
222
|
+
|
|
179
223
|
if (options.save != null) {
|
|
180
224
|
await fs.mkdir(path.dirname(options.save), { recursive: true });
|
|
181
225
|
const saved = {
|
|
@@ -202,6 +246,7 @@ if (options.json) {
|
|
|
202
246
|
targetMs: options.targetMs,
|
|
203
247
|
baseline: baselinePath,
|
|
204
248
|
baselineWarning,
|
|
249
|
+
summary,
|
|
205
250
|
results,
|
|
206
251
|
}, null, 2)}\n`);
|
|
207
252
|
} else {
|
|
@@ -220,9 +265,25 @@ if (options.json) {
|
|
|
220
265
|
printRow(headers);
|
|
221
266
|
printRow(widths.map((width) => '-'.repeat(width)));
|
|
222
267
|
for (const row of rows) printRow(row);
|
|
268
|
+
|
|
269
|
+
if (summary.comparable > 0) {
|
|
270
|
+
process.stdout.write(
|
|
271
|
+
`\nSuite score: ${summary.ratio.toFixed(3)}x baseline ` +
|
|
272
|
+
`(${changeText({ changePercent: summary.changePercent })}); ` +
|
|
273
|
+
`equal-weight geometric mean across ${summary.comparable}/${summary.total} comparable benchmarks.\n`,
|
|
274
|
+
);
|
|
275
|
+
process.stdout.write(
|
|
276
|
+
`Time-weighted total: ${formatMs(summary.currentTotalMs)} vs ${formatMs(summary.baselineTotalMs)} ` +
|
|
277
|
+
`(${changeText({ changePercent: summary.totalChangePercent })}).\n`,
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
223
281
|
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`);
|
|
224
282
|
if (baselineWarning) process.stdout.write(`${baselineWarning}\n`);
|
|
225
283
|
if (baselinePath == null && !baselineWarning) process.stdout.write('No timing baseline found; run npm run benchmark:baseline to create .benchmarks/baseline.json.\n');
|
|
226
284
|
if (options.save != null) process.stdout.write(`Saved timing baseline: ${path.relative(root, options.save)}\n`);
|
|
227
285
|
process.stdout.write('Change compares the current median/op directly with the saved baseline median/op; the measured range is shown separately.\n');
|
|
286
|
+
if (summary.comparable > 0) {
|
|
287
|
+
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');
|
|
288
|
+
}
|
|
228
289
|
}
|
|
@@ -19,7 +19,7 @@ relevant implementation-dependent overlap choice to an explicit audit outcome.
|
|
|
19
19
|
|
|
20
20
|
| Requirement | Status | EyeProlog evidence / remaining work |
|
|
21
21
|
| --- | --- | --- |
|
|
22
|
-
| 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
|
|
22
|
+
| 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 366-case vendored WG17 syntax matrix; 7.4 preparation/directive behavior is separately closed. |
|
|
23
23
|
| 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. |
|
|
24
24
|
| 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. |
|
|
25
25
|
| 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 `audit gap` category for future discoveries; no current release-facing row depends on one. |
|
|
@@ -31,7 +31,7 @@ relevant implementation-dependent overlap choice to an explicit audit outcome.
|
|
|
31
31
|
|
|
32
32
|
| Standard area | Status | Current evidence |
|
|
33
33
|
| --- | --- | --- |
|
|
34
|
-
| 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
|
|
34
|
+
| 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 366-case WG17 syntax matrix provides independent production-level coverage, 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. |
|
|
35
35
|
| 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. |
|
|
36
36
|
| 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. |
|
|
37
37
|
| 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. |
|
|
@@ -286,7 +286,7 @@ post-N289 STC drafts remain review input until standardized.
|
|
|
286
286
|
| Criterion | Status | Evidence |
|
|
287
287
|
| --- | --- | --- |
|
|
288
288
|
| 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. |
|
|
289
|
-
| Clause 6 lexical/syntactic requirements have explicit dispositions | covered | the strict Clause 6 production/rejection gate and the
|
|
289
|
+
| Clause 6 lexical/syntactic requirements have explicit dispositions | covered | the strict Clause 6 production/rejection gate and the 366-case vendored WG17 matrix cover the standard families; cross-profile preservation verifies that normal extensions do not reinterpret strict-accepted text. |
|
|
290
290
|
| 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. |
|
|
291
291
|
| Clause 8 built-in modes/errors have explicit dispositions | covered | `ISO-BUILTIN-MODE-ERROR-MATRIX.md` closes 8.2-8.17 row by row |
|
|
292
292
|
| 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 |
|
|
@@ -294,7 +294,7 @@ post-N289 STC drafts remain review input until standardized.
|
|
|
294
294
|
| 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. |
|
|
295
295
|
| Published Corrigenda 1-3 are incorporated | covered | dedicated conformance/strict tests and this ledger track all three published Corrigenda |
|
|
296
296
|
| 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 |
|
|
297
|
-
| Independent external syntax corpus is an offline release gate | covered | the vendored WG17 syntax matrix has
|
|
297
|
+
| Independent external syntax corpus is an offline release gate | covered | the vendored WG17 syntax matrix has 366 executable dispositions checked against its upstream expectations |
|
|
298
298
|
| 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` |
|
|
299
299
|
| No unexplained deviation remains in the release-facing ledger | covered | the release-facing ledger contains no remaining `audit` rows; documented variation points are implementation-defined/specific or draft-only rather than unexplained deviations. |
|
|
300
300
|
|
|
@@ -113,7 +113,7 @@ families; `--iso-strict` is intended to remove their Part 1 interpretation.
|
|
|
113
113
|
|
|
114
114
|
| Part 1 extension hook | EyeProlog normal-profile feature | Strict-core disposition |
|
|
115
115
|
| --- | --- | --- |
|
|
116
|
-
| 5.5.1 Syntax | Part 2 modules, Part 3 grammar-rule expansion, embedded quad syntax, and Trealla-compatible `"text"||Tail` right-splicing for double-quoted `chars`/`codes` lists | Module directives are rejected; grammar rules remain ordinary `-->/2` terms rather than being expanded; quad syntax and double-bar right-splicing are rejected. Unicode PCS membership/classification is implementation defined and therefore shared with normal mode rather than treated as an extension. |
|
|
116
|
+
| 5.5.1 Syntax | Part 2 modules, Part 3 grammar-rule expansion, embedded quad syntax, digit-separated integer constants (`1_000`, `0xCA_FE`, with optional layout after `_`), and Trealla-compatible `"text"||Tail` right-splicing for double-quoted `chars`/`codes` lists | Module directives are rejected; grammar rules remain ordinary `-->/2` terms rather than being expanded; quad syntax, integer digit separators, and double-bar right-splicing are rejected. Unicode PCS membership/classification is implementation defined and therefore shared with normal mode rather than treated as an extension. |
|
|
117
117
|
| 5.5.2 Predefined operators | Part 3 `|` and EyeProlog's labelable infix `(?-)/2`; CLP(Z) operators when that library is imported | Only the Part 1 operator table is predefined; a conforming `op/3` may still add permitted operators. |
|
|
118
118
|
| 5.5.3 Character-conversion mapping | No non-identity initial `Convc` extension | Identity initial mapping. |
|
|
119
119
|
| 5.5.4 Types | The normal JavaScript API exposes an implementation-specific string term `stringTerm(Text)`. It is disjoint from the five Part 1 term types; normal term order places it after atoms and before compound terms, and normal `atomic/1` treats it as atomic. It has no Prolog source token syntax (double-quoted source still follows `double_quotes`), is non-callable for term-to-clause conversion, is not evaluable as an arithmetic expression, and writes as a double-quoted host string. | Strict mode rejects a programmatic string term at program/goal entry with `representation_error(term)`, so the additional type cannot enter the Part 1 execution domain. **defined** — `src/term.js`, `src/program.js`, `src/solver.js`; strict API-boundary regression. |
|
|
@@ -34,7 +34,7 @@ certification claim.
|
|
|
34
34
|
| Requirement | Status | EyeProlog decision / evidence |
|
|
35
35
|
| --- | --- | --- |
|
|
36
36
|
| 5.5 general extension rule | covered | normal mode may provide documented extensions; strict mode removes their Part 1 interpretation rather than changing implementation-defined choices |
|
|
37
|
-
| 5.5.1 syntax extensions preserve standard token/text meaning | covered | WG17 syntax is a release gate and strict mode removes module/DCG/quad interpretation. Every vendored WG17 case that succeeds in the strict reader has the same observable outcome in normal mode; the focused Clause 6 gate separately covers each standard token/term family and malformed counterparts. |
|
|
37
|
+
| 5.5.1 syntax extensions preserve standard token/text meaning | covered | WG17 syntax is a release gate and strict mode removes module/DCG/quad, digit-separator, and double-bar interpretation. Every vendored WG17 case that succeeds in the strict reader has the same observable outcome in normal mode; the focused Clause 6 gate separately covers each standard token/term family and malformed counterparts. |
|
|
38
38
|
| 5.5.2 additional predefined operators | covered | strict mode starts from the Part 1 predefined operator table; normal-profile extra operators are documented and filtered |
|
|
39
39
|
| 5.5.3 initial character-conversion mapping | covered | identity initial mapping; user changes are exercised through preparation/execution `char_conversion/2` behavior |
|
|
40
40
|
| 5.5.4 additional term types | covered | the normal JavaScript API's `stringTerm(Text)` is documented as an implementation-specific sixth term type, including disjointness, ordering, clause conversion, lack of source token syntax, expression behavior, and writing; strict program/goal entry rejects that type with `representation_error(term)` |
|
|
@@ -145,7 +145,7 @@ are recorded in [THIRD_PARTY.md](THIRD_PARTY.md).
|
|
|
145
145
|
|
|
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
|
-
review evidence rather than normative ISO claims. The separate strict-reader WG17 matrix has
|
|
148
|
+
review evidence rather than normative ISO claims. The separate strict-reader WG17 matrix has 366 executable dispositions.
|
|
149
149
|
The generated `conformance-report.md` is the authoritative source for the current
|
|
150
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.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# WG17 syntax traceability status
|
|
2
2
|
|
|
3
3
|
Source: [Conformity Testing I: Syntax](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/conformity_testing)
|
|
4
|
-
Upstream inventory checked: 2026-
|
|
4
|
+
Upstream inventory checked: 2026-09-01
|
|
5
5
|
|
|
6
6
|
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
|
|
@@ -20,25 +20,25 @@ A represented case still has to pass the executable upstream assertion in
|
|
|
20
20
|
|
|
21
21
|
| Measure | Count |
|
|
22
22
|
| --- | ---: |
|
|
23
|
-
| Active upstream cases |
|
|
24
|
-
| Upstream cases represented in the executable matrix |
|
|
23
|
+
| Active upstream cases | 366 |
|
|
24
|
+
| Upstream cases represented in the executable matrix | 366 (100.0%) |
|
|
25
25
|
| Not yet traced one-by-one | 0 |
|
|
26
26
|
| Deleted upstream identifiers | #20, #273 |
|
|
27
27
|
|
|
28
28
|
The matrix runs in strict ISO stream-reader mode as part of `npm test`. The
|
|
29
29
|
3 upstream `waits` cases are checked through EyeProlog's interactive input
|
|
30
|
-
hook. All
|
|
31
|
-
upstream Codex expectation.
|
|
32
|
-
outcomes for stronger regression checking;
|
|
30
|
+
hook. All 366 executable cases are independently checked against the
|
|
31
|
+
upstream Codex expectation. 365 cases additionally retain exact reviewed
|
|
32
|
+
outcomes for stronger regression checking; 1 case currently relies on the upstream assertion alone.
|
|
33
33
|
|
|
34
34
|
## Traceable evidence
|
|
35
35
|
|
|
36
36
|
| Executable evidence | Referenced IDs | WG17 cases |
|
|
37
37
|
| --- | ---: | --- |
|
|
38
|
-
| [complete offline executable matrix](../run-wg17.mjs) |
|
|
38
|
+
| [complete offline executable matrix](../run-wg17.mjs) | 366 | #1–#19, #21–#272, #274–#368 |
|
|
39
39
|
|
|
40
|
-
The evidence groups overlap. Their union is **
|
|
41
|
-
#1–#19, #21–#272, #274–#
|
|
40
|
+
The evidence groups overlap. Their union is **366** active cases:
|
|
41
|
+
#1–#19, #21–#272, #274–#368.
|
|
42
42
|
|
|
43
43
|
## Untraced upstream identifiers
|
|
44
44
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"source": "https://www.complang.tuwien.ac.at/ulrich/iso-prolog/conformity_testing",
|
|
3
|
-
"checkedOn": "2026-
|
|
3
|
+
"checkedOn": "2026-09-01",
|
|
4
4
|
"protocol": "Each query is read and executed in strict ISO mode; /**/ rows reuse the preceding setup.",
|
|
5
5
|
"cases": [
|
|
6
6
|
{
|
|
@@ -4139,16 +4139,8 @@
|
|
|
4139
4139
|
"query": "write_canonical(B+B).",
|
|
4140
4140
|
"input": "write_canonical(B+B).",
|
|
4141
4141
|
"readCount": 1,
|
|
4142
|
-
"expected": "e.g. +(_1
|
|
4143
|
-
"
|
|
4144
|
-
"type": "success",
|
|
4145
|
-
"stages": [
|
|
4146
|
-
{
|
|
4147
|
-
"output": "+(_A,_A)",
|
|
4148
|
-
"variables": "['B'=B]"
|
|
4149
|
-
}
|
|
4150
|
-
]
|
|
4151
|
-
}
|
|
4142
|
+
"expected": "e.g. +(_1,_1)",
|
|
4143
|
+
"assertion": "upstream"
|
|
4152
4144
|
},
|
|
4153
4145
|
{
|
|
4154
4146
|
"id": 228,
|
|
@@ -5565,99 +5557,8 @@
|
|
|
5565
5557
|
}
|
|
5566
5558
|
]
|
|
5567
5559
|
}
|
|
5568
|
-
},
|
|
5569
|
-
{
|
|
5570
|
-
"id": 369,
|
|
5571
|
-
"query": "writeq(?-).",
|
|
5572
|
-
"input": "writeq(?-).",
|
|
5573
|
-
"readCount": 1,
|
|
5574
|
-
"expected": "?-",
|
|
5575
|
-
"outcome": {
|
|
5576
|
-
"type": "success",
|
|
5577
|
-
"stages": [
|
|
5578
|
-
{
|
|
5579
|
-
"output": "?-",
|
|
5580
|
-
"variables": "[]"
|
|
5581
|
-
}
|
|
5582
|
-
]
|
|
5583
|
-
}
|
|
5584
|
-
},
|
|
5585
|
-
{
|
|
5586
|
-
"id": 370,
|
|
5587
|
-
"query": "writeq(./*).",
|
|
5588
|
-
"input": "writeq(./*).",
|
|
5589
|
-
"readCount": 1,
|
|
5590
|
-
"expected": "./*",
|
|
5591
|
-
"outcome": {
|
|
5592
|
-
"type": "success",
|
|
5593
|
-
"stages": [
|
|
5594
|
-
{
|
|
5595
|
-
"output": "./*",
|
|
5596
|
-
"variables": "[]"
|
|
5597
|
-
}
|
|
5598
|
-
]
|
|
5599
|
-
}
|
|
5600
|
-
},
|
|
5601
|
-
{
|
|
5602
|
-
"id": 371,
|
|
5603
|
-
"query": "writeq({*}).",
|
|
5604
|
-
"input": "writeq({*}).",
|
|
5605
|
-
"readCount": 1,
|
|
5606
|
-
"expected": "{*}",
|
|
5607
|
-
"outcome": {
|
|
5608
|
-
"type": "success",
|
|
5609
|
-
"stages": [
|
|
5610
|
-
{
|
|
5611
|
-
"output": "{*}",
|
|
5612
|
-
"variables": "[]"
|
|
5613
|
-
}
|
|
5614
|
-
]
|
|
5615
|
-
}
|
|
5616
|
-
},
|
|
5617
|
-
{
|
|
5618
|
-
"id": 372,
|
|
5619
|
-
"query": "writeq(.*).",
|
|
5620
|
-
"input": "writeq(.*).",
|
|
5621
|
-
"readCount": 1,
|
|
5622
|
-
"expected": ".*",
|
|
5623
|
-
"outcome": {
|
|
5624
|
-
"type": "success",
|
|
5625
|
-
"stages": [
|
|
5626
|
-
{
|
|
5627
|
-
"output": ".*",
|
|
5628
|
-
"variables": "[]"
|
|
5629
|
-
}
|
|
5630
|
-
]
|
|
5631
|
-
}
|
|
5632
|
-
},
|
|
5633
|
-
{
|
|
5634
|
-
"id": 373,
|
|
5635
|
-
"query": "writeq(...*).",
|
|
5636
|
-
"input": "writeq(...*).",
|
|
5637
|
-
"readCount": 1,
|
|
5638
|
-
"expected": "...*",
|
|
5639
|
-
"outcome": {
|
|
5640
|
-
"type": "success",
|
|
5641
|
-
"stages": [
|
|
5642
|
-
{
|
|
5643
|
-
"output": "...*",
|
|
5644
|
-
"variables": "[]"
|
|
5645
|
-
}
|
|
5646
|
-
]
|
|
5647
|
-
}
|
|
5648
|
-
},
|
|
5649
|
-
{
|
|
5650
|
-
"id": 374,
|
|
5651
|
-
"query": "Finis ().",
|
|
5652
|
-
"input": "Finis ().",
|
|
5653
|
-
"readCount": 1,
|
|
5654
|
-
"expected": "syntax err.",
|
|
5655
|
-
"outcome": {
|
|
5656
|
-
"type": "error",
|
|
5657
|
-
"formal": "syntax_error(read_term)"
|
|
5658
|
-
}
|
|
5659
5560
|
}
|
|
5660
5561
|
],
|
|
5661
5562
|
"sourceRevision": "1.300",
|
|
5662
|
-
"sourceSha256": "
|
|
5563
|
+
"sourceSha256": "b6590c1c5269e8e62a1ec72a8f04f21f123a77709854a8a66c436df25f3ff418"
|
|
5663
5564
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"source": "https://www.complang.tuwien.ac.at/ulrich/iso-prolog/conformity_testing",
|
|
3
|
-
"checkedOn": "2026-
|
|
3
|
+
"checkedOn": "2026-09-01",
|
|
4
4
|
"upstream": {
|
|
5
5
|
"firstId": 1,
|
|
6
|
-
"lastId":
|
|
6
|
+
"lastId": 368,
|
|
7
7
|
"deletedIds": [
|
|
8
8
|
20,
|
|
9
9
|
273
|
|
10
10
|
],
|
|
11
|
-
"activeCases":
|
|
11
|
+
"activeCases": 366
|
|
12
12
|
},
|
|
13
13
|
"evidence": [
|
|
14
14
|
{
|
package/test/run-regression.mjs
CHANGED
|
@@ -5387,11 +5387,12 @@ ${profile}`;
|
|
|
5387
5387
|
assertEqual(report.total.positive + report.total.errors + report.total.warnings + report.total.proofs, report.total.total, 'conformance total');
|
|
5388
5388
|
assertEqual(report.rows.some((row) => row.category === 'legacy-numbered'), false, 'legacy-numbered category');
|
|
5389
5389
|
const wg17 = report.executable.find((gate) => gate.name === 'WG17 syntax');
|
|
5390
|
-
|
|
5391
|
-
assertEqual(wg17?.
|
|
5390
|
+
const wg17FixtureTotal = readWg17SyntaxFixture().cases.length;
|
|
5391
|
+
assertEqual(wg17?.total, wg17FixtureTotal, 'WG17 executable total');
|
|
5392
|
+
assertEqual(wg17?.passed, wg17FixtureTotal, 'WG17 executable passed');
|
|
5392
5393
|
assertArrayEqual(wg17?.failures ?? [], [], 'WG17 executable failures');
|
|
5393
5394
|
const text = formatConformanceReport(report);
|
|
5394
|
-
assertIncludes(text,
|
|
5395
|
+
assertIncludes(text, `| WG17 syntax | ${wg17FixtureTotal} | ${wg17FixtureTotal} | pass |`, 'report');
|
|
5395
5396
|
assertIncludes(text, '| variables |', 'report');
|
|
5396
5397
|
assertIncludes(text, '| Proofs |', 'report');
|
|
5397
5398
|
assertIncludes(text, '| **Total** |', 'report');
|
|
@@ -7251,6 +7252,54 @@ function whiteBoxCases() {
|
|
|
7251
7252
|
}
|
|
7252
7253
|
},
|
|
7253
7254
|
},
|
|
7255
|
+
{
|
|
7256
|
+
name: 'normal integer syntax accepts WG17 digit separators (issue #89)',
|
|
7257
|
+
run: () => {
|
|
7258
|
+
const values = parseGoalText(`values(
|
|
7259
|
+
1_000,
|
|
7260
|
+
0b1010_0101,
|
|
7261
|
+
0o7_ 7,
|
|
7262
|
+
0xCA_/* digit group */FE,
|
|
7263
|
+
-9_% line group
|
|
7264
|
+
223
|
|
7265
|
+
)`).args;
|
|
7266
|
+
assertEqual(values.map((value) => value.name).join(','), '1000,165,63,51966,-9223',
|
|
7267
|
+
'decimal and radix separator values');
|
|
7268
|
+
const program = Program.parse('decimal(1_000).\nhexadecimal(0xCA_FE).\n');
|
|
7269
|
+
assertEqual(program.findGroup('decimal', 1)?.clauses[0].head.args[0].name, '1000',
|
|
7270
|
+
'program decimal literal');
|
|
7271
|
+
assertEqual(program.findGroup('hexadecimal', 1)?.clauses[0].head.args[0].name, '51966',
|
|
7272
|
+
'program radix literal');
|
|
7273
|
+
assertEqual(parseNumberTokenText('1_ /* group */ 000').name, '1000',
|
|
7274
|
+
'number-token conversion with layout');
|
|
7275
|
+
assertEqual(
|
|
7276
|
+
run('', { goal: 'number_chars(N,"1_000")' }).stdout,
|
|
7277
|
+
'number_chars(1000, "1_000").\n',
|
|
7278
|
+
'normal number_chars input',
|
|
7279
|
+
);
|
|
7280
|
+
assertEqual(
|
|
7281
|
+
run('', { goal: 'read_term(N, [])', ioOptions: { input: '1_ /* group */ 000. ' } }).stdout,
|
|
7282
|
+
'read_term(1000, []).\n',
|
|
7283
|
+
'stream term input',
|
|
7284
|
+
);
|
|
7285
|
+
|
|
7286
|
+
for (const source of ['1__000', '1_', '1_a', '0b1_2', '1_1.25', '1.2_5', '1.0e1_0']) {
|
|
7287
|
+
let threw = false;
|
|
7288
|
+
try { parseGoalText(`p(${source})`); } catch (_) { threw = true; }
|
|
7289
|
+
assertEqual(threw, true, `malformed or non-integer separator rejected: ${source}`);
|
|
7290
|
+
}
|
|
7291
|
+
|
|
7292
|
+
for (const source of ['1_000', '0b1010_0101', '0o7_7', '0xCA_FE']) {
|
|
7293
|
+
let threw = false;
|
|
7294
|
+
try { parseGoalText(`p(${source})`, { isoStrict: true }); } catch (_) { threw = true; }
|
|
7295
|
+
assertEqual(threw, true, `strict syntax rejects ${source}`);
|
|
7296
|
+
}
|
|
7297
|
+
let strictConversion = null;
|
|
7298
|
+
try { run('', { isoStrict: true, goal: 'number_chars(N,"1_000")' }); }
|
|
7299
|
+
catch (error) { strictConversion = error; }
|
|
7300
|
+
assertEqual(strictConversion?.formal, 'syntax_error(number)', 'strict number_chars rejection');
|
|
7301
|
+
},
|
|
7302
|
+
},
|
|
7254
7303
|
{
|
|
7255
7304
|
name: 'double_quotes(true) remains effective with ignore_ops(true) in either option order (issue #88 follow-up)',
|
|
7256
7305
|
run: () => {
|
|
@@ -8342,6 +8391,7 @@ function documentedConformanceMetricIssues() {
|
|
|
8342
8391
|
const report = buildConformanceReport();
|
|
8343
8392
|
const iso = report.rows.find((row) => row.category === 'iso')?.total;
|
|
8344
8393
|
const total = report.total.total;
|
|
8394
|
+
const wg17 = report.executable.find((gate) => gate.name === 'WG17 syntax')?.total;
|
|
8345
8395
|
const checks = [
|
|
8346
8396
|
{
|
|
8347
8397
|
file: path.join(packageRoot, 'the-art-of-eyeprolog.md'),
|
|
@@ -8371,6 +8421,21 @@ function documentedConformanceMetricIssues() {
|
|
|
8371
8421
|
}
|
|
8372
8422
|
}
|
|
8373
8423
|
}
|
|
8424
|
+
for (const file of [
|
|
8425
|
+
path.join(packageRoot, 'test', 'conformance', 'README.md'),
|
|
8426
|
+
path.join(packageRoot, 'test', 'conformance', 'ISO-COMPLIANCE.md'),
|
|
8427
|
+
]) {
|
|
8428
|
+
const relative = path.relative(packageRoot, file);
|
|
8429
|
+
const text = fs.readFileSync(file, 'utf8');
|
|
8430
|
+
const claims = [
|
|
8431
|
+
...[...text.matchAll(/\b(\d+)-case[^\n|]*WG17/g)].map((match) => Number(match[1])),
|
|
8432
|
+
...[...text.matchAll(/WG17[^\n|]*?\b(\d+) executable/g)].map((match) => Number(match[1])),
|
|
8433
|
+
];
|
|
8434
|
+
if (claims.length === 0) issues.push(`${relative}: WG17 total not found`);
|
|
8435
|
+
for (const claim of claims) {
|
|
8436
|
+
if (claim !== wg17) issues.push(`${relative}: WG17 count ${claim} != ${wg17}`);
|
|
8437
|
+
}
|
|
8438
|
+
}
|
|
8374
8439
|
return issues.sort();
|
|
8375
8440
|
}
|
|
8376
8441
|
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -465,6 +465,11 @@ for `[a,b|Tail]`; with `double_quotes(codes)`, it denotes `[97,98|Tail]`. The
|
|
|
465
465
|
splice is not available when `double_quotes(atom)` is active, and
|
|
466
466
|
`--iso-strict` rejects it as an implementation-specific syntax extension.
|
|
467
467
|
|
|
468
|
+
Normal-mode integer constants may use one underscore between adjacent digits,
|
|
469
|
+
as in `1_000` or `0xCA_FE`. Layout, including comments and newlines, may follow
|
|
470
|
+
the underscore before the next digit. Separators do not apply to floating-point
|
|
471
|
+
fractions or exponents, and `--iso-strict` rejects them.
|
|
472
|
+
|
|
468
473
|
Plain atom constants begin with a lowercase ASCII letter. Variables begin with
|
|
469
474
|
an uppercase letter or underscore. The bare `_` is anonymous and every
|
|
470
475
|
occurrence is fresh. `_Name` is a named variable; repeated occurrences refer to
|
|
@@ -5648,8 +5653,10 @@ lowercase ASCII letter. Variables begin with uppercase or underscore. The bare
|
|
|
5648
5653
|
`_` is fresh each time. Single quotes delimit quoted atoms; double quotes use
|
|
5649
5654
|
ISO double-quoted-list notation. Integers, decimals, scientific notation,
|
|
5650
5655
|
binary/octal/hexadecimal integers, and character-code constants are accepted.
|
|
5651
|
-
Normal mode additionally accepts
|
|
5652
|
-
|
|
5656
|
+
Normal mode additionally accepts digit-separated integer constants such as
|
|
5657
|
+
`1_000` and `0xCA_FE`, with optional layout after the underscore, and the
|
|
5658
|
+
Trealla-compatible `"text"||Tail` right-splice for double-quoted `chars`/`codes`
|
|
5659
|
+
lists; strict ISO mode accepts neither syntax extension.
|
|
5653
5660
|
|
|
5654
5661
|
The processor character set is shared by normal and `--iso-strict` modes because
|
|
5655
5662
|
Part 1 makes it implementation defined rather than an extension boundary.
|
|
@@ -6190,7 +6197,7 @@ quoted_atom("ab"). % quoted_atom(ab)
|
|
|
6190
6197
|
- **`sub_atom(+Atom,?Before,?Length,?After,?SubAtom)`** — Enumerates substrings and their Unicode-code-point offsets. Supplied counts must be nonnegative integers.
|
|
6191
6198
|
- **`atom_chars(?Atom,?Chars)`, `atom_codes(?Atom,?Codes)`** — Convert between an atom and a proper list of one-character atoms or character codes. Both profiles use EyeProlog's Unicode scalar PCS/codes; surrogates and values above U+10FFFF are rejected. At least one side must be instantiated.
|
|
6192
6199
|
- **`char_code(?Character,?Code)`** — Converts one character atom and its collating/code value. Both profiles accept Unicode scalar codes and reject surrogates/out-of-range values.
|
|
6193
|
-
- **`number_chars(?Number,?Chars)`, `number_codes(?Number,?Codes)`** — Convert finite numbers to canonical text or parse a proper character/code list using
|
|
6200
|
+
- **`number_chars(?Number,?Chars)`, `number_codes(?Number,?Codes)`** — Convert finite numbers to canonical text or parse a proper character/code list using number and negative-number syntax, including radix integers, character-code constants, and leading layout. Normal mode also accepts digit separators in integer input; strict mode retains the ISO syntax. The input is not parsed as a general term: grouping such as `(0)` is a syntax error. At least one side must be instantiated; malformed numeric input raises *syntax_error(number)*.
|
|
6194
6201
|
|
|
6195
6202
|
Conversions accept partial output lists when the atomic input is known, but
|
|
6196
6203
|
constructing an atom or number requires a complete proper list with no unbound
|
package/tools/upgrade-wg17.mjs
CHANGED
|
@@ -11,6 +11,11 @@ const syntaxSource = 'https://www.complang.tuwien.ac.at/ulrich/iso-prolog/confor
|
|
|
11
11
|
const syntaxFixturePath = path.join(packageRoot, 'test', 'conformance', 'wg17-syntax-cases.json');
|
|
12
12
|
const syntaxCoveragePath = path.join(packageRoot, 'test', 'conformance', 'wg17-syntax-coverage.json');
|
|
13
13
|
const syntaxStatusPath = path.join(packageRoot, 'test', 'conformance', 'WG17-SYNTAX-STATUS.md');
|
|
14
|
+
const conformanceReportPath = path.join(packageRoot, 'conformance-report.md');
|
|
15
|
+
const countDocumentationPaths = [
|
|
16
|
+
path.join(packageRoot, 'test', 'conformance', 'README.md'),
|
|
17
|
+
path.join(packageRoot, 'test', 'conformance', 'ISO-COMPLIANCE.md'),
|
|
18
|
+
];
|
|
14
19
|
|
|
15
20
|
const namedEntities = new Map([
|
|
16
21
|
['amp', '&'], ['lt', '<'], ['gt', '>'], ['quot', '"'], ['apos', "'"],
|
|
@@ -318,6 +323,20 @@ function formatIdList(ids) {
|
|
|
318
323
|
return ids.length === 0 ? 'none' : ids.map((id) => `#${id}`).join(', ');
|
|
319
324
|
}
|
|
320
325
|
|
|
326
|
+
function updateDocumentedInventoryCount(previousCount, nextCount) {
|
|
327
|
+
if (previousCount === nextCount) return;
|
|
328
|
+
for (const filename of countDocumentationPaths) {
|
|
329
|
+
const original = fs.readFileSync(filename, 'utf8');
|
|
330
|
+
const updated = original
|
|
331
|
+
.replaceAll(`${previousCount}-case`, `${nextCount}-case`)
|
|
332
|
+
.replaceAll(`WG17 matrix has ${previousCount} executable`, `WG17 matrix has ${nextCount} executable`);
|
|
333
|
+
if (updated === original) {
|
|
334
|
+
throw new Error(`WG17 inventory count was not found in ${path.relative(packageRoot, filename)}`);
|
|
335
|
+
}
|
|
336
|
+
fs.writeFileSync(filename, updated);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
321
340
|
async function readSource(source) {
|
|
322
341
|
if (/^https?:\/\//i.test(source)) {
|
|
323
342
|
const response = await fetch(source, {
|
|
@@ -415,6 +434,13 @@ export async function upgradeWg17({ check = false, source = syntaxSource } = {})
|
|
|
415
434
|
// Generate status after the fixture/manifest are synchronized.
|
|
416
435
|
const { renderWg17SyntaxStatus } = await import('./report-wg17-syntax-coverage.mjs');
|
|
417
436
|
fs.writeFileSync(syntaxStatusPath, renderWg17SyntaxStatus());
|
|
437
|
+
updateDocumentedInventoryCount(previous.cases.length, fixture.cases.length);
|
|
438
|
+
|
|
439
|
+
// The public report executes the refreshed fixture, so keep its measured row
|
|
440
|
+
// synchronized as part of the same explicit upgrade operation.
|
|
441
|
+
const { buildConformanceReport, formatConformanceReport } = await import('../test/run-conformance-report.mjs');
|
|
442
|
+
const conformanceReport = buildConformanceReport();
|
|
443
|
+
fs.writeFileSync(conformanceReportPath, formatConformanceReport(conformanceReport));
|
|
418
444
|
|
|
419
445
|
const upstreamAssertions = fixture.cases
|
|
420
446
|
.filter((item) => item.outcome == null)
|