eyeprolog 1.2.11 → 1.2.13
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 +34 -10
- package/src/quads.js +12 -11
- package/test/run-regression.mjs +73 -14
- package/the-art-of-eyeprolog.md +18 -10
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
|
@@ -746,11 +746,11 @@ class Parser {
|
|
|
746
746
|
const accept = emit ?? ((clause) => clauses.push(clause));
|
|
747
747
|
while (this.token.type !== TOK.EOF) {
|
|
748
748
|
const line = this.token.line;
|
|
749
|
-
//
|
|
750
|
-
//
|
|
751
|
-
//
|
|
752
|
-
//
|
|
753
|
-
//
|
|
749
|
+
// Prefix operator notation needs one program-level distinction so
|
|
750
|
+
// a comma in `?- A, B.` remains inside the query rather than outside the
|
|
751
|
+
// prefix term. Ordinary functional notation is parsed as a term and then
|
|
752
|
+
// recognized structurally by parseQuadTerm; further equivalent spellings
|
|
753
|
+
// are recognized after the general head parser below.
|
|
754
754
|
if (this.operatorTokenName() === '?-' && !this.strictIso) {
|
|
755
755
|
if (this.peek() === '(') {
|
|
756
756
|
const quadTerm = this.parseTerm(0, true);
|
|
@@ -795,12 +795,36 @@ 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]);
|
|
820
|
+
}
|
|
821
|
+
// Parentheses and other ordinary term syntax may hide the surface ?-
|
|
822
|
+
// token from the program-level dispatch above. Once the complete head
|
|
823
|
+
// term has been parsed, recognize the same ?-/1 or ?-/2 structure here.
|
|
824
|
+
// Requiring the following dot prevents an ordinary rule whose head just
|
|
825
|
+
// happens to be ?-/1 or ?-/2 from being consumed as a quad mid-clause.
|
|
826
|
+
if (!this.strictIso && this.token.type === TOK.DOT && this.parseQuadTerm(head, line, accept)) {
|
|
827
|
+
continue;
|
|
804
828
|
}
|
|
805
829
|
if (this.operatorTokenName() === '?-') {
|
|
806
830
|
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
|
@@ -222,7 +222,7 @@ why(
|
|
|
222
222
|
},
|
|
223
223
|
},
|
|
224
224
|
{
|
|
225
|
-
name: 'quad parser treats
|
|
225
|
+
name: 'quad parser treats regular term spellings of ?- equivalently',
|
|
226
226
|
run: () => {
|
|
227
227
|
const labelled = Program.parse(
|
|
228
228
|
`0,passes
|
|
@@ -253,9 +253,68 @@ why(
|
|
|
253
253
|
const functionalReport = publicApi.runQuads(functional);
|
|
254
254
|
assertEqual(functionalReport.stdout, 'quads: 1 run, 1 passed, 0 failed.\n', 'functional quad report');
|
|
255
255
|
|
|
256
|
-
|
|
256
|
+
// Issue #11 is about ordinary term syntax, not one privileged
|
|
257
|
+
// canonical spelling. Parentheses, quoted functor syntax, and mixed
|
|
258
|
+
// operator/functional notation must all denote the same ?-/2 term and
|
|
259
|
+
// therefore the same quad in the normal EyeProlog profile.
|
|
260
|
+
for (const [name, source] of [
|
|
261
|
+
['mixed', `?-((0,passes), X = 1).\n X = 1.\n`],
|
|
262
|
+
['parenthesized', `(?-(','(0,passes),=(X,1))).\n X = 1.\n`],
|
|
263
|
+
['parenthesized mixed', `(?-((0,passes), X = 1)).\n X = 1.\n`],
|
|
264
|
+
['quoted functor', `'?-'(','(0,passes),=(X,1)).\n X = 1.\n`],
|
|
265
|
+
['quoted parenthesized', `('?-'(','(0,passes),=(X,1))).\n X = 1.\n`],
|
|
266
|
+
]) {
|
|
267
|
+
const regular = Program.parse(source);
|
|
268
|
+
assertEqual(regular.quads.length, 1, `${name} quad count`);
|
|
269
|
+
assertEqual(regular.clauses.length, 0, `${name} quad clause count`);
|
|
270
|
+
assertEqual(termToString(regular.quads[0].id), termToString(labelled.quads[0].id), `${name} label`);
|
|
271
|
+
assertEqual(termToString(regular.quads[0].query), termToString(labelled.quads[0].query), `${name} query`);
|
|
272
|
+
assertEqual(publicApi.runQuads(regular).stdout, 'quads: 1 run, 1 passed, 0 failed.\n', `${name} report`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const strict = Program.parse(`(?-(','(0,passes),=(X,1))).\n`, { isoStrict: true });
|
|
257
276
|
assertEqual(strict.quads.length, 0, 'strict mode has no quads');
|
|
258
|
-
assertEqual(strict.clauses.length, 1, 'strict
|
|
277
|
+
assertEqual(strict.clauses.length, 1, 'strict ?-/2 remains an ordinary term');
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
name: 'quad labels accept multiple metadata fields and each answer description is independent',
|
|
282
|
+
run: () => {
|
|
283
|
+
const source = `9, "✳54·43", passes
|
|
284
|
+
` +
|
|
285
|
+
`?- X is 1+1.
|
|
286
|
+
` +
|
|
287
|
+
` X = 3, unexpected. % almost
|
|
288
|
+
` +
|
|
289
|
+
` X = 1, unexpected. % too low
|
|
290
|
+
` +
|
|
291
|
+
` X = 2.0, unexpected.
|
|
292
|
+
` +
|
|
293
|
+
`% and after checking PM:
|
|
294
|
+
` +
|
|
295
|
+
` X = 2.
|
|
296
|
+
`;
|
|
297
|
+
const program = Program.parseSources([{ text: source, filename: 'issue-21.pl' }]);
|
|
298
|
+
assertEqual(program.quads.length, 1, 'query group count');
|
|
299
|
+
assertEqual(program.quads[0].answers.length, 4, 'answer-description count');
|
|
300
|
+
assertEqual(program.quads[0].id.name, ',', 'outer label comma');
|
|
301
|
+
assertEqual(program.quads[0].id.args[1].name, ',', 'right-associated label comma');
|
|
302
|
+
const result = publicApi.runQuads(program);
|
|
303
|
+
assertEqual(result.total, 4, 'answer-description total');
|
|
304
|
+
assertEqual(result.passed, 4, 'answer-description passed');
|
|
305
|
+
assertEqual(result.failed, 0, 'answer-description failed');
|
|
306
|
+
assertEqual(result.stdout, 'quads: 4 run, 4 passed, 0 failed.\n', 'issue #21 report');
|
|
307
|
+
|
|
308
|
+
const continuing = publicApi.runQuads(
|
|
309
|
+
`case ?- X is 1+1.
|
|
310
|
+
X = 3.
|
|
311
|
+
X = 2.
|
|
312
|
+
`,
|
|
313
|
+
);
|
|
314
|
+
assertEqual(continuing.total, 2, 'later descriptions still run after failure');
|
|
315
|
+
assertEqual(continuing.passed, 1, 'later passing description counted');
|
|
316
|
+
assertEqual(continuing.failed, 1, 'failed description counted');
|
|
317
|
+
assertIncludes(continuing.stdout, 'quads: 2 run, 1 passed, 1 failed.', 'continuation summary');
|
|
259
318
|
},
|
|
260
319
|
},
|
|
261
320
|
{
|
|
@@ -275,10 +334,10 @@ why(
|
|
|
275
334
|
`?- X = 1.\n X = 2, unexpected.\n X = 1.\n\n` +
|
|
276
335
|
`?- catch(throw(ball), E, true).\n E = ball | error(system_error, ...).\n`;
|
|
277
336
|
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:
|
|
337
|
+
assertEqual(result.total, 13, 'answer-description total');
|
|
338
|
+
assertEqual(result.passed, 13, 'answer-description passed');
|
|
339
|
+
assertEqual(result.failed, 0, 'answer-description failed');
|
|
340
|
+
assertEqual(result.stdout, 'quads: 13 run, 13 passed, 0 failed.\n', 'quad report');
|
|
282
341
|
},
|
|
283
342
|
},
|
|
284
343
|
{
|
|
@@ -288,10 +347,10 @@ why(
|
|
|
288
347
|
` throw(g(_X)).\n` +
|
|
289
348
|
` throw(g(X)), unexpected.\n`;
|
|
290
349
|
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:
|
|
350
|
+
assertEqual(result.total, 2, 'answer-description total');
|
|
351
|
+
assertEqual(result.passed, 2, 'answer-description passed');
|
|
352
|
+
assertEqual(result.failed, 0, 'answer-description failed');
|
|
353
|
+
assertEqual(result.stdout, 'quads: 2 run, 2 passed, 0 failed.\n', 'quad report');
|
|
295
354
|
|
|
296
355
|
const forbiddenFresh = publicApi.runQuads(
|
|
297
356
|
`?- throw(g(X)).\n throw(g(_X)), unexpected.\n`,
|
|
@@ -396,9 +455,9 @@ c4 ?- call((!;1)).
|
|
|
396
455
|
text: source,
|
|
397
456
|
filename,
|
|
398
457
|
}]));
|
|
399
|
-
assertEqual(result.total,
|
|
400
|
-
assertEqual(result.passed,
|
|
401
|
-
assertEqual(result.stdout, 'quads:
|
|
458
|
+
assertEqual(result.total, 77, 'answer-description total');
|
|
459
|
+
assertEqual(result.passed, 77, 'answer-description passed');
|
|
460
|
+
assertEqual(result.stdout, 'quads: 77 run, 77 passed, 0 failed.\n', 'quad report');
|
|
402
461
|
},
|
|
403
462
|
},
|
|
404
463
|
{
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -5156,10 +5156,12 @@ an optional label before the query marker (`Label ?- Query.`), so while quad
|
|
|
5156
5156
|
syntax is supported it additionally exposes `?-` at priority 1200 with
|
|
5157
5157
|
specifier `xfx` as an implementation-specific operator. Consequently
|
|
5158
5158
|
`current_op(Priority, Specifier, ?-)` enumerates both definitions. At top level in the normal EyeProlog profile, the quad marker is recognized
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5159
|
+
from the parsed `?-/1` or `?-/2` term rather than from one privileged surface
|
|
5160
|
+
spelling. Thus `Label ?- Query.`, `?-(Label, Query).`, mixed forms such as
|
|
5161
|
+
`?-((Label), Query).`, quoted-functor notation, and a parenthesized whole
|
|
5162
|
+
`(?-(Label, Query)).` denote the same quad when followed by indented answer
|
|
5163
|
+
descriptions. In `--iso-strict` mode this quad interpretation is disabled, and
|
|
5164
|
+
`?-/2` remains ordinary Prolog term syntax.
|
|
5163
5165
|
|
|
5164
5166
|
Run [`iso-dynamic-database.pl`](https://github.com/eyereasoner/eyeprolog/blob/main/examples/iso-dynamic-database.pl)
|
|
5165
5167
|
for an explicitly stateful queue and
|
|
@@ -6408,8 +6410,10 @@ descriptions; variables introduced only by a description are fresh. For example,
|
|
|
6408
6410
|
a query `throw(g(X))` is described by `throw(g(_X))`, while
|
|
6409
6411
|
`throw(g(X)), unexpected` verifies that ISO `throw/1` did not retain the query
|
|
6410
6412
|
variable in the renamed exception term. `...` and `ad_infinitum` accept further answers. Multiple
|
|
6411
|
-
indented descriptions after one query
|
|
6412
|
-
|
|
6413
|
+
indented descriptions after one query are independent checks: each re-runs the
|
|
6414
|
+
query, each is counted in the `quads:` summary, and a failing description does
|
|
6415
|
+
not suppress later descriptions for that query. `inputs/1` supplies and checks
|
|
6416
|
+
consumed characters; `outputs/1` checks emitted characters. `sto` marks
|
|
6413
6417
|
an answer description that this finite-tree implementation skips. `loops` is
|
|
6414
6418
|
checked with a deterministic solver-depth budget. The advanced stream
|
|
6415
6419
|
annotations `peeks/1` and `waits`, and the unordered `other_answer_sequence`
|
|
@@ -6428,10 +6432,14 @@ console.log(report.passed, report.failed, report.stdout);
|
|
|
6428
6432
|
The syntax follows the “queries using answer descriptions” convention used by
|
|
6429
6433
|
Trealla and the ISO Prolog working examples. Because answer descriptions are
|
|
6430
6434
|
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
|
-
|
|
6435
|
+
and the next quad query at the left margin. A quad label may contain any number
|
|
6436
|
+
of comma-separated metadata fields, including across layout before `?-`; for
|
|
6437
|
+
example `9, "case", passes ?- Goal.` is one labelled query. Quad recognition
|
|
6438
|
+
is structural after ordinary term parsing: functional, mixed, quoted-functor,
|
|
6439
|
+
and parenthesized spellings of the same `?-/1` or `?-/2` term are semantically
|
|
6440
|
+
equivalent. For example `?-(Label, Query).` and `(?-(Label, Query)).`, followed
|
|
6441
|
+
by the same indented answer descriptions, create the same labelled quad as
|
|
6442
|
+
`Label ?- Query.`.
|
|
6435
6443
|
|
|
6436
6444
|
Statistics are comparative evidence, not a score in isolation. Preserve the
|
|
6437
6445
|
program, input, runtime version, selected query, answers, and counters together.
|