eyeprolog 1.5.86 → 1.5.88
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 +61 -6
- package/test/regression/cases-regression.mjs +43 -0
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,43 @@ 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. Show that whole alternative in place, laid
|
|
998
|
+
// out the same way the answer-description syntax itself lays out alternatives
|
|
999
|
+
// and leaves (a leading `|` per alternative after the first, a leading `;`
|
|
1000
|
+
// per leaf after an alternative's first), and stand in for sibling
|
|
1001
|
+
// alternatives with a bare `...` rather than reproducing them (they were not
|
|
1002
|
+
// the problem) or dropping them silently (a reader can no longer tell how
|
|
1003
|
+
// many alternatives, or which one, this was) -- see issue #112's follow-up
|
|
1004
|
+
// (https://github.com/eyereasoner/eyeprolog/issues/112#issuecomment-5645403025).
|
|
1005
|
+
// Comments are not retained past parsing, so unlike a hand-written answer
|
|
1006
|
+
// description this reconstruction never has any to show. Returns null when
|
|
1007
|
+
// the description has only one alternative to begin with: there is then
|
|
1008
|
+
// nothing to elide, and the existing plain rendering already shows the whole
|
|
1009
|
+
// thing.
|
|
1010
|
+
function formatAlternativeContext(program, description, alternative) {
|
|
1011
|
+
const parts = splitOperator(description, '|');
|
|
1012
|
+
if (parts.length <= 1 || !parts.includes(alternative)) return null;
|
|
1013
|
+
const lines = [];
|
|
1014
|
+
parts.forEach((part, index) => {
|
|
1015
|
+
const prefix = index === 0 ? ' ' : '| ';
|
|
1016
|
+
if (part !== alternative) {
|
|
1017
|
+
lines.push(`${prefix}...`);
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
splitOperator(part, ';').forEach((leaf, leafIndex) => {
|
|
1021
|
+
lines.push(`${leafIndex === 0 ? prefix : '; '}${formatQuadTerm(program, leaf)}`);
|
|
1022
|
+
});
|
|
1023
|
+
});
|
|
1024
|
+
// A trailing `...` glued straight to a full stop reads as four dots; the
|
|
1025
|
+
// answer-description syntax itself always writes a space before the period
|
|
1026
|
+
// in that position (compare `..., ad_infinitum` with `... .`), so match it.
|
|
1027
|
+
const last = lines.length - 1;
|
|
1028
|
+
lines[last] = lines[last].endsWith('...') ? `${lines[last]} .` : `${lines[last]}.`;
|
|
1029
|
+
return lines.join('\n');
|
|
1030
|
+
}
|
|
1031
|
+
|
|
989
1032
|
function formatFailure(program, quad, result, description = quad.answers[0]) {
|
|
990
1033
|
const source = quad.source ?? { filename: '<input>', line: 1 };
|
|
991
1034
|
// Point at the failing answer description's own line rather than always the
|
|
@@ -995,9 +1038,21 @@ function formatFailure(program, quad, result, description = quad.answers[0]) {
|
|
|
995
1038
|
const label = quad.id == null ? '' : `${formatQuadTerm(program, quad.id)}, `;
|
|
996
1039
|
const reason = FAILURE_LABELS[result.kind] ?? 'FAILED';
|
|
997
1040
|
const expected = result.expected ?? description;
|
|
1041
|
+
const context = result.alternative != null
|
|
1042
|
+
? formatAlternativeContext(program, description, result.alternative)
|
|
1043
|
+
: null;
|
|
998
1044
|
const detail = result.kind === 'undecided'
|
|
999
1045
|
? ` undecided: ${result.reason}.\n`
|
|
1000
|
-
|
|
1046
|
+
// When the offending sub-term already *is* the whole alternative (an
|
|
1047
|
+
// `expected:` line would just repeat the context lines), the context
|
|
1048
|
+
// alone is the full, non-redundant report. formatAlternativeContext
|
|
1049
|
+
// already lays out and terminates its own lines, so it needs no further
|
|
1050
|
+
// wrapping here (unlike the plain single-line `expected:` case below).
|
|
1051
|
+
: context != null && expected === result.alternative
|
|
1052
|
+
? `${context}\n`
|
|
1053
|
+
: context != null
|
|
1054
|
+
? `${context}\n expected: ${formatQuadTerm(program, expected)}.\n`
|
|
1055
|
+
: ` expected: ${formatQuadTerm(program, expected)}.\n`;
|
|
1001
1056
|
return `quads: ${reason} ${label}${source.filename}:${line}\n` +
|
|
1002
1057
|
` ?- ${formatQuadTerm(program, quad.query)}.\n` + detail;
|
|
1003
1058
|
}
|
|
@@ -2552,6 +2552,49 @@ c4 ?- call((!;1)).
|
|
|
2552
2552
|
assertEqual(result.total, 1, 'quad total');
|
|
2553
2553
|
assertEqual(result.failed, 1, 'quad failed');
|
|
2554
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
|
+
// 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.
|
|
2590
|
+
assertIncludes(result.stdout,
|
|
2591
|
+
' ...\n' +
|
|
2592
|
+
'| sto, Xs = [E | Xs]\n' +
|
|
2593
|
+
'; Xs = [_A | _B], Ys = [E | Ys]\n' +
|
|
2594
|
+
'; ..., ad_infinitum\n' +
|
|
2595
|
+
'| ... .\n',
|
|
2596
|
+
'sibling-elided alternative context');
|
|
2597
|
+
assertIncludes(result.stdout, ' expected: Ys = [E | Ys].\n', 'still-precise offending sub-term');
|
|
2555
2598
|
},
|
|
2556
2599
|
},
|
|
2557
2600
|
{
|