eyeprolog 1.2.10 → 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/conformance-report.md +2 -2
- package/package.json +1 -1
- package/src/iso.js +7 -0
- package/src/parser.js +21 -5
- package/src/quads.js +12 -11
- package/test/conformance/ISO-MATRIX.md +1 -1
- package/test/conformance/README.md +1 -1
- package/test/conformance/errors/iso/number_chars_parenthesized.pl +2 -0
- package/test/conformance/errors/iso/number_codes_parenthesized.pl +2 -0
- package/test/conformance/expected-errors/iso/number_chars_parenthesized.txt +1 -0
- package/test/conformance/expected-errors/iso/number_codes_parenthesized.txt +1 -0
- package/test/fixtures/number_chars_cont_quad.pl +3 -0
- package/test/run-regression.mjs +66 -11
- package/the-art-of-eyeprolog.md +16 -9
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/conformance-report.md
CHANGED
|
@@ -11,7 +11,7 @@ This report summarizes the file-based conformance corpus under `test/conformance
|
|
|
11
11
|
| builtins | 11 | 0 | 0 | 0 | 11 |
|
|
12
12
|
| context | 11 | 0 | 0 | 0 | 11 |
|
|
13
13
|
| control | 15 | 0 | 0 | 0 | 15 |
|
|
14
|
-
| iso | 167 |
|
|
14
|
+
| iso | 167 | 218 | 0 | 0 | 385 |
|
|
15
15
|
| lists | 52 | 3 | 0 | 0 | 55 |
|
|
16
16
|
| modules | 2 | 0 | 0 | 0 | 2 |
|
|
17
17
|
| negation | 8 | 0 | 19 | 0 | 27 |
|
|
@@ -23,4 +23,4 @@ This report summarizes the file-based conformance corpus under `test/conformance
|
|
|
23
23
|
| terms | 26 | 3 | 0 | 0 | 29 |
|
|
24
24
|
| unification | 18 | 0 | 0 | 0 | 18 |
|
|
25
25
|
| variables | 16 | 9 | 0 | 0 | 25 |
|
|
26
|
-
| **Total** | **482** | **
|
|
26
|
+
| **Total** | **482** | **269** | **19** | **21** | **791** |
|
package/package.json
CHANGED
package/src/iso.js
CHANGED
|
@@ -1564,6 +1564,13 @@ function parseIsoNumber(text) {
|
|
|
1564
1564
|
}
|
|
1565
1565
|
|
|
1566
1566
|
const numericText = `${sign}${text.slice(position)}`;
|
|
1567
|
+
// 8.16.7/8.16.8 parse the character sequence according to the syntax rules
|
|
1568
|
+
// for numbers and negative numbers (6.3.1.1/6.3.1.2), not as an arbitrary
|
|
1569
|
+
// term whose value happens to be numeric. Every such number starts with a
|
|
1570
|
+
// decimal digit after an optional negative sign, so parenthesized terms such
|
|
1571
|
+
// as `(0)` or `-(0)` must be rejected before the general term parser sees
|
|
1572
|
+
// them. This keeps the parser reuse below from admitting grouping syntax.
|
|
1573
|
+
if (!/^-?\d/.test(numericText)) return null;
|
|
1567
1574
|
// ISO floating-point syntax requires a decimal fraction before an exponent.
|
|
1568
1575
|
if (/^-?\d+[eE][+-]?\d+$/.test(numericText)) return null;
|
|
1569
1576
|
try {
|
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`;
|
|
@@ -16,7 +16,7 @@ compliance audit and the remaining work before a full conformance claim.
|
|
|
16
16
|
| 8.8-8.10 database and solutions | logical update view, dynamic mutation, all-solutions grouping | `dynamic_database`, `trealla_logical_update_view`, `corrigenda_retractall`, `grouped_solutions_and_clauses` |
|
|
17
17
|
| 8.11-8.14 streams and term I/O | text/binary streams, properties, units, read/write options and operators | `streams_and_term_io`, `operators`, Corrigendum 3 option cases, stream error cases |
|
|
18
18
|
| 8.15 logic and control | negation, once, repeat, `call/2` through `call/8`, `false/0` | `logtalk_once`, `corrigenda_call_closure`, `false_builtin` |
|
|
19
|
-
| 8.16 atomic processing | atoms, characters, codes and number conversion with prescribed errors | `atomic_term_processing`, focused forward/reverse cases, Logtalk-derived error cases |
|
|
19
|
+
| 8.16 atomic processing | atoms, characters, codes and number conversion with prescribed errors | `atomic_term_processing`, focused forward/reverse cases, parenthesized-number rejection, Logtalk-derived error cases |
|
|
20
20
|
| 8.17 flags and hooks | required flags, mutation permissions, halt and character conversion | `exceptions_and_flags`, `remaining_builtins_and_directives`, flag error cases |
|
|
21
21
|
| Clause 9 evaluable functors | integer, float, rounding, transcendental and bitwise operations | `arithmetic`, `corrigenda_arithmetic`, `corrigenda_atan2_zero`, `corrigenda_integer_negative_power` |
|
|
22
22
|
| ISO/IEC 13211-2 modules | module declarations, exports, imports, qualification, meta-predicate context | `modules/qualified_call`, `modules/selective_library_import`, `dcg_module_nonterminal_indicator` |
|
|
@@ -102,7 +102,7 @@ Selected cases are adapted from the ISO and standard-core suites of Logtalk,
|
|
|
102
102
|
Scryer Prolog, Trealla Prolog, and SWI-Prolog. Their upstream identifiers and licenses
|
|
103
103
|
are recorded in [THIRD_PARTY.md](THIRD_PARTY.md).
|
|
104
104
|
|
|
105
|
-
The corpus has
|
|
105
|
+
The corpus has 385 cases in `iso/` and 791 file-based conformance cases in
|
|
106
106
|
total. The generated `conformance-report.md` is the authoritative source for
|
|
107
107
|
current category totals. Together with regression, documentation-sync, API,
|
|
108
108
|
example, and book-example checks, `npm test` is the release gate.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
error(syntax_error(number))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
error(syntax_error(number))
|
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,24 @@ 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');
|
|
442
|
+
},
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
name: 'number conversion rejects parenthesized numeric terms',
|
|
446
|
+
run: () => {
|
|
447
|
+
for (const goal of ['number_chars(N,"(0)")', 'number_codes(N,[40,48,41])']) {
|
|
448
|
+
let caught = null;
|
|
449
|
+
try {
|
|
450
|
+
publicApi.run('', { goal });
|
|
451
|
+
} catch (error) {
|
|
452
|
+
caught = error;
|
|
453
|
+
}
|
|
454
|
+
if (caught == null) throw new Error(`${goal} should throw`);
|
|
455
|
+
assertIncludes(String(caught?.message ?? caught), 'syntax_error(number)', goal);
|
|
456
|
+
}
|
|
402
457
|
},
|
|
403
458
|
},
|
|
404
459
|
{
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -5735,13 +5735,16 @@ quoted_atom("ab"). % quoted_atom(ab)
|
|
|
5735
5735
|
| `sub_atom(+Atom,?Before,?Length,?After,?SubAtom)` | Enumerates substrings and their Unicode-code-point offsets. Supplied counts must be nonnegative integers. |
|
|
5736
5736
|
| `atom_chars(?Atom,?Chars)`, `atom_codes(?Atom,?Codes)` | Convert between an atom and a proper list of one-character atoms or Unicode scalar codes. At least one side must be instantiated. |
|
|
5737
5737
|
| `char_code(?Character,?Code)` | Converts one character atom and one Unicode scalar code. Surrogates and values outside `0..0x10ffff` raise a representation error. |
|
|
5738
|
-
| `number_chars(?Number,?Chars)`, `number_codes(?Number,?Codes)` | Convert finite numbers to canonical text or parse a proper character/code list using ISO
|
|
5738
|
+
| `number_chars(?Number,?Chars)`, `number_codes(?Number,?Codes)` | Convert finite numbers to canonical text or parse a proper character/code list using ISO number and negative-number syntax, including radix integers, character-code constants, and leading layout. The input is not parsed as a general term: grouping such as `(0)` is a syntax error. At least one side must be instantiated; malformed numeric input raises *syntax_error(number)*. |
|
|
5739
5739
|
|
|
5740
5740
|
Conversions accept partial output lists when the atomic input is known, but
|
|
5741
5741
|
constructing an atom or number requires a complete proper list with no unbound
|
|
5742
5742
|
elements. Numeric parsing accepts leading ISO layout characters, an optional
|
|
5743
5743
|
sign, decimal fractions, and decimal exponents; it rejects trailing material
|
|
5744
|
-
and non-finite values.
|
|
5744
|
+
and non-finite values. The regression gate vendors all 73 numbered cases from
|
|
5745
|
+
Ulrich Neumerkel's contemporary `number_chars/2` comparison, including the
|
|
5746
|
+
Cor.2 error-precedence cases; `number_codes/2` shares the same numeric parser
|
|
5747
|
+
and has mirrored coverage for the parenthesized-number regression.
|
|
5745
5748
|
|
|
5746
5749
|
### Streams and unit I/O
|
|
5747
5750
|
|
|
@@ -6405,8 +6408,10 @@ descriptions; variables introduced only by a description are fresh. For example,
|
|
|
6405
6408
|
a query `throw(g(X))` is described by `throw(g(_X))`, while
|
|
6406
6409
|
`throw(g(X)), unexpected` verifies that ISO `throw/1` did not retain the query
|
|
6407
6410
|
variable in the renamed exception term. `...` and `ad_infinitum` accept further answers. Multiple
|
|
6408
|
-
indented descriptions after one query
|
|
6409
|
-
|
|
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
|
|
6410
6415
|
an answer description that this finite-tree implementation skips. `loops` is
|
|
6411
6416
|
checked with a deterministic solver-depth budget. The advanced stream
|
|
6412
6417
|
annotations `peeks/1` and `waits`, and the unordered `other_answer_sequence`
|
|
@@ -6425,10 +6430,12 @@ console.log(report.passed, report.failed, report.stdout);
|
|
|
6425
6430
|
The syntax follows the “queries using answer descriptions” convention used by
|
|
6426
6431
|
Trealla and the ISO Prolog working examples. Because answer descriptions are
|
|
6427
6432
|
layout-sensitive, indent every description while keeping ordinary clause heads
|
|
6428
|
-
and the next quad query at the left margin. A
|
|
6429
|
-
including across layout before
|
|
6430
|
-
|
|
6431
|
-
|
|
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.`.
|
|
6432
6439
|
|
|
6433
6440
|
Statistics are comparative evidence, not a score in isolation. Preserve the
|
|
6434
6441
|
program, input, runtime version, selected query, answers, and counters together.
|
|
@@ -6986,7 +6993,7 @@ precedence still need one-by-one closure. `test/conformance/ISO-MATRIX.md`
|
|
|
6986
6993
|
maps language families to representative executable cases.
|
|
6987
6994
|
|
|
6988
6995
|
The complete suite must pass before release. The file-based conformance corpus
|
|
6989
|
-
contains
|
|
6996
|
+
contains 791 cases, including 385 focused ISO
|
|
6990
6997
|
cases derived from the success, failure, mode, and error behavior in
|
|
6991
6998
|
ISO/IEC 13211-1 clauses 7 and 8, Part 2 modules, and Part 3 grammar rules.
|
|
6992
6999
|
Separate exact-output suites check 189 normal
|