eyeprolog 1.1.19 → 1.1.20
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/conformance-report.md +2 -2
- package/package.json +1 -1
- package/src/iso.js +4 -2
- package/src/repl.js +19 -3
- package/test/conformance/ISO-MATRIX.md +1 -1
- package/test/conformance/README.md +1 -1
- package/test/conformance/cases/iso/throw_copies_ball.pl +17 -0
- package/test/conformance/expected/iso/throw_copies_ball.pl +2 -0
- package/test/run-regression.mjs +26 -0
- package/the-art-of-eyeprolog.md +9 -3
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 |
|
|
14
|
+
| iso | 165 | 209 | 0 | 0 | 374 |
|
|
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** | **
|
|
26
|
+
| **Total** | **480** | **260** | **19** | **21** | **780** |
|
package/package.json
CHANGED
package/src/iso.js
CHANGED
|
@@ -1814,7 +1814,7 @@ function* catchBuiltin({ solver, goal, env }) {
|
|
|
1814
1814
|
const ball = error instanceof ThrownTerm
|
|
1815
1815
|
? error.term
|
|
1816
1816
|
: error instanceof PrologError
|
|
1817
|
-
? formalErrorTerm(error)
|
|
1817
|
+
? freshCopy(formalErrorTerm(error), new Env())
|
|
1818
1818
|
: null;
|
|
1819
1819
|
if (ball == null) throw error;
|
|
1820
1820
|
const recovered = env.clone();
|
|
@@ -1827,7 +1827,9 @@ function* catchBuiltin({ solver, goal, env }) {
|
|
|
1827
1827
|
function* throwBuiltin({ goal, env }) {
|
|
1828
1828
|
const ball = deref(goal.args[0], env);
|
|
1829
1829
|
if (ball.type === VAR) throw new PrologError('instantiation_error');
|
|
1830
|
-
throw
|
|
1830
|
+
// ISO throw/1 copies the thrown term before control unwinds. Freshen
|
|
1831
|
+
// variables so the catcher cannot retain aliases to the protected goal.
|
|
1832
|
+
throw new ThrownTerm(freshCopy(ball, env));
|
|
1831
1833
|
}
|
|
1832
1834
|
function* onceBuiltin({ solver, goal, env }) {
|
|
1833
1835
|
const child = solver.cloneForInnerGoal(1);
|
package/src/repl.js
CHANGED
|
@@ -57,7 +57,7 @@ export async function runRepl(engine, options = {}) {
|
|
|
57
57
|
exitCode = error.code;
|
|
58
58
|
break;
|
|
59
59
|
}
|
|
60
|
-
output.write(` ${formatError(error)}\n`);
|
|
60
|
+
output.write(` ${formatError(engine, state, error)}\n`);
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
} catch (error) {
|
|
@@ -368,13 +368,15 @@ function queryVariables(goal) {
|
|
|
368
368
|
|
|
369
369
|
function formatAnswer(engine, state, variables, env) {
|
|
370
370
|
const bindings = [];
|
|
371
|
+
const queryVariableNames = new Set(variables.map((variable) => variable.name));
|
|
371
372
|
const names = new Map(variables.map((variable) => [variable.name, variable.name]));
|
|
372
373
|
let generated = 0;
|
|
373
374
|
|
|
374
375
|
for (const variable of variables) collectUnboundVariables(engine, variable, env, names, () => `_${letterName(generated++)}`);
|
|
375
376
|
for (const variable of variables) {
|
|
376
377
|
const value = engine.deref(variable, env);
|
|
377
|
-
if (value.type === 'var' &&
|
|
378
|
+
if (value.type === 'var' &&
|
|
379
|
+
(value.name === variable.name || !queryVariableNames.has(value.name))) continue;
|
|
378
380
|
bindings.push(`${variable.name} = ${engine.formatTermForWrite(value, env, {
|
|
379
381
|
quoted: true,
|
|
380
382
|
operators: [...state.program.operators.values()],
|
|
@@ -406,7 +408,21 @@ function letterName(index) {
|
|
|
406
408
|
return suffix === 0 ? letter : `${letter}${suffix}`;
|
|
407
409
|
}
|
|
408
410
|
|
|
409
|
-
function formatError(error) {
|
|
411
|
+
function formatError(engine, state, error) {
|
|
412
|
+
if (error?.name === 'PrologError') {
|
|
413
|
+
const env = new engine.Env();
|
|
414
|
+
const variableNames = new Map();
|
|
415
|
+
let generated = 0;
|
|
416
|
+
if (error.culprit != null) {
|
|
417
|
+
collectUnboundVariables(engine, error.culprit, env, variableNames, () => `_${letterName(generated++)}`);
|
|
418
|
+
}
|
|
419
|
+
const culprit = error.culprit == null ? '' : `, ${engine.formatTermForWrite(error.culprit, env, {
|
|
420
|
+
quoted: true,
|
|
421
|
+
operators: [...state.program.operators.values()],
|
|
422
|
+
variableNames,
|
|
423
|
+
})}`;
|
|
424
|
+
return `error(${error.formal}${culprit}).`;
|
|
425
|
+
}
|
|
410
426
|
const message = error?.message ?? String(error);
|
|
411
427
|
return message.endsWith('.') ? message : `${message}.`;
|
|
412
428
|
}
|
|
@@ -9,7 +9,7 @@ case totals.
|
|
|
9
9
|
| --- | --- | --- |
|
|
10
10
|
| Clause 6 lexical and term syntax | tokenizer, operator parser, lists, curly terms, quotes, numeric syntax, comments | `scryer_lexical_terms`, `lexical_and_curly_terms`, `double_quoted_lists`, `corrigendum1_double_quote_operator`, syntax error cases |
|
|
11
11
|
| Clause 7 term order and unification | finite-tree unification, identity, standard order, errors | `unification_control_information`, `swipl_occurs_check`, `term_modes_and_ordering`, `logtalk_compare_standard_order` |
|
|
12
|
-
| Clause 7 control and exceptions | call, cut, conjunction, disjunction, if-then-else, catch and throw | `cut_control`, `control_and_terms`, `exceptions_and_flags`, `corrigenda_catch_callability` |
|
|
12
|
+
| Clause 7 control and exceptions | call, cut, conjunction, disjunction, if-then-else, catch and throw | `cut_control`, `control_and_terms`, `exceptions_and_flags`, `corrigenda_catch_callability`, `throw_copies_ball` |
|
|
13
13
|
| 8.2-8.5 term predicates | unification, Corrigendum 2 tests, comparison, sorting, creation and decomposition | `corrigenda_term_predicates`, `corrigenda_sort_keysort`, `logtalk_arg_unification`, `logtalk_univ`, associated error cases |
|
|
14
14
|
| 8.6-8.7 arithmetic predicates | `is/2` and all six arithmetic comparisons | `arithmetic`, comparison cases, isolated evaluation errors |
|
|
15
15
|
| 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` |
|
|
@@ -94,7 +94,7 @@ Selected cases are adapted from the ISO and standard-core suites of Logtalk,
|
|
|
94
94
|
Scryer Prolog, Trealla Prolog, and SWI-Prolog. Their upstream identifiers and licenses
|
|
95
95
|
are recorded in [THIRD_PARTY.md](THIRD_PARTY.md).
|
|
96
96
|
|
|
97
|
-
The corpus has
|
|
97
|
+
The corpus has 374 cases in `iso/` and 780 file-based conformance cases in
|
|
98
98
|
total. The generated `conformance-report.md` is the authoritative source for
|
|
99
99
|
current category totals. Together with regression, documentation-sync, API,
|
|
100
100
|
example, and book-example checks, `npm test` is the release gate.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
% ISO 7.12.1: throw/1 copies the ball before searching for a catcher.
|
|
2
|
+
%% goal: user_throw_copy
|
|
3
|
+
%% goal: error_throw_copy
|
|
4
|
+
|
|
5
|
+
user_throw_copy :-
|
|
6
|
+
catch(throw(g(X)), g(V), true),
|
|
7
|
+
X = protected,
|
|
8
|
+
V = caught.
|
|
9
|
+
|
|
10
|
+
error_throw_copy :-
|
|
11
|
+
catch(
|
|
12
|
+
number_chars(_, [1, [], X|2]),
|
|
13
|
+
error(type_error(list, Culprit), _),
|
|
14
|
+
true
|
|
15
|
+
),
|
|
16
|
+
X = protected,
|
|
17
|
+
Culprit = [1, [], caught|2].
|
package/test/run-regression.mjs
CHANGED
|
@@ -496,6 +496,32 @@ c4 ?- call((!;1)).
|
|
|
496
496
|
assertEqual(result.stderr, '', 'stderr');
|
|
497
497
|
},
|
|
498
498
|
},
|
|
499
|
+
{
|
|
500
|
+
name: 'REPL hides aliases to fresh throw variables while preserving query aliases',
|
|
501
|
+
run: () => {
|
|
502
|
+
const result = runCli([], {
|
|
503
|
+
input: 'catch(throw(g(X)),g(V),true).\nX = Y.\nhalt.\n',
|
|
504
|
+
});
|
|
505
|
+
assertEqual(result.status, 0, 'exit status');
|
|
506
|
+
assertEqual(result.stdout, '?- true.\n?- X = Y.\n?- ', 'stdout');
|
|
507
|
+
assertEqual(result.stderr, '', 'stderr');
|
|
508
|
+
},
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
name: 'REPL freshens variables displayed in uncaught ISO errors',
|
|
512
|
+
run: () => {
|
|
513
|
+
const result = runCli([], {
|
|
514
|
+
input: 'number_chars(V,[1,[],X|2]).\nnumber_chars(V,[1,[],Xx|2]).\nhalt.\n',
|
|
515
|
+
});
|
|
516
|
+
assertEqual(result.status, 0, 'exit status');
|
|
517
|
+
assertEqual(result.stdout,
|
|
518
|
+
'?- error(type_error(list), [1, [], _A | 2]).\n' +
|
|
519
|
+
'?- error(type_error(list), [1, [], _A | 2]).\n' +
|
|
520
|
+
'?- ',
|
|
521
|
+
'stdout');
|
|
522
|
+
assertEqual(result.stderr, '', 'stderr');
|
|
523
|
+
},
|
|
524
|
+
},
|
|
499
525
|
{
|
|
500
526
|
name: 'REPL enumerates and stops answers like the Scryer top level',
|
|
501
527
|
run: () => {
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -5749,8 +5749,14 @@ instances whose message contains the corresponding Prolog error term.
|
|
|
5749
5749
|
| *syntax_error(number)*, *syntax_error(read_term)* | Lexical number conversion or streamed term parsing failed. |
|
|
5750
5750
|
|
|
5751
5751
|
`catch/3` converts a `PrologError` into a catchable
|
|
5752
|
-
`error(Formal,eyeprolog)` term.
|
|
5753
|
-
|
|
5752
|
+
`error(Formal,eyeprolog)` term. `throw/1` copies its ball before unwinding: bound
|
|
5753
|
+
parts are preserved, repeated variables remain shared within the copied ball,
|
|
5754
|
+
and unbound variables are fresh with respect to the protected goal and catcher.
|
|
5755
|
+
Catchable error terms follow the same variable-freshening rule. At the interactive
|
|
5756
|
+
top level, variables that occur in an uncaught ISO error are rendered with fresh
|
|
5757
|
+
answer names such as `_A` rather than reusing query-variable spellings such as
|
|
5758
|
+
`X` or `Xx`; this keeps the displayed error consistent with the copied exception
|
|
5759
|
+
term. An unmatched ball or error continues outward.
|
|
5754
5760
|
|
|
5755
5761
|
Streams belong to one solver run and are shared by nested calls, exceptions,
|
|
5756
5762
|
and solution collectors. `user_input` and `user_output` are always present.
|
|
@@ -6815,7 +6821,7 @@ node test/run-conformance-report.mjs
|
|
|
6815
6821
|
```
|
|
6816
6822
|
|
|
6817
6823
|
The complete suite must pass before release. The file-based conformance corpus
|
|
6818
|
-
contains
|
|
6824
|
+
contains 780 cases, including 374 focused ISO
|
|
6819
6825
|
cases derived from the success, failure, mode, and error behavior in
|
|
6820
6826
|
ISO/IEC 13211-1 clauses 7 and 8, Part 2 modules, and Part 3 grammar rules.
|
|
6821
6827
|
Separate exact-output suites check 189 normal
|