eyeprolog 1.5.59 → 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/iso.js +5 -0
- package/src/lib/error.pl +30 -24
- package/src/lib/random.pl +21 -13
- package/src/repl.js +9 -1
- package/test/regression/cases-regression.mjs +34 -0
package/package.json
CHANGED
package/src/iso.js
CHANGED
|
@@ -3219,6 +3219,11 @@ function* catchSolutions({ solver, goal, env }, state) {
|
|
|
3219
3219
|
if (child != null) solver.absorbStatsFrom(child);
|
|
3220
3220
|
}
|
|
3221
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.
|
|
3222
3227
|
function* throwBuiltin({ goal, env }) {
|
|
3223
3228
|
const ball = deref(goal.args[0], env);
|
|
3224
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) :-
|
|
@@ -122,9 +128,9 @@ resource_error(Resource) :- throw(error(resource_error(Resource), [])).
|
|
|
122
128
|
resource_error(Resource, Context) :- throw(error(resource_error(Resource), Context)).
|
|
123
129
|
|
|
124
130
|
% Context elements are assembled only when an error actually propagates, so
|
|
125
|
-
% the
|
|
126
|
-
%
|
|
127
|
-
%
|
|
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.
|
|
128
134
|
call_with_error_context(Goal, Pair) :-
|
|
129
135
|
catch(Goal,
|
|
130
136
|
error(Error, 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,8 +861,16 @@ 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, {
|
|
@@ -4476,6 +4476,40 @@ answer(Result) :- countdown(2048, Result), Result = 2048.
|
|
|
4476
4476
|
assertIncludes(result.stdout, 'answer([outer - 1, atom_length / 2])', 'composed context');
|
|
4477
4477
|
},
|
|
4478
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
|
+
},
|
|
4479
4513
|
{
|
|
4480
4514
|
// Nesting prepends outermost-first and keeps the raising predicate last.
|
|
4481
4515
|
name: 'nested call_with_error_context/2 accumulates outermost first',
|