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.
Files changed (39) hide show
  1. package/README.md +6 -6
  2. package/examples/binomial-vandermonde.pl +1 -1
  3. package/examples/book/chapter-26/02-triple.pl +1 -1
  4. package/examples/book/chapter-28/02-triangle.pl +1 -1
  5. package/examples/book/chapter-28/04-integer_rectangle.pl +1 -1
  6. package/examples/book/chapter-29/01-counterexample_to_odd_square.pl +1 -1
  7. package/examples/book/chapter-31/03-double.pl +1 -1
  8. package/examples/book/chapter-32/04-reference_square.pl +1 -1
  9. package/examples/book/chapter-33/03-chosen_pair.pl +1 -1
  10. package/examples/book/chapter-39/01-answer.pl +1 -1
  11. package/examples/catalan-convolution.pl +1 -1
  12. package/examples/collatz-1000.pl +1 -1
  13. package/examples/continued-fraction-sqrt2.pl +1 -1
  14. package/examples/easter-computus.pl +1 -1
  15. package/examples/equality-saturation.pl +1 -1
  16. package/examples/goldbach.pl +1 -1
  17. package/examples/integer-partitions.pl +1 -1
  18. package/examples/job-shop-scheduling.pl +1 -1
  19. package/examples/matrix-chain-order.pl +1 -1
  20. package/examples/missionaries-cannibals.pl +1 -1
  21. package/examples/pell-equation.pl +1 -1
  22. package/examples/polynomial.pl +1 -1
  23. package/examples/prime-range.pl +1 -1
  24. package/examples/stirling-bell-numbers.pl +1 -1
  25. package/examples/totient-summatory.pl +1 -1
  26. package/package.json +1 -1
  27. package/src/iso.js +31 -15
  28. package/src/lib/lists.pl +15 -5
  29. package/src/lib/prologue.pl +30 -166
  30. package/src/program.js +20 -8
  31. package/src/quads.js +24 -3
  32. package/src/repl.js +4 -2
  33. package/src/solver.js +5 -5
  34. package/src/standard-library.js +5 -6
  35. package/test/fixtures/README.md +8 -0
  36. package/test/fixtures/variable_names_quad.pl +163 -0
  37. package/test/run-regression.mjs +71 -6
  38. package/test/test-support.mjs +1 -1
  39. 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
- The aligned `library(lists)` and `library(iso_ext)` exports are kept disjoint so
339
- they can be imported together without an accidental import conflict. EyeProlog's
340
- legacy `library(prologue)` remains a compatibility umbrella and should be
341
- selectively imported when mixed with the aligned modules.
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 EyeProlog's internal
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,
@@ -1,5 +1,5 @@
1
1
  :- use_module(library(aggregate)).
2
- :- use_module(library(prologue), [between/3]).
2
+ :- use_module(library(between), [between/3]).
3
3
 
4
4
  % Binomial coefficients and Vandermonde's identity.
5
5
  %
@@ -1,5 +1,5 @@
1
1
  % From The Art of EyeProlog, Chapter 26 — Answers are existential witnesses.
2
- :- use_module(library(prologue), [between/3]).
2
+ :- use_module(library(between), [between/3]).
3
3
  :- use_module(library(lists)).
4
4
 
5
5
  triple(A, B, C) :-
@@ -1,5 +1,5 @@
1
1
  % From The Art of EyeProlog, Chapter 28 — Symmetry reduces search.
2
- :- use_module(library(prologue), [between/3]).
2
+ :- use_module(library(between), [between/3]).
3
3
  :- use_module(library(lists)).
4
4
 
5
5
  triangle(A, B, C) :-
@@ -1,5 +1,5 @@
1
1
  % From The Art of EyeProlog, Chapter 28.
2
- :- use_module(library(prologue), [between/3]).
2
+ :- use_module(library(between), [between/3]).
3
3
  :- use_module(library(lists)).
4
4
 
5
5
  integer_rectangle(Area, W, H) :-
@@ -1,5 +1,5 @@
1
1
  % From The Art of EyeProlog, Chapter 29 — Examples suggest; proofs compel.
2
- :- use_module(library(prologue), [between/3]).
2
+ :- use_module(library(between), [between/3]).
3
3
  :- use_module(library(lists)).
4
4
 
5
5
  counterexample_to_odd_square(N) :-
@@ -1,5 +1,5 @@
1
1
  % From The Art of EyeProlog, Chapter 31 — Properties over finite domains.
2
- :- use_module(library(prologue), [between/3]).
2
+ :- use_module(library(between), [between/3]).
3
3
  :- use_module(library(lists)).
4
4
 
5
5
  double(N, D) :- (D is N + N).
@@ -1,5 +1,5 @@
1
1
  % From The Art of EyeProlog, Chapter 32 — Compare specification and implementation.
2
- :- use_module(library(prologue), [between/3]).
2
+ :- use_module(library(between), [between/3]).
3
3
  :- use_module(library(lists)).
4
4
 
5
5
  reference_square(N, S) :-
@@ -1,5 +1,5 @@
1
1
  % From The Art of EyeProlog, Chapter 33 — Pattern 3: Generate, constrain, describe.
2
- :- use_module(library(prologue), [between/3]).
2
+ :- use_module(library(between), [between/3]).
3
3
  :- use_module(library(lists)).
4
4
 
5
5
  chosen_pair(pair(X, Y), reason(sum_is_ten)) :-
@@ -1,6 +1,6 @@
1
1
  % From The Art of EyeProlog, Chapter 39 — The EyeProlog library.
2
2
  :- use_module(library(dates)).
3
- :- use_module(library(prologue), [between/3]).
3
+ :- use_module(library(between), [between/3]).
4
4
  :- use_module(library(random)).
5
5
  :- use_module(library(lists)).
6
6
 
@@ -1,5 +1,5 @@
1
1
  :- use_module(library(aggregate)).
2
- :- use_module(library(prologue), [between/3]).
2
+ :- use_module(library(between), [between/3]).
3
3
 
4
4
  % Catalan numbers by automatically tabled convolution.
5
5
  %
@@ -1,4 +1,4 @@
1
- :- use_module(library(prologue), [between/3]).
1
+ :- use_module(library(between), [between/3]).
2
2
 
3
3
  % Collatz conjecture suite translated from Eyeling's examples/collatz-1000.n3.
4
4
  % It enumerates starts N = 1000, 999, ..., 1 by deriving N = 1000 - N0
@@ -1,5 +1,5 @@
1
1
  :- use_module(library(aggregate)).
2
- :- use_module(library(prologue), [between/3]).
2
+ :- use_module(library(between), [between/3]).
3
3
 
4
4
  % Convergents of sqrt(2) by automatically tabled recurrence.
5
5
  %
@@ -1,4 +1,4 @@
1
- :- use_module(library(prologue), [between/3]).
1
+ :- use_module(library(between), [between/3]).
2
2
 
3
3
  % Gregorian Easter computus adapted from Eyeling's easter.n3.
4
4
  % Each case is a year in a sample decade. The rules derive the Meeus/Jones/
@@ -1,5 +1,5 @@
1
1
  :- use_module(library(aggregate)).
2
- :- use_module(library(prologue), [between/3]).
2
+ :- use_module(library(between), [between/3]).
3
3
  :- use_module(library(lists)).
4
4
  :- use_module(library(iso_ext)).
5
5
 
@@ -1,5 +1,5 @@
1
1
  :- use_module(library(primes)).
2
- :- use_module(library(prologue), [between/3]).
2
+ :- use_module(library(between), [between/3]).
3
3
 
4
4
  % Goldbach's_conjecture
5
5
  % See https://en.wikipedia.org/wiki/Goldbach%27s_conjecture:
@@ -1,5 +1,5 @@
1
1
  :- use_module(library(aggregate)).
2
- :- use_module(library(prologue), [between/3]).
2
+ :- use_module(library(between), [between/3]).
3
3
 
4
4
  % Integer partition counts by tabled dynamic programming.
5
5
  %
@@ -1,5 +1,5 @@
1
1
  :- use_module(library(aggregate)).
2
- :- use_module(library(prologue), [between/3]).
2
+ :- use_module(library(between), [between/3]).
3
3
  :- use_module(library(lists)).
4
4
  :- use_module(library(iso_ext)).
5
5
 
@@ -1,5 +1,5 @@
1
1
  :- use_module(library(aggregate)).
2
- :- use_module(library(prologue), [between/3]).
2
+ :- use_module(library(between), [between/3]).
3
3
  :- use_module(library(lists)).
4
4
  :- use_module(library(iso_ext)).
5
5
 
@@ -1,4 +1,4 @@
1
- :- use_module(library(prologue), [between/3]).
1
+ :- use_module(library(between), [between/3]).
2
2
  :- use_module(library(lists)).
3
3
  :- use_module(library(iso_ext)).
4
4
 
@@ -1,5 +1,5 @@
1
1
  :- use_module(library(aggregate)).
2
- :- use_module(library(prologue), [between/3]).
2
+ :- use_module(library(between), [between/3]).
3
3
 
4
4
  % Pell equation x^2 - 2y^2 = 1 by automatically tabled recurrence.
5
5
  %
@@ -1,4 +1,4 @@
1
- :- use_module(library(prologue), [between/3]).
1
+ :- use_module(library(between), [between/3]).
2
2
  :- use_module(library(lists)).
3
3
 
4
4
  % Polynomial roots over complex integer candidates, adapted from Eyelet's
@@ -1,4 +1,4 @@
1
- :- use_module(library(prologue), [between/3]).
1
+ :- use_module(library(between), [between/3]).
2
2
  :- use_module(library(lists)).
3
3
  :- use_module(library(iso_ext)).
4
4
 
@@ -1,5 +1,5 @@
1
1
  :- use_module(library(aggregate)).
2
- :- use_module(library(prologue), [between/3]).
2
+ :- use_module(library(between), [between/3]).
3
3
 
4
4
  % Stirling numbers of the second kind and Bell numbers.
5
5
  %
@@ -1,5 +1,5 @@
1
1
  :- use_module(library(aggregate)).
2
- :- use_module(library(prologue), [between/3]).
2
+ :- use_module(library(between), [between/3]).
3
3
  :- use_module(library(lists)).
4
4
  :- use_module(library(iso_ext)).
5
5
 
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.3.76",
6
+ "version": "1.3.77",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
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
- for (const candidate of termTextCandidates(stream, solver)) {
1590
- sawCandidate = true;
1591
- if (candidate.lexicalError) {
1592
- stream.position = candidate.end;
1593
- throw new PrologError('syntax_error(read_term)');
1594
- }
1595
- try {
1596
- const term = parseReadTermText(candidate.text, solver);
1597
- stream.position = candidate.end;
1598
- return scopeReadTerm(term);
1599
- } catch (error) {
1600
- if (error instanceof NumberRepresentationError || error instanceof CharacterRepresentationError) {
1601
- throw new PrologError(error.formal);
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(0, [Elem|Rest], Elem, Rest).
115
- nth0(N, [X|Xs], Elem, [X|Rest]) :-
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
- nth0(N0, Xs, Elem, Rest),
124
+ lists__nth0(N0, Xs, Elem, Rest),
118
125
  N is N0 + 1.
119
- nth0(N, [X|Xs], Elem, [X|Rest]) :-
126
+ lists__nth0(N, [X|Xs], Elem, [X|Rest]) :-
120
127
  nonvar(N),
121
128
  N > 0,
122
129
  N0 is N - 1,
123
- nth0(N0, Xs, Elem, Rest).
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).
@@ -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
- :- meta_predicate(maplist(1, '?')).
30
- :- meta_predicate(maplist(2, '?', '?')).
31
- :- meta_predicate(maplist(3, '?', '?', '?')).
32
- :- meta_predicate(maplist(4, '?', '?', '?', '?')).
33
- :- meta_predicate(maplist(5, '?', '?', '?', '?', '?')).
34
- :- meta_predicate(maplist(6, '?', '?', '?', '?', '?', '?')).
35
- :- meta_predicate(maplist(7, '?', '?', '?', '?', '?', '?', '?')).
36
- :- meta_predicate(call_nth(0, '?')).
37
- :- meta_predicate(freeze('?', 0)).
38
- :- meta_predicate(foldl(3, '?', '?', '?')).
39
- :- meta_predicate(foldl(4, '?', '?', '?', '?')).
40
- :- meta_predicate(foldl(5, '?', '?', '?', '?', '?')).
41
- :- meta_predicate(countall(0, '?')).
42
-
43
- member(X, [X|_]).
44
- member(X, [_|Xs]) :- member(X, Xs).
45
-
46
- append([], Ys, Ys).
47
- append([X|Xs], Ys, [X|Zs]) :- append(Xs, Ys, Zs).
48
-
49
- length(List, Length) :-
50
- nonvar(Length), !,
51
- prologue__integer(Length),
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 direct = this.groups.get(modulePredicateKey(module, name, arity));
184
- if (direct) return direct;
185
- const importedModule = this.moduleImports.get(module)?.get(`${name}/${arity}`);
186
- return importedModule == null
187
- ? null
188
- : this.groups.get(modulePredicateKey(importedModule, name, arity)) ?? null;
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
- if (previous != null && previous !== source) {
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, source);
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' || item.name === 'other_answer_sequence') {
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.error != null || leaf.output != null;
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').strictUtf8 = true;
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 = formalErrorTerm(error);
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 !== 'prologue' || group.name !== 'between' || group.arity !== 3 ||
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 Prologue definition is intentionally kept as the semantic
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
- !['lists', 'prologue'].includes(group.module) || group.name !== 'member' || group.arity !== 2 ||
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
- !['lists', 'prologue'].includes(group.module) || group.name !== 'length' || group.arity !== 2 ||
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
- !['lists', 'prologue'].includes(lengthGroup.module)) return null;
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;
@@ -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
- // Trealla and Scryer expose between/3 without an EyeProlog-style
145
- // library(prologue) dependency. EyeProlog keeps its implementation in the
146
- // Prologue module but autoloads it so portable source need not name that
147
- // implementation-specific library.
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);
@@ -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.
@@ -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:prologue,member/2:lists', 'autoload mapping');
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'], 'prologue', 'between/3 canonical internal autoload');
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 the documented legacy compatibility umbrella;
5122
- // aligned libraries must remain pairwise collision-free so full
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,
@@ -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(prologue), [between/3]).
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)).
@@ -3568,7 +3568,7 @@ methods.
3568
3568
  Consider a relation for a Pythagorean triple:
3569
3569
 
3570
3570
  ```eyeprolog
3571
- :- use_module(library(prologue), [between/3]).
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(prologue), [between/3]).
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(prologue), [between/3]).
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(prologue), [between/3]).
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(prologue), [between/3]).
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(prologue), [between/3]).
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(prologue), [between/3]).
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/atts.pl`, `src/lib/clpz.pl`, `src/lib/comparison.pl`,
6226
- `src/lib/dates.pl`, `src/lib/iso_ext.pl`, `src/lib/lambda.pl`,
6227
- `src/lib/lists.pl`, `src/lib/primes.pl`, `src/lib/prologue.pl`,
6228
- `src/lib/random.pl`, `src/lib/strings.pl`, and `src/lib/uuid.pl`. Each declares a same-named module
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 implements p.p.1 through p.p.11 of the
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
- with the published Prologue, `call_nth/2`, and `length/2` quads retained as
6235
- offline regressions. `src/standard-library.js` registers the module sources and private control and
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(prologue)` | EyeProlog compatibility module, not a common interop library name. `between/3` is nevertheless autoloaded from it so portable source need not name this EyeProlog-specific provider. |
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 interop exports of `library(lists)` and
6327
- `library(iso_ext)` are kept disjoint, so both modules can be imported together
6328
- without an accidental collision. `library(prologue)` remains a compatibility
6329
- umbrella and overlaps them; use selective imports when legacy code combines it
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(prologue)` |
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(prologue), [between/3]).
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(prologue)` and
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