eyeprolog 1.5.87 → 1.5.89
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/package.json +1 -1
- package/src/quads.js +58 -19
- package/test/regression/cases-regression.mjs +38 -1
package/package.json
CHANGED
package/src/quads.js
CHANGED
|
@@ -162,6 +162,21 @@ function checkAlternative(program, quad, alternative, options, context, unordere
|
|
|
162
162
|
// matchLeaf accepts it once this disproof gate has been cleared.
|
|
163
163
|
if (requiresSto && actual.nstoObserved) return { ok: false };
|
|
164
164
|
|
|
165
|
+
// The reverse of the check above: execution that actually triggered
|
|
166
|
+
// occurs-check produced an implementation-dependent outcome (ISO 7.3.3
|
|
167
|
+
// Note 2 -- succeed, loop, or fail are all conforming), even when none of
|
|
168
|
+
// this alternative's own leaves say so. A leaf making a positive claim
|
|
169
|
+
// about that outcome without an `sto` tag would silently misrepresent an
|
|
170
|
+
// implementation choice as a portably decided one (see issue #111's
|
|
171
|
+
// `\+ \+ -X=X` follow-up: a bare `false.` happens to match here, but only
|
|
172
|
+
// because this engine always occurs-checks -- a non-occurs-checking engine
|
|
173
|
+
// would see `true` instead). An `unexpected` leaf is a negative claim
|
|
174
|
+
// instead, and already has its own STO carve-out below
|
|
175
|
+
// (`stoPermitsUnexpected`, issue #60), so leave those alone here.
|
|
176
|
+
if (!requiresSto && actual.stoObserved && !leaves.some((leaf) => leaf.unexpected)) {
|
|
177
|
+
return { ok: false, alternative };
|
|
178
|
+
}
|
|
179
|
+
|
|
165
180
|
if (hasInputSpec) {
|
|
166
181
|
const leaf = leaves[0];
|
|
167
182
|
const exactConsumption = actual.inputPosition === input.length;
|
|
@@ -994,24 +1009,46 @@ const FAILURE_LABELS = {
|
|
|
994
1009
|
|
|
995
1010
|
// With several `|`-separated alternatives -- the norm for `sto`-annotated
|
|
996
1011
|
// queries -- a bare offending sub-term like `_B = [E|_B]` gives no sense of
|
|
997
|
-
// which alternative it came from
|
|
998
|
-
//
|
|
999
|
-
//
|
|
1000
|
-
//
|
|
1001
|
-
//
|
|
1002
|
-
//
|
|
1003
|
-
//
|
|
1012
|
+
// which alternative it came from. Show that whole alternative in place, laid
|
|
1013
|
+
// out the same way the answer-description syntax itself lays out alternatives
|
|
1014
|
+
// and leaves (a leading `|` per alternative after the first, a leading `;`
|
|
1015
|
+
// per leaf after an alternative's first), and stand in for sibling
|
|
1016
|
+
// alternatives with a bare `...` rather than reproducing them (they were not
|
|
1017
|
+
// the problem) or dropping them silently (a reader can no longer tell how
|
|
1018
|
+
// many alternatives, or which one, this was) -- see issue #112's follow-up
|
|
1019
|
+
// (https://github.com/eyereasoner/eyeprolog/issues/112#issuecomment-5645403025).
|
|
1020
|
+
// Comments are not retained past parsing, so unlike a hand-written answer
|
|
1021
|
+
// description this reconstruction never has any to show. Returns null when
|
|
1022
|
+
// the description has only one alternative to begin with: there is then
|
|
1023
|
+
// nothing to elide, and the existing plain rendering already shows the whole
|
|
1024
|
+
// thing.
|
|
1004
1025
|
function formatAlternativeContext(program, description, alternative) {
|
|
1005
1026
|
const parts = splitOperator(description, '|');
|
|
1006
1027
|
if (parts.length <= 1 || !parts.includes(alternative)) return null;
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
//
|
|
1011
|
-
//
|
|
1012
|
-
//
|
|
1013
|
-
|
|
1014
|
-
|
|
1028
|
+
// Every detail line under a `quads: ...` header shares one 3-space report
|
|
1029
|
+
// margin (the same margin the plain `?- ...` and `expected: ...` lines
|
|
1030
|
+
// already use) -- reproduce the answer-description syntax's own layout
|
|
1031
|
+
// (a bare 3-space indent for the first alternative, a leading `| `/`; `
|
|
1032
|
+
// marker for a later alternative/leaf) *inside* that margin, so a marker
|
|
1033
|
+
// character lines up under the `?` above it rather than at column 0.
|
|
1034
|
+
const margin = ' ';
|
|
1035
|
+
const lines = [];
|
|
1036
|
+
parts.forEach((part, index) => {
|
|
1037
|
+
const marker = index === 0 ? ' ' : '| ';
|
|
1038
|
+
if (part !== alternative) {
|
|
1039
|
+
lines.push(`${margin}${marker}...`);
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
splitOperator(part, ';').forEach((leaf, leafIndex) => {
|
|
1043
|
+
lines.push(`${margin}${leafIndex === 0 ? marker : '; '}${formatQuadTerm(program, leaf)}`);
|
|
1044
|
+
});
|
|
1045
|
+
});
|
|
1046
|
+
// A trailing `...` glued straight to a full stop reads as four dots; the
|
|
1047
|
+
// answer-description syntax itself always writes a space before the period
|
|
1048
|
+
// in that position (compare `..., ad_infinitum` with `... .`), so match it.
|
|
1049
|
+
const last = lines.length - 1;
|
|
1050
|
+
lines[last] = lines[last].endsWith('...') ? `${lines[last]} .` : `${lines[last]}.`;
|
|
1051
|
+
return lines.join('\n');
|
|
1015
1052
|
}
|
|
1016
1053
|
|
|
1017
1054
|
function formatFailure(program, quad, result, description = quad.answers[0]) {
|
|
@@ -1029,12 +1066,14 @@ function formatFailure(program, quad, result, description = quad.answers[0]) {
|
|
|
1029
1066
|
const detail = result.kind === 'undecided'
|
|
1030
1067
|
? ` undecided: ${result.reason}.\n`
|
|
1031
1068
|
// When the offending sub-term already *is* the whole alternative (an
|
|
1032
|
-
// `expected:` line would just repeat the context
|
|
1033
|
-
// alone is the full, non-redundant report.
|
|
1069
|
+
// `expected:` line would just repeat the context lines), the context
|
|
1070
|
+
// alone is the full, non-redundant report. formatAlternativeContext
|
|
1071
|
+
// already lays out and terminates its own lines, so it needs no further
|
|
1072
|
+
// wrapping here (unlike the plain single-line `expected:` case below).
|
|
1034
1073
|
: context != null && expected === result.alternative
|
|
1035
|
-
?
|
|
1074
|
+
? `${context}\n`
|
|
1036
1075
|
: context != null
|
|
1037
|
-
?
|
|
1076
|
+
? `${context}\n expected: ${formatQuadTerm(program, expected)}.\n`
|
|
1038
1077
|
: ` expected: ${formatQuadTerm(program, expected)}.\n`;
|
|
1039
1078
|
return `quads: ${reason} ${label}${source.filename}:${line}\n` +
|
|
1040
1079
|
` ?- ${formatQuadTerm(program, quad.query)}.\n` + detail;
|
|
@@ -2584,8 +2584,18 @@ c4 ?- call((!;1)).
|
|
|
2584
2584
|
const result = publicApi.runQuads(Program.parseSources([{ text: source, filename: 'select-quad.pl' }]));
|
|
2585
2585
|
assertEqual(result.total, 1, 'quad total');
|
|
2586
2586
|
assertEqual(result.failed, 1, 'quad failed');
|
|
2587
|
+
// Laid out the same way the answer-description syntax itself lays
|
|
2588
|
+
// out alternatives (a leading `|`) and leaves (a leading `;`), per
|
|
2589
|
+
// Ulrich's own proposed shape in that follow-up comment, inside the
|
|
2590
|
+
// same 3-space report margin every other detail line under a
|
|
2591
|
+
// `quads: ...` header uses -- so each `|`/`;` marker lines up under
|
|
2592
|
+
// the `?` of the `?- ...` line above it, not at column 0.
|
|
2587
2593
|
assertIncludes(result.stdout,
|
|
2588
|
-
'
|
|
2594
|
+
' ...\n' +
|
|
2595
|
+
' | sto, Xs = [E | Xs]\n' +
|
|
2596
|
+
' ; Xs = [_A | _B], Ys = [E | Ys]\n' +
|
|
2597
|
+
' ; ..., ad_infinitum\n' +
|
|
2598
|
+
' | ... .\n',
|
|
2589
2599
|
'sibling-elided alternative context');
|
|
2590
2600
|
assertIncludes(result.stdout, ' expected: Ys = [E | Ys].\n', 'still-precise offending sub-term');
|
|
2591
2601
|
},
|
|
@@ -2681,6 +2691,33 @@ c4 ?- call((!;1)).
|
|
|
2681
2691
|
assertIncludes(nsto.stdout, 'quads: FAILED 34, <input>:3', 'NSTO diagnostic');
|
|
2682
2692
|
},
|
|
2683
2693
|
},
|
|
2694
|
+
{
|
|
2695
|
+
name: 'quad sto is required, not merely tolerated, once execution observes occurs-check (issue #111 follow-up)',
|
|
2696
|
+
run: () => {
|
|
2697
|
+
// https://github.com/eyereasoner/eyeprolog/issues/111#issuecomment-5645863132:
|
|
2698
|
+
// `\+ \+ -X=X` fails only because this engine always occurs-checks
|
|
2699
|
+
// plain `=`/2 (ISO 7.3.3 Note 2 leaves succeed/loop/fail all
|
|
2700
|
+
// conforming); a non-occurs-checking engine sees `-X=X` succeed
|
|
2701
|
+
// (creating a cyclic X), so `\+ \+` sees `true` instead. A bare
|
|
2702
|
+
// `false.` -- with no `sto` tag -- misrepresents that as a portably
|
|
2703
|
+
// decided outcome, so it must be rejected even though it happens to
|
|
2704
|
+
// match this engine's own behavior; only the honestly `sto`-tagged
|
|
2705
|
+
// claim may pass.
|
|
2706
|
+
const bare = publicApi.runQuads('?- \\+ \\+ -X=X.\n false.\n');
|
|
2707
|
+
assertEqual(bare.passed, 0, 'undeclared sto claim rejected');
|
|
2708
|
+
assertEqual(bare.failed, 1, 'undeclared sto claim rejected');
|
|
2709
|
+
|
|
2710
|
+
const declared = publicApi.runQuads('?- \\+ \\+ -X=X.\n sto, false.\n');
|
|
2711
|
+
assertEqual(declared.passed, 1, 'declared sto claim accepted');
|
|
2712
|
+
assertEqual(declared.failed, 0, 'declared sto claim accepted');
|
|
2713
|
+
|
|
2714
|
+
// An `unexpected` leaf is a negative claim with its own, separate STO
|
|
2715
|
+
// carve-out (issue #60, tested above); this new rule must not touch it.
|
|
2716
|
+
const unexpected = publicApi.runQuads('?- \\+ \\+ -X=X.\n false, unexpected.\n');
|
|
2717
|
+
assertEqual(unexpected.passed, 0, 'wrong negative claim still rejected on its own terms');
|
|
2718
|
+
assertEqual(unexpected.failed, 1, 'wrong negative claim still rejected on its own terms');
|
|
2719
|
+
},
|
|
2720
|
+
},
|
|
2684
2721
|
{
|
|
2685
2722
|
name: 'quads peeks/1 supplies one unconsumed look-ahead character (issue #62)',
|
|
2686
2723
|
run: () => {
|