eyeprolog 1.5.65 → 1.5.67

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 CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.5.65",
6
+ "version": "1.5.67",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
package/src/lib/error.pl CHANGED
@@ -17,10 +17,6 @@
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).
24
20
  % The context is named in exactly one place -- error__must_be_throw/1 -- so no
25
21
  % raise site hands one over (issue #98). It is a throw helper rather than a
26
22
  % call_with_error_context/2 wrapper because wrapping a Prolog goal in catch/3
@@ -57,9 +53,20 @@ error__must_be(ground, Term) :- !,
57
53
  error__must_be(acyclic, Term) :- !,
58
54
  ( acyclic_term(Term) -> true ; type_error(acyclic_term, Term) ).
59
55
  error__must_be(list, Term) :- !,
60
- error__proper_list(Term).
56
+ error__list(Term, Term, must_be/2, complete).
61
57
  error__must_be(list(Type), Term) :- !,
58
+ error__list(Term, Term, must_be/2, complete),
62
59
  error__proper_list_of(Term, Type).
60
+ error__must_be(character, Term) :- !,
61
+ ( var(Term) -> error__must_be_throw(instantiation_error)
62
+ ; atom(Term), atom_length(Term, 1) -> true
63
+ ; error__must_be_throw(type_error(character, Term))
64
+ ).
65
+ error__must_be(chars, Term) :- !,
66
+ error__list(Term, Term, must_be/2, partial),
67
+ error__partial_list_of(Term, character, must_be/2),
68
+ error__list(Term, Term, must_be/2, complete),
69
+ error__proper_list_of(Term, character).
63
70
  error__must_be(pair, Term) :- !,
64
71
  ( var(Term) -> error__must_be_throw(instantiation_error)
65
72
  ; Term = _-_ -> true
@@ -73,12 +80,17 @@ error__must_be(Type, Term) :-
73
80
  ; error__must_be_throw(type_error(Type, Term))
74
81
  ).
75
82
 
76
- error__proper_list([]) :- !.
77
- error__proper_list([_|Tail]) :- !, error__proper_list(Tail).
78
- error__proper_list(Term) :-
79
- ( var(Term) -> error__must_be_throw(instantiation_error)
80
- ; error__must_be_throw(type_error(list, Term))
81
- ).
83
+ % A list ends in []; a partial list ends in a variable (including a variable
84
+ % alone). Inspect before matching so validation never completes a partial list.
85
+ % Keep the original term as the culprit when a tail cannot become a list.
86
+ error__list(Term, _, Predicate, Mode) :- var(Term), !,
87
+ ( Mode == partial -> true
88
+ ; throw(error(instantiation_error, [predicate-Predicate])) ).
89
+ error__list([], _, _, _) :- !.
90
+ error__list([_|Tail], Original, Predicate, Mode) :- !,
91
+ error__list(Tail, Original, Predicate, Mode).
92
+ error__list(_, Original, Predicate, _) :-
93
+ throw(error(type_error(list, Original), [predicate-Predicate])).
82
94
 
83
95
  error__proper_list_of([], _) :- !.
84
96
  error__proper_list_of([Head|Tail], Type) :- !,
@@ -90,37 +102,46 @@ error__proper_list_of(Term, _) :-
90
102
  ).
91
103
 
92
104
  can_be(Type, Term) :-
93
- ( var(Type) -> instantiation_error(can_be/2)
105
+ ( var(Type) -> error__can_be_throw(instantiation_error)
94
106
  ; var(Term) -> true
95
107
  ; error__can_be(Type, Term)
96
108
  ).
97
109
 
110
+ error__can_be_throw(Formal) :-
111
+ throw(error(Formal, [predicate-can_be/2])).
112
+
98
113
  error__can_be(integer, Term) :- !,
99
- ( integer(Term) -> true ; type_error(integer, Term, can_be/2) ).
114
+ ( integer(Term) -> true ; error__can_be_throw(type_error(integer, Term)) ).
100
115
  error__can_be(atom, Term) :- !,
101
- ( atom(Term) -> true ; type_error(atom, Term, can_be/2) ).
116
+ ( atom(Term) -> true ; error__can_be_throw(type_error(atom, Term)) ).
102
117
  error__can_be(number, Term) :- !,
103
- ( number(Term) -> true ; type_error(number, Term, can_be/2) ).
104
- error__can_be(list, Term) :- !, error__partial_list(Term).
105
- error__can_be(list(Type), Term) :- !, error__partial_list_of(Term, Type).
118
+ ( number(Term) -> true ; error__can_be_throw(type_error(number, Term)) ).
119
+ error__can_be(list, Term) :- !, error__list(Term, Term, can_be/2, partial).
120
+ error__can_be(list(Type), Term) :- !,
121
+ error__list(Term, Term, can_be/2, partial),
122
+ error__partial_list_of(Term, Type, can_be/2).
123
+ error__can_be(character, Term) :- !,
124
+ ( atom(Term), atom_length(Term, 1) -> true
125
+ ; error__can_be_throw(type_error(character, Term)) ).
126
+ error__can_be(chars, Term) :- !,
127
+ error__list(Term, Term, can_be/2, partial),
128
+ error__partial_list_of(Term, character, can_be/2).
106
129
  error__can_be(not_less_than_zero, Term) :- !,
107
130
  ( integer(Term) ->
108
- ( Term >= 0 -> true ; domain_error(not_less_than_zero, Term, can_be/2) )
109
- ; type_error(integer, Term, can_be/2)
131
+ ( Term >= 0 -> true ; error__can_be_throw(domain_error(not_less_than_zero, Term)) )
132
+ ; error__can_be_throw(type_error(integer, Term))
110
133
  ).
111
- error__can_be(Type, Term) :- error__must_be(Type, Term).
112
-
113
- error__partial_list(Term) :- var(Term), !.
114
- error__partial_list([]) :- !.
115
- error__partial_list([_|Tail]) :- !, error__partial_list(Tail).
116
- error__partial_list(Term) :- type_error(list, Term, can_be/2).
134
+ error__can_be(Type, Term) :-
135
+ catch(error__must_be(Type, Term), error(Formal, _), error__can_be_throw(Formal)).
117
136
 
118
- error__partial_list_of(Term, _) :- var(Term), !.
119
- error__partial_list_of([], _) :- !.
120
- error__partial_list_of([Head|Tail], Type) :- !,
121
- can_be(Type, Head),
122
- error__partial_list_of(Tail, Type).
123
- error__partial_list_of(Term, _) :- type_error(list, Term, can_be/2).
137
+ error__partial_list_of(Term, _, _) :- var(Term), !.
138
+ error__partial_list_of([], _, _) :- !.
139
+ error__partial_list_of([Head|Tail], Type, Predicate) :-
140
+ ( var(Head) -> true
141
+ ; Predicate == must_be/2 -> must_be(Type, Head)
142
+ ; can_be(Type, Head)
143
+ ),
144
+ error__partial_list_of(Tail, Type, Predicate).
124
145
 
125
146
  instantiation_error :- throw(error(instantiation_error, [])).
126
147
  instantiation_error(Context) :- throw(error(instantiation_error, Context)).
@@ -140,8 +161,7 @@ call_with_error_context(Goal, Pair) :-
140
161
  error__require_pair(Pair),
141
162
  catch(Goal,
142
163
  error(Error, Context),
143
- ( copy_term(Pair, Element),
144
- throw(error(Error, [Element|Context])) )).
164
+ throw(error(Error, [Pair|Context]))).
145
165
 
146
166
  % The element must be a pair (issue #99). The test is inlined rather than
147
167
  % delegated to must_be(pair, _) so that the wrapper stays independent of the
package/src/lib/si.pl CHANGED
@@ -69,32 +69,50 @@ si__condition((A;B)) :- si__condition(A), si__condition(B).
69
69
  % names, f(X,a) @< f(X,b) by the second arguments, and X == X gives (=), while
70
70
  % f(a) vs f(Y) and X vs 1 are genuinely undecided.
71
71
  compare_si(Order, A, B) :-
72
- ( A == B
73
- -> Order = (=)
74
- ; si__order_decided(A, B)
75
- -> compare(Order, A, B)
76
- ; throw(error(instantiation_error, [predicate-compare_si/3]))
72
+ si__require_order(Order),
73
+ si__compare(Order, [A-B]).
74
+
75
+ si__require_order(Order) :- var(Order), !.
76
+ si__require_order(Order) :-
77
+ ( atom(Order) ->
78
+ ( (Order == (<) ; Order == (=) ; Order == (>)) -> true
79
+ ; throw(error(domain_error(order, Order), [predicate-compare_si/3]))
80
+ )
81
+ ; throw(error(type_error(atom, Order), [predicate-compare_si/3]))
77
82
  ).
78
83
 
79
- % Both arguments are known to be non-identical here.
80
- si__order_decided(A, B) :- ( var(A) ; var(B) ), !, fail.
81
- si__order_decided(A, B) :-
84
+ % Keep pending argument pairs in a work list. Recursing inside an if-then-else
85
+ % condition retains a child Solver per list cell; testing whole-tail identity
86
+ % at each step also repeatedly scans the same suffix (issue #105).
87
+ si__compare(=, []).
88
+ si__compare(Order, [A-B|Pairs]) :-
89
+ si__compare_pair(A, B, Comparison, Pairs, Next),
90
+ si__compare_next(Comparison, Order, Next).
91
+
92
+ si__compare_next(=, Order, Pairs) :- si__compare(Order, Pairs).
93
+ si__compare_next(<, <, _).
94
+ si__compare_next(>, >, _).
95
+
96
+ si__compare_pair(A, B, =, Pairs, Pairs) :-
97
+ ( var(A) ; var(B) ), !,
98
+ ( A == B -> true
99
+ ; throw(error(instantiation_error, [predicate-compare_si/3])) ).
100
+ si__compare_pair([A|As], [B|Bs], =, Pairs, [A-B,As-Bs|Pairs]) :- !.
101
+ si__compare_pair(A, B, Comparison, Pairs, Next) :-
82
102
  compound(A), compound(B), !,
83
103
  functor(A, NameA, ArityA),
84
104
  functor(B, NameB, ArityB),
85
- ( ArityA =\= ArityB -> true
86
- ; NameA \== NameB -> true
105
+ ( ArityA =\= ArityB -> compare(Comparison, ArityA, ArityB), Next = Pairs
106
+ ; NameA \== NameB -> compare(Comparison, NameA, NameB), Next = Pairs
87
107
  ; A =.. [_|ArgsA],
88
108
  B =.. [_|ArgsB],
89
- si__args_decided(ArgsA, ArgsB)
90
- ).
91
- % Otherwise both are non-variables and at least one is atomic, so either the
92
- % type order or the values themselves settle it and no instantiation can
93
- % change the outcome.
94
- si__order_decided(_, _).
95
-
96
- si__args_decided([A|As], [B|Bs]) :-
97
- ( A == B
98
- -> si__args_decided(As, Bs)
99
- ; si__order_decided(A, B)
109
+ Comparison = (=),
110
+ si__prepend_pairs(ArgsA, ArgsB, Next, Pairs)
100
111
  ).
112
+ si__compare_pair(A, _, >, Pairs, Pairs) :- compound(A), !.
113
+ si__compare_pair(_, B, <, Pairs, Pairs) :- compound(B), !.
114
+ si__compare_pair(A, B, Comparison, Pairs, Pairs) :- compare(Comparison, A, B).
115
+
116
+ si__prepend_pairs([], [], Pairs, Pairs).
117
+ si__prepend_pairs([A|As], [B|Bs], [A-B|Pairs], Tail) :-
118
+ si__prepend_pairs(As, Bs, Pairs, Tail).
package/src/program.js CHANGED
@@ -1219,7 +1219,7 @@ function collectAutoloadGoalDependencies(goal, out = []) {
1219
1219
  collectAutoloadGoalDependencies(goal.args[2], out);
1220
1220
  } else if ((goal.name === 'call_cleanup' || goal.name === 'setup_call_cleanup') && (goal.arity === 2 || goal.arity === 3)) {
1221
1221
  for (const arg of goal.args) collectAutoloadGoalDependencies(arg, out);
1222
- } else if (goal.name === 'call' && goal.arity === 1) {
1222
+ } else if ((goal.name === 'call' || goal.name === 'time') && goal.arity === 1) {
1223
1223
  collectAutoloadGoalDependencies(goal.args[0], out);
1224
1224
  }
1225
1225
  return out;
@@ -28,6 +28,103 @@ import {
28
28
 
29
29
  export function regressionCases() {
30
30
  return [
31
+ {
32
+ name: 'timed compare_si/3 backtracks over growing prefixes without exhausting the host stack (issue #105)',
33
+ run: () => {
34
+ // Bound the reported generator so the regression exhausts every
35
+ // answer, including sizes beyond the original 256-cell failure.
36
+ const result = runCli(['-'], { input:
37
+ '%% goal: answer(I,R,S)\n' +
38
+ 'answer(I,R,S) :- length(_,I), (I =< 9 -> true ; !, fail), ' +
39
+ 'N is 2^I, length(P,N), append(P,[1],L1), append(P,[2],L2), ' +
40
+ 'time(compare(R,L1,L2)), time(compare_si(S,L1,L2)).\n',
41
+ timeout: 20000,
42
+ });
43
+ assertEqual(result.status, 0, `exit status; error=${result.error}; stderr=${result.stderr}`);
44
+ for (let i = 0; i <= 9; i++) {
45
+ assertIncludes(result.stdout, `answer(${i}, <, <).`, 'comparison result');
46
+ }
47
+ assertNotIncludes(result.stderr + result.stdout, 'Maximum call stack', 'host stack');
48
+ },
49
+ },
50
+ {
51
+ name: 'compare_si/3 work list preserves argument priority and instantiation errors',
52
+ run: () => {
53
+ const goals = [
54
+ 'compare_si(=,f(X,g(Y)),f(X,g(Y))),var(X),var(Y)',
55
+ 'compare_si(<,f(g(X),a),f(g(X),b)),var(X)',
56
+ 'compare_si(>,f(g(b),a),f(g(a),z))',
57
+ 'compare_si(<,[a|X],[b|Y]),var(X),var(Y)',
58
+ 'compare_si(>,f(X),a),var(X)',
59
+ 'compare_si(<,a,f(X)),var(X)',
60
+ '\\+ compare_si(>,f(g(X),a),f(g(X),b))',
61
+ 'catch((compare_si(_,f(g(X),a),f(g(Y),b)),fail),error(instantiation_error,[predicate-compare_si/3]),true),var(X),var(Y)',
62
+ ];
63
+ for (const goal of goals) {
64
+ const result = runEyeProlog(`answer(ok) :- ${goal}.`, { goals: ['answer(X)'] });
65
+ assertIncludes(result.stdout, 'answer(ok)', goal);
66
+ }
67
+ },
68
+ },
69
+ {
70
+ name: 'compare_si/3 validates Order before term instantiation (issue #100)',
71
+ run: () => {
72
+ for (const terms of ['A,B', 'A,A', '1,2']) {
73
+ for (const [order, formal] of [['x', 'domain_error(order, x)'], ['1', 'type_error(atom, 1)']]) {
74
+ const result = runCli([], { input: `compare_si(${order},${terms}).\nhalt.\n` });
75
+ assertIncludes(result.stdout, `error(${formal}, [predicate-compare_si/3])`, 'order error');
76
+ }
77
+ }
78
+ },
79
+ },
80
+ {
81
+ name: 'list and chars type checks preserve partial lists and report precise errors (issues #102 and #103)',
82
+ run: () => {
83
+ const goals = [
84
+ 'catch(must_be(list,L),error(instantiation_error,[predicate-must_be/2]),true),var(L)',
85
+ 'catch(must_be(list,[a|L]),error(instantiation_error,[predicate-must_be/2]),true),var(L)',
86
+ 'catch(must_be(list(integer),L),error(instantiation_error,[predicate-must_be/2]),true),var(L)',
87
+ 'catch(must_be(list(integer),[1|L]),error(instantiation_error,[predicate-must_be/2]),true),var(L)',
88
+ 'must_be(list,[X]),var(X)',
89
+ 'must_be(chars,[a,b])',
90
+ 'catch(must_be(chars,[C]),error(instantiation_error,[predicate-must_be/2]),true),var(C)',
91
+ 'catch(must_be(chars,[C,ab]),error(type_error(character,ab),[predicate-must_be/2]),true),var(C)',
92
+ 'can_be(chars,[C|L]),var(C),var(L)',
93
+ 'can_be(list(integer),[C|L]),var(C),var(L)',
94
+ 'catch(can_be(chars,[C|1]),error(type_error(list,[D|1]),[predicate-can_be/2]),true),var(C),var(D)',
95
+ 'catch(must_be(chars,[C|1]),error(type_error(list,[D|1]),[predicate-must_be/2]),true),var(C),var(D)',
96
+ 'catch(can_be(chars,[ab|L]),error(type_error(character,ab),[predicate-can_be/2]),true),var(L)',
97
+ 'catch(can_be(pair,a),error(type_error(pair,a),[predicate-can_be/2]),true)',
98
+ 'catch(can_be(integer,a),error(type_error(integer,a),[predicate-can_be/2]),true)',
99
+ ];
100
+ for (const goal of goals) {
101
+ const result = runCli(['-'], { input: `:- use_module(library(error)).\n%% goal: answer(X)\nanswer(ok) :- ${goal}.\n` });
102
+ assertEqual(result.status, 0, `${goal}: ${result.stderr}`);
103
+ assertIncludes(result.stdout, 'answer(ok)', goal);
104
+ }
105
+ },
106
+ },
107
+ {
108
+ name: 'call_with_error_context/2 preserves repeated variables in context pairs (issue #104)',
109
+ run: () => {
110
+ const result = runCli(['-'], { input: ':- use_module(library(error)).\n%% goal: answer(X)\n' +
111
+ 'answer(ok) :- catch(call_with_error_context(throw(error(problem(X),[inner-X])),outer-f(Y,Y)),error(problem(A),[outer-f(B,C),inner-D]),true),B==C,A==D,var(Y).\n' });
112
+ assertEqual(result.status, 0, result.stderr);
113
+ assertIncludes(result.stdout, 'answer(ok)', 'shared error variables');
114
+ },
115
+ },
116
+ {
117
+ name: 'time/1 autoloads compare_si/3 on its first call (issue #105)',
118
+ run: () => {
119
+ const goal = 'length(P,8),append(P,[1],L1),append(P,[2],L2),time(compare(R,L1,L2)),time(compare_si(S,L1,L2))';
120
+ const repl = runCli([], { input: `${goal}.\n.\nhalt.\n` });
121
+ assertNotIncludes(repl.stdout, 'existence_error', 'first REPL call');
122
+ assertIncludes(repl.stdout, 'S = (<)', 'timed comparison');
123
+ const file = runCli(['-'], { input: `%% goal: answer(X)\nanswer(ok) :- ${goal},R==S.\n` });
124
+ assertEqual(file.status, 0, file.stderr);
125
+ assertIncludes(file.stdout, 'answer(ok)', 'file autoload');
126
+ },
127
+ },
31
128
  {
32
129
  name: 'dif/2 passes the WG17 finite-tree comparison cases (issue #68)',
33
130
  run: () => {
@@ -4638,9 +4735,9 @@ answer(Result) :- countdown(2048, Result), Result = 2048.
4638
4735
  },
4639
4736
  },
4640
4737
  {
4641
- // UWN asked for copy_term/2 semantics on the added element so that
4642
- // variables in it are not shared with bindings undone during unwinding.
4643
- name: 'call_with_error_context/2 copies the context element',
4738
+ // Throwing the error ball already freshens its variables; the wrapper
4739
+ // does not need a separate copy_term/2 call (issue #104).
4740
+ name: 'throw/1 freshens variables in the propagated context element',
4644
4741
  run: () => {
4645
4742
  const input = ':- use_module(library(error)).\n%% goal: answer(X)\n' +
4646
4743
  'answer(ok) :- catch(call_with_error_context(atom_length(1.0,_), ctx-V), error(_,[ctx-W|_]), (V == W -> fail ; true)).\n';
@@ -10794,8 +10794,10 @@ The host decides whether to reject, report, or inspect those answers.
10794
10794
  definite program; equivalently, the fixed point obtained by repeatedly adding
10795
10795
  supported ground consequences.
10796
10796
 
10797
- **List.** Either `[]` or a cons cell written `[Head | Tail]`. A proper list
10798
- eventually ends in `[]`.
10797
+ **List.** Either `[]` or `[Head | Tail]` where `Tail` is a list. A list
10798
+ ends in `[]`. A partial list ends in a variable, for example `[X,Y|Xs]`;
10799
+ a variable alone is also a partial list. `[X,Y|non_list]` is an instance
10800
+ of a partial list that is not a list.
10799
10801
 
10800
10802
  **Mode.** An intended direction of use described by which arguments are
10801
10803
  supplied and which are produced.