eyeprolog 1.2.11 → 1.2.12
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 +5 -0
- package/package.json +1 -1
- package/src/parser.js +21 -5
- package/src/quads.js +12 -11
- package/test/run-regression.mjs +51 -11
- package/the-art-of-eyeprolog.md +10 -6
package/README.md
CHANGED
|
@@ -79,6 +79,11 @@ member_test ?- member(X, [prolog, logic]).
|
|
|
79
79
|
; X = logic.
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
+
A label may contain several comma-separated metadata fields. When a query has
|
|
83
|
+
multiple indented answer descriptions, each description is checked and counted
|
|
84
|
+
independently, so one failed expectation does not prevent the later ones from
|
|
85
|
+
running.
|
|
86
|
+
|
|
82
87
|
## Strict ISO/IEC 13211-1 core
|
|
83
88
|
|
|
84
89
|
For portability and conformance work, run the Part 1 core with Technical
|
package/package.json
CHANGED
package/src/parser.js
CHANGED
|
@@ -795,12 +795,28 @@ class Parser {
|
|
|
795
795
|
continue;
|
|
796
796
|
}
|
|
797
797
|
let head = this.parseTerm(3);
|
|
798
|
-
// Both a quad label and a TS 13211-3 semicontext may contain
|
|
799
|
-
// unparenthesized
|
|
800
|
-
//
|
|
798
|
+
// Both a quad label and a TS 13211-3 semicontext may contain one or more
|
|
799
|
+
// unparenthesized commas before their priority-1200 operator. Parse the
|
|
800
|
+
// complete comma sequence here instead of stopping after one separator;
|
|
801
|
+
// portable quad labels commonly carry several metadata fields, e.g.
|
|
802
|
+
// `9, "case", passes ?- Goal.`. Build the standard right-associative
|
|
803
|
+
// comma term so this is the same label as `(9, "case", passes)`.
|
|
801
804
|
if (this.token.type === TOK.COMMA) {
|
|
802
|
-
|
|
803
|
-
|
|
805
|
+
const items = [head];
|
|
806
|
+
let extraCommaLine = null;
|
|
807
|
+
while (this.token.type === TOK.COMMA) {
|
|
808
|
+
if (items.length >= 2 && extraCommaLine == null) extraCommaLine = this.token.line;
|
|
809
|
+
this.advance();
|
|
810
|
+
items.push(this.parseTerm(3));
|
|
811
|
+
}
|
|
812
|
+
// Historically the program grammar admitted exactly one comma in a
|
|
813
|
+
// DCG semicontext. Keep that boundary: the broader comma sequence is
|
|
814
|
+
// specifically the quad-label extension, not a new DCG syntax.
|
|
815
|
+
if (extraCommaLine != null && this.operatorTokenName() !== '?-') {
|
|
816
|
+
throw new Error(`parse line ${extraCommaLine}: expected ., got ,`);
|
|
817
|
+
}
|
|
818
|
+
head = items.pop();
|
|
819
|
+
while (items.length > 0) head = compound(',', [items.pop(), head]);
|
|
804
820
|
}
|
|
805
821
|
if (this.operatorTokenName() === '?-') {
|
|
806
822
|
if (this.strictIso) {
|
package/src/quads.js
CHANGED
|
@@ -33,9 +33,14 @@ export function runQuads(source, options = {}) {
|
|
|
33
33
|
const results = [];
|
|
34
34
|
const lines = [];
|
|
35
35
|
for (const quad of quads) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
// Every indented answer description is an independent portable quad test.
|
|
37
|
+
// Re-run the query for each description so a failed expectation does not
|
|
38
|
+
// prevent later expectations for the same query from being checked.
|
|
39
|
+
for (const description of quad.answers) {
|
|
40
|
+
const result = checkQuadDescription(program, quad, description, options);
|
|
41
|
+
results.push(result);
|
|
42
|
+
if (!result.ok) lines.push(formatFailure(program, quad, result, description));
|
|
43
|
+
}
|
|
39
44
|
}
|
|
40
45
|
const passed = results.filter((result) => result.ok).length;
|
|
41
46
|
const failed = results.length - passed;
|
|
@@ -43,15 +48,11 @@ export function runQuads(source, options = {}) {
|
|
|
43
48
|
return { stdout: lines.join(''), total: results.length, passed, failed, results };
|
|
44
49
|
}
|
|
45
50
|
|
|
46
|
-
function
|
|
51
|
+
function checkQuadDescription(program, quad, description, options) {
|
|
47
52
|
if (quad.id != null && !termIsGround(quad.id, new Env())) {
|
|
48
53
|
return { ok: false, kind: 'bad_identifier', expected: quad.id };
|
|
49
54
|
}
|
|
50
|
-
|
|
51
|
-
const checked = checkDescription(program, quad, description, options);
|
|
52
|
-
if (!checked.ok) return checked;
|
|
53
|
-
}
|
|
54
|
-
return { ok: true };
|
|
55
|
+
return checkDescription(program, quad, description, options);
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
function checkDescription(program, quad, description, options) {
|
|
@@ -398,14 +399,14 @@ function splitOperator(term, name) {
|
|
|
398
399
|
return [term];
|
|
399
400
|
}
|
|
400
401
|
|
|
401
|
-
function formatFailure(program, quad, result) {
|
|
402
|
+
function formatFailure(program, quad, result, description = quad.answers[0]) {
|
|
402
403
|
const source = quad.source ?? { filename: '<input>', line: 1 };
|
|
403
404
|
const label = quad.id == null ? '' : `${formatQuadTerm(program, quad.id)}, `;
|
|
404
405
|
const reason = result.kind === 'malformed' ? 'MALFORMED'
|
|
405
406
|
: result.kind === 'bad_identifier' ? 'BAD_ID'
|
|
406
407
|
: result.kind === 'unsupported' ? 'UNSUPPORTED'
|
|
407
408
|
: 'FAILED';
|
|
408
|
-
const expected = result.expected ??
|
|
409
|
+
const expected = result.expected ?? description;
|
|
409
410
|
return `quads: ${reason} ${label}${source.filename}:${source.line}\n` +
|
|
410
411
|
` ?- ${formatQuadTerm(program, quad.query)}.\n` +
|
|
411
412
|
` expected: ${formatQuadTerm(program, expected)}.\n`;
|
package/test/run-regression.mjs
CHANGED
|
@@ -258,6 +258,46 @@ why(
|
|
|
258
258
|
assertEqual(strict.clauses.length, 1, 'strict functional ?-/2 remains an ordinary term');
|
|
259
259
|
},
|
|
260
260
|
},
|
|
261
|
+
{
|
|
262
|
+
name: 'quad labels accept multiple metadata fields and each answer description is independent',
|
|
263
|
+
run: () => {
|
|
264
|
+
const source = `9, "✳54·43", passes
|
|
265
|
+
` +
|
|
266
|
+
`?- X is 1+1.
|
|
267
|
+
` +
|
|
268
|
+
` X = 3, unexpected. % almost
|
|
269
|
+
` +
|
|
270
|
+
` X = 1, unexpected. % too low
|
|
271
|
+
` +
|
|
272
|
+
` X = 2.0, unexpected.
|
|
273
|
+
` +
|
|
274
|
+
`% and after checking PM:
|
|
275
|
+
` +
|
|
276
|
+
` X = 2.
|
|
277
|
+
`;
|
|
278
|
+
const program = Program.parseSources([{ text: source, filename: 'issue-21.pl' }]);
|
|
279
|
+
assertEqual(program.quads.length, 1, 'query group count');
|
|
280
|
+
assertEqual(program.quads[0].answers.length, 4, 'answer-description count');
|
|
281
|
+
assertEqual(program.quads[0].id.name, ',', 'outer label comma');
|
|
282
|
+
assertEqual(program.quads[0].id.args[1].name, ',', 'right-associated label comma');
|
|
283
|
+
const result = publicApi.runQuads(program);
|
|
284
|
+
assertEqual(result.total, 4, 'answer-description total');
|
|
285
|
+
assertEqual(result.passed, 4, 'answer-description passed');
|
|
286
|
+
assertEqual(result.failed, 0, 'answer-description failed');
|
|
287
|
+
assertEqual(result.stdout, 'quads: 4 run, 4 passed, 0 failed.\n', 'issue #21 report');
|
|
288
|
+
|
|
289
|
+
const continuing = publicApi.runQuads(
|
|
290
|
+
`case ?- X is 1+1.
|
|
291
|
+
X = 3.
|
|
292
|
+
X = 2.
|
|
293
|
+
`,
|
|
294
|
+
);
|
|
295
|
+
assertEqual(continuing.total, 2, 'later descriptions still run after failure');
|
|
296
|
+
assertEqual(continuing.passed, 1, 'later passing description counted');
|
|
297
|
+
assertEqual(continuing.failed, 1, 'failed description counted');
|
|
298
|
+
assertIncludes(continuing.stdout, 'quads: 2 run, 1 passed, 1 failed.', 'continuation summary');
|
|
299
|
+
},
|
|
300
|
+
},
|
|
261
301
|
{
|
|
262
302
|
name: 'runQuads checks portable answer descriptions',
|
|
263
303
|
run: () => {
|
|
@@ -275,10 +315,10 @@ why(
|
|
|
275
315
|
`?- X = 1.\n X = 2, unexpected.\n X = 1.\n\n` +
|
|
276
316
|
`?- catch(throw(ball), E, true).\n E = ball | error(system_error, ...).\n`;
|
|
277
317
|
const result = publicApi.runQuads(Program.parseSources([{ text: source, filename: 'quads.pl' }]));
|
|
278
|
-
assertEqual(result.total,
|
|
279
|
-
assertEqual(result.passed,
|
|
280
|
-
assertEqual(result.failed, 0, '
|
|
281
|
-
assertEqual(result.stdout, 'quads:
|
|
318
|
+
assertEqual(result.total, 13, 'answer-description total');
|
|
319
|
+
assertEqual(result.passed, 13, 'answer-description passed');
|
|
320
|
+
assertEqual(result.failed, 0, 'answer-description failed');
|
|
321
|
+
assertEqual(result.stdout, 'quads: 13 run, 13 passed, 0 failed.\n', 'quad report');
|
|
282
322
|
},
|
|
283
323
|
},
|
|
284
324
|
{
|
|
@@ -288,10 +328,10 @@ why(
|
|
|
288
328
|
` throw(g(_X)).\n` +
|
|
289
329
|
` throw(g(X)), unexpected.\n`;
|
|
290
330
|
const result = publicApi.runQuads(Program.parseSources([{ text: source, filename: 'throw-copy-quad.pl' }]));
|
|
291
|
-
assertEqual(result.total,
|
|
292
|
-
assertEqual(result.passed,
|
|
293
|
-
assertEqual(result.failed, 0, '
|
|
294
|
-
assertEqual(result.stdout, 'quads:
|
|
331
|
+
assertEqual(result.total, 2, 'answer-description total');
|
|
332
|
+
assertEqual(result.passed, 2, 'answer-description passed');
|
|
333
|
+
assertEqual(result.failed, 0, 'answer-description failed');
|
|
334
|
+
assertEqual(result.stdout, 'quads: 2 run, 2 passed, 0 failed.\n', 'quad report');
|
|
295
335
|
|
|
296
336
|
const forbiddenFresh = publicApi.runQuads(
|
|
297
337
|
`?- throw(g(X)).\n throw(g(_X)), unexpected.\n`,
|
|
@@ -396,9 +436,9 @@ c4 ?- call((!;1)).
|
|
|
396
436
|
text: source,
|
|
397
437
|
filename,
|
|
398
438
|
}]));
|
|
399
|
-
assertEqual(result.total,
|
|
400
|
-
assertEqual(result.passed,
|
|
401
|
-
assertEqual(result.stdout, 'quads:
|
|
439
|
+
assertEqual(result.total, 77, 'answer-description total');
|
|
440
|
+
assertEqual(result.passed, 77, 'answer-description passed');
|
|
441
|
+
assertEqual(result.stdout, 'quads: 77 run, 77 passed, 0 failed.\n', 'quad report');
|
|
402
442
|
},
|
|
403
443
|
},
|
|
404
444
|
{
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -6408,8 +6408,10 @@ descriptions; variables introduced only by a description are fresh. For example,
|
|
|
6408
6408
|
a query `throw(g(X))` is described by `throw(g(_X))`, while
|
|
6409
6409
|
`throw(g(X)), unexpected` verifies that ISO `throw/1` did not retain the query
|
|
6410
6410
|
variable in the renamed exception term. `...` and `ad_infinitum` accept further answers. Multiple
|
|
6411
|
-
indented descriptions after one query
|
|
6412
|
-
|
|
6411
|
+
indented descriptions after one query are independent checks: each re-runs the
|
|
6412
|
+
query, each is counted in the `quads:` summary, and a failing description does
|
|
6413
|
+
not suppress later descriptions for that query. `inputs/1` supplies and checks
|
|
6414
|
+
consumed characters; `outputs/1` checks emitted characters. `sto` marks
|
|
6413
6415
|
an answer description that this finite-tree implementation skips. `loops` is
|
|
6414
6416
|
checked with a deterministic solver-depth budget. The advanced stream
|
|
6415
6417
|
annotations `peeks/1` and `waits`, and the unordered `other_answer_sequence`
|
|
@@ -6428,10 +6430,12 @@ console.log(report.passed, report.failed, report.stdout);
|
|
|
6428
6430
|
The syntax follows the “queries using answer descriptions” convention used by
|
|
6429
6431
|
Trealla and the ISO Prolog working examples. Because answer descriptions are
|
|
6430
6432
|
layout-sensitive, indent every description while keeping ordinary clause heads
|
|
6431
|
-
and the next quad query at the left margin. A
|
|
6432
|
-
including across layout before
|
|
6433
|
-
|
|
6434
|
-
|
|
6433
|
+
and the next quad query at the left margin. A quad label may contain any number
|
|
6434
|
+
of comma-separated metadata fields, including across layout before `?-`; for
|
|
6435
|
+
example `9, "case", passes ?- Goal.` is one labelled query. Canonical
|
|
6436
|
+
functional notation is semantically equivalent: `?-(Label, Query).` followed by
|
|
6437
|
+
the same indented answer descriptions creates the same labelled quad as
|
|
6438
|
+
`Label ?- Query.`.
|
|
6435
6439
|
|
|
6436
6440
|
Statistics are comparative evidence, not a score in isolation. Preserve the
|
|
6437
6441
|
program, input, runtime version, selected query, answers, and counters together.
|