eyeprolog 1.3.76 → 1.3.77
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 +6 -6
- package/examples/binomial-vandermonde.pl +1 -1
- package/examples/book/chapter-26/02-triple.pl +1 -1
- package/examples/book/chapter-28/02-triangle.pl +1 -1
- package/examples/book/chapter-28/04-integer_rectangle.pl +1 -1
- package/examples/book/chapter-29/01-counterexample_to_odd_square.pl +1 -1
- package/examples/book/chapter-31/03-double.pl +1 -1
- package/examples/book/chapter-32/04-reference_square.pl +1 -1
- package/examples/book/chapter-33/03-chosen_pair.pl +1 -1
- package/examples/book/chapter-39/01-answer.pl +1 -1
- package/examples/catalan-convolution.pl +1 -1
- package/examples/collatz-1000.pl +1 -1
- package/examples/continued-fraction-sqrt2.pl +1 -1
- package/examples/easter-computus.pl +1 -1
- package/examples/equality-saturation.pl +1 -1
- package/examples/goldbach.pl +1 -1
- package/examples/integer-partitions.pl +1 -1
- package/examples/job-shop-scheduling.pl +1 -1
- package/examples/matrix-chain-order.pl +1 -1
- package/examples/missionaries-cannibals.pl +1 -1
- package/examples/pell-equation.pl +1 -1
- package/examples/polynomial.pl +1 -1
- package/examples/prime-range.pl +1 -1
- package/examples/stirling-bell-numbers.pl +1 -1
- package/examples/totient-summatory.pl +1 -1
- package/package.json +1 -1
- package/src/iso.js +31 -15
- package/src/lib/lists.pl +15 -5
- package/src/lib/prologue.pl +30 -166
- package/src/program.js +20 -8
- package/src/quads.js +24 -3
- package/src/repl.js +4 -2
- package/src/solver.js +5 -5
- package/src/standard-library.js +5 -6
- package/test/fixtures/README.md +8 -0
- package/test/fixtures/variable_names_quad.pl +163 -0
- package/test/run-regression.mjs +71 -6
- package/test/test-support.mjs +1 -1
- package/the-art-of-eyeprolog.md +30 -23
package/README.md
CHANGED
|
@@ -335,10 +335,11 @@ may be imported explicitly, while source/CLI/API dependency loading can resolve
|
|
|
335
335
|
their unqualified forms conservatively. For Trealla-style interactive timing,
|
|
336
336
|
`time/1` is also available directly in the normal EyeProlog runtime; strict ISO
|
|
337
337
|
mode does not expose it.
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
338
|
+
`library(lists)`, `library(iso_ext)`, and `library(freeze)` follow the focused
|
|
339
|
+
module organization used by Trealla and Scryer. EyeProlog's legacy
|
|
340
|
+
`library(prologue)` is now a compatibility facade over these canonical modules
|
|
341
|
+
and `library(between)`, so it can be imported before or after the focused
|
|
342
|
+
libraries without duplicate-import errors.
|
|
342
343
|
|
|
343
344
|
`library(lambda)` follows Scryer's higher-order lambda notation, adapted from
|
|
344
345
|
Ulrich Neumerkel's permissively licensed implementation. Importing it installs
|
|
@@ -362,8 +363,7 @@ Outside `--iso-strict`, an otherwise undefined unqualified call may autoload a
|
|
|
362
363
|
predicate only when the interop profile has one canonical EyeProlog provider.
|
|
363
364
|
For example, `member/2` autoloads from `library(lists)`, `call_nth/2` from
|
|
364
365
|
`library(iso_ext)`, `call_residue_vars/2` from `library(atts)`, and `between/3`
|
|
365
|
-
from
|
|
366
|
-
`library(prologue)` implementation. Use `--no-autoload` to disable this
|
|
366
|
+
from `library(between)`. Use `--no-autoload` to disable this
|
|
367
367
|
convenience. Strict ISO mode never autoloads library predicates.
|
|
368
368
|
|
|
369
369
|
Use `-w` / `--warnings` to diagnose dependencies outside the interop profile,
|
package/examples/collatz-1000.pl
CHANGED
package/examples/goldbach.pl
CHANGED
package/examples/polynomial.pl
CHANGED
package/examples/prime-range.pl
CHANGED
package/package.json
CHANGED
package/src/iso.js
CHANGED
|
@@ -1586,23 +1586,39 @@ function readTermFromStream(stream, solver) {
|
|
|
1586
1586
|
let requestedInteractiveTerm = false;
|
|
1587
1587
|
while (true) {
|
|
1588
1588
|
let sawCandidate = false;
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1589
|
+
let lastCandidateEnd = stream.position;
|
|
1590
|
+
try {
|
|
1591
|
+
for (const candidate of termTextCandidates(stream, solver)) {
|
|
1592
|
+
sawCandidate = true;
|
|
1593
|
+
lastCandidateEnd = candidate.end;
|
|
1594
|
+
if (candidate.lexicalError) {
|
|
1595
|
+
stream.position = candidate.end;
|
|
1596
|
+
throw new PrologError('syntax_error(read_term)');
|
|
1597
|
+
}
|
|
1598
|
+
try {
|
|
1599
|
+
const term = parseReadTermText(candidate.text, solver);
|
|
1600
|
+
stream.position = candidate.end;
|
|
1601
|
+
return scopeReadTerm(term);
|
|
1602
|
+
} catch (error) {
|
|
1603
|
+
if (error instanceof NumberRepresentationError || error instanceof CharacterRepresentationError) {
|
|
1604
|
+
throw new PrologError(error.formal);
|
|
1605
|
+
}
|
|
1606
|
+
// A dot inside a graphic operator, such as =.., is only a possible
|
|
1607
|
+
// terminator. Keep scanning until a complete term parses.
|
|
1602
1608
|
}
|
|
1603
|
-
// A dot inside a graphic operator, such as =.., is only a possible
|
|
1604
|
-
// terminator. Keep scanning until a complete term parses.
|
|
1605
1609
|
}
|
|
1610
|
+
} catch (error) {
|
|
1611
|
+
// A synthetic input boundary uses an invalid-character sentinel after
|
|
1612
|
+
// the required peek character. If a complete candidate already ended
|
|
1613
|
+
// at that boundary but did not parse, the term itself is malformed;
|
|
1614
|
+
// consume it and report syntax_error rather than blaming the sentinel.
|
|
1615
|
+
if (sawCandidate && Number.isInteger(stream.syntheticInputBoundary) &&
|
|
1616
|
+
lastCandidateEnd === stream.syntheticInputBoundary && error?.name === 'PrologError' &&
|
|
1617
|
+
error.formal === 'representation_error(character)') {
|
|
1618
|
+
stream.position = lastCandidateEnd;
|
|
1619
|
+
throw new PrologError('syntax_error(read_term)');
|
|
1620
|
+
}
|
|
1621
|
+
throw error;
|
|
1606
1622
|
}
|
|
1607
1623
|
|
|
1608
1624
|
// The interactive top level may attach a synchronous reader to the
|
package/src/lib/lists.pl
CHANGED
|
@@ -111,16 +111,23 @@ same_length([_|Xs], [_|Ys]) :- same_length(Xs, Ys).
|
|
|
111
111
|
|
|
112
112
|
nth0(N, List, Elem) :- nth0(N, List, Elem, _).
|
|
113
113
|
|
|
114
|
-
nth0(
|
|
115
|
-
|
|
114
|
+
nth0(N, List, Elem, Rest) :-
|
|
115
|
+
lists__integer_or_variable(N),
|
|
116
|
+
( var(N) -> lists__nth0(N, List, Elem, Rest)
|
|
117
|
+
; lists__not_less_than_zero(N),
|
|
118
|
+
lists__nth0(N, List, Elem, Rest)
|
|
119
|
+
).
|
|
120
|
+
|
|
121
|
+
lists__nth0(0, [Elem|Rest], Elem, Rest).
|
|
122
|
+
lists__nth0(N, [X|Xs], Elem, [X|Rest]) :-
|
|
116
123
|
var(N),
|
|
117
|
-
|
|
124
|
+
lists__nth0(N0, Xs, Elem, Rest),
|
|
118
125
|
N is N0 + 1.
|
|
119
|
-
|
|
126
|
+
lists__nth0(N, [X|Xs], Elem, [X|Rest]) :-
|
|
120
127
|
nonvar(N),
|
|
121
128
|
N > 0,
|
|
122
129
|
N0 is N - 1,
|
|
123
|
-
|
|
130
|
+
lists__nth0(N0, Xs, Elem, Rest).
|
|
124
131
|
|
|
125
132
|
nth1(N, List, Elem) :- nth1(N, List, Elem, _).
|
|
126
133
|
|
|
@@ -208,6 +215,9 @@ lists__integer(X) :- var(X), !, 0 is X.
|
|
|
208
215
|
% arg/3 performs the ISO integer type check before inspecting its term.
|
|
209
216
|
lists__integer(X) :- arg(X, type_check, _).
|
|
210
217
|
|
|
218
|
+
lists__integer_or_variable(X) :- var(X), !.
|
|
219
|
+
lists__integer_or_variable(X) :- lists__integer(X).
|
|
220
|
+
|
|
211
221
|
lists__not_less_than_zero(X) :- X >= 0, !.
|
|
212
222
|
% atom_length/2 reports domain_error(not_less_than_zero) for a negative value.
|
|
213
223
|
lists__not_less_than_zero(X) :- atom_length('', X).
|
package/src/lib/prologue.pl
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
/** Predicates proposed by the ISO Prolog Prologue working draft.
|
|
1
|
+
/** Predicates proposed by the ISO Prolog Prologue working draft.
|
|
2
|
+
|
|
3
|
+
This legacy compatibility module is a facade over the canonical library
|
|
4
|
+
organization shared with Scryer and Trealla. Keeping one implementation
|
|
5
|
+
owner per predicate lets applications combine library(prologue) with the
|
|
6
|
+
specialized libraries without import conflicts.
|
|
7
|
+
*/
|
|
2
8
|
|
|
3
9
|
:- module(prologue, [
|
|
4
10
|
member/2,
|
|
@@ -26,168 +32,26 @@
|
|
|
26
32
|
countall/2
|
|
27
33
|
]).
|
|
28
34
|
|
|
29
|
-
:-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
prologue__not_less_than_zero(Length),
|
|
53
|
-
prologue__length_fixed(Length, List).
|
|
54
|
-
length(List, Length) :-
|
|
55
|
-
prologue__length_generate(List, 0, Length).
|
|
56
|
-
|
|
57
|
-
prologue__length_fixed(0, []).
|
|
58
|
-
prologue__length_fixed(N, [_|Xs]) :-
|
|
59
|
-
N > 0,
|
|
60
|
-
Next is N - 1,
|
|
61
|
-
prologue__length_fixed(Next, Xs).
|
|
62
|
-
|
|
63
|
-
prologue__length_generate([], N, N).
|
|
64
|
-
prologue__length_generate([_|Xs], N0, N) :-
|
|
65
|
-
N1 is N0 + 1,
|
|
66
|
-
prologue__length_generate(Xs, N1, N).
|
|
67
|
-
|
|
68
|
-
between(Lower, Upper, X) :-
|
|
69
|
-
prologue__integer(Lower),
|
|
70
|
-
prologue__integer(Upper),
|
|
71
|
-
prologue__integer_or_variable(X),
|
|
72
|
-
prologue__between(Lower, Upper, X).
|
|
73
|
-
|
|
74
|
-
prologue__between(Lower, Upper, Lower) :- Lower =< Upper.
|
|
75
|
-
prologue__between(Lower, Upper, X) :-
|
|
76
|
-
Lower < Upper,
|
|
77
|
-
Next is Lower + 1,
|
|
78
|
-
prologue__between(Next, Upper, X).
|
|
79
|
-
|
|
80
|
-
select(X, [X|Xs], Xs).
|
|
81
|
-
select(X, [Y|Ys], [Y|Zs]) :- select(X, Ys, Zs).
|
|
82
|
-
|
|
83
|
-
succ(X, S) :-
|
|
84
|
-
var(X), !,
|
|
85
|
-
( var(S) -> 0 is S
|
|
86
|
-
; prologue__integer(S),
|
|
87
|
-
prologue__not_less_than_zero(S),
|
|
88
|
-
S > 0,
|
|
89
|
-
X is S - 1
|
|
90
|
-
).
|
|
91
|
-
succ(X, S) :-
|
|
92
|
-
prologue__integer(X),
|
|
93
|
-
prologue__not_less_than_zero(X),
|
|
94
|
-
( var(S) -> S is X + 1
|
|
95
|
-
; prologue__integer(S),
|
|
96
|
-
prologue__not_less_than_zero(S),
|
|
97
|
-
S =:= X + 1
|
|
98
|
-
).
|
|
99
|
-
|
|
100
|
-
maplist(_, []).
|
|
101
|
-
maplist(Closure, [X|Xs]) :-
|
|
102
|
-
call(Closure, X),
|
|
103
|
-
maplist(Closure, Xs).
|
|
104
|
-
|
|
105
|
-
maplist(_, [], []).
|
|
106
|
-
maplist(Closure, [A|As], [B|Bs]) :-
|
|
107
|
-
call(Closure, A, B),
|
|
108
|
-
maplist(Closure, As, Bs).
|
|
109
|
-
|
|
110
|
-
maplist(_, [], [], []).
|
|
111
|
-
maplist(Closure, [A|As], [B|Bs], [C|Cs]) :-
|
|
112
|
-
call(Closure, A, B, C),
|
|
113
|
-
maplist(Closure, As, Bs, Cs).
|
|
114
|
-
|
|
115
|
-
maplist(_, [], [], [], []).
|
|
116
|
-
maplist(Closure, [A|As], [B|Bs], [C|Cs], [D|Ds]) :-
|
|
117
|
-
call(Closure, A, B, C, D),
|
|
118
|
-
maplist(Closure, As, Bs, Cs, Ds).
|
|
119
|
-
|
|
120
|
-
maplist(_, [], [], [], [], []).
|
|
121
|
-
maplist(Closure, [A|As], [B|Bs], [C|Cs], [D|Ds], [E|Es]) :-
|
|
122
|
-
call(Closure, A, B, C, D, E),
|
|
123
|
-
maplist(Closure, As, Bs, Cs, Ds, Es).
|
|
124
|
-
|
|
125
|
-
maplist(_, [], [], [], [], [], []).
|
|
126
|
-
maplist(Closure, [A|As], [B|Bs], [C|Cs], [D|Ds], [E|Es], [F|Fs]) :-
|
|
127
|
-
call(Closure, A, B, C, D, E, F),
|
|
128
|
-
maplist(Closure, As, Bs, Cs, Ds, Es, Fs).
|
|
129
|
-
|
|
130
|
-
maplist(_, [], [], [], [], [], [], []).
|
|
131
|
-
maplist(Closure, [A|As], [B|Bs], [C|Cs], [D|Ds], [E|Es], [F|Fs], [G|Gs]) :-
|
|
132
|
-
call(Closure, A, B, C, D, E, F, G),
|
|
133
|
-
maplist(Closure, As, Bs, Cs, Ds, Es, Fs, Gs).
|
|
134
|
-
|
|
135
|
-
nth0(N, List, Elem) :- nth0(N, List, Elem, _).
|
|
136
|
-
|
|
137
|
-
nth0(N, List, Elem, Rest) :-
|
|
138
|
-
prologue__integer_or_variable(N),
|
|
139
|
-
( var(N) -> prologue__nth0(N, List, Elem, Rest)
|
|
140
|
-
; prologue__not_less_than_zero(N),
|
|
141
|
-
prologue__nth0(N, List, Elem, Rest)
|
|
142
|
-
).
|
|
143
|
-
|
|
144
|
-
prologue__nth0(0, [Elem|Rest], Elem, Rest).
|
|
145
|
-
prologue__nth0(N, [X|Xs], Elem, [X|Rest]) :-
|
|
146
|
-
var(N),
|
|
147
|
-
prologue__nth0(N0, Xs, Elem, Rest),
|
|
148
|
-
N is N0 + 1.
|
|
149
|
-
prologue__nth0(N, [X|Xs], Elem, [X|Rest]) :-
|
|
150
|
-
nonvar(N),
|
|
151
|
-
N > 0,
|
|
152
|
-
N0 is N - 1,
|
|
153
|
-
prologue__nth0(N0, Xs, Elem, Rest).
|
|
154
|
-
|
|
155
|
-
nth1(N, List, Elem) :- nth1(N, List, Elem, _).
|
|
156
|
-
|
|
157
|
-
nth1(N, List, Elem, Rest) :-
|
|
158
|
-
N \== 0,
|
|
159
|
-
nth0(N, [_|List], Elem, [_|Rest]),
|
|
160
|
-
N \== 0.
|
|
161
|
-
|
|
162
|
-
call_nth(Goal, Nth) :- eyeprolog__call_nth(Goal, Nth).
|
|
163
|
-
|
|
164
|
-
freeze(Var, Goal) :- eyeprolog__freeze(Var, Goal).
|
|
165
|
-
|
|
166
|
-
foldl(_, [], Acc, Acc).
|
|
167
|
-
foldl(Closure, [A|As], Acc0, Acc) :-
|
|
168
|
-
call(Closure, A, Acc0, Acc1),
|
|
169
|
-
foldl(Closure, As, Acc1, Acc).
|
|
170
|
-
|
|
171
|
-
foldl(_, [], [], Acc, Acc).
|
|
172
|
-
foldl(Closure, [A|As], [B|Bs], Acc0, Acc) :-
|
|
173
|
-
call(Closure, A, B, Acc0, Acc1),
|
|
174
|
-
foldl(Closure, As, Bs, Acc1, Acc).
|
|
175
|
-
|
|
176
|
-
foldl(_, [], [], [], Acc, Acc).
|
|
177
|
-
foldl(Closure, [A|As], [B|Bs], [C|Cs], Acc0, Acc) :-
|
|
178
|
-
call(Closure, A, B, C, Acc0, Acc1),
|
|
179
|
-
foldl(Closure, As, Bs, Cs, Acc1, Acc).
|
|
180
|
-
|
|
181
|
-
countall(Goal, Count) :- eyeprolog__countall(Goal, Count).
|
|
182
|
-
|
|
183
|
-
prologue__integer_or_variable(X) :- var(X), !.
|
|
184
|
-
prologue__integer_or_variable(X) :- prologue__integer(X).
|
|
185
|
-
|
|
186
|
-
prologue__integer(X) :- integer(X), !.
|
|
187
|
-
prologue__integer(X) :- var(X), !, 0 is X.
|
|
188
|
-
% arg/3 performs the required integer type check before inspecting its term.
|
|
189
|
-
prologue__integer(X) :- arg(X, type_check, _).
|
|
190
|
-
|
|
191
|
-
prologue__not_less_than_zero(X) :- X >= 0, !.
|
|
192
|
-
% atom_length/2 reports domain_error(not_less_than_zero) for a negative value.
|
|
193
|
-
prologue__not_less_than_zero(X) :- atom_length('', X).
|
|
35
|
+
:- use_module(library(lists), [
|
|
36
|
+
member/2,
|
|
37
|
+
append/3,
|
|
38
|
+
length/2,
|
|
39
|
+
select/3,
|
|
40
|
+
maplist/2,
|
|
41
|
+
maplist/3,
|
|
42
|
+
maplist/4,
|
|
43
|
+
maplist/5,
|
|
44
|
+
maplist/6,
|
|
45
|
+
maplist/7,
|
|
46
|
+
maplist/8,
|
|
47
|
+
nth0/3,
|
|
48
|
+
nth0/4,
|
|
49
|
+
nth1/3,
|
|
50
|
+
nth1/4,
|
|
51
|
+
foldl/4,
|
|
52
|
+
foldl/5,
|
|
53
|
+
foldl/6
|
|
54
|
+
]).
|
|
55
|
+
:- use_module(library(between), [between/3]).
|
|
56
|
+
:- use_module(library(iso_ext), [succ/2, call_nth/2, countall/2]).
|
|
57
|
+
:- use_module(library(freeze), [freeze/2]).
|
package/src/program.js
CHANGED
|
@@ -180,12 +180,18 @@ export class Program {
|
|
|
180
180
|
for (let i = 0; i < head.arity; i++) indexOne(group.argIndexes[i], head.args[i], clause, group.clauses, clausePosition);
|
|
181
181
|
}
|
|
182
182
|
findGroup(name, arity, module = 'user') {
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
183
|
+
const indicator = `${name}/${arity}`;
|
|
184
|
+
const visited = new Set();
|
|
185
|
+
let current = module;
|
|
186
|
+
while (!visited.has(current)) {
|
|
187
|
+
visited.add(current);
|
|
188
|
+
const direct = this.groups.get(modulePredicateKey(current, name, arity));
|
|
189
|
+
if (direct) return direct;
|
|
190
|
+
const importedModule = this.moduleImports.get(current)?.get(indicator);
|
|
191
|
+
if (importedModule == null) return null;
|
|
192
|
+
current = importedModule;
|
|
193
|
+
}
|
|
194
|
+
return null;
|
|
189
195
|
}
|
|
190
196
|
defineModule(name, exports, filename = '<input>') {
|
|
191
197
|
const indicators = new Map(exports.map((indicator) => [`${indicator.name}/${indicator.arity}`, indicator]));
|
|
@@ -202,10 +208,16 @@ export class Program {
|
|
|
202
208
|
throw new PrologError('existence_error(procedure)', compound('/', [atom(indicator.name), numberTerm(indicator.arity)]));
|
|
203
209
|
}
|
|
204
210
|
const previous = imports.get(key);
|
|
205
|
-
|
|
211
|
+
const resolved = this.findGroup(indicator.name, indicator.arity, source);
|
|
212
|
+
const canonicalSource = resolved?.module ?? source;
|
|
213
|
+
const previousResolved = previous == null
|
|
214
|
+
? null
|
|
215
|
+
: this.findGroup(indicator.name, indicator.arity, previous);
|
|
216
|
+
const canonicalPrevious = previousResolved?.module ?? previous;
|
|
217
|
+
if (previous != null && canonicalPrevious !== canonicalSource) {
|
|
206
218
|
throw new PrologError('permission_error(import, procedure)', compound('/', [atom(indicator.name), numberTerm(indicator.arity)]));
|
|
207
219
|
}
|
|
208
|
-
imports.set(key,
|
|
220
|
+
imports.set(key, canonicalSource);
|
|
209
221
|
}
|
|
210
222
|
this.moduleImports.set(target, imports);
|
|
211
223
|
}
|
package/src/quads.js
CHANGED
|
@@ -110,10 +110,15 @@ function checkAlternative(program, quad, alternative, options, context) {
|
|
|
110
110
|
const actual = executeQuery(program, quad.query, input, maxSolutions, {
|
|
111
111
|
...options,
|
|
112
112
|
detectLoops: leaves.some((leaf) => leaf.loops),
|
|
113
|
+
detectWaits: leaves.some((leaf) => leaf.waits),
|
|
113
114
|
inputBoundary: hasInputSpec,
|
|
114
115
|
inputPeek: peek,
|
|
115
116
|
});
|
|
116
117
|
|
|
118
|
+
if (leaves.some((leaf) => leaf.waits)) {
|
|
119
|
+
return { ok: leaves.length === 1 && actual.inputWaitObserved };
|
|
120
|
+
}
|
|
121
|
+
|
|
117
122
|
if (requiresSto) {
|
|
118
123
|
// Trealla currently treats the answer part of an `sto`-annotated leaf as
|
|
119
124
|
// implementation-dependent and skips it. EyeProlog can strengthen that
|
|
@@ -199,6 +204,7 @@ function describeLeaf(term) {
|
|
|
199
204
|
false: false,
|
|
200
205
|
truth: false,
|
|
201
206
|
loops: false,
|
|
207
|
+
waits: false,
|
|
202
208
|
error: null,
|
|
203
209
|
input: null,
|
|
204
210
|
peek: null,
|
|
@@ -213,7 +219,8 @@ function describeLeaf(term) {
|
|
|
213
219
|
else if (item.name === '...' || item.name === 'ad_infinitum') leaf.more = true;
|
|
214
220
|
else if (item.name === 'sto') leaf.sto = true;
|
|
215
221
|
else if (item.name === 'loops') leaf.loops = true;
|
|
216
|
-
else if (item.name === 'waits'
|
|
222
|
+
else if (item.name === 'waits') leaf.waits = true;
|
|
223
|
+
else if (item.name === 'other_answer_sequence') {
|
|
217
224
|
leaf.unsupported ??= item;
|
|
218
225
|
} else if (item.name === 'false') leaf.false = true;
|
|
219
226
|
else if (item.name === 'true') leaf.truth = true;
|
|
@@ -246,7 +253,8 @@ function describeLeaf(term) {
|
|
|
246
253
|
if (isErrorDescription(item)) leaf.error = item;
|
|
247
254
|
else leaf.malformed ??= item;
|
|
248
255
|
}
|
|
249
|
-
leaf.hasExpectation = leaf.bindings.length > 0 || leaf.truth || leaf.false || leaf.loops || leaf.
|
|
256
|
+
leaf.hasExpectation = leaf.bindings.length > 0 || leaf.truth || leaf.false || leaf.loops || leaf.waits ||
|
|
257
|
+
leaf.error != null || leaf.output != null;
|
|
250
258
|
if (!leaf.hasExpectation && !leaf.more && !leaf.sto && leaf.unsupported == null) leaf.malformed ??= term;
|
|
251
259
|
if ([leaf.false, leaf.truth, leaf.error != null].filter(Boolean).length > 1) leaf.malformed ??= term;
|
|
252
260
|
return leaf;
|
|
@@ -254,6 +262,7 @@ function describeLeaf(term) {
|
|
|
254
262
|
|
|
255
263
|
function executeQuery(program, query, input, maxSolutions, options) {
|
|
256
264
|
let pendingOutput = '';
|
|
265
|
+
let inputWaitObserved = false;
|
|
257
266
|
const boundedInput = options.inputBoundary
|
|
258
267
|
? `${input}${options.inputPeek ?? ''}${INVALID_UTF8_SENTINEL}`
|
|
259
268
|
: input;
|
|
@@ -274,13 +283,24 @@ function executeQuery(program, query, input, maxSolutions, options) {
|
|
|
274
283
|
write: (text) => { pendingOutput += String(text); },
|
|
275
284
|
},
|
|
276
285
|
});
|
|
286
|
+
if (options.detectWaits) {
|
|
287
|
+
const inputStream = solver.io.resolve('user_input');
|
|
288
|
+
const observeWait = () => {
|
|
289
|
+
inputWaitObserved = true;
|
|
290
|
+
return null;
|
|
291
|
+
};
|
|
292
|
+
inputStream.interactiveReadTerm = observeWait;
|
|
293
|
+
inputStream.interactiveReadUnit = observeWait;
|
|
294
|
+
}
|
|
277
295
|
if (options.inputBoundary) {
|
|
278
296
|
// Quads need a boundary that is neither EOF nor a valid character. This
|
|
279
297
|
// mirrors Trealla's sentinel technique: inputs/1 says exactly what was
|
|
280
298
|
// consumed, while peeks/1 supplies one additional character that must
|
|
281
299
|
// remain unread. If the query asks for anything beyond that boundary it
|
|
282
300
|
// sees representation_error(character), not an artificial EOF.
|
|
283
|
-
solver.io.resolve('user_input')
|
|
301
|
+
const inputStream = solver.io.resolve('user_input');
|
|
302
|
+
inputStream.strictUtf8 = true;
|
|
303
|
+
inputStream.syntheticInputBoundary = input.length;
|
|
284
304
|
}
|
|
285
305
|
// Undefined predicates are test failures rather than silent negative
|
|
286
306
|
// answers unless the source explicitly selected another unknown policy.
|
|
@@ -338,6 +358,7 @@ function executeQuery(program, query, input, maxSolutions, options) {
|
|
|
338
358
|
inputRemainder,
|
|
339
359
|
complete,
|
|
340
360
|
loopObserved,
|
|
361
|
+
inputWaitObserved,
|
|
341
362
|
stoObserved: solver.occursCheckObserved,
|
|
342
363
|
nstoObserved: complete && !solver.occursCheckObserved && !loopObserved &&
|
|
343
364
|
!bounded && !resourceInterrupted,
|
package/src/repl.js
CHANGED
|
@@ -803,7 +803,7 @@ function letterName(index) {
|
|
|
803
803
|
}
|
|
804
804
|
|
|
805
805
|
function formatError(engine, state, error) {
|
|
806
|
-
if (error?.name === 'PrologError') {
|
|
806
|
+
if (error?.name === 'PrologError' || (error?.name === 'ThrownTerm' && error.term != null)) {
|
|
807
807
|
const env = new engine.Env();
|
|
808
808
|
const variableNames = new Map();
|
|
809
809
|
let generated = 0;
|
|
@@ -811,7 +811,9 @@ function formatError(engine, state, error) {
|
|
|
811
811
|
// Reuse the same conversion as catch/3 so uncaught errors at the top
|
|
812
812
|
// level cannot lose the implementation-defined context or misplace a
|
|
813
813
|
// culprit as the second argument of error/2.
|
|
814
|
-
const term =
|
|
814
|
+
const term = error.name === 'ThrownTerm'
|
|
815
|
+
? engine.compound('throw', [error.term])
|
|
816
|
+
: formalErrorTerm(error);
|
|
815
817
|
collectUnboundVariables(engine, term, env, variableNames, () => `_${letterName(generated++)}`);
|
|
816
818
|
return `${engine.formatTermForWrite(term, env, {
|
|
817
819
|
quoted: true,
|
package/src/solver.js
CHANGED
|
@@ -1428,12 +1428,12 @@ function pushWfsAnswerFrames(stack, model, group, goal, rest, env, depth, active
|
|
|
1428
1428
|
|
|
1429
1429
|
function bundledBetweenIterator(solver, group, goal, env) {
|
|
1430
1430
|
if (solver.registry.eyePrologLibrary !== true ||
|
|
1431
|
-
group.module !== '
|
|
1431
|
+
group.module !== 'between' || group.name !== 'between' || group.arity !== 3 ||
|
|
1432
1432
|
group.bundledLibrary !== true || group.clauses.length !== 1) {
|
|
1433
1433
|
return null;
|
|
1434
1434
|
}
|
|
1435
1435
|
|
|
1436
|
-
// The portable
|
|
1436
|
+
// The portable library(between) definition is kept as the semantic
|
|
1437
1437
|
// source of between/3. Its recursive helper, however, carries the output
|
|
1438
1438
|
// variable through one fresh clause instance per integer. With persistent
|
|
1439
1439
|
// environments that builds a growing variable-alias chain, so dereferencing
|
|
@@ -1475,7 +1475,7 @@ function* bundledBetweenSolutions(solver, goal, env) {
|
|
|
1475
1475
|
|
|
1476
1476
|
function bundledMemberIterator(solver, group, goal, env) {
|
|
1477
1477
|
if (solver.registry.eyePrologLibrary !== true ||
|
|
1478
|
-
|
|
1478
|
+
group.module !== 'lists' || group.name !== 'member' || group.arity !== 2 ||
|
|
1479
1479
|
group.bundledLibrary !== true || group.clauses.length !== 2) {
|
|
1480
1480
|
return null;
|
|
1481
1481
|
}
|
|
@@ -1588,7 +1588,7 @@ function* bundledEllipsisSolutions(solver, output, input, env) {
|
|
|
1588
1588
|
|
|
1589
1589
|
function bundledLengthIterator(solver, group, goal, env) {
|
|
1590
1590
|
if (solver.registry.eyePrologLibrary !== true ||
|
|
1591
|
-
|
|
1591
|
+
group.module !== 'lists' || group.name !== 'length' || group.arity !== 2 ||
|
|
1592
1592
|
group.bundledLibrary !== true || group.clauses.length !== 2) {
|
|
1593
1593
|
return null;
|
|
1594
1594
|
}
|
|
@@ -1900,7 +1900,7 @@ function findallLengthFusion(solver, goal, rest, env) {
|
|
|
1900
1900
|
if (lengthGoal.type !== COMPOUND || lengthGoal.name !== 'length' || lengthGoal.arity !== 2) return null;
|
|
1901
1901
|
const lengthGroup = solver.program.findGroup('length', 2, lengthGoal.module ?? 'user');
|
|
1902
1902
|
if (!lengthGroup || lengthGroup.bundledLibrary !== true ||
|
|
1903
|
-
|
|
1903
|
+
lengthGroup.module !== 'lists') return null;
|
|
1904
1904
|
const lengthList = derefForLocal(lengthGoal.args[0], env);
|
|
1905
1905
|
const countArg = derefForLocal(lengthGoal.args[1], env);
|
|
1906
1906
|
if (lengthList.type !== 'var' || lengthList.name !== bag.name || countArg.type !== 'var') return null;
|
package/src/standard-library.js
CHANGED
|
@@ -141,11 +141,10 @@ export const eyePrologInteropAutoload = Object.freeze({
|
|
|
141
141
|
// unchanged while their implementations remain outside the ISO core.
|
|
142
142
|
'time/1': 'iso_ext',
|
|
143
143
|
'.../2': 'iso_ext',
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
|
|
148
|
-
'between/3': 'prologue',
|
|
144
|
+
// Scryer provides library(between), while Trealla exposes the relation as a
|
|
145
|
+
// system predicate. Keep EyeProlog's canonical portable owner in the
|
|
146
|
+
// specialized module; library(prologue) re-exports it for compatibility.
|
|
147
|
+
'between/3': 'between',
|
|
149
148
|
});
|
|
150
149
|
|
|
151
150
|
export const eyePrologInteropLibraryIndicators = Object.freeze(
|
|
@@ -155,7 +154,7 @@ export const eyePrologInteropLibraryIndicators = Object.freeze(
|
|
|
155
154
|
// Libraries whose *name* is part of the current interop profile. A program
|
|
156
155
|
// may freely use_module/1 with these common module names; predicates in those
|
|
157
156
|
// modules outside eyePrologInteropLibraryIndicators are still diagnosed when used.
|
|
158
|
-
export const eyePrologInteropLibraryModules = Object.freeze(['lists', 'iso_ext', 'lambda', 'atts']);
|
|
157
|
+
export const eyePrologInteropLibraryModules = Object.freeze(['lists', 'iso_ext', 'lambda', 'atts', 'freeze']);
|
|
159
158
|
|
|
160
159
|
function* tabledNegationBuiltin({ solver, goal, env }) {
|
|
161
160
|
yield* solver.solveTabledNegation(goal.args[0], env);
|
package/test/fixtures/README.md
CHANGED
|
@@ -9,6 +9,14 @@ Retrieved on 2026-08-11. It is vendored so the regression suite exercises all
|
|
|
9
9
|
58 quads without depending on network access or availability of the source
|
|
10
10
|
server.
|
|
11
11
|
|
|
12
|
+
`variable_names_quad.pl` is an unmodified snapshot of the 75 machine-readable
|
|
13
|
+
quads for the ISO read and write option `variable_names/1`:
|
|
14
|
+
|
|
15
|
+
<https://www.complang.tuwien.ac.at/ulrich/iso-prolog/variable_names_quad.pl>
|
|
16
|
+
|
|
17
|
+
Retrieved on 2026-08-25. It is vendored so all input, output, waiting, and
|
|
18
|
+
error cases remain release-gated.
|
|
19
|
+
|
|
12
20
|
`prologue_quad.pl` is an unmodified snapshot of the 33 machine-readable quads
|
|
13
21
|
for the predicates proposed by the Prolog Prologue working draft:
|
|
14
22
|
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
1 ?- write_term(T,[quoted(true),variable_names([N=T])]).
|
|
2
|
+
instantiation_error.
|
|
3
|
+
2 ?- N = 'X', /**/write_term(T,[quoted(true),variable_names([N=T])]).
|
|
4
|
+
outputs("X"), N = 'X'.
|
|
5
|
+
outputs("X"), N = ... . % Same, ignoring 'X'
|
|
6
|
+
3 ?- N = T, /**/write_term(T,[quoted(true),variable_names([N=T])]).
|
|
7
|
+
instantiation_error.
|
|
8
|
+
4 ?- N = '_', /**/write_term(T,[quoted(true),variable_names([N=T])]).
|
|
9
|
+
outputs("_"), N = ... .
|
|
10
|
+
65 ?- N = '_/*.*/', /**/write_term(T,[quoted(true),variable_names([N=T])]).
|
|
11
|
+
outputs("_/*.*/"), N = ... .
|
|
12
|
+
5 ?- N = x, /**/write_term(T,[quoted(true),variable_names([N=T])]).
|
|
13
|
+
outputs("x"), N = ... .
|
|
14
|
+
6 ?- N = 'x+y', /**/write_term(T,[quoted(true),variable_names([N=T])]).
|
|
15
|
+
outputs("x+y"), N = ... .
|
|
16
|
+
50 ?- N = '))', /**/write_term(T,[quoted(true),variable_names([N=T])]).
|
|
17
|
+
outputs("))"), N = ... .
|
|
18
|
+
7 ?- N = 7, /**/write_term(T,[quoted(true),variable_names([N=T])]).
|
|
19
|
+
domain_error(write_option, variable_names(...)).
|
|
20
|
+
8 ?- N = 1+2, /**/write_term(T,[quoted(true),variable_names([N=T])]).
|
|
21
|
+
domain_error(write_option, variable_names(...)).
|
|
22
|
+
9 ?- N = '$VAR'(9), /**/write_term(T,[quoted(true),variable_names([N=T])]).
|
|
23
|
+
domain_error(write_option, variable_names(...)).
|
|
24
|
+
10 ?- T = a, /**/write_term(T,[quoted(true),variable_names([N=T])]).
|
|
25
|
+
instantiation_error.
|
|
26
|
+
11 ?- T = a, N = 'Any', /**/write_term(T,[quoted(true),variable_names([N=T])]).
|
|
27
|
+
outputs("a"), T = ..., N = ... .
|
|
28
|
+
12 ?- T = '$VAR'(9), N = '_', /**/write_term(T,[quoted(true),variable_names([N=T])]).
|
|
29
|
+
outputs("'$VAR'(9)"), T = ..., N = ... .
|
|
30
|
+
74 ?- T = f(_), N = 'Bad', /**/write_term(T,[quoted(true),variable_names([N=T])]).
|
|
31
|
+
outputs(("f(_",...,")")), T = ..., N = ... .
|
|
32
|
+
28 ?- freeze(T,throw(g(T))), N = 'X', /**/write_term(T,[quoted(true),variable_names([N=T])]), false.
|
|
33
|
+
outputs("X"), false.
|
|
34
|
+
13 ?- write_term(T,[quoted(true),variable_names(['X'=X,'Y'=Y,'Z'=Z])]).
|
|
35
|
+
outputs(("_",...)).
|
|
36
|
+
14 ?- T=(X,Y,Z), /**/write_term(T,[quoted(true),variable_names(['X'=X,'Y'=Y,'Z'=Z])]).
|
|
37
|
+
outputs("X,Y,Z"), T = ... .
|
|
38
|
+
15 ?- Z=Y, T=(X,Y,Z), /**/write_term(T,[quoted(true),variable_names(['X'=X,'Y'=Y,'Z'=Z])]).
|
|
39
|
+
outputs("X,Y,Y"), Z = Y, T = ... .
|
|
40
|
+
16 ?- Z=Y, Y=X, T=(X,Y,Z), /**/write_term(T,[quoted(true),variable_names(['X'=X,'Y'=Y,'Z'=Z])]).
|
|
41
|
+
outputs("X,X,X"), Z = X, Y = X, T = ... .
|
|
42
|
+
17 ?- T=(Y,Z), /**/write_term(T,[quoted(true),variable_names(['X'=X,'Y'=Y,'Z'=Z])]).
|
|
43
|
+
outputs("Y,Z"), T = ... .
|
|
44
|
+
18 ?- T=(Z,Y), /**/write_term(T,[quoted(true),variable_names(['X'=X,'Y'=Y,'Z'=Z])]).
|
|
45
|
+
outputs("Z,Y"), T = ... .
|
|
46
|
+
19 ?- write_term(T,[quoted(true),variable_names(['Z'=Z,'Y'=Y,'X'=X])]).
|
|
47
|
+
outputs(("_",...)).
|
|
48
|
+
20 ?- T=(X,Y,Z), /**/write_term(T,[quoted(true),variable_names(['Z'=Z,'Y'=Y,'X'=X])]).
|
|
49
|
+
outputs("X,Y,Z"), T = ... .
|
|
50
|
+
21 ?- Z=Y, T=(X,Y,Z), /**/write_term(T,[quoted(true),variable_names(['Z'=Z,'Y'=Y,'X'=X])]).
|
|
51
|
+
outputs("X,Z,Z"), Z = Y, T = ... .
|
|
52
|
+
22 ?- Z=Y, Y=X, T=(X,Y,Z), /**/write_term(T,[quoted(true),variable_names(['Z'=Z,'Y'=Y,'X'=X])]).
|
|
53
|
+
outputs("Z,Z,Z"), Z = X, Y = X, T = ... .
|
|
54
|
+
23 ?- T=(Y,Z), /**/write_term(T,[quoted(true),variable_names(['Z'=Z,'Y'=Y,'X'=X])]).
|
|
55
|
+
outputs("Y,Z"), T = ... .
|
|
56
|
+
24 ?- T=(Z,Y), /**/write_term(T,[quoted(true),variable_names(['Z'=Z,'Y'=Y,'X'=X])]).
|
|
57
|
+
outputs("Z,Y"), T = ... .
|
|
58
|
+
25 ?- write_term(T,[quoted(true),variable_names(['X'=Z,'X'=Y,'X'=X])]).
|
|
59
|
+
outputs(("_",...)).
|
|
60
|
+
26 ?- T=(X,Y,Z), /**/write_term(T,[quoted(true),variable_names(['X'=Z,'X'=Y,'X'=X])]).
|
|
61
|
+
outputs("X,X,X"), T = ... .
|
|
62
|
+
27 ?- T=(1,2,3), T=(X,Y,Z), /**/write_term(T,[quoted(true),variable_names(['X'=Z,'X'=Y,'X'=X])]).
|
|
63
|
+
outputs("1,2,3"), T = ..., X = 1, Y = 2, Z = 3.
|
|
64
|
+
32 ?- read_term(T,[variable_names(VN_list)]),VN_list=[_=1,_=2,_=3],writeq(VN_list).
|
|
65
|
+
waits.
|
|
66
|
+
29 ?- /**/read_term(T,[variable_names(VN_list)]),VN_list=[_=1,_=2,_=3],writeq(VN_list).
|
|
67
|
+
inputs("B+C+A+B+C+A."), peeks("\n"),
|
|
68
|
+
outputs("['B'=1,'C'=2,'A'=3]"),
|
|
69
|
+
T = 1+2+3+1+2+3,
|
|
70
|
+
VN_list=['B'=1,'C'=2,'A'=3].
|
|
71
|
+
30 ?- write_term(T,[variable_names(VN_list)]).
|
|
72
|
+
instantiation_error.
|
|
73
|
+
31 ?- VN_list = 1, /**/write_term(T,[variable_names(VN_list)]).
|
|
74
|
+
domain_error(write_option, variable_names(1)).
|
|
75
|
+
33 ?- VN_list = [[]], /**/write_term(T,[variable_names(VN_list)]).
|
|
76
|
+
domain_error(write_option, variable_names([[]])).
|
|
77
|
+
34 ?- VN_list = non_list, /**/write_term(T,[variable_names(VN_list)]).
|
|
78
|
+
domain_error(write_option, variable_names(non_list)).
|
|
79
|
+
35 ?- VN_list = [T='T'|non_list], /**/write_term(T,[variable_names(VN_list)]).
|
|
80
|
+
domain_error(write_option, variable_names([_='T'|non_list]))
|
|
81
|
+
| instantiation_error.
|
|
82
|
+
52 ?- VN_list = ['T'=T|_], /**/write_term(T,[variable_names(VN_list)]).
|
|
83
|
+
instantiation_error.
|
|
84
|
+
51 ?- VN_list = ['T'=T|non_list], /**/write_term(T,[variable_names(VN_list)]).
|
|
85
|
+
domain_error(write_option, variable_names(...)).
|
|
86
|
+
36 ?- VN_list = [T-'T'], /**/write_term(T,[variable_names(VN_list)]).
|
|
87
|
+
domain_error(write_option, variable_names(...)).
|
|
88
|
+
63 ?- VN_list = [_,a], /**/write_term(T,[variable_names(VN_list)]).
|
|
89
|
+
domain_error(write_option, variable_names(...))
|
|
90
|
+
| instantiation_error.
|
|
91
|
+
64 ?- VN_list = [a,_], /**/write_term(T,[variable_names(VN_list)]).
|
|
92
|
+
domain_error(write_option, variable_names(...))
|
|
93
|
+
| instantiation_error.
|
|
94
|
+
66 ?- VN_list = [a|_], /**/write_term(T,[variable_names(VN_list)]).
|
|
95
|
+
domain_error(write_option, variable_names(...))
|
|
96
|
+
| instantiation_error.
|
|
97
|
+
67 ?- VN_list = [i=i,7=i], /**/write_term(T,[variable_names(VN_list)]).
|
|
98
|
+
domain_error(write_option,variable_names([i=i,7=i])).
|
|
99
|
+
68 ?- VN_list = [_,_], /**/write_term(T,[variable_names(VN_list)]).
|
|
100
|
+
instantiation_error.
|
|
101
|
+
43 ?- write_term(-X^2,[variable_names(['X'=X])]).
|
|
102
|
+
outputs("- (X^2)").
|
|
103
|
+
44 ?- X=1, /**/write_term(-X^2,[variable_names(['X'=X])]).
|
|
104
|
+
outputs("- (1^2)"), X = ... .
|
|
105
|
+
37 ?- open(f,write,_,[O]).
|
|
106
|
+
instantiation_error.
|
|
107
|
+
38 ?- O = 1, /**/open(f,write,_,[O]).
|
|
108
|
+
domain_error(stream_option,1).
|
|
109
|
+
56 ?- O = typex(_), /**/open(f,write,_,[O]).
|
|
110
|
+
domain_error(stream_option,typex(_)).
|
|
111
|
+
57 ?- O = typex(1), /**/open(f,write,_,[O]).
|
|
112
|
+
domain_error(stream_option,typex(1)).
|
|
113
|
+
62 ?- O = typex(s(_)), /**/open(f,write,_,[O]).
|
|
114
|
+
domain_error(stream_option,typex(s(_))).
|
|
115
|
+
39 ?- O = type(text), /**/open(f,write,_,[O]).
|
|
116
|
+
O = ... .
|
|
117
|
+
40 ?- O = type(1), /**/open(f,write,_,[O]).
|
|
118
|
+
domain_error(stream_option,type(1)).
|
|
119
|
+
41 ?- O = type(_), /**/open(f,write,_,[O]).
|
|
120
|
+
instantiation_error.
|
|
121
|
+
60 ?- O = alias(_), /**/open(f,write,_,[O]).
|
|
122
|
+
instantiation_error.
|
|
123
|
+
42 ?- O = type(nontype), /**/open(f,write,_,[O]).
|
|
124
|
+
domain_error(stream_option,type(nontype)).
|
|
125
|
+
61 ?- O = alias(1), /**/open(f,write,_,[O]).
|
|
126
|
+
domain_error(stream_option,alias(1)).
|
|
127
|
+
45 ?- read_term(T,[variable_names(VN_list)]).
|
|
128
|
+
waits.
|
|
129
|
+
46 ?- /**/read_term(T,[variable_names(VN_list)]).
|
|
130
|
+
inputs("a."), peeks("\n"),
|
|
131
|
+
T = a, VN_list = [].
|
|
132
|
+
47 ?- VN_list = 42, /**/read_term(T,[variable_names(VN_list)]).
|
|
133
|
+
waits.
|
|
134
|
+
48 ?- VN_list = 42, /**/read_term(T,[variable_names(VN_list)]).
|
|
135
|
+
inputs("a."), peeks("\n"),
|
|
136
|
+
false.
|
|
137
|
+
49 ?- VN_list = 42, /**/read_term(T,[variable_names(VN_list)]).
|
|
138
|
+
inputs("a b."), peeks("\n"),
|
|
139
|
+
syntax_error(...).
|
|
140
|
+
53 ?- write_term(S,[quoted(true),variable_names([N=T])]).
|
|
141
|
+
instantiation_error.
|
|
142
|
+
54 ?- S=1+T,N='/*r*/V',/**/write_term(S,[quoted(true),variable_names([N=T])]).
|
|
143
|
+
outputs("1+/*r*/V"), S = ..., N = ...
|
|
144
|
+
| outputs("1+ /*r*/V"), S = ..., N = ... .
|
|
145
|
+
55 ?- S=1+T,N=' /*r*/V',/**/write_term(S,[quoted(true),variable_names([N=T])]).
|
|
146
|
+
outputs("1+ /*r*/V"), S = ..., N = ... .
|
|
147
|
+
58 ?- S=1+T,N=(+),/**/write_term(S,[quoted(true),variable_names([N=T])]).
|
|
148
|
+
outputs("1++"), S = ..., N = ...
|
|
149
|
+
| outputs("1+ +"), S = ..., N = ... .
|
|
150
|
+
59 ?- S=T+1,N=(+),/**/write_term(S,[quoted(true),variable_names([N=T])]).
|
|
151
|
+
outputs("++1"), S = ..., N = ...
|
|
152
|
+
| outputs("+ +1"), S = ..., N = ... .
|
|
153
|
+
73 ?- S=(1 is T),N='X',/**/write_term(S,[quoted(true),variable_names([N=T])]).
|
|
154
|
+
outputs("1 is X"), S = ..., N = ... .
|
|
155
|
+
69 ?- read_term(T,[singletons(1)]).
|
|
156
|
+
waits.
|
|
157
|
+
70 ?- /**/read_term(T,[singletons(1)]).
|
|
158
|
+
inputs("a."), peeks("\n"),
|
|
159
|
+
false.
|
|
160
|
+
71 ?- write_term(T,[variable_names(['Bad'=T]),variable_names(['Good'=T])]).
|
|
161
|
+
outputs("Good").
|
|
162
|
+
72 ?- read_term(T,[singletons([])]).
|
|
163
|
+
waits.
|
package/test/run-regression.mjs
CHANGED
|
@@ -1565,6 +1565,18 @@ c4 ?- call((!;1)).
|
|
|
1565
1565
|
assertEqual(result.stderr, '', 'quad stderr');
|
|
1566
1566
|
},
|
|
1567
1567
|
},
|
|
1568
|
+
{
|
|
1569
|
+
name: 'CLI passes the complete authoritative variable_names quad corpus (issue #69)',
|
|
1570
|
+
run: () => {
|
|
1571
|
+
const filename = path.join(testRoot, 'fixtures', 'variable_names_quad.pl');
|
|
1572
|
+
const source = fs.readFileSync(filename, 'utf8');
|
|
1573
|
+
assertEqual(Program.parse(source).quads.length, 74, 'vendored query-record total');
|
|
1574
|
+
const result = runCli(['-q', filename], { cwd: tmp });
|
|
1575
|
+
assertEqual(result.status, 0, 'quad exit status');
|
|
1576
|
+
assertEqual(result.stdout, 'quads: 75 run, 75 passed, 0 failed.\n', 'quad report');
|
|
1577
|
+
assertEqual(result.stderr, '', 'quad stderr');
|
|
1578
|
+
},
|
|
1579
|
+
},
|
|
1568
1580
|
{
|
|
1569
1581
|
name: 'REPL advances anonymous Prologue length checks through I = 28',
|
|
1570
1582
|
run: () => {
|
|
@@ -1825,6 +1837,15 @@ c4 ?- call((!;1)).
|
|
|
1825
1837
|
assertEqual(result.stderr, '', 'stderr');
|
|
1826
1838
|
},
|
|
1827
1839
|
},
|
|
1840
|
+
{
|
|
1841
|
+
name: 'REPL displays uncaught user exceptions as throw terms (issue #75)',
|
|
1842
|
+
run: () => {
|
|
1843
|
+
const result = runCli([], { input: 'throw(stopped).\nhalt.\n' });
|
|
1844
|
+
assertEqual(result.status, 0, 'exit status');
|
|
1845
|
+
assertEqual(result.stdout, '?- throw(stopped).\n?- ', 'stdout');
|
|
1846
|
+
assertEqual(result.stderr, '', 'stderr');
|
|
1847
|
+
},
|
|
1848
|
+
},
|
|
1828
1849
|
{
|
|
1829
1850
|
name: 'REPL freshens variables displayed in uncaught ISO errors',
|
|
1830
1851
|
run: () => {
|
|
@@ -2830,6 +2851,22 @@ child.stdin.write(\`consult(${consultedAtom}).\\n\`);
|
|
|
2830
2851
|
assertEqual(result.stderr, '', 'stderr');
|
|
2831
2852
|
},
|
|
2832
2853
|
},
|
|
2854
|
+
{
|
|
2855
|
+
name: 'REPL combines canonical libraries with the Prologue facade',
|
|
2856
|
+
run: () => {
|
|
2857
|
+
const result = runCli([], {
|
|
2858
|
+
input:
|
|
2859
|
+
'use_module(library(freeze)).\n' +
|
|
2860
|
+
'use_module(library(lists)).\n' +
|
|
2861
|
+
'use_module(library(iso_ext)).\n' +
|
|
2862
|
+
'use_module(library(prologue)).\n' +
|
|
2863
|
+
'halt.\n',
|
|
2864
|
+
});
|
|
2865
|
+
assertEqual(result.status, 0, 'exit status');
|
|
2866
|
+
assertEqual(result.stdout, '?- true.\n?- true.\n?- true.\n?- true.\n?- ', 'stdout');
|
|
2867
|
+
assertEqual(result.stderr, '', 'stderr');
|
|
2868
|
+
},
|
|
2869
|
+
},
|
|
2833
2870
|
{
|
|
2834
2871
|
name: 'REPL halt status is returned by the CLI',
|
|
2835
2872
|
run: () => {
|
|
@@ -4566,7 +4603,7 @@ answer(ok) :-
|
|
|
4566
4603
|
const program = Program.parse('answer(X) :- member(X, [a]), between(1, 1, _).\n');
|
|
4567
4604
|
assertEqual(program.autoloadedPredicates.length, 2, 'autoloaded predicate count');
|
|
4568
4605
|
assertEqual(program.autoloadedPredicates.map((entry) => `${entry.indicator}:${entry.library}`).sort().join(','),
|
|
4569
|
-
'between/3:
|
|
4606
|
+
'between/3:between,member/2:lists', 'autoload mapping');
|
|
4570
4607
|
assertEqual(program.interopPortabilityWarnings.length, 0, 'autoloaded portable calls are warning-free');
|
|
4571
4608
|
},
|
|
4572
4609
|
},
|
|
@@ -5050,9 +5087,9 @@ answer(ok) :-
|
|
|
5050
5087
|
assertEqual(registeredNativeEyePrologLibraryNames().length, 49, 'public native EyeProlog builtin count');
|
|
5051
5088
|
assertEqual(eyePrologPortableLibraryIndicators.length, 87, 'portable Prolog library count');
|
|
5052
5089
|
assertEqual(eyePrologInteropLibraryIndicators.length, 30, 'cross-implementation interop profile count');
|
|
5053
|
-
assertEqual(eyePrologInteropLibraryModules.join(','), 'lists,iso_ext,lambda,atts', 'common explicit library module profile');
|
|
5090
|
+
assertEqual(eyePrologInteropLibraryModules.join(','), 'lists,iso_ext,lambda,atts,freeze', 'common explicit library module profile');
|
|
5054
5091
|
assertEqual(eyePrologInteropAutoload['member/2'], 'lists', 'member/2 canonical autoload');
|
|
5055
|
-
assertEqual(eyePrologInteropAutoload['between/3'], '
|
|
5092
|
+
assertEqual(eyePrologInteropAutoload['between/3'], 'between', 'between/3 canonical internal autoload');
|
|
5056
5093
|
assertEqual(eyePrologInteropAutoload['call_nth/2'], 'iso_ext', 'call_nth/2 canonical interop autoload');
|
|
5057
5094
|
assertEqual(eyePrologInteropAutoload['call_residue_vars/2'], 'atts', 'call_residue_vars/2 canonical interop autoload');
|
|
5058
5095
|
assertEqual(eyePrologInteropAutoload['time/1'], 'iso_ext', 'time/1 canonical interop autoload');
|
|
@@ -5113,13 +5150,41 @@ answer(ok) :-
|
|
|
5113
5150
|
assertEqual(program.findGroup('random', 3)?.module, 'random', 'random/3 is imported from library(random)');
|
|
5114
5151
|
},
|
|
5115
5152
|
},
|
|
5153
|
+
{
|
|
5154
|
+
name: 'Prologue facade re-exports canonical library predicates without conflicts',
|
|
5155
|
+
run: () => {
|
|
5156
|
+
const sources = [
|
|
5157
|
+
':- use_module(library(freeze)).\n:- use_module(library(lists)).\n:- use_module(library(iso_ext)).\n:- use_module(library(prologue)).',
|
|
5158
|
+
':- use_module(library(prologue)).\n:- use_module(library(freeze)).\n:- use_module(library(lists)).\n:- use_module(library(iso_ext)).',
|
|
5159
|
+
];
|
|
5160
|
+
for (const source of sources) {
|
|
5161
|
+
const program = Program.parse(source);
|
|
5162
|
+
assertEqual(program.findGroup('freeze', 2)?.module, 'freeze', 'freeze/2 canonical owner');
|
|
5163
|
+
assertEqual(program.findGroup('member', 2)?.module, 'lists', 'member/2 canonical owner');
|
|
5164
|
+
assertEqual(program.findGroup('call_nth', 2)?.module, 'iso_ext', 'call_nth/2 canonical owner');
|
|
5165
|
+
assertEqual(program.findGroup('between', 3)?.module, 'between', 'between/3 canonical owner');
|
|
5166
|
+
}
|
|
5167
|
+
|
|
5168
|
+
const conflicting = new Program();
|
|
5169
|
+
conflicting.defineModule('first', [{ name: 'shared', arity: 1 }]);
|
|
5170
|
+
conflicting.defineModule('second', [{ name: 'shared', arity: 1 }]);
|
|
5171
|
+
conflicting.importModule('user', 'first');
|
|
5172
|
+
let conflict = null;
|
|
5173
|
+
try {
|
|
5174
|
+
conflicting.importModule('user', 'second');
|
|
5175
|
+
} catch (error) {
|
|
5176
|
+
conflict = error;
|
|
5177
|
+
}
|
|
5178
|
+
assertEqual(conflict?.formal, 'permission_error(import, procedure)', 'distinct implementations still conflict');
|
|
5179
|
+
},
|
|
5180
|
+
},
|
|
5116
5181
|
{
|
|
5117
5182
|
name: 'aligned bundled libraries have no overlapping full-module exports',
|
|
5118
5183
|
run: () => {
|
|
5119
5184
|
const owners = new Map();
|
|
5120
5185
|
for (const [moduleName, entry] of standardLibrarySources) {
|
|
5121
|
-
// library(prologue) is
|
|
5122
|
-
//
|
|
5186
|
+
// library(prologue) is a facade whose nominal exports are re-exports;
|
|
5187
|
+
// canonical libraries must remain pairwise collision-free so full
|
|
5123
5188
|
// use_module/1 imports can be combined safely.
|
|
5124
5189
|
if (moduleName === 'prologue') continue;
|
|
5125
5190
|
const parsed = Program.parse(entry.source);
|
|
@@ -6541,7 +6606,7 @@ child.on('close', (status, signal) => {
|
|
|
6541
6606
|
|
|
6542
6607
|
function runCli(args, options = {}) {
|
|
6543
6608
|
return spawnSync(process.execPath, [bin, ...args], {
|
|
6544
|
-
cwd: packageRoot,
|
|
6609
|
+
cwd: options.cwd ?? packageRoot,
|
|
6545
6610
|
encoding: 'utf8',
|
|
6546
6611
|
env: options.env ? { ...process.env, ...options.env } : process.env,
|
|
6547
6612
|
input: options.input ?? undefined,
|
package/test/test-support.mjs
CHANGED
|
@@ -10,7 +10,7 @@ const standardModulePrelude = `:- use_module(library(aggregate)).
|
|
|
10
10
|
:- use_module(library(iso_ext)).
|
|
11
11
|
:- use_module(library(lists)).
|
|
12
12
|
:- use_module(library(primes)).
|
|
13
|
-
:- use_module(library(
|
|
13
|
+
:- use_module(library(between), [between/3]).
|
|
14
14
|
:- use_module(library(random)).
|
|
15
15
|
:- use_module(library(strings)).
|
|
16
16
|
:- use_module(library(uuid)).
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -3568,7 +3568,7 @@ methods.
|
|
|
3568
3568
|
Consider a relation for a Pythagorean triple:
|
|
3569
3569
|
|
|
3570
3570
|
```eyeprolog
|
|
3571
|
-
:- use_module(library(
|
|
3571
|
+
:- use_module(library(between), [between/3]).
|
|
3572
3572
|
:- use_module(library(lists)).
|
|
3573
3573
|
|
|
3574
3574
|
triple(A, B, C) :-
|
|
@@ -3880,7 +3880,7 @@ permutations repeats the same geometric object six times. Ordering the sides
|
|
|
3880
3880
|
removes the symmetry:
|
|
3881
3881
|
|
|
3882
3882
|
```eyeprolog
|
|
3883
|
-
:- use_module(library(
|
|
3883
|
+
:- use_module(library(between), [between/3]).
|
|
3884
3884
|
:- use_module(library(lists)).
|
|
3885
3885
|
|
|
3886
3886
|
triangle(A, B, C) :-
|
|
@@ -3918,7 +3918,7 @@ it from width and height. With a finite generator it can also search for
|
|
|
3918
3918
|
factorizations:
|
|
3919
3919
|
|
|
3920
3920
|
```eyeprolog
|
|
3921
|
-
:- use_module(library(
|
|
3921
|
+
:- use_module(library(between), [between/3]).
|
|
3922
3922
|
:- use_module(library(lists)).
|
|
3923
3923
|
|
|
3924
3924
|
integer_rectangle(Area, W, H) :-
|
|
@@ -4031,7 +4031,7 @@ theorem over an unbounded domain.
|
|
|
4031
4031
|
This boundary can be written directly:
|
|
4032
4032
|
|
|
4033
4033
|
```eyeprolog
|
|
4034
|
-
:- use_module(library(
|
|
4034
|
+
:- use_module(library(between), [between/3]).
|
|
4035
4035
|
:- use_module(library(lists)).
|
|
4036
4036
|
|
|
4037
4037
|
counterexample_to_odd_square(N) :-
|
|
@@ -4470,7 +4470,7 @@ Examples test selected points. A finite generated property tests every point
|
|
|
4470
4470
|
in a declared scope:
|
|
4471
4471
|
|
|
4472
4472
|
```eyeprolog
|
|
4473
|
-
:- use_module(library(
|
|
4473
|
+
:- use_module(library(between), [between/3]).
|
|
4474
4474
|
:- use_module(library(lists)).
|
|
4475
4475
|
|
|
4476
4476
|
double(N, D) :- (D is N + N).
|
|
@@ -4724,7 +4724,7 @@ compare it with the optimized one. The reference may be slow; its purpose is
|
|
|
4724
4724
|
clarity.
|
|
4725
4725
|
|
|
4726
4726
|
```eyeprolog
|
|
4727
|
-
:- use_module(library(
|
|
4727
|
+
:- use_module(library(between), [between/3]).
|
|
4728
4728
|
:- use_module(library(lists)).
|
|
4729
4729
|
|
|
4730
4730
|
reference_square(N, S) :-
|
|
@@ -4865,7 +4865,7 @@ explanation until none can be reasoned about separately.
|
|
|
4865
4865
|
in dependency order, then construct a witness or reason.
|
|
4866
4866
|
|
|
4867
4867
|
```eyeprolog
|
|
4868
|
-
:- use_module(library(
|
|
4868
|
+
:- use_module(library(between), [between/3]).
|
|
4869
4869
|
:- use_module(library(lists)).
|
|
4870
4870
|
|
|
4871
4871
|
chosen_pair(pair(X, Y), reason(sum_is_ten)) :-
|
|
@@ -6222,17 +6222,23 @@ clauses over private adapters. `time/1` is additionally registered directly in
|
|
|
6222
6222
|
the normal EyeProlog runtime so Trealla-style timing works at the interactive
|
|
6223
6223
|
top level without an import; it is absent from the strict ISO registry.
|
|
6224
6224
|
|
|
6225
|
-
The sources are `src/lib/aggregate.pl`, `src/lib/
|
|
6226
|
-
`src/lib/
|
|
6227
|
-
`src/lib/
|
|
6228
|
-
`src/lib/
|
|
6225
|
+
The sources are `src/lib/aggregate.pl`, `src/lib/arithmetic.pl`,
|
|
6226
|
+
`src/lib/assoc.pl`, `src/lib/atts.pl`, `src/lib/between.pl`,
|
|
6227
|
+
`src/lib/clpz.pl`, `src/lib/comparison.pl`, `src/lib/dates.pl`,
|
|
6228
|
+
`src/lib/dcgs.pl`, `src/lib/debug.pl`, `src/lib/error.pl`,
|
|
6229
|
+
`src/lib/format.pl`, `src/lib/freeze.pl`, `src/lib/iso_ext.pl`,
|
|
6230
|
+
`src/lib/lambda.pl`, `src/lib/lists.pl`, `src/lib/pairs.pl`,
|
|
6231
|
+
`src/lib/primes.pl`, `src/lib/prologue.pl`, `src/lib/random.pl`,
|
|
6232
|
+
`src/lib/si.pl`, `src/lib/strings.pl`, `src/lib/terms.pl`, and
|
|
6233
|
+
`src/lib/uuid.pl`. Each declares a same-named module
|
|
6229
6234
|
with `module/2`; there is no catch-all `library(eyeprolog)`. A program imports
|
|
6230
6235
|
only the modules it needs, and
|
|
6231
6236
|
`use_module/2` can select an even smaller indicator list. The Prologue
|
|
6232
|
-
module
|
|
6237
|
+
module exposes p.p.1 through p.p.11 of the
|
|
6233
6238
|
[working-draft Prologue](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/prologue),
|
|
6234
|
-
|
|
6235
|
-
|
|
6239
|
+
as a compatibility facade over the canonical `lists`, `between`, `iso_ext`,
|
|
6240
|
+
and `freeze` modules. The published Prologue, `call_nth/2`, and `length/2`
|
|
6241
|
+
quads are retained as offline regressions. `src/standard-library.js` registers the module sources and private control and
|
|
6236
6242
|
constraint adapters for Node and browser resolution. Explicit `use_module/1-2`
|
|
6237
6243
|
loads remain supported; outside strict ISO mode, the conservative interop
|
|
6238
6244
|
autoloader may also load a module for one uniquely mapped common predicate.
|
|
@@ -6294,7 +6300,9 @@ The current interoperability profile recognizes these library roles:
|
|
|
6294
6300
|
| `library(lists)` | Common list module. A conservative subset of its exports is in the shared predicate profile. |
|
|
6295
6301
|
| `library(iso_ext)` | Common extension-module name. `call_nth/2`, `time/1`, and the `... //0` arbitrary-sequence helper are conservatively autoloaded for cross-engine source. |
|
|
6296
6302
|
| `library(lambda)` | Scryer-aligned higher-order notation. It is imported explicitly because loading it also installs the `+\` operator. |
|
|
6297
|
-
| `library(
|
|
6303
|
+
| `library(atts)` | Common attributed-variable interface used by constraint libraries. |
|
|
6304
|
+
| `library(freeze)` | Common delayed-goal module backed by attributed variables. |
|
|
6305
|
+
| `library(prologue)` | EyeProlog compatibility facade, not a common interop library name. Its exports resolve to canonical focused modules instead of duplicate implementations. |
|
|
6298
6306
|
|
|
6299
6307
|
Other modules from the catalog, including `library(clpz)`, `library(strings)`,
|
|
6300
6308
|
`library(random)`, and `library(uuid)`, remain normal EyeProlog libraries. They
|
|
@@ -6323,11 +6331,10 @@ for example `% Time elapsed 0.832s, 65551 Inferences, 0.079 MLips`; `... //0`
|
|
|
6323
6331
|
describes an arbitrary number of input elements. Together they let the
|
|
6324
6332
|
Trealla/Scryer DCG hand-off benchmark run in EyeProlog without source changes
|
|
6325
6333
|
(assuming the usual list library is already imported in an interactive
|
|
6326
|
-
session). The
|
|
6327
|
-
`library(
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
with the aligned modules.
|
|
6334
|
+
session). The focused modules have one canonical implementation owner per
|
|
6335
|
+
predicate. `library(prologue)` re-exports those same owners, so legacy code can
|
|
6336
|
+
combine the facade with `library(lists)`, `library(iso_ext)`, and
|
|
6337
|
+
`library(freeze)` in either import order without an accidental collision.
|
|
6331
6338
|
|
|
6332
6339
|
`library(lambda)` follows Scryer's higher-order notation, adapted from Ulrich
|
|
6333
6340
|
Neumerkel's permissively licensed implementation. Its public syntax is:
|
|
@@ -6372,7 +6379,7 @@ extension. For example, the canonical build-time providers are:
|
|
|
6372
6379
|
| `call_nth/2` | `library(iso_ext)` |
|
|
6373
6380
|
| `time/1` | `library(iso_ext)` |
|
|
6374
6381
|
| `.../2` | `library(iso_ext)` |
|
|
6375
|
-
| `between/3` | `library(
|
|
6382
|
+
| `between/3` | `library(between)` |
|
|
6376
6383
|
|
|
6377
6384
|
Predicates outside that table require an explicit import even when EyeProlog
|
|
6378
6385
|
provides them. Explicit imports therefore remain the clearest way to state
|
|
@@ -6494,7 +6501,7 @@ before calling the corresponding predicate.
|
|
|
6494
6501
|
|
|
6495
6502
|
```eyeprolog
|
|
6496
6503
|
:- use_module(library(dates)).
|
|
6497
|
-
:- use_module(library(
|
|
6504
|
+
:- use_module(library(between), [between/3]).
|
|
6498
6505
|
:- use_module(library(random)).
|
|
6499
6506
|
:- use_module(library(lists)).
|
|
6500
6507
|
|
|
@@ -6515,7 +6522,7 @@ expresses them: for example, `R is A + B`, `R is abs(A)`, and
|
|
|
6515
6522
|
modulo, powers, sine, cosine, exponential, logarithm, and the ISO rounding
|
|
6516
6523
|
functions.
|
|
6517
6524
|
|
|
6518
|
-
The bundled library layer defines `between/3` in `library(
|
|
6525
|
+
The bundled library layer defines `between/3` in `library(between)` and
|
|
6519
6526
|
`smallest_divisor_from/3` in `library(primes)` as ordinary Prolog clauses.
|
|
6520
6527
|
Choose the smaller or
|
|
6521
6528
|
larger of two arithmetic values directly with ISO control, for example
|