eyeprolog 1.5.58 → 1.5.60
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/errors.js +8 -2
- package/src/iso.js +10 -1
- package/src/lib/error.pl +35 -22
- package/src/lib/random.pl +21 -13
- package/src/repl.js +13 -1
- package/test/conformance/ISO-IMPLEMENTATION-DEFINED.md +1 -1
- package/test/conformance/expected/iso/exceptions_and_flags.pl +2 -2
- package/test/regression/cases-regression.mjs +79 -9
- package/test/run-iso-strict.mjs +11 -11
package/package.json
CHANGED
package/src/errors.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Keep these independent of the ISO builtin registry so syntax, DCG, program,
|
|
3
3
|
// and solver layers can report Prolog errors without importing the whole ISO
|
|
4
4
|
// implementation (and without creating semantic-layer import cycles).
|
|
5
|
-
import { atom, compound, numberTerm, termToString } from './term.js';
|
|
5
|
+
import { atom, compound, emptyList, numberTerm, termToString } from './term.js';
|
|
6
6
|
|
|
7
7
|
export class PrologError extends Error {
|
|
8
8
|
constructor(formal, culprit = null) {
|
|
@@ -30,7 +30,13 @@ export class HaltSignal extends Error {
|
|
|
30
30
|
function builtinErrorContext(def, goal) {
|
|
31
31
|
let context = def._errorContextTerm;
|
|
32
32
|
if (context === undefined) {
|
|
33
|
-
|
|
33
|
+
// A one-element list, not a bare indicator: error contexts are lists so
|
|
34
|
+
// that library(error)'s call_with_error_context/2 can prepend its own
|
|
35
|
+
// elements and still yield a proper list (issue #98).
|
|
36
|
+
context = compound('.', [
|
|
37
|
+
compound('/', [atom(goal.name), numberTerm(goal.arity)]),
|
|
38
|
+
emptyList(),
|
|
39
|
+
]);
|
|
34
40
|
def._errorContextTerm = context;
|
|
35
41
|
}
|
|
36
42
|
return context;
|
package/src/iso.js
CHANGED
|
@@ -3091,7 +3091,11 @@ function* phraseSolutions({ solver, goal, env }, state) {
|
|
|
3091
3091
|
solver.trimInnerTableScope('phrase');
|
|
3092
3092
|
}
|
|
3093
3093
|
}
|
|
3094
|
-
|
|
3094
|
+
// ISO 7.12.2 leaves this implementation defined. EyeProlog uses a list of
|
|
3095
|
+
// context elements so that contexts compose: the raising built-in contributes
|
|
3096
|
+
// its predicate indicator and each enclosing call_with_error_context/2
|
|
3097
|
+
// prepends its own element, always yielding a proper list (issue #98).
|
|
3098
|
+
const defaultErrorContext = emptyList();
|
|
3095
3099
|
|
|
3096
3100
|
function parseFormalErrorTerm(text) {
|
|
3097
3101
|
const open = text.indexOf('(');
|
|
@@ -3215,6 +3219,11 @@ function* catchSolutions({ solver, goal, env }, state) {
|
|
|
3215
3219
|
if (child != null) solver.absorbStatsFrom(child);
|
|
3216
3220
|
}
|
|
3217
3221
|
}
|
|
3222
|
+
// library(error) call_with_error_context/2, as a primitive rather than a
|
|
3223
|
+
// Prolog catch/3 wrapper (issue #98). Context elements are assembled only while
|
|
3224
|
+
// an error unwinds, so the success path pays for neither a clause resolution
|
|
3225
|
+
// nor a Prolog-level catch frame. The element is copied as it is added, so
|
|
3226
|
+
// variables in it cannot alias bindings that the unwinding undoes.
|
|
3218
3227
|
function* throwBuiltin({ goal, env }) {
|
|
3219
3228
|
const ball = deref(goal.args[0], env);
|
|
3220
3229
|
if (ball.type === VAR) throw new PrologError('instantiation_error');
|
package/src/lib/error.pl
CHANGED
|
@@ -17,56 +17,62 @@
|
|
|
17
17
|
|
|
18
18
|
:- meta_predicate(call_with_error_context(0, +)).
|
|
19
19
|
|
|
20
|
+
% The context is supplied once here rather than handed over manually at every
|
|
21
|
+
% raise site: the raise sites throw with [] and this wrapper prepends its
|
|
22
|
+
% element, so contexts stay proper lists and compose with any enclosing
|
|
23
|
+
% call_with_error_context/2 (issue #98).
|
|
20
24
|
must_be(Type, Term) :-
|
|
21
|
-
(
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
call_with_error_context(
|
|
26
|
+
( var(Type) -> instantiation_error
|
|
27
|
+
; error__must_be(Type, Term)
|
|
28
|
+
),
|
|
29
|
+
must_be/2).
|
|
24
30
|
|
|
25
31
|
error__must_be(integer, Term) :- !,
|
|
26
|
-
( var(Term) -> instantiation_error
|
|
32
|
+
( var(Term) -> instantiation_error
|
|
27
33
|
; integer(Term) -> true
|
|
28
|
-
; type_error(integer, Term
|
|
34
|
+
; type_error(integer, Term)
|
|
29
35
|
).
|
|
30
36
|
error__must_be(atom, Term) :- !,
|
|
31
|
-
( var(Term) -> instantiation_error
|
|
37
|
+
( var(Term) -> instantiation_error
|
|
32
38
|
; atom(Term) -> true
|
|
33
|
-
; type_error(atom, Term
|
|
39
|
+
; type_error(atom, Term)
|
|
34
40
|
).
|
|
35
41
|
error__must_be(number, Term) :- !,
|
|
36
|
-
( var(Term) -> instantiation_error
|
|
42
|
+
( var(Term) -> instantiation_error
|
|
37
43
|
; number(Term) -> true
|
|
38
|
-
; type_error(number, Term
|
|
44
|
+
; type_error(number, Term)
|
|
39
45
|
).
|
|
40
46
|
error__must_be(var, Term) :- !,
|
|
41
47
|
( var(Term) -> true
|
|
42
|
-
; throw(error(uninstantiation_error(Term),
|
|
48
|
+
; throw(error(uninstantiation_error(Term), []))
|
|
43
49
|
).
|
|
44
50
|
error__must_be(ground, Term) :- !,
|
|
45
|
-
( ground(Term) -> true ; instantiation_error
|
|
51
|
+
( ground(Term) -> true ; instantiation_error ).
|
|
46
52
|
error__must_be(acyclic, Term) :- !,
|
|
47
|
-
( acyclic_term(Term) -> true ; type_error(acyclic_term, Term
|
|
53
|
+
( acyclic_term(Term) -> true ; type_error(acyclic_term, Term) ).
|
|
48
54
|
error__must_be(list, Term) :- !,
|
|
49
55
|
error__proper_list(Term).
|
|
50
56
|
error__must_be(list(Type), Term) :- !,
|
|
51
57
|
error__proper_list_of(Term, Type).
|
|
52
58
|
error__must_be(pair, Term) :- !,
|
|
53
|
-
( var(Term) -> instantiation_error
|
|
59
|
+
( var(Term) -> instantiation_error
|
|
54
60
|
; Term = _-_ -> true
|
|
55
|
-
; type_error(pair, Term
|
|
61
|
+
; type_error(pair, Term)
|
|
56
62
|
).
|
|
57
63
|
error__must_be(not_less_than_zero, Term) :- !,
|
|
58
64
|
must_be(integer, Term),
|
|
59
|
-
( Term >= 0 -> true ; domain_error(not_less_than_zero, Term
|
|
65
|
+
( Term >= 0 -> true ; domain_error(not_less_than_zero, Term) ).
|
|
60
66
|
error__must_be(Type, Term) :-
|
|
61
|
-
( var(Term) -> instantiation_error
|
|
62
|
-
; type_error(Type, Term
|
|
67
|
+
( var(Term) -> instantiation_error
|
|
68
|
+
; type_error(Type, Term)
|
|
63
69
|
).
|
|
64
70
|
|
|
65
71
|
error__proper_list([]) :- !.
|
|
66
72
|
error__proper_list([_|Tail]) :- !, error__proper_list(Tail).
|
|
67
73
|
error__proper_list(Term) :-
|
|
68
|
-
( var(Term) -> instantiation_error
|
|
69
|
-
; type_error(list, Term
|
|
74
|
+
( var(Term) -> instantiation_error
|
|
75
|
+
; type_error(list, Term)
|
|
70
76
|
).
|
|
71
77
|
|
|
72
78
|
error__proper_list_of([], _) :- !.
|
|
@@ -74,8 +80,8 @@ error__proper_list_of([Head|Tail], Type) :- !,
|
|
|
74
80
|
must_be(Type, Head),
|
|
75
81
|
error__proper_list_of(Tail, Type).
|
|
76
82
|
error__proper_list_of(Term, _) :-
|
|
77
|
-
( var(Term) -> instantiation_error
|
|
78
|
-
; type_error(list, Term
|
|
83
|
+
( var(Term) -> instantiation_error
|
|
84
|
+
; type_error(list, Term)
|
|
79
85
|
).
|
|
80
86
|
|
|
81
87
|
can_be(Type, Term) :-
|
|
@@ -121,5 +127,12 @@ representation_error(Flag) :- throw(error(representation_error(Flag), [])).
|
|
|
121
127
|
resource_error(Resource) :- throw(error(resource_error(Resource), [])).
|
|
122
128
|
resource_error(Resource, Context) :- throw(error(resource_error(Resource), Context)).
|
|
123
129
|
|
|
130
|
+
% Context elements are assembled only when an error actually propagates, so
|
|
131
|
+
% the prepend costs nothing on success. The enclosing catch/3 frame is not
|
|
132
|
+
% free, though: see issue #98 for measurements and the primitive design that
|
|
133
|
+
% would remove it.
|
|
124
134
|
call_with_error_context(Goal, Pair) :-
|
|
125
|
-
catch(Goal,
|
|
135
|
+
catch(Goal,
|
|
136
|
+
error(Error, Context),
|
|
137
|
+
( copy_term(Pair, Element),
|
|
138
|
+
throw(error(Error, [Element|Context])) )).
|
package/src/lib/random.pl
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
:- module(random, [maybe/0, maybe/1, maybe/2, random/1, random/3, random_integer/3, set_random/1]).
|
|
11
11
|
|
|
12
12
|
:- use_module(library(iso_ext), [bb_get/2, bb_put/2]).
|
|
13
|
-
:- use_module(library(error), [instantiation_error/
|
|
13
|
+
:- use_module(library(error), [call_with_error_context/2, instantiation_error/0, type_error/2]).
|
|
14
14
|
|
|
15
15
|
maybe :-
|
|
16
16
|
random_integer(0, 2, 0).
|
|
@@ -28,15 +28,10 @@ maybe(K, N) :-
|
|
|
28
28
|
random(Value) :-
|
|
29
29
|
eyeprolog__random_value(Value).
|
|
30
30
|
|
|
31
|
+
% Context declared once per predicate instead of at each raise site, so the
|
|
32
|
+
% raise sites throw with [] and contexts stay proper composable lists.
|
|
31
33
|
random_integer(Lower, Upper, R) :-
|
|
32
|
-
(
|
|
33
|
-
; var(Upper) -> instantiation_error(random_integer/3)
|
|
34
|
-
; integer(Lower) -> true
|
|
35
|
-
; type_error(integer, Lower, random_integer/3)
|
|
36
|
-
),
|
|
37
|
-
( integer(Upper) -> true
|
|
38
|
-
; type_error(integer, Upper, random_integer/3)
|
|
39
|
-
),
|
|
34
|
+
call_with_error_context(random__check_integer_range(Lower, Upper), random_integer/3),
|
|
40
35
|
Lower < Upper,
|
|
41
36
|
random__current_seed(Seed0),
|
|
42
37
|
random(Seed0, _, Seed),
|
|
@@ -44,15 +39,28 @@ random_integer(Lower, Upper, R) :-
|
|
|
44
39
|
R is Lower + Seed mod (Upper - Lower).
|
|
45
40
|
|
|
46
41
|
set_random(Seed) :-
|
|
47
|
-
(
|
|
42
|
+
call_with_error_context(random__set_seed(Seed), set_random/1).
|
|
43
|
+
|
|
44
|
+
random__check_integer_range(Lower, Upper) :-
|
|
45
|
+
( var(Lower) -> instantiation_error
|
|
46
|
+
; var(Upper) -> instantiation_error
|
|
47
|
+
; integer(Lower) -> true
|
|
48
|
+
; type_error(integer, Lower)
|
|
49
|
+
),
|
|
50
|
+
( integer(Upper) -> true
|
|
51
|
+
; type_error(integer, Upper)
|
|
52
|
+
).
|
|
53
|
+
|
|
54
|
+
random__set_seed(Seed) :-
|
|
55
|
+
( var(Seed) -> instantiation_error
|
|
48
56
|
; Seed = seed(S) ->
|
|
49
|
-
( var(S) -> instantiation_error
|
|
57
|
+
( var(S) -> instantiation_error
|
|
50
58
|
; integer(S) ->
|
|
51
59
|
random__random_normalize_seed(S, Normalized),
|
|
52
60
|
bb_put('$random_seed', Normalized)
|
|
53
|
-
; type_error(integer, S
|
|
61
|
+
; type_error(integer, S)
|
|
54
62
|
)
|
|
55
|
-
; type_error(random_state, Seed
|
|
63
|
+
; type_error(random_state, Seed)
|
|
56
64
|
).
|
|
57
65
|
|
|
58
66
|
random__current_seed(Seed) :- bb_get('$random_seed', Seed), !.
|
package/src/repl.js
CHANGED
|
@@ -861,12 +861,24 @@ function formatError(engine, state, error) {
|
|
|
861
861
|
// Reuse the same conversion as catch/3 so uncaught errors at the top
|
|
862
862
|
// level cannot lose the implementation-defined context or misplace a
|
|
863
863
|
// culprit as the second argument of error/2.
|
|
864
|
+
// A thrown ball that is already an error/2 envelope is the same thing a
|
|
865
|
+
// built-in raises, so display it the same way. Wrapping only that case in
|
|
866
|
+
// throw/1 made one error print two different ways depending on whether the
|
|
867
|
+
// engine or Prolog code raised it. Other balls keep the wrapper, because
|
|
868
|
+
// throw(foo) has no error/2 envelope to show.
|
|
869
|
+
const thrownBall = error.name === 'ThrownTerm' ? engine.deref(error.term, env) : null;
|
|
870
|
+
const thrownIsErrorEnvelope = thrownBall != null
|
|
871
|
+
&& thrownBall.type === 'compound' && thrownBall.name === 'error' && thrownBall.arity === 2;
|
|
864
872
|
const term = error.name === 'ThrownTerm'
|
|
865
|
-
? engine.compound('throw', [error.term])
|
|
873
|
+
? (thrownIsErrorEnvelope ? thrownBall : engine.compound('throw', [error.term]))
|
|
866
874
|
: formalErrorTerm(error);
|
|
867
875
|
collectUnboundVariables(engine, term, env, variableNames, () => `_${letterName(generated++)}`);
|
|
868
876
|
return `${engine.formatTermForWrite(term, env, {
|
|
869
877
|
quoted: true,
|
|
878
|
+
// Match the spacing successful answers use: operator layout only where
|
|
879
|
+
// ISO 6.3.4/6.4 needs it to keep the text readable back as the same
|
|
880
|
+
// term, so contexts print as [outer-1, atom_length/2].
|
|
881
|
+
minimalOperatorSpacing: true,
|
|
870
882
|
operators: [...state.program.operators.values()],
|
|
871
883
|
variableNames,
|
|
872
884
|
doubleBar: !state.strictIso,
|
|
@@ -67,7 +67,7 @@ Status values are:
|
|
|
67
67
|
| 7.11.2.2 | Effect when `debug=on` | The flag is accepted and stored; it does not change goal semantics or enable a debugger. | **defined** — `src/solver.js`; no semantic branch depends on `debug`. |
|
|
68
68
|
| 7.11.2.3 | Default `max_arity` | `unbounded`: EyeProlog imposes no fixed semantic ceiling on compound-term arity. Practical host allocation exhaustion is a resource condition. This flag is distinct from any potential implementation-specific procedure-arity limit; EyeProlog currently declares no separate finite procedure limit. | **defined** — `src/iso-limits.js`, `src/solver.js`, `src/parser.js`, `src/iso.js`; strict regression coverage. |
|
|
69
69
|
| 7.11.2.5 | Default `double_quotes` | `chars`. | **defined** — `src/solver.js`, parser flag state. |
|
|
70
|
-
| 7.12.1 | Second argument of `error/2` |
|
|
70
|
+
| 7.12.1 | Second argument of `error/2` | A list of context elements, innermost last. A built-in contributes its own predicate indicator, so `atom_length(1.0, _)` raises `error(type_error(atom, 1.0), [atom_length/2])`. Errors raised outside a built-in frame, and errors thrown from shared pre-built instances reused by several built-ins, use `[]`. Each enclosing `call_with_error_context/2` prepends a copy of its own element, so contexts compose into proper lists. | **defined** — `attachBuiltinErrorContext()` in `src/errors.js`, `formalErrorTerm()` in `src/iso.js`, `call_with_error_context/2` in `src/lib/error.pl`. |
|
|
71
71
|
| 7.12.2(f) | Implementation-defined representation limits | Character and character-code operations are limited to Unicode scalar values; surrogates and values above U+10FFFF are representation errors. Arity/integer values are modeled as unbounded but may hit host/resource limits. Float input overflow uses the implementation-specific `max_float`/`min_float` representation names documented by the STC-oriented tests. | **defined** — parser/ISO numeric and character guards. |
|
|
72
72
|
| 8.17.1 | Implementation-defined flag value ranges | Strict mode exposes only Part 1 core flags and their standard value sets. Normal mode additionally exposes EyeProlog's `occurs_check` and `default_procedure_access` flags. With `bounded=false`, `max_integer` and `min_integer` have no current or selectable value and their `current_prolog_flag/2` queries fail. Valid alternative values of fixed standard flags are distinguished from invalid values so `set_prolog_flag/2` reports permission versus domain errors as prescribed. | **defined** — strict registry/flag filtering in `src/solver.js`; strict flag tests. |
|
|
73
73
|
| 8.17.3 | Other effects of `halt/0` | Terminates EyeProlog execution and returns host/process status `0`; it produces no Prolog solution. | **defined** — `HaltSignal`, `haltBuiltin()`, CLI/runner handling. |
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
answer(7, red, caught(atom_length / 2), first, caught(
|
|
2
|
-
answer(7, red, caught(atom_length / 2), second, caught(
|
|
1
|
+
answer(7, red, caught([atom_length / 2]), first, caught([])).
|
|
2
|
+
answer(7, red, caught([atom_length / 2]), second, caught([])).
|
|
3
3
|
flags(off, on, [pair(bounded, false), pair(integer_rounding_function, toward_zero), pair(char_conversion, on), pair(debug, off), pair(max_arity, unbounded), pair(unknown, fail), pair(double_quotes, chars), pair(occurs_check, true), pair(default_procedure_access, private)]).
|
|
@@ -1475,7 +1475,7 @@ c4 ?- call((!;1)).
|
|
|
1475
1475
|
});
|
|
1476
1476
|
assertEqual(result.status, 0, 'exit status');
|
|
1477
1477
|
assertEqual(result.stdout,
|
|
1478
|
-
'?- error(type_error(callable, (write(3),
|
|
1478
|
+
'?- error(type_error(callable, (write(3),3)), []).\n' +
|
|
1479
1479
|
'?- ',
|
|
1480
1480
|
'fully instantiated call/1 culprit');
|
|
1481
1481
|
assertEqual(result.stderr, '', 'stderr');
|
|
@@ -1891,7 +1891,7 @@ c4 ?- call((!;1)).
|
|
|
1891
1891
|
assertIncludes(repl.stdout, 'T = ./*. .', 'REPL dotted graphic atom answer');
|
|
1892
1892
|
assertNotIncludes(repl.stdout, "T = './*.'", 'REPL dotted graphic atom has no spurious quotes');
|
|
1893
1893
|
assertIncludes(repl.stdout, 'T = ok.', 'REPL following read answer');
|
|
1894
|
-
assertIncludes(repl.stdout, 'error(syntax_error(read_term), read
|
|
1894
|
+
assertIncludes(repl.stdout, 'error(syntax_error(read_term), [read/1])', 'REPL syntax error');
|
|
1895
1895
|
assertEqual(repl.stderr, '', 'REPL stderr');
|
|
1896
1896
|
|
|
1897
1897
|
const continuedGraphic = runCli([], {
|
|
@@ -2719,8 +2719,8 @@ c4 ?- call((!;1)).
|
|
|
2719
2719
|
});
|
|
2720
2720
|
assertEqual(result.status, 0, 'exit status');
|
|
2721
2721
|
assertEqual(result.stdout,
|
|
2722
|
-
'?- error(type_error(list, [1, [], _A | 2]), number_chars
|
|
2723
|
-
'?- error(type_error(list, [1, [], _A | 2]), number_chars
|
|
2722
|
+
'?- error(type_error(list, [1, [], _A | 2]), [number_chars/2]).\n' +
|
|
2723
|
+
'?- error(type_error(list, [1, [], _A | 2]), [number_chars/2]).\n' +
|
|
2724
2724
|
'?- ',
|
|
2725
2725
|
'stdout');
|
|
2726
2726
|
assertEqual(result.stderr, '', 'stderr');
|
|
@@ -2734,8 +2734,8 @@ c4 ?- call((!;1)).
|
|
|
2734
2734
|
});
|
|
2735
2735
|
assertEqual(result.status, 0, 'exit status');
|
|
2736
2736
|
assertEqual(result.stdout,
|
|
2737
|
-
'?- error(instantiation_error, (is)
|
|
2738
|
-
'?- Error = instantiation_error, Imp_def = (is)/2.\n' +
|
|
2737
|
+
'?- error(instantiation_error, [(is)/2]).\n' +
|
|
2738
|
+
'?- Error = instantiation_error, Imp_def = [(is)/2].\n' +
|
|
2739
2739
|
'?- ',
|
|
2740
2740
|
'stdout');
|
|
2741
2741
|
assertEqual(result.stderr, '', 'stderr');
|
|
@@ -3710,7 +3710,7 @@ child.stdin.write(\`consult(${consultedAtom}).\\n\`);
|
|
|
3710
3710
|
'?- V = fail.\n' +
|
|
3711
3711
|
'?- true.\n' +
|
|
3712
3712
|
'?- true.\n' +
|
|
3713
|
-
'?- error(existence_error(procedure, missing_after_consult
|
|
3713
|
+
'?- error(existence_error(procedure, missing_after_consult/0), []).\n' +
|
|
3714
3714
|
'?- ',
|
|
3715
3715
|
'stdout');
|
|
3716
3716
|
assertEqual(result.stderr, '', 'stderr');
|
|
@@ -4044,7 +4044,7 @@ child.stdin.write(\`consult(${consultedAtom}).\\n\`);
|
|
|
4044
4044
|
const result = runCli([], { input: 'statistics(nonsense, Value).\nhalt.\n' });
|
|
4045
4045
|
assertEqual(result.status, 0, 'exit status');
|
|
4046
4046
|
assertIncludes(result.stdout,
|
|
4047
|
-
'error(domain_error(statistics_key, nonsense), statistics
|
|
4047
|
+
'error(domain_error(statistics_key, nonsense), [statistics/2]).',
|
|
4048
4048
|
'statistics key error');
|
|
4049
4049
|
assertEqual(result.stderr, '', 'stderr');
|
|
4050
4050
|
},
|
|
@@ -4463,6 +4463,76 @@ answer(Result) :- countdown(2048, Result), Result = 2048.
|
|
|
4463
4463
|
assertNotIncludes(result.stderr, 'unknown directive', 'not a directive message');
|
|
4464
4464
|
},
|
|
4465
4465
|
},
|
|
4466
|
+
{
|
|
4467
|
+
// Issue #98: error contexts are lists of context elements, so a built-in
|
|
4468
|
+
// error composes with library(error)'s call_with_error_context/2 into a
|
|
4469
|
+
// proper list instead of the improper [Element|eyeprolog] it used to be.
|
|
4470
|
+
name: 'error contexts compose into proper lists (issue #98)',
|
|
4471
|
+
run: () => {
|
|
4472
|
+
const input = ':- use_module(library(error)).\n%% goal: answer(X)\n' +
|
|
4473
|
+
'answer(C) :- catch(call_with_error_context(atom_length(1.0,_), outer-1), error(_,C), true).\n';
|
|
4474
|
+
const result = runCli(['-'], { input });
|
|
4475
|
+
assertEqual(result.status, 0, `exit status; stderr=${result.stderr}`);
|
|
4476
|
+
assertIncludes(result.stdout, 'answer([outer - 1, atom_length / 2])', 'composed context');
|
|
4477
|
+
},
|
|
4478
|
+
},
|
|
4479
|
+
{
|
|
4480
|
+
// Issue #98: library predicates declare their context once with
|
|
4481
|
+
// call_with_error_context/2 rather than handing it over manually at each
|
|
4482
|
+
// raise site. Manual handover produced a bare, non-list context, so it
|
|
4483
|
+
// composed into an improper list.
|
|
4484
|
+
name: 'library predicates yield composable contexts, not manual ones',
|
|
4485
|
+
run: () => {
|
|
4486
|
+
const input = ':- use_module(library(error)).\n:- use_module(library(random)).\n%% goal: answer(X)\n' +
|
|
4487
|
+
'answer(C) :- catch(must_be(integer, a), error(_,C), true).\n';
|
|
4488
|
+
const result = runCli(['-'], { input });
|
|
4489
|
+
assertEqual(result.status, 0, `exit status; stderr=${result.stderr}`);
|
|
4490
|
+
assertIncludes(result.stdout, 'answer([must_be / 2])', 'must_be context is a list');
|
|
4491
|
+
|
|
4492
|
+
const composed = ':- use_module(library(error)).\n%% goal: answer(X)\n' +
|
|
4493
|
+
'answer(C) :- catch(call_with_error_context(must_be(integer, a), outer-1), error(_,C), true).\n';
|
|
4494
|
+
const nested = runCli(['-'], { input: composed });
|
|
4495
|
+
assertEqual(nested.status, 0, `nested exit status; stderr=${nested.stderr}`);
|
|
4496
|
+
assertIncludes(nested.stdout, 'answer([outer - 1, must_be / 2])', 'composes as a proper list');
|
|
4497
|
+
},
|
|
4498
|
+
},
|
|
4499
|
+
{
|
|
4500
|
+
// An error/2 ball raised by Prolog throw/1 is the same thing a built-in
|
|
4501
|
+
// raises, so the top level must display it the same way. Only balls
|
|
4502
|
+
// without an error/2 envelope keep the throw/1 wrapper.
|
|
4503
|
+
name: 'uncaught error/2 balls display without a throw/1 wrapper',
|
|
4504
|
+
run: () => {
|
|
4505
|
+
const repl = runCli([], {
|
|
4506
|
+
input: 'must_be(integer, a).\nthrow(foo).\nhalt.\n',
|
|
4507
|
+
});
|
|
4508
|
+
assertIncludes(repl.stdout, 'error(type_error(integer, a), [must_be/2])', 'thrown ISO error');
|
|
4509
|
+
assertNotIncludes(repl.stdout, 'throw(error(type_error', 'no throw/1 wrapper on error/2');
|
|
4510
|
+
assertIncludes(repl.stdout, 'throw(foo)', 'non-error ball keeps the wrapper');
|
|
4511
|
+
},
|
|
4512
|
+
},
|
|
4513
|
+
{
|
|
4514
|
+
// Nesting prepends outermost-first and keeps the raising predicate last.
|
|
4515
|
+
name: 'nested call_with_error_context/2 accumulates outermost first',
|
|
4516
|
+
run: () => {
|
|
4517
|
+
const input = ':- use_module(library(error)).\n%% goal: answer(X)\n' +
|
|
4518
|
+
'answer(C) :- catch(call_with_error_context(call_with_error_context(atom_length(1.0,_), inner-1), outer-2), error(_,C), true).\n';
|
|
4519
|
+
const result = runCli(['-'], { input });
|
|
4520
|
+
assertEqual(result.status, 0, `exit status; stderr=${result.stderr}`);
|
|
4521
|
+
assertIncludes(result.stdout, 'answer([outer - 2, inner - 1, atom_length / 2])', 'nesting order');
|
|
4522
|
+
},
|
|
4523
|
+
},
|
|
4524
|
+
{
|
|
4525
|
+
// UWN asked for copy_term/2 semantics on the added element so that
|
|
4526
|
+
// variables in it are not shared with bindings undone during unwinding.
|
|
4527
|
+
name: 'call_with_error_context/2 copies the context element',
|
|
4528
|
+
run: () => {
|
|
4529
|
+
const input = ':- use_module(library(error)).\n%% goal: answer(X)\n' +
|
|
4530
|
+
'answer(ok) :- catch(call_with_error_context(atom_length(1.0,_), ctx(V)), error(_,[ctx(W)|_]), (V == W -> fail ; true)).\n';
|
|
4531
|
+
const result = runCli(['-'], { input });
|
|
4532
|
+
assertEqual(result.status, 0, `exit status; stderr=${result.stderr}`);
|
|
4533
|
+
assertIncludes(result.stdout, 'answer(ok)', 'context element is a fresh copy');
|
|
4534
|
+
},
|
|
4535
|
+
},
|
|
4466
4536
|
{
|
|
4467
4537
|
name: 'double dash permits option-shaped file names',
|
|
4468
4538
|
run: () => {
|
|
@@ -5082,7 +5152,7 @@ answer(Result) :- countdown(2048, Result), Result = 2048.
|
|
|
5082
5152
|
goal: 'answer(T)',
|
|
5083
5153
|
ioOptions: { input: invalidOctal },
|
|
5084
5154
|
}).stdout,
|
|
5085
|
-
'answer(error(syntax_error(read_term), read / 1)).\n',
|
|
5155
|
+
'answer(error(syntax_error(read_term), [read / 1])).\n',
|
|
5086
5156
|
'read/1 rejects non-octal numeric escape',
|
|
5087
5157
|
);
|
|
5088
5158
|
},
|
package/test/run-iso-strict.mjs
CHANGED
|
@@ -649,7 +649,7 @@ export function runIsoStrict(reporter = new TestReporter()) {
|
|
|
649
649
|
'close(tmp_in)',
|
|
650
650
|
'current_input(C)',
|
|
651
651
|
'stream_property(C,alias(user_input))',
|
|
652
|
-
'catch(set_input(tmp_in),error(existence_error(stream,tmp_in),set_input/1),true)',
|
|
652
|
+
'catch(set_input(tmp_in),error(existence_error(stream,tmp_in),[set_input/1]),true)',
|
|
653
653
|
].join(','),
|
|
654
654
|
}).stats.completed_goal_lists, 1, 'stream-term, alias lifetime, target/current input, and close fallback');
|
|
655
655
|
|
|
@@ -856,14 +856,14 @@ export function runIsoStrict(reporter = new TestReporter()) {
|
|
|
856
856
|
|
|
857
857
|
reporter.test('closes the ISO 7.12 processor error envelope and classification rows', () => {
|
|
858
858
|
const caught = [
|
|
859
|
-
"catch(atom_length(X,N),error(instantiation_error,atom_length/2),true)",
|
|
860
|
-
"catch(atom_length(1,N),error(type_error(atom,1),atom_length/2),true)",
|
|
861
|
-
"catch(op(1300,xfx,foo),error(domain_error(operator_priority,1300),op/3),true)",
|
|
862
|
-
"catch(call(no_such_predicate),error(existence_error(procedure,no_such_predicate/0),
|
|
863
|
-
"catch(abolish(atom/1),error(permission_error(modify,static_procedure,atom/1),abolish/1),true)",
|
|
864
|
-
"catch(char_code(C,1114112),error(representation_error(character_code),char_code/2),true)",
|
|
865
|
-
"catch(X is 1/0,error(evaluation_error(zero_divisor),(is)/2),true)",
|
|
866
|
-
"catch(X is 1<<4294967296,error(resource_error(memory),(is)/2),true)",
|
|
859
|
+
"catch(atom_length(X,N),error(instantiation_error,[atom_length/2]),true)",
|
|
860
|
+
"catch(atom_length(1,N),error(type_error(atom,1),[atom_length/2]),true)",
|
|
861
|
+
"catch(op(1300,xfx,foo),error(domain_error(operator_priority,1300),[op/3]),true)",
|
|
862
|
+
"catch(call(no_such_predicate),error(existence_error(procedure,no_such_predicate/0),[]),true)",
|
|
863
|
+
"catch(abolish(atom/1),error(permission_error(modify,static_procedure,atom/1),[abolish/1]),true)",
|
|
864
|
+
"catch(char_code(C,1114112),error(representation_error(character_code),[char_code/2]),true)",
|
|
865
|
+
"catch(X is 1/0,error(evaluation_error(zero_divisor),[(is)/2]),true)",
|
|
866
|
+
"catch(X is 1<<4294967296,error(resource_error(memory),[(is)/2]),true)",
|
|
867
867
|
];
|
|
868
868
|
for (const goal of caught) {
|
|
869
869
|
equal(run('', { isoStrict: true, goal }).stats.completed_goal_lists, 1, goal);
|
|
@@ -871,14 +871,14 @@ export function runIsoStrict(reporter = new TestReporter()) {
|
|
|
871
871
|
|
|
872
872
|
equal(run('', {
|
|
873
873
|
isoStrict: true,
|
|
874
|
-
goal: "catch(read_term(T,[]),error(syntax_error(read_term),read_term/2),true)",
|
|
874
|
+
goal: "catch(read_term(T,[]),error(syntax_error(read_term),[read_term/2]),true)",
|
|
875
875
|
ioOptions: { input: "'unterminated." },
|
|
876
876
|
}).stats.completed_goal_lists, 1, 'syntax error uses error/2 envelope and implementation-defined context');
|
|
877
877
|
|
|
878
878
|
const systemSolver = new Solver(Program.parse('', { isoStrict: true }), { isoStrict: true });
|
|
879
879
|
systemSolver.io.flush = () => { throw new Error('simulated host I/O failure'); };
|
|
880
880
|
equal([...systemSolver.solve([
|
|
881
|
-
parseGoalText('catch(flush_output(user_output),error(system_error,flush_output/1),true)', { isoStrict: true }),
|
|
881
|
+
parseGoalText('catch(flush_output(user_output),error(system_error,[flush_output/1]),true)', { isoStrict: true }),
|
|
882
882
|
], new Env(), 0)].length, 1, 'system error uses error/2 envelope and implementation-defined context');
|
|
883
883
|
|
|
884
884
|
// ISO 7.12 deliberately leaves the choice implementation-dependent when
|