eyeprolog 1.5.86 → 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/package.json +1 -1
- package/src/quads.js +44 -6
- package/test/regression/cases-regression.mjs +36 -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,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
|
}
|
|
@@ -2552,6 +2552,42 @@ 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
|
+
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');
|
|
2555
2591
|
},
|
|
2556
2592
|
},
|
|
2557
2593
|
{
|