eyeprolog 1.2.25 → 1.2.26

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.
@@ -0,0 +1,18 @@
1
+ {
2
+ "source": "https://www.complang.tuwien.ac.at/ulrich/iso-prolog/conformity_testing",
3
+ "checkedOn": "2026-08-15",
4
+ "upstream": {
5
+ "firstId": 1,
6
+ "lastId": 368,
7
+ "deletedIds": [20, 273],
8
+ "activeCases": 366
9
+ },
10
+ "evidence": [
11
+ {
12
+ "name": "complete offline executable matrix",
13
+ "path": "test/conformance/wg17-syntax-cases.json",
14
+ "link": "../run-wg17-syntax.mjs",
15
+ "ids": "all-active"
16
+ }
17
+ ]
18
+ }
package/test/run-all.mjs CHANGED
@@ -9,12 +9,14 @@ import { runIsoStrict } from './run-iso-strict.mjs';
9
9
  import { runPlayground } from './run-playground.mjs';
10
10
  import { runExamples } from './run-examples.mjs';
11
11
  import { runBookExamples } from './run-book-examples.mjs';
12
+ import { runWg17Syntax } from './run-wg17-syntax.mjs';
12
13
 
13
14
  const reporter = new TestReporter();
14
15
 
15
16
  try {
16
17
  runConformance(reporter);
17
18
  runIsoStrict(reporter);
19
+ runWg17Syntax(reporter);
18
20
  runRegression(reporter);
19
21
  await runPlayground(reporter);
20
22
  runExamples(reporter);
@@ -45,6 +45,7 @@ import { TestReporter, isMainModule } from './test-style.mjs';
45
45
  import { buildConformanceReport, formatConformanceReport } from './run-conformance-report.mjs';
46
46
  import { proofExamples } from './run-examples.mjs';
47
47
  import { goalsFromSource } from './goal-metadata.mjs';
48
+ import { renderWg17SyntaxStatus } from '../tools/report-wg17-syntax-coverage.mjs';
48
49
 
49
50
  const testRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)));
50
51
  const packageRoot = path.resolve(testRoot, '..');
@@ -652,18 +653,18 @@ c4 ?- call((!;1)).
652
653
  },
653
654
  },
654
655
  {
655
- name: 'number_chars uses one generated representation for floating-point zero',
656
+ name: 'number syntax and number_chars normalize floating-point negative zero',
656
657
  run: () => {
657
658
  const result = runCli([], {
658
659
  input: 'X = -0.0, number_chars(X,C), number_chars(Y,C), X == Y, number_chars(Y,D).\nhalt.\n',
659
660
  });
660
661
  assertEqual(result.status, 0, 'exit status');
661
- assertIncludes(result.stdout, "X = -0.0, C = ['0', '.', '0'], Y = 0.0, D = ['0', '.', '0'].", 'answer');
662
+ assertIncludes(result.stdout, "X = 0.0, C = ['0', '.', '0'], Y = 0.0, D = ['0', '.', '0'].", 'answer');
662
663
  assertEqual(result.stderr, '', 'stderr');
663
664
  },
664
665
  },
665
666
  {
666
- name: 'readers keep a full stop inside a character-code constant (WG17 #211)',
667
+ name: 'readers keep a full stop inside a character-code constant (WG17 #367)',
667
668
  run: () => {
668
669
  const streamResult = runEyeProlog('', {
669
670
  goal: 'read((46 = 46))',
@@ -675,6 +676,10 @@ c4 ?- call((!;1)).
675
676
  assertEqual(cliResult.status, 0, 'top-level character-code full stop status');
676
677
  assertIncludes(cliResult.stdout, 'X = 46', 'top-level character-code answer');
677
678
  assertNotIncludes(cliResult.stdout, 'syntax_error', 'top-level character-code syntax');
679
+
680
+ const upstreamResult = runCli([], { input: "writeq(0'. ).\nhalt.\n" });
681
+ assertEqual(upstreamResult.status, 0, 'WG17 #367 exit status');
682
+ assertIncludes(upstreamResult.stdout, '46 true.', 'WG17 #367 output');
678
683
  },
679
684
  },
680
685
  {
@@ -2052,6 +2057,13 @@ c4 ?- call((!;1)).
2052
2057
 
2053
2058
  function documentationSyncCases() {
2054
2059
  return [
2060
+ {
2061
+ name: 'WG17 syntax status matches its executable-coverage manifest',
2062
+ run: () => {
2063
+ const filename = path.join(testRoot, 'conformance', 'WG17-SYNTAX-STATUS.md');
2064
+ assertEqual(fs.readFileSync(filename, 'utf8'), renderWg17SyntaxStatus(), 'WG17 syntax status');
2065
+ },
2066
+ },
2055
2067
  {
2056
2068
  name: 'book builtins match runtime registry',
2057
2069
  run: () => assertArrayEqual(bookBuiltinNames(), registeredBuiltinNames(), 'builtins'),
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env node
2
+ // Offline, one-by-one execution of the 366 active WG17 syntax cases. The
3
+ // fixture is a dated snapshot of the public conformity-testing table, so the
4
+ // release gate does not depend on the network or another Prolog system.
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ import {
9
+ Env, Program, Solver, parseGoalText, run,
10
+ } from '../src/index.js';
11
+ import { TestReporter, isMainModule } from './test-style.mjs';
12
+
13
+ const testRoot = path.dirname(fileURLToPath(import.meta.url));
14
+ const fixturePath = path.join(testRoot, 'conformance', 'wg17-syntax-cases.json');
15
+
16
+ function runnerStage(index, maximum) {
17
+ if (index > maximum) return `write('\\n<WG17-COMPLETE>')`;
18
+ return `read_term(G${index}, [variable_names(V${index})]), ` +
19
+ `(G${index} == end_of_file -> write('\\n<WG17-COMPLETE>') ; (` +
20
+ `write('\\n<WG17-BEGIN-${index}>'), call(G${index}), ` +
21
+ `write('<WG17-VARS>'), writeq(V${index}), write('<WG17-END>'), ` +
22
+ `${runnerStage(index + 1, maximum)}))`;
23
+ }
24
+
25
+ function capturedStages(stdout) {
26
+ const complete = stdout.indexOf('<WG17-COMPLETE>');
27
+ if (complete < 0) return null;
28
+ const captured = stdout.slice(0, complete);
29
+ return [...captured.matchAll(/<WG17-BEGIN-(\d+)>([\s\S]*?)<WG17-VARS>([\s\S]*?)<WG17-END>/g)]
30
+ .map((match) => ({ output: match[2], variables: match[3] }));
31
+ }
32
+
33
+ function executeFinite(item) {
34
+ try {
35
+ const result = run('', {
36
+ isoStrict: true,
37
+ goal: runnerStage(1, item.readCount),
38
+ ioOptions: { input: `${item.input}\n` },
39
+ });
40
+ const stages = capturedStages(result.stdout);
41
+ return stages == null ? { type: 'failure' } : { type: 'success', stages };
42
+ } catch (error) {
43
+ return { type: 'error', formal: error?.formal ?? null };
44
+ }
45
+ }
46
+
47
+ function executeWait(item) {
48
+ const program = Program.parse('', { isoStrict: true });
49
+ const solver = new Solver(program, {
50
+ isoStrict: true,
51
+ ioOptions: { input: item.input },
52
+ });
53
+ const stream = solver.io.resolve('user_input');
54
+ let requests = 0;
55
+ stream.interactiveReadTerm = () => {
56
+ requests++;
57
+ return null;
58
+ };
59
+ const goal = parseGoalText('read_term(G, [])', {
60
+ isoStrict: true,
61
+ operatorDefinitions: [...program.operators.values()],
62
+ });
63
+ try {
64
+ [...solver.solve([goal], new Env(), 0)];
65
+ } catch (_) {
66
+ // Returning null from the hook models EOF after EyeProlog has asked the
67
+ // interactive source for the continuation that the upstream case awaits.
68
+ }
69
+ return requests === 1 ? { type: 'waits' } : { type: 'did_not_wait', requests };
70
+ }
71
+
72
+ function assertOutcome(item) {
73
+ const actual = item.outcome.type === 'waits' ? executeWait(item) : executeFinite(item);
74
+ if (JSON.stringify(actual) !== JSON.stringify(item.outcome)) {
75
+ throw new Error(
76
+ `WG17 #${item.id} (${item.expected})\n` +
77
+ `expected ${JSON.stringify(item.outcome)}\n` +
78
+ `actual ${JSON.stringify(actual)}`,
79
+ );
80
+ }
81
+ }
82
+
83
+ export function runWg17Syntax(reporter = new TestReporter()) {
84
+ const fixture = JSON.parse(fs.readFileSync(fixturePath, 'utf8'));
85
+ if (fixture.cases.length !== 366) {
86
+ throw new Error(`WG17 fixture has ${fixture.cases.length} cases instead of 366`);
87
+ }
88
+ const ids = new Set(fixture.cases.map(({ id }) => id));
89
+ if (ids.size !== 366 || ids.has(20) || ids.has(273)) {
90
+ throw new Error('WG17 fixture identifiers do not match the active upstream inventory');
91
+ }
92
+
93
+ reporter.section('WG17 syntax');
94
+ for (const item of fixture.cases) reporter.test(`#${item.id}`, () => assertOutcome(item));
95
+ reporter.sectionTotal('WG17 syntax');
96
+ }
97
+
98
+ if (isMainModule(import.meta.url)) {
99
+ const reporter = new TestReporter();
100
+ try {
101
+ runWg17Syntax(reporter);
102
+ reporter.totalLine();
103
+ } catch (_) {
104
+ process.exitCode = 1;
105
+ }
106
+ }
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env node
2
+ // Render the traceability status for the public WG17 syntax corpus. This is a
3
+ // coverage ledger, not a claim that untraced cases fail or that traced cases
4
+ // are the only local tests exercising the same language rules.
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+
9
+ const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
10
+ const manifestPath = path.join(packageRoot, 'test', 'conformance', 'wg17-syntax-coverage.json');
11
+ const statusPath = path.join(packageRoot, 'test', 'conformance', 'WG17-SYNTAX-STATUS.md');
12
+
13
+ export function readWg17SyntaxCoverage() {
14
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
15
+ const { firstId, lastId, deletedIds, activeCases } = manifest.upstream;
16
+ const deleted = new Set(deletedIds);
17
+ const activeIds = [];
18
+ for (let id = firstId; id <= lastId; id++) if (!deleted.has(id)) activeIds.push(id);
19
+ if (activeIds.length !== activeCases) {
20
+ throw new Error(`WG17 active-case count is ${activeIds.length}, manifest says ${activeCases}`);
21
+ }
22
+
23
+ const active = new Set(activeIds);
24
+ const covered = new Set();
25
+ const evidenceEntries = [];
26
+ for (const evidence of manifest.evidence) {
27
+ if (!evidence.name || !evidence.path || !evidence.link ||
28
+ (!Array.isArray(evidence.ids) && evidence.ids !== 'all-active')) {
29
+ throw new Error('invalid WG17 evidence entry');
30
+ }
31
+ const evidenceIds = evidence.ids === 'all-active' ? activeIds : evidence.ids;
32
+ const evidenceFilename = path.join(packageRoot, evidence.path);
33
+ if (!fs.existsSync(evidenceFilename)) throw new Error(`missing WG17 evidence file ${evidence.path}`);
34
+ const referenced = referencedWg17Ids(fs.readFileSync(evidenceFilename, 'utf8'));
35
+ const withinEntry = new Set();
36
+ for (const id of evidenceIds) {
37
+ if (!Number.isInteger(id) || !active.has(id)) throw new Error(`invalid active WG17 id #${id}`);
38
+ if (withinEntry.has(id)) throw new Error(`duplicate WG17 id #${id} in ${evidence.name}`);
39
+ if (!referenced.has(id)) throw new Error(`WG17 id #${id} is not referenced by ${evidence.path}`);
40
+ withinEntry.add(id);
41
+ covered.add(id);
42
+ }
43
+ evidenceEntries.push({ ...evidence, ids: evidenceIds });
44
+ }
45
+
46
+ return {
47
+ manifest,
48
+ evidenceEntries,
49
+ activeIds,
50
+ coveredIds: activeIds.filter((id) => covered.has(id)),
51
+ untracedIds: activeIds.filter((id) => !covered.has(id)),
52
+ };
53
+ }
54
+
55
+ function referencedWg17Ids(source) {
56
+ const ids = new Set();
57
+ for (const match of source.matchAll(/#(\d+)(?:-(\d+))?/g)) {
58
+ const first = Number(match[1]);
59
+ const last = match[2] == null ? first : Number(match[2]);
60
+ for (let id = first; id <= last; id++) ids.add(id);
61
+ }
62
+ for (const match of source.matchAll(/"id"\s*:\s*(\d+)/g)) ids.add(Number(match[1]));
63
+ return ids;
64
+ }
65
+
66
+ export function renderWg17SyntaxStatus() {
67
+ const { manifest, evidenceEntries, activeIds, coveredIds, untracedIds } = readWg17SyntaxCoverage();
68
+ const percentage = (100 * coveredIds.length / activeIds.length).toFixed(1);
69
+ const evidenceRows = evidenceEntries.map((evidence) =>
70
+ `| [${evidence.name}](${evidence.link}) | ${evidence.ids.length} | ${formatRanges(evidence.ids)} |`);
71
+
72
+ return `# WG17 syntax traceability status
73
+
74
+ Source: [Conformity Testing I: Syntax](${manifest.source})
75
+ Upstream inventory checked: ${manifest.checkedOn}
76
+
77
+ This ledger counts an upstream case only when its WG17 identifier, query,
78
+ expected ISO disposition, and observed EyeProlog outcome are stored in the
79
+ offline executable matrix. Semantically similar parser tests are not inferred
80
+ as coverage.
81
+
82
+ ## Current standing
83
+
84
+ | Measure | Count |
85
+ | --- | ---: |
86
+ | Active upstream cases | ${activeIds.length} |
87
+ | Executable EyeProlog dispositions | ${coveredIds.length} (${percentage}%) |
88
+ | Not yet traced one-by-one | ${untracedIds.length} |
89
+ | Deleted upstream identifiers | ${formatRanges(manifest.upstream.deletedIds)} |
90
+
91
+ The matrix runs in strict ISO stream-reader mode as part of \`npm test\`. The
92
+ three upstream \`waits\` cases are checked through EyeProlog's interactive input
93
+ hook; the other ${activeIds.length - 3} cases are checked for their exact stored
94
+ success output, bindings, failure, or ISO error category.
95
+
96
+ ## Traceable evidence
97
+
98
+ | Executable evidence | Referenced IDs | WG17 cases |
99
+ | --- | ---: | --- |
100
+ ${evidenceRows.join('\n')}
101
+
102
+ The evidence groups overlap. Their union is **${coveredIds.length}** active cases:
103
+ ${formatRanges(coveredIds)}.
104
+
105
+ ## Untraced upstream identifiers
106
+
107
+ ${untracedIds.length === 0 ? 'None.' : `${formatRanges(untracedIds)}.`}
108
+
109
+ ## Maintenance
110
+
111
+ 1. Refresh the dated fixture when the upstream table changes.
112
+ 2. Review any changed ISO expectation before updating an observed snapshot.
113
+ 3. Keep this generated status page synchronized in the release gate.
114
+ `;
115
+ }
116
+
117
+ function formatRanges(ids) {
118
+ const sorted = [...new Set(ids)].sort((left, right) => left - right);
119
+ const ranges = [];
120
+ for (let index = 0; index < sorted.length;) {
121
+ const start = sorted[index];
122
+ let end = start;
123
+ while (index + 1 < sorted.length && sorted[index + 1] === end + 1) end = sorted[++index];
124
+ ranges.push(start === end ? `#${start}` : `#${start}–#${end}`);
125
+ index++;
126
+ }
127
+ return ranges.join(', ');
128
+ }
129
+
130
+ if (process.argv[1] != null && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
131
+ const rendered = renderWg17SyntaxStatus();
132
+ if (process.argv.includes('--check')) {
133
+ const current = fs.readFileSync(statusPath, 'utf8');
134
+ if (current !== rendered) {
135
+ process.stderr.write('WG17 syntax status is stale; run npm run report:wg17-syntax and update the file.\n');
136
+ process.exitCode = 1;
137
+ }
138
+ } else {
139
+ process.stdout.write(rendered);
140
+ }
141
+ }