eyeprolog 1.2.17 → 1.2.19
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/index.d.ts +3 -0
- package/package.json +1 -1
- package/src/iso.js +9 -1
- package/src/platform.js +42 -0
- package/src/solver.js +16 -0
- package/test/conformance/README.md +1 -1
- package/test/conformance/cases/iso/number_chars_minus_line_comment.pl +8 -0
- package/test/conformance/expected/iso/number_chars_minus_line_comment.pl +1 -0
- package/test/fixtures/number_chars_cont_quad.pl +3 -0
- package/test/run-regression.mjs +70 -3
- package/the-art-of-eyeprolog.md +40 -23
package/conformance-report.md
CHANGED
|
@@ -11,7 +11,7 @@ This report summarizes the file-based conformance corpus under `test/conformance
|
|
|
11
11
|
| builtins | 11 | 0 | 0 | 0 | 11 |
|
|
12
12
|
| context | 11 | 0 | 0 | 0 | 11 |
|
|
13
13
|
| control | 15 | 0 | 0 | 0 | 15 |
|
|
14
|
-
| iso |
|
|
14
|
+
| iso | 168 | 218 | 0 | 0 | 386 |
|
|
15
15
|
| lists | 52 | 3 | 0 | 0 | 55 |
|
|
16
16
|
| modules | 2 | 0 | 0 | 0 | 2 |
|
|
17
17
|
| negation | 8 | 0 | 19 | 0 | 27 |
|
|
@@ -23,4 +23,4 @@ This report summarizes the file-based conformance corpus under `test/conformance
|
|
|
23
23
|
| terms | 26 | 3 | 0 | 0 | 29 |
|
|
24
24
|
| unification | 18 | 0 | 0 | 0 | 18 |
|
|
25
25
|
| variables | 16 | 9 | 0 | 0 | 25 |
|
|
26
|
-
| **Total** | **
|
|
26
|
+
| **Total** | **483** | **269** | **19** | **21** | **792** |
|
package/index.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export interface EyePrologRunOptions {
|
|
|
12
12
|
explain?: boolean;
|
|
13
13
|
maxDepth?: number;
|
|
14
14
|
maxInferences?: number;
|
|
15
|
+
/** Soft JavaScript heap ceiling in bytes; exhaustion raises resource_error(memory). */
|
|
16
|
+
maxMemoryBytes?: number;
|
|
15
17
|
solutionLimit?: number;
|
|
16
18
|
registry?: BuiltinRegistry;
|
|
17
19
|
sourceMetadata?: boolean;
|
|
@@ -171,6 +173,7 @@ export class Solver {
|
|
|
171
173
|
maxInferences: number;
|
|
172
174
|
inferences: number;
|
|
173
175
|
inferenceLimitExceeded: boolean;
|
|
176
|
+
maxMemoryBytes: number;
|
|
174
177
|
solutionLimit: number;
|
|
175
178
|
solutionsSeen: number;
|
|
176
179
|
active: unknown[];
|
package/package.json
CHANGED
package/src/iso.js
CHANGED
|
@@ -1556,7 +1556,15 @@ function parseIsoNumber(text) {
|
|
|
1556
1556
|
let sign = '';
|
|
1557
1557
|
|
|
1558
1558
|
if (text[position] === '-') {
|
|
1559
|
-
|
|
1559
|
+
const next = text[position + 1] ?? '';
|
|
1560
|
+
// Every token class may carry leading layout (6.4). Thus a negative
|
|
1561
|
+
// number may have layout between the name `-` and its numeric token. A
|
|
1562
|
+
// `%...\n` comment can start immediately after `-` because `%` cannot
|
|
1563
|
+
// continue a graphic name token. In contrast `/*...*/` cannot start
|
|
1564
|
+
// there without separating layout: `/` *can* continue the graphic token,
|
|
1565
|
+
// and the eager-consumer rule therefore keeps `-/**/1` ill-formed (the
|
|
1566
|
+
// number_chars continuation corpus case 24).
|
|
1567
|
+
if (/[\u0009-\u000d\u0020]/.test(next) || next === '%') {
|
|
1560
1568
|
position = skipNumberLayout(text, position + 1);
|
|
1561
1569
|
sign = '-';
|
|
1562
1570
|
}
|
package/src/platform.js
CHANGED
|
@@ -6,10 +6,12 @@ const isNode = typeof process !== 'undefined' && Boolean(process.versions?.node)
|
|
|
6
6
|
let fs = null;
|
|
7
7
|
let path = null;
|
|
8
8
|
let BufferCtor = null;
|
|
9
|
+
let v8 = null;
|
|
9
10
|
|
|
10
11
|
if (isNode) {
|
|
11
12
|
({ default: fs } = await import('node:fs'));
|
|
12
13
|
({ default: path } = await import('node:path'));
|
|
14
|
+
({ default: v8 } = await import('node:v8'));
|
|
13
15
|
BufferCtor = globalThis.Buffer ?? null;
|
|
14
16
|
}
|
|
15
17
|
|
|
@@ -18,3 +20,43 @@ export { fs, path, BufferCtor, isNode };
|
|
|
18
20
|
export function currentWorkingDirectory() {
|
|
19
21
|
return isNode && typeof process.cwd === 'function' ? process.cwd() : '/';
|
|
20
22
|
}
|
|
23
|
+
|
|
24
|
+
export function usedHeapSize() {
|
|
25
|
+
if (isNode && typeof process.memoryUsage === 'function') {
|
|
26
|
+
return process.memoryUsage().heapUsed;
|
|
27
|
+
}
|
|
28
|
+
const memory = globalThis.performance?.memory;
|
|
29
|
+
return Number.isFinite(memory?.usedJSHeapSize) ? memory.usedJSHeapSize : null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function softHeapLimit() {
|
|
33
|
+
let limit = null;
|
|
34
|
+
if (isNode) {
|
|
35
|
+
limit = v8?.getHeapStatistics?.().heap_size_limit ?? null;
|
|
36
|
+
const configuredOldSpace = configuredOldSpaceBytes();
|
|
37
|
+
if (configuredOldSpace != null) limit = Math.min(limit ?? Infinity, configuredOldSpace);
|
|
38
|
+
} else {
|
|
39
|
+
const memory = globalThis.performance?.memory;
|
|
40
|
+
if (Number.isFinite(memory?.jsHeapSizeLimit)) limit = memory.jsHeapSizeLimit;
|
|
41
|
+
}
|
|
42
|
+
// Leave ample room for the generator stack to unwind and for the top level
|
|
43
|
+
// to construct and print resource_error(memory). Fatal V8 OOMs cannot be
|
|
44
|
+
// caught after the heap limit itself has been reached.
|
|
45
|
+
return Number.isFinite(limit) && limit > 0 ? Math.floor(limit * 0.75) : Infinity;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function configuredOldSpaceBytes() {
|
|
49
|
+
if (!isNode) return null;
|
|
50
|
+
const argumentsText = [
|
|
51
|
+
...(process.execArgv ?? []),
|
|
52
|
+
...(String(process.env?.NODE_OPTIONS ?? '').match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) ?? []),
|
|
53
|
+
];
|
|
54
|
+
for (let index = 0; index < argumentsText.length; index++) {
|
|
55
|
+
const argument = argumentsText[index];
|
|
56
|
+
const match = /^--max[-_]old[-_]space[-_]size(?:=(\d+))?$/.exec(argument);
|
|
57
|
+
if (!match) continue;
|
|
58
|
+
const megabytes = match[1] ?? argumentsText[index + 1];
|
|
59
|
+
if (/^\d+$/.test(megabytes ?? '')) return Number(megabytes) * 1024 * 1024;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
package/src/solver.js
CHANGED
|
@@ -9,6 +9,7 @@ import { getEyePrologRegistry } from './standard-library.js';
|
|
|
9
9
|
import { selectClauseCandidates, selectClauseCandidatesForValues, selectGroundClauseCandidates } from './program.js';
|
|
10
10
|
import { StreamManager } from './io.js';
|
|
11
11
|
import { clpzStateConsistent } from './clpz.js';
|
|
12
|
+
import { softHeapLimit, usedHeapSize } from './platform.js';
|
|
12
13
|
|
|
13
14
|
let freshCounter = 0;
|
|
14
15
|
|
|
@@ -47,6 +48,8 @@ export class Solver {
|
|
|
47
48
|
this.maxInferences = options.maxInferences ?? Infinity;
|
|
48
49
|
this.inferences = 0;
|
|
49
50
|
this.inferenceLimitExceeded = false;
|
|
51
|
+
this.maxMemoryBytes = options.maxMemoryBytes ?? softHeapLimit();
|
|
52
|
+
this.nextMemoryCheck = 0;
|
|
50
53
|
// Do not impose an implicit answer cap. Infinite and very large searches are
|
|
51
54
|
// part of normal Prolog semantics; callers that need a resource bound can
|
|
52
55
|
// still supply solutionLimit explicitly.
|
|
@@ -115,6 +118,7 @@ export class Solver {
|
|
|
115
118
|
registry: this.registry,
|
|
116
119
|
maxDepth: this.maxDepth,
|
|
117
120
|
maxInferences: this.maxInferences,
|
|
121
|
+
maxMemoryBytes: this.maxMemoryBytes,
|
|
118
122
|
solutionLimit,
|
|
119
123
|
isoStrict: this.isoStrict,
|
|
120
124
|
prologFlags: this.prologFlags,
|
|
@@ -177,6 +181,7 @@ export class Solver {
|
|
|
177
181
|
this.solveStacks.push(stack);
|
|
178
182
|
while (stack.length) {
|
|
179
183
|
this.inferences++;
|
|
184
|
+
this.checkMemoryLimit();
|
|
180
185
|
if (this.inferences > this.maxInferences) {
|
|
181
186
|
this.inferenceLimitExceeded = true;
|
|
182
187
|
break;
|
|
@@ -228,6 +233,7 @@ export class Solver {
|
|
|
228
233
|
|
|
229
234
|
while (true) {
|
|
230
235
|
this.inferences++;
|
|
236
|
+
this.checkMemoryLimit();
|
|
231
237
|
if (this.inferences > this.maxInferences) {
|
|
232
238
|
this.inferenceLimitExceeded = true;
|
|
233
239
|
stack.length = 0;
|
|
@@ -411,6 +417,16 @@ export class Solver {
|
|
|
411
417
|
return activeVariantIn(goal, env, this.active);
|
|
412
418
|
}
|
|
413
419
|
|
|
420
|
+
checkMemoryLimit() {
|
|
421
|
+
if (this.inferences < this.nextMemoryCheck) return;
|
|
422
|
+
this.nextMemoryCheck = this.inferences + 256;
|
|
423
|
+
if (!Number.isFinite(this.maxMemoryBytes)) return;
|
|
424
|
+
const used = usedHeapSize();
|
|
425
|
+
if (used != null && used >= this.maxMemoryBytes) {
|
|
426
|
+
throw new PrologError('resource_error(memory)');
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
414
430
|
*solveUserGoal(goal, rest, env, depth) {
|
|
415
431
|
this.stats.solve_one_goal_calls++;
|
|
416
432
|
if (depth > this.maxDepth) {
|
|
@@ -102,7 +102,7 @@ Selected cases are adapted from the ISO and standard-core suites of Logtalk,
|
|
|
102
102
|
Scryer Prolog, Trealla Prolog, and SWI-Prolog. Their upstream identifiers and licenses
|
|
103
103
|
are recorded in [THIRD_PARTY.md](THIRD_PARTY.md).
|
|
104
104
|
|
|
105
|
-
The corpus has
|
|
105
|
+
The corpus has 386 cases in `iso/` and 792 file-based conformance cases in
|
|
106
106
|
total. The generated `conformance-report.md` is the authoritative source for
|
|
107
107
|
current category totals. Together with regression, documentation-sync, API,
|
|
108
108
|
example, and book-example checks, `npm test` is the release gate.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
% ISO 13211-1 8.16.7/8.16.8 with 6.3.1 and token layout from 6.4.
|
|
2
|
+
%% goal: answer(X0, X1, X2, X3)
|
|
3
|
+
|
|
4
|
+
answer(CharZero, CharOne, CodeZero, CodeOne) :-
|
|
5
|
+
number_chars(CharZero, "-%\n0"),
|
|
6
|
+
number_chars(CharOne, "-% comment\n1"),
|
|
7
|
+
number_codes(CodeZero, [45, 37, 10, 48]),
|
|
8
|
+
number_codes(CodeOne, [45, 37, 32, 99, 111, 109, 109, 101, 110, 116, 10, 49]).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
answer(0, -1, 0, -1).
|
package/test/run-regression.mjs
CHANGED
|
@@ -491,13 +491,51 @@ c4 ?- call((!;1)).
|
|
|
491
491
|
run: () => {
|
|
492
492
|
const filename = path.join(testRoot, 'fixtures', 'number_chars_cont_quad.pl');
|
|
493
493
|
const source = fs.readFileSync(filename, 'utf8');
|
|
494
|
+
const numbered = new Set([...source.matchAll(/^(\d+)\s+\?-/gm)].map((match) => Number(match[1])));
|
|
495
|
+
assertEqual(numbered.size, 74, 'numbered case total');
|
|
496
|
+
for (let id = 1; id <= 74; id++) {
|
|
497
|
+
if (!numbered.has(id)) throw new Error(`number_chars continuation case #${id} is missing`);
|
|
498
|
+
}
|
|
494
499
|
const result = publicApi.runQuads(Program.parseSources([{
|
|
495
500
|
text: source,
|
|
496
501
|
filename,
|
|
497
502
|
}]));
|
|
498
|
-
assertEqual(result.total,
|
|
499
|
-
assertEqual(result.passed,
|
|
500
|
-
assertEqual(result.stdout, 'quads:
|
|
503
|
+
assertEqual(result.total, 78, 'answer-description total');
|
|
504
|
+
assertEqual(result.passed, 78, 'answer-description passed');
|
|
505
|
+
assertEqual(result.stdout, 'quads: 78 run, 78 passed, 0 failed.\n', 'quad report');
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
name: 'number conversion accepts line-comment layout after a minus token',
|
|
510
|
+
run: () => {
|
|
511
|
+
const source = String.raw`
|
|
512
|
+
?- number_chars(N,"-%\n0").
|
|
513
|
+
N = 0.
|
|
514
|
+
?- number_chars(N,"-% comment\n1").
|
|
515
|
+
N = -1.
|
|
516
|
+
?- number_codes(N,[45,37,10,48]).
|
|
517
|
+
N = 0.
|
|
518
|
+
?- number_codes(N,[45,37,32,99,111,109,109,101,110,116,10,49]).
|
|
519
|
+
N = -1.
|
|
520
|
+
`;
|
|
521
|
+
const result = publicApi.runQuads(source);
|
|
522
|
+
assertEqual(result.total, 4, 'quad total');
|
|
523
|
+
assertEqual(result.passed, 4, 'quad passed');
|
|
524
|
+
assertEqual(result.stdout, 'quads: 4 run, 4 passed, 0 failed.\n', 'quad report');
|
|
525
|
+
|
|
526
|
+
// Keep the eager-consumer distinction from continuation case #24:
|
|
527
|
+
// an adjacent bracketed comment can be consumed as part of a graphic
|
|
528
|
+
// token after `-`, so it is not equivalent to the `%` line comment.
|
|
529
|
+
for (const goal of ['number_chars(N,"-/**/1")', 'number_codes(N,[45,47,42,42,47,49])']) {
|
|
530
|
+
let caught = null;
|
|
531
|
+
try {
|
|
532
|
+
publicApi.run('', { goal });
|
|
533
|
+
} catch (error) {
|
|
534
|
+
caught = error;
|
|
535
|
+
}
|
|
536
|
+
if (caught == null) throw new Error(`${goal} should throw`);
|
|
537
|
+
assertIncludes(String(caught?.message ?? caught), 'syntax_error(number)', goal);
|
|
538
|
+
}
|
|
501
539
|
},
|
|
502
540
|
},
|
|
503
541
|
{
|
|
@@ -2286,6 +2324,35 @@ open(X) :- candidate(X), \\+ closed(X).
|
|
|
2286
2324
|
}
|
|
2287
2325
|
},
|
|
2288
2326
|
},
|
|
2327
|
+
{
|
|
2328
|
+
name: 'list allocation heap pressure becomes resource_error(memory)',
|
|
2329
|
+
run: () => {
|
|
2330
|
+
const engineUrl = new URL('../src/index.js', import.meta.url).href;
|
|
2331
|
+
const programText = ':- use_module(library(prologue)).\n';
|
|
2332
|
+
const goalText = 'length(_, I), I > 9, N is 2^I, \\+ \\+ length(_, N)';
|
|
2333
|
+
const script = `
|
|
2334
|
+
import { Program, Solver, Env, parseGoalText, getEyePrologRegistry } from ${JSON.stringify(engineUrl)};
|
|
2335
|
+
const program = Program.parse(${JSON.stringify(programText)});
|
|
2336
|
+
const solver = new Solver(program, { registry: getEyePrologRegistry() });
|
|
2337
|
+
const goal = parseGoalText(${JSON.stringify(goalText)}, {
|
|
2338
|
+
operatorDefinitions: [...program.operators.values()],
|
|
2339
|
+
});
|
|
2340
|
+
let caught = null;
|
|
2341
|
+
try { [...solver.solve([goal], new Env(), 0)]; } catch (error) { caught = error; }
|
|
2342
|
+
if (caught?.formal !== 'resource_error(memory)') throw caught ?? new Error('no resource error');
|
|
2343
|
+
process.stdout.write(caught.formal);
|
|
2344
|
+
`;
|
|
2345
|
+
const result = spawnSync(process.execPath, [
|
|
2346
|
+
'--max-old-space-size=64',
|
|
2347
|
+
'--input-type=module',
|
|
2348
|
+
'--eval',
|
|
2349
|
+
script,
|
|
2350
|
+
], { cwd: packageRoot, encoding: 'utf8', timeout: 30000 });
|
|
2351
|
+
if (result.error) throw result.error;
|
|
2352
|
+
assertEqual(result.status, 0, `bounded-heap child status; stderr=${result.stderr}`);
|
|
2353
|
+
assertEqual(result.stdout, 'resource_error(memory)', 'heap pressure resource error');
|
|
2354
|
+
},
|
|
2355
|
+
},
|
|
2289
2356
|
{
|
|
2290
2357
|
name: 'solver honors solution limits',
|
|
2291
2358
|
run: () => {
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -1725,6 +1725,8 @@ const result = run(reasoningSource(record), {
|
|
|
1725
1725
|
goal: `thermal_alert(${record.sensor})`,
|
|
1726
1726
|
proof: true,
|
|
1727
1727
|
maxDepth: 10_000,
|
|
1728
|
+
maxInferences: 100_000,
|
|
1729
|
+
maxMemoryBytes: 256 * 1024 * 1024,
|
|
1728
1730
|
solutionLimit: 10
|
|
1729
1731
|
});
|
|
1730
1732
|
|
|
@@ -1774,10 +1776,10 @@ prints numeric work counters; those counters describe this run rather than an
|
|
|
1774
1776
|
additional logical answer.
|
|
1775
1777
|
|
|
1776
1778
|
`run/2` accepts source text or an already parsed `Program`. Its options include
|
|
1777
|
-
`proof` (with `why` and `explain` as aliases), `maxDepth`, `
|
|
1778
|
-
custom `registry`, and `strictNegation` or
|
|
1779
|
-
`stdout`, the solver's numeric `stats`, and a
|
|
1780
|
-
write to the process streams.
|
|
1779
|
+
`proof` (with `why` and `explain` as aliases), `maxDepth`, `maxInferences`,
|
|
1780
|
+
`maxMemoryBytes`, `solutionLimit`, a custom `registry`, and `strictNegation` or
|
|
1781
|
+
`analyzeNegation`. It returns `stdout`, the solver's numeric `stats`, and a
|
|
1782
|
+
nullable `haltCode`; it does not write to the process streams.
|
|
1781
1783
|
|
|
1782
1784
|
For applications that inspect or prepare a theory before running it, use
|
|
1783
1785
|
`Program` directly:
|
|
@@ -1800,18 +1802,24 @@ console.log(path?.recursive, path?.tabled, path?.tableInputPositions);
|
|
|
1800
1802
|
|
|
1801
1803
|
const solver = new Solver(program, {
|
|
1802
1804
|
maxDepth: 50_000,
|
|
1805
|
+
maxInferences: 1_000_000,
|
|
1806
|
+
maxMemoryBytes: 256 * 1024 * 1024,
|
|
1803
1807
|
solutionLimit: 100_000
|
|
1804
1808
|
});
|
|
1805
1809
|
```
|
|
1806
1810
|
|
|
1807
|
-
The limits are safety ceilings, not logical declarations. Reaching
|
|
1808
|
-
truncate search; it does not prove that no
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1811
|
+
The limits are safety ceilings, not logical declarations. Reaching the depth,
|
|
1812
|
+
inference, or solution ceiling may truncate search; it does not prove that no
|
|
1813
|
+
further answer exists. Reaching `maxMemoryBytes` instead raises
|
|
1814
|
+
`resource_error(memory)`, because continuing until the JavaScript engine's hard
|
|
1815
|
+
heap limit would let the host abort before Prolog could report an exception. At
|
|
1816
|
+
the `Solver` API boundary, `solutionLimit` is opt-in: if it is omitted, ordinary
|
|
1817
|
+
solving and child searches that inherit the solver limit do not stop after a
|
|
1818
|
+
fixed number of solutions. This matters for re-executable goals such as
|
|
1819
|
+
`repeat/0` and for library relations such as `call_nth/2`; an implementation
|
|
1820
|
+
safety threshold must not turn a still re-executable search into logical
|
|
1821
|
+
failure. Embedders that need a finite answer budget should pass `solutionLimit`
|
|
1822
|
+
explicitly.
|
|
1815
1823
|
|
|
1816
1824
|
Variable term order is deliberately scoped rather than stored as a permanent
|
|
1817
1825
|
property of a variable. ISO 13211-1 section 7.2.1 leaves the order of two
|
|
@@ -1822,10 +1830,15 @@ sorting step of `setof/3` share one ranking for the duration of that single
|
|
|
1822
1830
|
sorted-list operation. No process-global variable registry or creation ordinal
|
|
1823
1831
|
is retained or exposed through later comparisons.
|
|
1824
1832
|
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
`resource_error(memory)`
|
|
1828
|
-
|
|
1833
|
+
EyeProlog periodically checks detectable JavaScript heap use and keeps a quarter
|
|
1834
|
+
of the host heap ceiling in reserve so the solver can unwind and report
|
|
1835
|
+
`resource_error(memory)` before a fatal host out-of-memory abort. Embedders may
|
|
1836
|
+
replace that automatically derived soft ceiling with `maxMemoryBytes`; setting
|
|
1837
|
+
it to `Infinity` disables the proactive check. Environments that do not expose
|
|
1838
|
+
heap use cannot provide the proactive check. Host capacity failures that V8
|
|
1839
|
+
reports as `Map maximum size exceeded` or `Set maximum size exceeded` are also
|
|
1840
|
+
normalized at the solver boundary instead of leaking a JavaScript `RangeError`.
|
|
1841
|
+
ISO 13211-1 leaves the resource atom implementation dependent. EyeProlog uses
|
|
1829
1842
|
`memory` for a finite host allocation/capacity ceiling and reserves the
|
|
1830
1843
|
`finite_memory` spelling for the distinct convention where no finite amount of
|
|
1831
1844
|
memory could complete the computation.
|
|
@@ -5758,12 +5771,16 @@ quoted_atom("ab"). % quoted_atom(ab)
|
|
|
5758
5771
|
|
|
5759
5772
|
Conversions accept partial output lists when the atomic input is known, but
|
|
5760
5773
|
constructing an atom or number requires a complete proper list with no unbound
|
|
5761
|
-
elements. Numeric parsing accepts
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
and
|
|
5774
|
+
elements. Numeric parsing accepts ISO layout before tokens, including layout between a
|
|
5775
|
+
minus token and the following numeric token. A single-line `%...` comment may
|
|
5776
|
+
therefore follow `-` directly because `%` cannot continue a graphic token; an
|
|
5777
|
+
adjacent bracketed comment in `-/**/1` remains a syntax error under the eager
|
|
5778
|
+
token-consumer rule. Decimal fractions and decimal exponents are supported;
|
|
5779
|
+
trailing material and non-finite values are rejected. The regression gate
|
|
5780
|
+
vendors all 74 numbered cases from Ulrich Neumerkel's contemporary
|
|
5781
|
+
`number_chars/2` comparison, including the Cor.2 error-precedence cases;
|
|
5782
|
+
`number_codes/2` shares the same numeric parser and has mirrored coverage for
|
|
5783
|
+
the recent numeric-syntax regressions.
|
|
5767
5784
|
|
|
5768
5785
|
### Streams and unit I/O
|
|
5769
5786
|
|
|
@@ -7020,7 +7037,7 @@ precedence still need one-by-one closure. `test/conformance/ISO-MATRIX.md`
|
|
|
7020
7037
|
maps language families to representative executable cases.
|
|
7021
7038
|
|
|
7022
7039
|
The complete suite must pass before release. The file-based conformance corpus
|
|
7023
|
-
contains
|
|
7040
|
+
contains 792 cases, including 386 focused ISO
|
|
7024
7041
|
cases derived from the success, failure, mode, and error behavior in
|
|
7025
7042
|
ISO/IEC 13211-1 clauses 7 and 8, Part 2 modules, and Part 3 grammar rules.
|
|
7026
7043
|
Separate exact-output suites check 189 normal
|