eyeprolog 1.5.85 → 1.5.87
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/package.json +1 -1
- package/src/quads.js +44 -6
- package/test/README.md +1 -2
- package/test/fixtures/README.md +15 -18
- package/test/regression/cases-regression.mjs +36 -35
- package/test/run-benchmark-tests.mjs +0 -12
- package/the-art-of-eyeprolog.md +0 -10
- package/test/fixtures/prologue_quad.pl +0 -287
- package/test/fixtures/prologue_quad_runner.pl +0 -2
- package/test/lips-benchmark.mjs +0 -93
package/README.md
CHANGED
|
@@ -84,7 +84,7 @@ The checked [Symbiotic Knowledge Graphs example](examples/symbiotic-knowledge-gr
|
|
|
84
84
|
The same RDF → Prolog → RDF boundary is exercised by five additional checked scenarios: [cross-organization data sharing](https://eyereasoner.github.io/eyeprolog/examples/deck/cross-organization-data-sharing), [explainable EV-depot configuration](https://eyereasoner.github.io/eyeprolog/examples/deck/explainable-ev-depot-configuration), [operational incident response](https://eyereasoner.github.io/eyeprolog/examples/deck/operational-incident-response), [software supply-chain vulnerability response](https://eyereasoner.github.io/eyeprolog/examples/deck/sbom-vulnerability-response), and a [scientific evidence graph](https://eyereasoner.github.io/eyeprolog/examples/deck/scientific-evidence-graph). Together they cover policy decisions, reversible configuration reasoning, dependency-graph diagnosis, transitive SBOM exposure, and evidence aggregation with explicit disagreement.
|
|
85
85
|
|
|
86
86
|
## Benchmarks
|
|
87
|
-
EyeProlog has 21 checksum-protected wall-clock benchmarks spanning recursion/indexing, constraints, tabling/WFS, DCGs, Eyelet, search, term I/O, attributes, rewriting, the dynamic database, and bignum arithmetic. Short workloads are adaptively batched before timing so millisecond-scale noise is not mistaken for a regression. Run `npm run benchmark`; create a machine-local comparison point with `npm run benchmark -- --save .benchmarks/baseline.json`; use `node test/run-benchmark-tests.mjs` for harness checks.
|
|
87
|
+
EyeProlog has 21 checksum-protected wall-clock benchmarks spanning recursion/indexing, constraints, tabling/WFS, DCGs, Eyelet, search, term I/O, attributes, rewriting, the dynamic database, and bignum arithmetic. Short workloads are adaptively batched before timing so millisecond-scale noise is not mistaken for a regression. Run `npm run benchmark`; create a machine-local comparison point with `npm run benchmark -- --save .benchmarks/baseline.json`; use `node test/run-benchmark-tests.mjs` for harness checks. Details are in [*The Art of EyeProlog*](the-art-of-eyeprolog.md).
|
|
88
88
|
For the project policy on post-ISO-standard and WG17 compatibility features such as digit separators, see [ISO/WG17 compatibility extensions](test/conformance/ISO-WG17-EXTENSIONS.md).
|
|
89
89
|
## Development
|
|
90
90
|
```sh
|
package/package.json
CHANGED
package/src/quads.js
CHANGED
|
@@ -82,7 +82,7 @@ function checkDescription(program, quad, description, options, context) {
|
|
|
82
82
|
if (part.type === ATOM && part.name === 'other_answer_sequence') {
|
|
83
83
|
const previous = alternatives.at(-1);
|
|
84
84
|
if (previous == null || unordered.has(previous)) {
|
|
85
|
-
return { ok: false, kind: 'malformed', expected: part };
|
|
85
|
+
return { ok: false, kind: 'malformed', expected: part, alternative: previous ?? undefined };
|
|
86
86
|
}
|
|
87
87
|
unordered.add(previous);
|
|
88
88
|
} else alternatives.push(part);
|
|
@@ -93,7 +93,13 @@ function checkDescription(program, quad, description, options, context) {
|
|
|
93
93
|
Number(alternativeDescribesLoop(right)) - Number(alternativeDescribesLoop(left)));
|
|
94
94
|
for (const alternative of ordered) {
|
|
95
95
|
const malformed = malformedAlternative(quad.query, alternative);
|
|
96
|
-
|
|
96
|
+
// Carry the whole offending `|`-alternative alongside the narrow
|
|
97
|
+
// sub-term the check actually tripped on: with several alternatives (the
|
|
98
|
+
// norm for `sto`-annotated queries -- see issue #112's follow-up),
|
|
99
|
+
// formatFailure shows this alternative in full and elides its siblings
|
|
100
|
+
// instead of leaving a reader to guess which branch a bare sub-term like
|
|
101
|
+
// `_B = [E|_B]` came from.
|
|
102
|
+
if (malformed != null) return { ok: false, kind: 'malformed', expected: malformed, alternative };
|
|
97
103
|
}
|
|
98
104
|
let unsupported = null;
|
|
99
105
|
let undecided = null;
|
|
@@ -111,19 +117,19 @@ function checkAlternative(program, quad, alternative, options, context, unordere
|
|
|
111
117
|
const requiresSto = leaves.some((leaf) => leaf.sto);
|
|
112
118
|
const unsupported = leaves.find((leaf) => leaf.unsupported != null)?.unsupported;
|
|
113
119
|
if (unsupported != null) {
|
|
114
|
-
return { ok: false, kind: 'unsupported', expected: unsupported };
|
|
120
|
+
return { ok: false, kind: 'unsupported', expected: unsupported, alternative };
|
|
115
121
|
}
|
|
116
122
|
|
|
117
123
|
// A permutation describes a complete sequence, not an arbitrary prefix or
|
|
118
124
|
// a negative assertion. Do not silently weaken those annotations.
|
|
119
125
|
if (unordered && leaves.some((leaf) => leaf.more || leaf.unexpected || leaf.sto ||
|
|
120
126
|
leaf.waits || leaf.input != null || leaf.peek != null)) {
|
|
121
|
-
return { ok: false, kind: 'unsupported', expected: alternative };
|
|
127
|
+
return { ok: false, kind: 'unsupported', expected: alternative, alternative };
|
|
122
128
|
}
|
|
123
129
|
|
|
124
130
|
const ioLeaves = leaves.filter((leaf) => leaf.input != null || leaf.peek != null);
|
|
125
131
|
if (ioLeaves.length > 1 || (ioLeaves.length > 0 && leaves.length !== 1)) {
|
|
126
|
-
return { ok: false, kind: 'malformed', expected: alternative };
|
|
132
|
+
return { ok: false, kind: 'malformed', expected: alternative, alternative };
|
|
127
133
|
}
|
|
128
134
|
const hasInputSpec = ioLeaves.length === 1;
|
|
129
135
|
const inputLeaf = ioLeaves[0] ?? null;
|
|
@@ -986,6 +992,28 @@ const FAILURE_LABELS = {
|
|
|
986
992
|
undecided: 'UNDECIDED',
|
|
987
993
|
};
|
|
988
994
|
|
|
995
|
+
// With several `|`-separated alternatives -- the norm for `sto`-annotated
|
|
996
|
+
// queries -- a bare offending sub-term like `_B = [E|_B]` gives no sense of
|
|
997
|
+
// which alternative it came from (issue #112's follow-up). Show that whole
|
|
998
|
+
// alternative in place, and stand in for its siblings with `...` rather than
|
|
999
|
+
// reproducing them (they were not the problem) or dropping them silently
|
|
1000
|
+
// (a reader can no longer tell how many alternatives, or which one, this was).
|
|
1001
|
+
// Returns null when the description has only one alternative to begin with:
|
|
1002
|
+
// there is then nothing to elide, and the existing plain rendering already
|
|
1003
|
+
// shows the whole thing.
|
|
1004
|
+
function formatAlternativeContext(program, description, alternative) {
|
|
1005
|
+
const parts = splitOperator(description, '|');
|
|
1006
|
+
if (parts.length <= 1 || !parts.includes(alternative)) return null;
|
|
1007
|
+
return parts.map((part) => (part === alternative ? formatQuadTerm(program, part) : '...')).join(' | ');
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
// A trailing `...` glued straight to a full stop reads as four dots; the
|
|
1011
|
+
// quad answer-description syntax itself always writes a space before the
|
|
1012
|
+
// period in that position (see `..., ad_infinitum` vs `... .`), so match it.
|
|
1013
|
+
function terminate(text) {
|
|
1014
|
+
return text.endsWith('...') ? `${text} .` : `${text}.`;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
989
1017
|
function formatFailure(program, quad, result, description = quad.answers[0]) {
|
|
990
1018
|
const source = quad.source ?? { filename: '<input>', line: 1 };
|
|
991
1019
|
// Point at the failing answer description's own line rather than always the
|
|
@@ -995,9 +1023,19 @@ function formatFailure(program, quad, result, description = quad.answers[0]) {
|
|
|
995
1023
|
const label = quad.id == null ? '' : `${formatQuadTerm(program, quad.id)}, `;
|
|
996
1024
|
const reason = FAILURE_LABELS[result.kind] ?? 'FAILED';
|
|
997
1025
|
const expected = result.expected ?? description;
|
|
1026
|
+
const context = result.alternative != null
|
|
1027
|
+
? formatAlternativeContext(program, description, result.alternative)
|
|
1028
|
+
: null;
|
|
998
1029
|
const detail = result.kind === 'undecided'
|
|
999
1030
|
? ` undecided: ${result.reason}.\n`
|
|
1000
|
-
|
|
1031
|
+
// When the offending sub-term already *is* the whole alternative (an
|
|
1032
|
+
// `expected:` line would just repeat the context line), the context line
|
|
1033
|
+
// alone is the full, non-redundant report.
|
|
1034
|
+
: context != null && expected === result.alternative
|
|
1035
|
+
? ` ${terminate(context)}\n`
|
|
1036
|
+
: context != null
|
|
1037
|
+
? ` ${terminate(context)}\n expected: ${formatQuadTerm(program, expected)}.\n`
|
|
1038
|
+
: ` expected: ${formatQuadTerm(program, expected)}.\n`;
|
|
1001
1039
|
return `quads: ${reason} ${label}${source.filename}:${line}\n` +
|
|
1002
1040
|
` ?- ${formatQuadTerm(program, quad.query)}.\n` + detail;
|
|
1003
1041
|
}
|
package/test/README.md
CHANGED
|
@@ -27,7 +27,6 @@ These runners retain their existing options; there is no separate npm alias for
|
|
|
27
27
|
each one. Focused checks do not replace the full release gate.
|
|
28
28
|
|
|
29
29
|
For performance measurements, use `npm run benchmark`. Save a local baseline with
|
|
30
|
-
`npm run benchmark -- --save .benchmarks/baseline.json
|
|
31
|
-
`node test/lips-benchmark.mjs` for the classic LIPS measurement.
|
|
30
|
+
`npm run benchmark -- --save .benchmarks/baseline.json`.
|
|
32
31
|
|
|
33
32
|
See the [conformance guide](conformance/README.md) for report maintenance.
|
package/test/fixtures/README.md
CHANGED
|
@@ -17,21 +17,18 @@ quads for the ISO read and write option `variable_names/1`:
|
|
|
17
17
|
Retrieved on 2026-08-25. It is vendored so all input, output, waiting, and
|
|
18
18
|
error cases remain release-gated.
|
|
19
19
|
|
|
20
|
-
`prologue_quad.pl`
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
`prologue_quad_runner.pl` loads EyeProlog's `library(prologue)` and includes
|
|
36
|
-
the unmodified corpus, mirroring the draft's requirement that a Prologue be
|
|
37
|
-
included before its examples are run.
|
|
20
|
+
There used to be a `prologue_quad.pl`/`prologue_quad_runner.pl` pair here: an
|
|
21
|
+
unmodified snapshot of the Prolog Prologue working draft's machine-readable
|
|
22
|
+
quads (<https://www.complang.tuwien.ac.at/ulrich/iso-prolog/prologue_quad.pl>),
|
|
23
|
+
plus a runner that loaded `library(prologue)` before including it. It was
|
|
24
|
+
retired (see git history) once its coverage became strictly redundant with
|
|
25
|
+
the *live* Prologue source the Neumerkel gate already fetches and checks on
|
|
26
|
+
every `npm test` run -- the same reason the WG17 syntax matrix has no
|
|
27
|
+
vendored snapshot of its own either (see
|
|
28
|
+
[NEUMERKEL-LIVE.md](../conformance/NEUMERKEL-LIVE.md) and
|
|
29
|
+
[conformance/README.md](../conformance/README.md)). Unlike `phrase_quad.pl`
|
|
30
|
+
and `variable_names_quad.pl` below, the vendored Prologue corpus also
|
|
31
|
+
included two open-ended STO/rational-tree generator quads
|
|
32
|
+
(`member(X,X)`, `select(E,Xs,Xs)`) that made checking it offline
|
|
33
|
+
disproportionately slow, with no coverage benefit over the live gate to
|
|
34
|
+
justify the cost.
|
|
@@ -2396,41 +2396,6 @@ c4 ?- call((!;1)).
|
|
|
2396
2396
|
}
|
|
2397
2397
|
},
|
|
2398
2398
|
},
|
|
2399
|
-
{
|
|
2400
|
-
name: 'runQuads passes the complete vendored Prolog Prologue corpus',
|
|
2401
|
-
run: () => {
|
|
2402
|
-
const filename = path.join(testRoot, 'fixtures', 'prologue_quad_runner.pl');
|
|
2403
|
-
const source = fs.readFileSync(filename, 'utf8');
|
|
2404
|
-
const program = Program.parseSources([{
|
|
2405
|
-
text: source,
|
|
2406
|
-
filename,
|
|
2407
|
-
baseDir: path.dirname(filename),
|
|
2408
|
-
}]);
|
|
2409
|
-
assertEqual(program.quads.length, 72, 'vendored quad total');
|
|
2410
|
-
const maxIntegerQuads = program.quads.filter(({ query }) =>
|
|
2411
|
-
termToString(query).includes('current_prolog_flag(max_integer, MI)'));
|
|
2412
|
-
assertEqual(maxIntegerQuads.length, 4, 'max_integer quad count');
|
|
2413
|
-
|
|
2414
|
-
// The full 72-quad corpus, including its rational-tree/occurs-check
|
|
2415
|
-
// STO examples (member(X,X) and select(E,Xs,Xs), both open-ended
|
|
2416
|
-
// native-generator searches -- see the maxInferences accounting added
|
|
2417
|
-
// to generatedLengthAllocationCheckpoint in src/solver.js) is bounded
|
|
2418
|
-
// and takes on the order of several seconds, not the indefinite hang
|
|
2419
|
-
// it used to depend on ambient heap pressure to avoid (see
|
|
2420
|
-
// Solver#reclaimMemory in src/solver.js).
|
|
2421
|
-
const result = publicApi.runQuads(program);
|
|
2422
|
-
// The current upstream max_integer quads already accept EyeProlog's
|
|
2423
|
-
// own bounded=false behavior (no max_integer value, so
|
|
2424
|
-
// current_prolog_flag(max_integer, MI) simply fails); a stale, since
|
|
2425
|
-
// superseded vendored snapshot once needed a documented divergence
|
|
2426
|
-
// for this (see git history), but the current upstream corpus needs
|
|
2427
|
-
// none: the whole vendored corpus passes outright.
|
|
2428
|
-
assertEqual(result.total, 72, 'quad total');
|
|
2429
|
-
assertEqual(result.passed, 72, 'quad passed');
|
|
2430
|
-
assertEqual(result.failed, 0, 'quad failed');
|
|
2431
|
-
assertEqual(result.stdout, 'quads: 72 run, 72 passed, 0 failed.\n', 'quad report');
|
|
2432
|
-
},
|
|
2433
|
-
},
|
|
2434
2399
|
{
|
|
2435
2400
|
name: 'CLI passes the complete authoritative length quad corpus',
|
|
2436
2401
|
run: () => {
|
|
@@ -2587,6 +2552,42 @@ c4 ?- call((!;1)).
|
|
|
2587
2552
|
assertEqual(result.total, 1, 'quad total');
|
|
2588
2553
|
assertEqual(result.failed, 1, 'quad failed');
|
|
2589
2554
|
assertIncludes(result.stdout, 'quads: MALFORMED malformed-quad.pl:2', 'malformed report');
|
|
2555
|
+
// A single `|`-alternative already is the whole answer description --
|
|
2556
|
+
// there is nothing to elide, so the plain, unelided report is
|
|
2557
|
+
// unchanged (contrast the multi-alternative case just below).
|
|
2558
|
+
assertNotIncludes(result.stdout, ' | ', 'single-alternative report stays unelided');
|
|
2559
|
+
},
|
|
2560
|
+
},
|
|
2561
|
+
{
|
|
2562
|
+
name: 'runQuads shows a malformed sub-term in the context of its full sibling-elided alternative (issue #112)',
|
|
2563
|
+
run: () => {
|
|
2564
|
+
// Adapted from the upstream Prologue `select(E, Xs, Xs)` quad
|
|
2565
|
+
// (https://github.com/eyereasoner/eyeprolog/issues/112#issuecomment-5645403025):
|
|
2566
|
+
// a query-unrelated `Ys` stands in for the legitimate forward-reference
|
|
2567
|
+
// idiom so this stays malformed regardless of that fix. With three
|
|
2568
|
+
// `|`-alternatives, a bare `expected: Ys = [E|Ys].` gives no sense of
|
|
2569
|
+
// which one it came from; the report should instead show that whole
|
|
2570
|
+
// alternative in place and stand in for its siblings with `...`.
|
|
2571
|
+
const source = `:- use_module(library(prologue)).
|
|
2572
|
+
?- select(E, Xs, Xs).
|
|
2573
|
+
sto,
|
|
2574
|
+
loops
|
|
2575
|
+
| sto,
|
|
2576
|
+
Xs = [E|Xs]
|
|
2577
|
+
; Xs = [_A|_B], Ys = [E|Ys]
|
|
2578
|
+
; ..., ad_infinitum
|
|
2579
|
+
| sto,
|
|
2580
|
+
Xs = [E,E|_A]
|
|
2581
|
+
; Xs = [_A,E,E|_B]
|
|
2582
|
+
; ..., ad_infinitum.
|
|
2583
|
+
`;
|
|
2584
|
+
const result = publicApi.runQuads(Program.parseSources([{ text: source, filename: 'select-quad.pl' }]));
|
|
2585
|
+
assertEqual(result.total, 1, 'quad total');
|
|
2586
|
+
assertEqual(result.failed, 1, 'quad failed');
|
|
2587
|
+
assertIncludes(result.stdout,
|
|
2588
|
+
' ... | sto, Xs = [E | Xs] ; Xs = [_A | _B], Ys = [E | Ys] ; ..., ad_infinitum | ... .\n',
|
|
2589
|
+
'sibling-elided alternative context');
|
|
2590
|
+
assertIncludes(result.stdout, ' expected: Ys = [E | Ys].\n', 'still-precise offending sub-term');
|
|
2590
2591
|
},
|
|
2591
2592
|
},
|
|
2592
2593
|
{
|
|
@@ -70,16 +70,4 @@ ok(adaptive.results[0].batchSize > 1, 'adaptive benchmark smoke test should batc
|
|
|
70
70
|
ok(adaptive.results[0].sha256 === manifest.find((item) => item.name === 'dcg-expression').expectedSha256,
|
|
71
71
|
'adaptive batching should preserve the semantic checksum');
|
|
72
72
|
|
|
73
|
-
const classicLips = await spawnJson([
|
|
74
|
-
path.join(root, 'test', 'lips-benchmark.mjs'),
|
|
75
|
-
'--count', '20',
|
|
76
|
-
'--runs', '1',
|
|
77
|
-
'--warmup', '1',
|
|
78
|
-
'--json',
|
|
79
|
-
]);
|
|
80
|
-
ok(classicLips.count === 20, 'classic LIPS harness should honor the requested reversal count');
|
|
81
|
-
ok(classicLips.methodology.includes('496 calls/reversal'), 'classic LIPS harness should identify the historical accounting');
|
|
82
|
-
ok(Number.isFinite(classicLips.lips) && classicLips.lips > 0, 'classic LIPS harness should report positive CPU LIPS');
|
|
83
|
-
ok(Number.isFinite(classicLips.netCpuMs) && classicLips.netCpuMs > 0, 'classic LIPS harness should subtract positive CPU time');
|
|
84
|
-
|
|
85
73
|
process.stdout.write(`Benchmark harness tests: ${passed}/${passed} passed.\n`);
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -10366,7 +10366,6 @@ and fast:
|
|
|
10366
10366
|
```sh
|
|
10367
10367
|
npm run benchmark
|
|
10368
10368
|
npm run benchmark -- --save .benchmarks/baseline.json
|
|
10369
|
-
node test/lips-benchmark.mjs
|
|
10370
10369
|
```
|
|
10371
10370
|
|
|
10372
10371
|
The benchmark suite contains 21 representative workloads and stores their
|
|
@@ -10378,15 +10377,6 @@ with independent `run()` calls until a batch is roughly 400 ms long. After one
|
|
|
10378
10377
|
warm-up batch, five measured batches are reported as milliseconds per workload
|
|
10379
10378
|
execution. Naturally long workloads keep a batch size of one.
|
|
10380
10379
|
|
|
10381
|
-
The classic `examples/bench.pl` workload also has a dedicated LIPS harness.
|
|
10382
|
-
`node test/lips-benchmark.mjs` follows the 1984 Quintus method more closely than the
|
|
10383
|
-
generic runner: `dobench/1` and `dodummy/1` execute failure-driven loops inside
|
|
10384
|
-
Prolog, the dummy CPU time is subtracted, and the remaining time is converted
|
|
10385
|
-
using 496 procedure calls for one reversal of the 30-element list. Node's
|
|
10386
|
-
process CPU clock is used for the primary figure, with wall-clock LIPS printed
|
|
10387
|
-
as a cross-check. Because LIPS measures a deliberately small recursive kernel,
|
|
10388
|
-
it is useful for engine tuning but is not a complete application benchmark.
|
|
10389
|
-
|
|
10390
10380
|
The report shows the median, per-operation range, chosen batch size, saved
|
|
10391
10381
|
baseline median, and the percentage change between the current median and baseline
|
|
10392
10382
|
median. The range remains visible as context, but it does not suppress or reinterpret
|
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
% p.p.1 member/2
|
|
2
|
-
|
|
3
|
-
?- member(X, [1,2]).
|
|
4
|
-
X = 1
|
|
5
|
-
; X = 2.
|
|
6
|
-
|
|
7
|
-
?- member(1, L).
|
|
8
|
-
L = [1|_A]
|
|
9
|
-
; L = [_A,1|_B]
|
|
10
|
-
; L = [_A,_B,1|_C]
|
|
11
|
-
; ..., ad_infinitum.
|
|
12
|
-
|
|
13
|
-
?- member(X, [Y,Z|nonlist]).
|
|
14
|
-
X = Y
|
|
15
|
-
; X = Z.
|
|
16
|
-
|
|
17
|
-
?- member(X, nonlist).
|
|
18
|
-
false.
|
|
19
|
-
|
|
20
|
-
?- member(X, X).
|
|
21
|
-
sto, % occurs-check
|
|
22
|
-
loops
|
|
23
|
-
| sto, % rational trees
|
|
24
|
-
X = [X|_A]
|
|
25
|
-
; X = [_A,X|_B]
|
|
26
|
-
; X = [_A,_B,X|_C]
|
|
27
|
-
; ..., ad_infinitum
|
|
28
|
-
| sto, % literal substitutions
|
|
29
|
-
X = [_A|_B]
|
|
30
|
-
; X = [_A,[_A,[_A|_B]|_C]|_C]
|
|
31
|
-
; X = [_A,_B,[_A,_B,[_A,_B|_C]|_D]|_D]
|
|
32
|
-
; ..., ad_infinitum.
|
|
33
|
-
|
|
34
|
-
% p.p.2 append/3
|
|
35
|
-
|
|
36
|
-
?- append([a,b],[c,d], Xs).
|
|
37
|
-
Xs = [a,b,c,d].
|
|
38
|
-
|
|
39
|
-
?- append([a], nonlist, Xs).
|
|
40
|
-
Xs = [a|nonlist].
|
|
41
|
-
|
|
42
|
-
?- append([a], Ys, Zs).
|
|
43
|
-
Zs = [a|Ys].
|
|
44
|
-
|
|
45
|
-
?- append(Xs, Ys, [a,b,c]).
|
|
46
|
-
Xs = [], Ys = [a,b,c]
|
|
47
|
-
; Xs = [a], Ys = [b,c]
|
|
48
|
-
; Xs = [a,b], Ys = [c]
|
|
49
|
-
; Xs = [a,b,c], Ys = [].
|
|
50
|
-
|
|
51
|
-
% p.p.3 length/2
|
|
52
|
-
|
|
53
|
-
?- length([a,b,c], Length).
|
|
54
|
-
Length = 3.
|
|
55
|
-
|
|
56
|
-
?- length(List, 5).
|
|
57
|
-
List = [_,_,_,_,_].
|
|
58
|
-
|
|
59
|
-
?- length(List, Length).
|
|
60
|
-
List = [], Length = 0
|
|
61
|
-
; List = [_], Length = 1
|
|
62
|
-
; List = [_,_], Length = 2
|
|
63
|
-
; ... . % Ad infinitum.
|
|
64
|
-
|
|
65
|
-
% p.p.4 between/3
|
|
66
|
-
|
|
67
|
-
?- between(1, 2, 0).
|
|
68
|
-
false.
|
|
69
|
-
|
|
70
|
-
?- between(1, 2, I).
|
|
71
|
-
I = 1
|
|
72
|
-
; I = 2.
|
|
73
|
-
|
|
74
|
-
?- between(2, 1, I).
|
|
75
|
-
false.
|
|
76
|
-
|
|
77
|
-
?- between(I, I, 0).
|
|
78
|
-
instantiation_error.
|
|
79
|
-
|
|
80
|
-
?- between(1, I, 0).
|
|
81
|
-
instantiation_error.
|
|
82
|
-
|
|
83
|
-
?- between(I, -1, 0).
|
|
84
|
-
instantiation_error.
|
|
85
|
-
|
|
86
|
-
?- between(1, c, 0).
|
|
87
|
-
type_error(integer,c).
|
|
88
|
-
|
|
89
|
-
?- between(1+1,2,I).
|
|
90
|
-
type_error(integer,1+1).
|
|
91
|
-
|
|
92
|
-
% p.p.5 select/3
|
|
93
|
-
|
|
94
|
-
?- select(X, [1,2], Xs).
|
|
95
|
-
X = 1, Xs = [2]
|
|
96
|
-
; X = 2, Xs = [1].
|
|
97
|
-
|
|
98
|
-
?- select(X, [Y|nonlist], Xs).
|
|
99
|
-
X = Y, Xs = nonlist.
|
|
100
|
-
|
|
101
|
-
?- select(E, Xs, Xs).
|
|
102
|
-
sto, % occurs-check
|
|
103
|
-
loops
|
|
104
|
-
| sto, % rational trees
|
|
105
|
-
Xs = [E|Xs]
|
|
106
|
-
; Xs = [_A|_B], _B = [E|_B]
|
|
107
|
-
; ..., ad_infinitum
|
|
108
|
-
| sto, % literal substitutions
|
|
109
|
-
Xs = [E,E|_A]
|
|
110
|
-
; Xs = [_A,E,E|_B]
|
|
111
|
-
; ..., ad_infinitum.
|
|
112
|
-
|
|
113
|
-
% p.p.6 succ/2
|
|
114
|
-
|
|
115
|
-
?- succ(X, S).
|
|
116
|
-
instantiation_error.
|
|
117
|
-
|
|
118
|
-
?- succ(X, X).
|
|
119
|
-
instantiation_error.
|
|
120
|
-
|
|
121
|
-
?- succ(0, S).
|
|
122
|
-
S = 1.
|
|
123
|
-
|
|
124
|
-
?- succ(1, 1+1).
|
|
125
|
-
type_error(integer, 1+1).
|
|
126
|
-
|
|
127
|
-
?- succ(X, 0).
|
|
128
|
-
false.
|
|
129
|
-
|
|
130
|
-
?- succ(-1, S).
|
|
131
|
-
domain_error(not_less_than_zero, -1).
|
|
132
|
-
|
|
133
|
-
?- current_prolog_flag(max_integer, MI), succ(MI, 0).
|
|
134
|
-
false.
|
|
135
|
-
|
|
136
|
-
?- current_prolog_flag(max_integer, MI), succ(MI, 1).
|
|
137
|
-
false.
|
|
138
|
-
|
|
139
|
-
?- current_prolog_flag(max_integer, MI), succ(MI, MI).
|
|
140
|
-
false.
|
|
141
|
-
|
|
142
|
-
?- current_prolog_flag(max_integer, MI), succ(MI, S).
|
|
143
|
-
false
|
|
144
|
-
| evaluation_error(int_overflow)
|
|
145
|
-
| representation_error(max_integer).
|
|
146
|
-
|
|
147
|
-
% p.p.7
|
|
148
|
-
|
|
149
|
-
?- maplist(>(3), [1, 2]).
|
|
150
|
-
true.
|
|
151
|
-
|
|
152
|
-
?- maplist(>(3), [1, 2, 3]).
|
|
153
|
-
false.
|
|
154
|
-
|
|
155
|
-
?- maplist(=(X), Xs).
|
|
156
|
-
Xs = []
|
|
157
|
-
; Xs = [X]
|
|
158
|
-
; Xs = [X, X]
|
|
159
|
-
; Xs = [X, X, X]
|
|
160
|
-
; ... . % Ad infinitum.
|
|
161
|
-
|
|
162
|
-
% p.p.8
|
|
163
|
-
|
|
164
|
-
?- nth0(1, [a,b,c], E).
|
|
165
|
-
E = b.
|
|
166
|
-
|
|
167
|
-
?- nth0(N, [a,b,c], E).
|
|
168
|
-
N = 0, E = a
|
|
169
|
-
; N = 1, E = b
|
|
170
|
-
; N = 2, E = c.
|
|
171
|
-
|
|
172
|
-
?- nth0(0, [A,B|non_list], E).
|
|
173
|
-
A = E.
|
|
174
|
-
|
|
175
|
-
?- nth0(2, Es, E).
|
|
176
|
-
Es = [_A,_B,E|_C].
|
|
177
|
-
|
|
178
|
-
?- nth0(N, Es, E).
|
|
179
|
-
N = 0, Es = [E|_A]
|
|
180
|
-
; N = 1, Es = [_A,E|_B]
|
|
181
|
-
; N = 2, Es = [_A,_B,E|_C]
|
|
182
|
-
; N = 3, Es = [_A,_B,_C,E|_D]
|
|
183
|
-
; ..., ad_infinitum.
|
|
184
|
-
|
|
185
|
-
?- nth0(non_integer, Es, E).
|
|
186
|
-
type_error(integer, non_integer).
|
|
187
|
-
|
|
188
|
-
?- nth0(-1, Es, E).
|
|
189
|
-
domain_error(not_less_than_zero, -1).
|
|
190
|
-
|
|
191
|
-
?- nth0(N, [[]|Es], Es).
|
|
192
|
-
N = 0, Es = []
|
|
193
|
-
; sto, % occurs-check
|
|
194
|
-
loops
|
|
195
|
-
| N = 0, Es = []
|
|
196
|
-
; sto, % rational trees
|
|
197
|
-
N = 1, Es = [Es|_A]
|
|
198
|
-
; N = 2, Es = [_A,Es|_B]
|
|
199
|
-
; ..., ad_infinitum.
|
|
200
|
-
|
|
201
|
-
?- nth1(0, Es, E).
|
|
202
|
-
false.
|
|
203
|
-
|
|
204
|
-
% p.p.9
|
|
205
|
-
|
|
206
|
-
?- call_nth(true, Nth).
|
|
207
|
-
Nth = 1.
|
|
208
|
-
|
|
209
|
-
?- call_nth(false, Nth).
|
|
210
|
-
false.
|
|
211
|
-
|
|
212
|
-
?- call_nth(repeat, Nth).
|
|
213
|
-
Nth = 1
|
|
214
|
-
; Nth = 2
|
|
215
|
-
; Nth = 3
|
|
216
|
-
; Nth = 4
|
|
217
|
-
; Nth = 5
|
|
218
|
-
; ... .
|
|
219
|
-
|
|
220
|
-
?- call_nth(( N = 1 ; N = 2 ), Nth).
|
|
221
|
-
N = 1, Nth = 1
|
|
222
|
-
; N = 2, Nth = 2.
|
|
223
|
-
|
|
224
|
-
?- call_nth(true, non_integer).
|
|
225
|
-
type_error(integer,non_integer).
|
|
226
|
-
|
|
227
|
-
?- call_nth(true, 1.0).
|
|
228
|
-
type_error(integer,1.0).
|
|
229
|
-
|
|
230
|
-
?- call_nth(true, 0).
|
|
231
|
-
false.
|
|
232
|
-
|
|
233
|
-
?- call_nth(repeat, 0).
|
|
234
|
-
false.
|
|
235
|
-
|
|
236
|
-
?- call_nth(repeat, -1).
|
|
237
|
-
domain_error(not_less_than_zero,-1).
|
|
238
|
-
|
|
239
|
-
?- call_nth(length(L,N), 3).
|
|
240
|
-
L = [_A,_B], N = 2.
|
|
241
|
-
|
|
242
|
-
?- call_nth(inex, 0).
|
|
243
|
-
false. % thus not existence_error(procedure,inex/0)
|
|
244
|
-
|
|
245
|
-
?- call_nth(inex, 0).
|
|
246
|
-
existence_error(procedure,...), unexpected.
|
|
247
|
-
|
|
248
|
-
?- call_nth(1, 0).
|
|
249
|
-
false.
|
|
250
|
-
|
|
251
|
-
?- call_nth(V, 0).
|
|
252
|
-
false.
|
|
253
|
-
|
|
254
|
-
?- call_nth(N = 1, N).
|
|
255
|
-
N = 1.
|
|
256
|
-
|
|
257
|
-
?- call_nth(N = -1, N).
|
|
258
|
-
false.
|
|
259
|
-
|
|
260
|
-
?- call_nth(repeat,1+1).
|
|
261
|
-
type_error(integer,1+1).
|
|
262
|
-
|
|
263
|
-
% p.p.10
|
|
264
|
-
|
|
265
|
-
?- foldl(append, [[1,2],[3],[4,5]], [],Xs).
|
|
266
|
-
Xs = [4,5,3,1,2].
|
|
267
|
-
|
|
268
|
-
% p.p.11
|
|
269
|
-
|
|
270
|
-
?- countall((X=1;X=2), N).
|
|
271
|
-
N = 2.
|
|
272
|
-
?- countall((true;true), N).
|
|
273
|
-
N = 2.
|
|
274
|
-
?- countall(G_0, N).
|
|
275
|
-
instantiation_error.
|
|
276
|
-
?- countall((length(L,5),nth0(_,L,_),nth0(_,L,_)), N).
|
|
277
|
-
N = 25.
|
|
278
|
-
?- countall(N = 1, N).
|
|
279
|
-
N = 1.
|
|
280
|
-
?- countall(N = non_integer, N).
|
|
281
|
-
N = 1.
|
|
282
|
-
?- countall(false, 1).
|
|
283
|
-
false.
|
|
284
|
-
?- countall(false, -1).
|
|
285
|
-
domain_error(not_less_than_zero,-1).
|
|
286
|
-
?- countall(false, non_integer).
|
|
287
|
-
type_error(integer,non_integer).
|
package/test/lips-benchmark.mjs
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import process from 'node:process';
|
|
4
|
-
import { performance } from 'node:perf_hooks';
|
|
5
|
-
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import { Program, parseGoalText, run } from '../index.js';
|
|
7
|
-
|
|
8
|
-
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
9
|
-
|
|
10
|
-
function integer(value, name, minimum) {
|
|
11
|
-
const n = Number(value);
|
|
12
|
-
if (!Number.isInteger(n) || n < minimum) throw new Error(`${name} must be an integer >= ${minimum}`);
|
|
13
|
-
return n;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const options = { count: 50, runs: 5, warmup: 2, json: false };
|
|
17
|
-
for (let i = 2; i < process.argv.length; i++) {
|
|
18
|
-
const arg = process.argv[i];
|
|
19
|
-
if (arg === '--count') options.count = integer(process.argv[++i], '--count', 1);
|
|
20
|
-
else if (arg === '--runs') options.runs = integer(process.argv[++i], '--runs', 1);
|
|
21
|
-
else if (arg === '--warmup') options.warmup = integer(process.argv[++i], '--warmup', 0);
|
|
22
|
-
else if (arg === '--json') options.json = true;
|
|
23
|
-
else if (arg === '-h' || arg === '--help') {
|
|
24
|
-
process.stdout.write('Usage: node test/lips-benchmark.mjs [--count N] [--runs N] [--warmup N] [--json]\n');
|
|
25
|
-
process.exit(0);
|
|
26
|
-
} else throw new Error(`unknown option: ${arg}`);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const source = await fs.readFile(path.join(root, 'examples', 'bench.pl'), 'utf8');
|
|
30
|
-
const program = Program.parse(source);
|
|
31
|
-
const dobench = parseGoalText(`dobench(${options.count})`);
|
|
32
|
-
const dodummy = parseGoalText(`dodummy(${options.count})`);
|
|
33
|
-
|
|
34
|
-
function execute(goal) {
|
|
35
|
-
const cpuStart = process.cpuUsage();
|
|
36
|
-
const wallStart = performance.now();
|
|
37
|
-
const result = run(program, { goals: [goal] });
|
|
38
|
-
const wallMs = performance.now() - wallStart;
|
|
39
|
-
const cpu = process.cpuUsage(cpuStart);
|
|
40
|
-
if (result.haltCode != null) throw new Error(`benchmark halted with code ${result.haltCode}`);
|
|
41
|
-
if (!result.stdout) throw new Error('benchmark goal produced no success answer');
|
|
42
|
-
return { cpuMs: (cpu.user + cpu.system) / 1000, wallMs };
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
for (let i = 0; i < options.warmup; i++) {
|
|
46
|
-
execute(dodummy);
|
|
47
|
-
execute(dobench);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const samples = [];
|
|
51
|
-
for (let i = 0; i < options.runs; i++) {
|
|
52
|
-
const dummy = execute(dodummy);
|
|
53
|
-
const bench = execute(dobench);
|
|
54
|
-
const netCpuMs = bench.cpuMs - dummy.cpuMs;
|
|
55
|
-
const netWallMs = bench.wallMs - dummy.wallMs;
|
|
56
|
-
if (netCpuMs <= 0) throw new Error(`non-positive control-subtracted CPU time (${netCpuMs.toFixed(3)} ms); increase --count`);
|
|
57
|
-
samples.push({
|
|
58
|
-
dummyCpuMs: dummy.cpuMs,
|
|
59
|
-
benchCpuMs: bench.cpuMs,
|
|
60
|
-
netCpuMs,
|
|
61
|
-
netWallMs,
|
|
62
|
-
lips: 496 * options.count * 1000 / netCpuMs,
|
|
63
|
-
wallLips: netWallMs > 0 ? 496 * options.count * 1000 / netWallMs : null,
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function median(values) {
|
|
68
|
-
const sorted = [...values].sort((a, b) => a - b);
|
|
69
|
-
const middle = Math.floor(sorted.length / 2);
|
|
70
|
-
return sorted.length % 2 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const result = {
|
|
74
|
-
methodology: 'Quintus 1984 naive reverse; dobench minus dodummy; 496 calls/reversal',
|
|
75
|
-
count: options.count,
|
|
76
|
-
runs: options.runs,
|
|
77
|
-
warmup: options.warmup,
|
|
78
|
-
lips: median(samples.map((sample) => sample.lips)),
|
|
79
|
-
wallLips: median(samples.map((sample) => sample.wallLips).filter(Number.isFinite)),
|
|
80
|
-
netCpuMs: median(samples.map((sample) => sample.netCpuMs)),
|
|
81
|
-
samples,
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
if (options.json) {
|
|
85
|
-
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
86
|
-
} else {
|
|
87
|
-
process.stdout.write(`EyeProlog classic nrev: ${Math.round(result.lips).toLocaleString('en-US')} LIPS\n`);
|
|
88
|
-
process.stdout.write(` ${options.count} reversals/run, ${options.runs} measured runs, ${options.warmup} warmups\n`);
|
|
89
|
-
process.stdout.write(` median control-subtracted CPU: ${result.netCpuMs.toFixed(1)} ms\n`);
|
|
90
|
-
if (Number.isFinite(result.wallLips)) {
|
|
91
|
-
process.stdout.write(` wall-clock cross-check: ${Math.round(result.wallLips).toLocaleString('en-US')} LIPS\n`);
|
|
92
|
-
}
|
|
93
|
-
}
|