eyeprolog 1.1.8 → 1.1.10

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 CHANGED
@@ -66,12 +66,12 @@ noun --> [world] | [prolog].
66
66
  %% goal: phrase(sentence, Words)
67
67
  ```
68
68
 
69
- EyeProlog also adds 44 public library predicates to its 129-entry ISO registry.
70
- **All 44 are ordinary Prolog clauses** in `src/lib/eyeprolog.pl` and
71
- `src/lib/lists.pl`. They are ISO/IEC 13211-2 modules, loaded explicitly with
72
- `use_module(library(eyeprolog))` or `use_module(library(lists))`, following the
73
- layout used by Scryer Prolog. None requires host support. Portable text
74
- predicates use ISO atoms or character lists.
69
+ EyeProlog also adds 46 public library predicates to its 129-entry ISO registry.
70
+ **All 46 are ordinary Prolog clauses** in `src/lib/eyeprolog.pl`,
71
+ `src/lib/lists.pl`, and `src/lib/prologue.pl`. They are ISO/IEC 13211-2 modules,
72
+ loaded explicitly with `use_module(library(eyeprolog))`,
73
+ `use_module(library(lists))`, or `use_module(library(prologue))`. None requires
74
+ host support. Portable text predicates use ISO atoms or character lists.
75
75
 
76
76
  ## Development
77
77
 
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.1.8",
6
+ "version": "1.1.10",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
@@ -0,0 +1,87 @@
1
+ /** Predicates proposed by the ISO Prolog Prologue working draft. */
2
+
3
+ :- module(prologue, [
4
+ member/2,
5
+ append/3,
6
+ length/2,
7
+ between/3,
8
+ select/3,
9
+ succ/2,
10
+ maplist/2
11
+ ]).
12
+
13
+ :- meta_predicate(maplist(1, '?')).
14
+
15
+ member(X, [X|_]).
16
+ member(X, [_|Xs]) :- member(X, Xs).
17
+
18
+ append([], Ys, Ys).
19
+ append([X|Xs], Ys, [X|Zs]) :- append(Xs, Ys, Zs).
20
+
21
+ length(List, Length) :-
22
+ nonvar(Length), !,
23
+ prologue__integer(Length),
24
+ prologue__not_less_than_zero(Length),
25
+ prologue__length_fixed(Length, List).
26
+ length(List, Length) :-
27
+ prologue__length_generate(List, 0, Length).
28
+
29
+ prologue__length_fixed(0, []).
30
+ prologue__length_fixed(N, [_|Xs]) :-
31
+ N > 0,
32
+ Next is N - 1,
33
+ prologue__length_fixed(Next, Xs).
34
+
35
+ prologue__length_generate([], N, N).
36
+ prologue__length_generate([_|Xs], N0, N) :-
37
+ N1 is N0 + 1,
38
+ prologue__length_generate(Xs, N1, N).
39
+
40
+ between(Lower, Upper, X) :-
41
+ prologue__integer(Lower),
42
+ prologue__integer(Upper),
43
+ prologue__integer_or_variable(X),
44
+ prologue__between(Lower, Upper, X).
45
+
46
+ prologue__between(Lower, Upper, Lower) :- Lower =< Upper.
47
+ prologue__between(Lower, Upper, X) :-
48
+ Lower < Upper,
49
+ Next is Lower + 1,
50
+ prologue__between(Next, Upper, X).
51
+
52
+ select(X, [X|Xs], Xs).
53
+ select(X, [Y|Ys], [Y|Zs]) :- select(X, Ys, Zs).
54
+
55
+ succ(X, S) :-
56
+ var(X), !,
57
+ ( var(S) -> 0 is S
58
+ ; prologue__integer(S),
59
+ prologue__not_less_than_zero(S),
60
+ S > 0,
61
+ X is S - 1
62
+ ).
63
+ succ(X, S) :-
64
+ prologue__integer(X),
65
+ prologue__not_less_than_zero(X),
66
+ ( var(S) -> S is X + 1
67
+ ; prologue__integer(S),
68
+ prologue__not_less_than_zero(S),
69
+ S =:= X + 1
70
+ ).
71
+
72
+ maplist(_, []).
73
+ maplist(Closure, [X|Xs]) :-
74
+ call(Closure, X),
75
+ maplist(Closure, Xs).
76
+
77
+ prologue__integer_or_variable(X) :- var(X), !.
78
+ prologue__integer_or_variable(X) :- prologue__integer(X).
79
+
80
+ prologue__integer(X) :- integer(X), !.
81
+ prologue__integer(X) :- var(X), !, 0 is X.
82
+ % arg/3 performs the required integer type check before inspecting its term.
83
+ prologue__integer(X) :- arg(X, type_check, _).
84
+
85
+ prologue__not_less_than_zero(X) :- X >= 0, !.
86
+ % atom_length/2 reports domain_error(not_less_than_zero) for a negative value.
87
+ prologue__not_less_than_zero(X) :- atom_length('', X).
package/src/solver.js CHANGED
@@ -431,6 +431,8 @@ function defaultPrologFlags(unknown = 'error') {
431
431
  ['integer_rounding_function', { value: compound('toward_zero', []), allowed: ['toward_zero'], changeable: false }],
432
432
  ['char_conversion', { value: compound('on', []), allowed: ['on', 'off'], changeable: true }],
433
433
  ['debug', { value: compound('off', []), allowed: ['on', 'off'], changeable: true }],
434
+ ['max_integer', { value: compound('unbounded', []), allowed: ['unbounded'], changeable: false }],
435
+ ['min_integer', { value: compound('unbounded', []), allowed: ['unbounded'], changeable: false }],
434
436
  ['max_arity', { value: compound('unbounded', []), allowed: ['unbounded'], changeable: false }],
435
437
  ['unknown', { value: compound(unknown, []), allowed: ['error', 'fail', 'warning'], changeable: true }],
436
438
  ['double_quotes', { value: compound('chars', []), allowed: ['chars', 'codes', 'atom'], changeable: true }],
@@ -8,6 +8,7 @@ import { fs, isNode } from './platform.js';
8
8
  const moduleFiles = Object.freeze({
9
9
  eyeprolog: 'eyeprolog.pl',
10
10
  lists: 'lists.pl',
11
+ prologue: 'prologue.pl',
11
12
  });
12
13
 
13
14
  const cacheKey = isNode
@@ -36,14 +37,14 @@ function libraryUrl(filename) {
36
37
 
37
38
  export const eyePrologNativeLibraryIndicators = Object.freeze([]);
38
39
  export const eyePrologPortableLibraryIndicators = Object.freeze([
39
- 'uuid/3', 'difference/3', 'maplist/3', 'lt/2', 'gt/2', 'le/2', 'ge/2',
40
+ 'uuid/3', 'difference/3', 'maplist/2', 'maplist/3', 'lt/2', 'gt/2', 'le/2', 'ge/2',
40
41
  'between/3', 'smallest_divisor_from/3', 'random/3', 'matches/3', 'split/3',
41
42
  'replace/4', 'lowercase/2', 'uppercase/2', 'trim/2', 'number_string/2',
42
43
  'atom_string/2', 'term_string/2', 'append/3', 'string_concat/3', 'contains/2',
43
44
  'matches/2', 'join/3', 'substring/4', 'member/2', 'select/3', 'last/2',
44
45
  'nth0/3', 'nth1/3', 'set_nth0/4', 'take/3', 'drop/3', 'slice/4', 'reverse/2',
45
46
  'length/2', 'sum_list/2', 'min_list/2', 'max_list/2', 'list_to_set/2',
46
- 'countall/2', 'sumall/3', 'aggregate_min/5', 'aggregate_max/5',
47
+ 'countall/2', 'succ/2', 'sumall/3', 'aggregate_min/5', 'aggregate_max/5',
47
48
  ]);
48
49
  export const eyePrologLibraryIndicators = Object.freeze([...eyePrologPortableLibraryIndicators]);
49
50
 
@@ -1,3 +1,3 @@
1
1
  answer(7, red, caught(eyeprolog), first, caught(eyeprolog)).
2
2
  answer(7, red, caught(eyeprolog), second, caught(eyeprolog)).
3
- flags(off, on, [pair(bounded, false), pair(integer_rounding_function, toward_zero), pair(char_conversion, on), pair(debug, off), pair(max_arity, unbounded), pair(unknown, fail), pair(double_quotes, chars)]).
3
+ flags(off, on, [pair(bounded, false), pair(integer_rounding_function, toward_zero), pair(char_conversion, on), pair(debug, off), pair(max_integer, unbounded), pair(min_integer, unbounded), pair(max_arity, unbounded), pair(unknown, fail), pair(double_quotes, chars)]).
@@ -0,0 +1,23 @@
1
+ # Regression fixtures
2
+
3
+ `phrase_quad.pl` is an unmodified snapshot of Ulrich Neumerkel's ISO Prolog
4
+ `phrase/2-3` quad corpus:
5
+
6
+ <https://www.complang.tuwien.ac.at/ulrich/iso-prolog/phrase_quad.pl>
7
+
8
+ Retrieved on 2026-08-11. It is vendored so the regression suite exercises all
9
+ 58 quads without depending on network access or availability of the source
10
+ server.
11
+
12
+ `prologue_quad.pl` is an unmodified snapshot of the 33 machine-readable quads
13
+ for the predicates proposed by the Prolog Prologue working draft:
14
+
15
+ <https://www.complang.tuwien.ac.at/ulrich/iso-prolog/prologue_quad.pl>
16
+
17
+ The corresponding working draft is at
18
+ <https://www.complang.tuwien.ac.at/ulrich/iso-prolog/prologue>.
19
+ The corpus snapshot was retrieved on 2026-08-11.
20
+
21
+ `prologue_quad_runner.pl` loads EyeProlog's `library(prologue)` and includes
22
+ the unmodified corpus, mirroring the draft's requirement that a Prologue be
23
+ included before its examples are run.
@@ -0,0 +1,195 @@
1
+ c1 ?- call(1).
2
+ type_error(callable,1).
3
+
4
+ c2 ?- call((1,fail)).
5
+ type_error(callable,(1,fail)).
6
+
7
+ c3 ?- call((fail,1)).
8
+ type_error(callable,(fail,1)).
9
+
10
+ c4 ?- call((!;1)).
11
+ type_error(callable,(!;1)).
12
+
13
+ c5 ?- call((!;\+1)).
14
+ true.
15
+
16
+ c6 ?- call((!;call(1))).
17
+ true.
18
+
19
+ c7 ?- call((\+!;X=a)).
20
+ X=a.
21
+
22
+ f1 ?- functor(T,F,A).
23
+ instantiation_error.
24
+
25
+ f2 ?- functor(T,1,2).
26
+ type_error(atom,1).
27
+
28
+ f3 ?- functor([_],.,2).
29
+ true.
30
+
31
+ 1 ?- phrase(=,L).
32
+ L = []
33
+ | existence_error(non_terminal,(=)//0).
34
+
35
+ 2 ?- phrase(1,L).
36
+ type_error(callable,1).
37
+
38
+ 39 ?- phrase(K,L).
39
+ instantiation_error.
40
+
41
+ 40 ?- K = [], phrase(K,L).
42
+ K = [], L = [].
43
+
44
+ 24 ?- asserta((a-->b)).
45
+ permission_error(modify,static_procedure,(-->)/2).
46
+
47
+ 25 ?- clause((a-->b),B).
48
+ permission_error(access,private_procedure,(-->)/2).
49
+
50
+ 26 ?- (X-->Y).
51
+ existence_error(procedure,(-->)/2).
52
+
53
+ 3 ?- phrase(!,L).
54
+ L = [].
55
+
56
+ 4 ?- phrase([a],L).
57
+ L = [a].
58
+
59
+ 5 ?- phrase([a|b],L).
60
+ type_error(list,[a|b]).
61
+
62
+ 6 ?- phrase([a|L],K).
63
+ instantiation_error.
64
+
65
+ 38 ?- phrase([a|L],L).
66
+ instantiation_error.
67
+
68
+ 7 ?- phrase([a|L],[a,b]).
69
+ instantiation_error.
70
+
71
+ 8 ?- phrase([a|L],[]).
72
+ instantiation_error.
73
+
74
+ 9 ?- phrase(([a],[]),[a]).
75
+ true.
76
+
77
+ 10 ?- phrase(([a],{1}),[]).
78
+ type_error(callable,(...,...)).
79
+
80
+ 37 ?- phrase((!,[a],{1}),[]).
81
+ type_error(callable,(...,...)).
82
+
83
+ 11 ?- phrase(({!,fail};[]),L).
84
+ false.
85
+
86
+ 12 ?- phrase('|'([],[a]),[a]).
87
+ true.
88
+
89
+ 13 ?- phrase(({fail},1),L).
90
+ type_error(callable,1).
91
+
92
+ 14 ?- phrase(([a];[]),L).
93
+ L = [a] ; L = [].
94
+
95
+ 15 ?- phrase({fail,1},L).
96
+ type_error(callable,((fail,1),[]=_A))
97
+ | type_error(callable,((fail,1),...)).
98
+
99
+ 16 ?- phrase({throw(h)},[a]).
100
+ throw(h).
101
+
102
+ 17 ?- phrase(({L=[]},[a|L]),[a]).
103
+ instantiation_error.
104
+
105
+ 18 ?- phrase([a|L],K), L=[b].
106
+ instantiation_error.
107
+
108
+ 19 ?- phrase(([a|L],1),[]).
109
+ type_error(callable,1)
110
+ | instantiation_error.
111
+
112
+ 20 ?- phrase((1,[a|L]),[]).
113
+ type_error(callable,1)
114
+ | instantiation_error.
115
+
116
+ 21 ?- phrase((1,[a|b]),[]).
117
+ type_error(callable,1)
118
+ | type_error(list,[a|b]).
119
+
120
+ 46 ?- phrase((1,{2}),[]).
121
+ type_error(callable,1).
122
+
123
+ 47 ?- phrase(({2},1),[]).
124
+ type_error(callable,1).
125
+
126
+ 48 ?- phrase((1,(2,[_|_],3),4),[]).
127
+ type_error(callable,1)
128
+ | type_error(callable,2)
129
+ | instantiation_error
130
+ | type_error(callable,3)
131
+ | type_error(callable,4).
132
+
133
+
134
+ 22 ?- phrase('|'(([x]->[y]),[z]),L).
135
+ representation_error(dcg_body)
136
+ | L=[x,y].
137
+
138
+ 23 ?- phrase(;(([x]->[y]),[z]),L).
139
+ L=[x,y].
140
+
141
+ 45 ?- phrase(([a],phrase(2)),[]).
142
+ false.
143
+
144
+ 27 ?- phrase(\+[a],[]).
145
+ representation_error(dcg_body)
146
+ | true.
147
+
148
+ 28 ?- phrase(\+1,L).
149
+ representation_error(dcg_body)
150
+ | type_error(callable,1).
151
+
152
+ 29 ?- phrase(([a],\+1),[]).
153
+ representation_error(dcg_body)
154
+ | false.
155
+
156
+ 30 ?- phrase(([a],\+1;[]),[]).
157
+ representation_error(dcg_body)
158
+ | true.
159
+
160
+ 31 ?- phrase(phrase(phrase,[]),L).
161
+ existence_error(procedure,phrase/4)
162
+ | existence_error(non_terminal,phrase//2)
163
+ | L = [].
164
+
165
+ 32 ?- phrase(call([]),[]).
166
+ existence_error(procedure,[]/2)
167
+ | true.
168
+
169
+ 33 ?- L=[],phrase([a|L],[b]).
170
+ false.
171
+
172
+ 36 ?- L=[],phrase([a|L],[a]).
173
+ L=[].
174
+
175
+ 34 ?- phrase([a],[b]).
176
+ false.
177
+
178
+ 35 ?- phrase(!,[_]) ; L=1.
179
+ L=1.
180
+
181
+ 41 ?- phrase([], non_list).
182
+ false
183
+ | type_error(list,non_list).
184
+
185
+ 42 ?- phrase([], [a|non_list]).
186
+ false
187
+ | type_error(list,[a|non_list]).
188
+
189
+ 43 ?- phrase([], L,non_list).
190
+ L = non_list
191
+ | type_error(list,non_list).
192
+
193
+ 44 ?- phrase([], L,[a|non_list]).
194
+ L = [a|non_list]
195
+ | type_error(list,[a|non_list]).
@@ -0,0 +1,134 @@
1
+ % p.p.1 member/2
2
+
3
+ ?- member(X, [1,2]).
4
+ X = 1
5
+ ; X = 2.
6
+
7
+ ?- member(1, L).
8
+ L = [1|_]
9
+ ; L = [_,1|_]
10
+ ; L = [_,_,1|_]
11
+ ; ... . % Ad infinitum.
12
+
13
+ ?- member(X, [Y,Z|nonlist]).
14
+ X = Y
15
+ ; X = Z.
16
+
17
+ ?- member(X, nonlist).
18
+ false.
19
+
20
+ ?- member(X, X).
21
+ sto, % undefined, STO 7.3.3
22
+ true
23
+ | sto,
24
+ loops.
25
+
26
+ % p.p.2 append/3
27
+
28
+ ?- append([a,b],[c,d], Xs).
29
+ Xs = [a,b,c,d].
30
+
31
+ ?- append([a], nonlist, Xs).
32
+ Xs = [a|nonlist].
33
+
34
+ ?- append([a], Ys, Zs).
35
+ Zs = [a|Ys].
36
+
37
+ ?- append(Xs, Ys, [a,b,c]).
38
+ Xs = [], Ys = [a,b,c]
39
+ ; Xs = [a], Ys = [b,c]
40
+ ; Xs = [a,b], Ys = [c]
41
+ ; Xs = [a,b,c], Ys = [].
42
+
43
+ % p.p.3 length/2
44
+
45
+ ?- length([a,b,c], Length).
46
+ Length = 3.
47
+
48
+ ?- length(List, 5).
49
+ List = [_,_,_,_,_].
50
+
51
+ ?- length(List, Length).
52
+ List = [], Length = 0
53
+ ; List = [_], Length = 1
54
+ ; List = [_,_], Length = 2
55
+ ; ... . % Ad infinitum.
56
+
57
+ % p.p.4 between/3
58
+
59
+ ?- between(1, 2, 0).
60
+ false.
61
+
62
+ ?- between(1, 2, I).
63
+ I = 1
64
+ ; I = 2.
65
+
66
+ ?- between(2, 1, I).
67
+ false.
68
+
69
+ ?- between(I, I, 0).
70
+ instantiation_error.
71
+
72
+ ?- between(1, I, 0).
73
+ instantiation_error.
74
+
75
+ ?- between(I, -1, 0).
76
+ instantiation_error.
77
+
78
+ ?- between(1, c, 0).
79
+ type_error(integer,c).
80
+
81
+ ?- between(1+1,2,I).
82
+ type_error(integer,1+1).
83
+
84
+ % p.p.5 select/3
85
+
86
+ ?- select(X, [1,2], Xs).
87
+ X = 1, Xs = [2]
88
+ ; X = 2, Xs = [1].
89
+
90
+ ?- select(X, [Y|nonlist], Xs).
91
+ X = Y, Xs = nonlist.
92
+
93
+ ?- select(E, Xs, Xs).
94
+ sto.
95
+
96
+ % p.p.6 succ/2
97
+
98
+ ?- succ(X, S).
99
+ instantiation_error.
100
+
101
+ ?- succ(X, X).
102
+ instantiation_error.
103
+
104
+ ?- succ(0, S).
105
+ S = 1.
106
+
107
+ ?- succ(1, 1+1).
108
+ type_error(integer, 1+1).
109
+
110
+ ?- succ(X, 0).
111
+ false.
112
+
113
+ ?- succ(-1, S).
114
+ domain_error(not_less_than_zero, -1).
115
+
116
+ ?- current_prolog_flag(max_integer, Max),
117
+ ( integer(Max) -> succ(Max, S) ; true ).
118
+ evaluation_error(int_overflow)
119
+ | Max = unbounded.
120
+
121
+ % p.p.7
122
+
123
+ ?- maplist(>(3), [1, 2]).
124
+ true.
125
+
126
+ ?- maplist(>(3), [1, 2, 3]).
127
+ false.
128
+
129
+ ?- maplist(=(X), Xs).
130
+ Xs = []
131
+ ; Xs = [X]
132
+ ; Xs = [X, X]
133
+ ; Xs = [X, X, X]
134
+ ; ... . % Ad infinitum.
@@ -0,0 +1,2 @@
1
+ :- use_module(library(prologue)).
2
+ :- include('prologue_quad.pl').
@@ -309,6 +309,34 @@ c4 ?- call((!;1)).
309
309
  assertEqual(result.stdout, 'quads: 22 run, 22 passed, 0 failed.\n', 'quad report');
310
310
  },
311
311
  },
312
+ {
313
+ name: 'runQuads passes the complete vendored ISO phrase quad corpus',
314
+ run: () => {
315
+ const source = fs.readFileSync(path.join(testRoot, 'fixtures', 'phrase_quad.pl'), 'utf8');
316
+ const result = publicApi.runQuads(Program.parseSources([{
317
+ text: source,
318
+ filename: 'test/fixtures/phrase_quad.pl',
319
+ }]));
320
+ assertEqual(result.total, 58, 'quad total');
321
+ assertEqual(result.passed, 58, 'quad passed');
322
+ assertEqual(result.stdout, 'quads: 58 run, 58 passed, 0 failed.\n', 'quad report');
323
+ },
324
+ },
325
+ {
326
+ name: 'runQuads passes the complete vendored Prolog Prologue quad corpus',
327
+ run: () => {
328
+ const filename = path.join(testRoot, 'fixtures', 'prologue_quad_runner.pl');
329
+ const source = fs.readFileSync(filename, 'utf8');
330
+ const result = publicApi.runQuads(Program.parseSources([{
331
+ text: source,
332
+ filename,
333
+ baseDir: path.dirname(filename),
334
+ }]));
335
+ assertEqual(result.total, 33, 'quad total');
336
+ assertEqual(result.passed, 33, 'quad passed');
337
+ assertEqual(result.stdout, 'quads: 33 run, 33 passed, 0 failed.\n', 'quad report');
338
+ },
339
+ },
312
340
  {
313
341
  name: 'runQuads rejects malformed answer substitutions',
314
342
  run: () => {
@@ -848,7 +876,7 @@ function documentationSyncCases() {
848
876
  '<a href="https://eyereasoner.github.io/eyeprolog/the-art-of-eyeprolog">\n <img src="book-assets/title-page.svg" alt="Read The Art of EyeProlog"',
849
877
  'README cover links to the book',
850
878
  );
851
- for (const filename of ['src/iso.js', 'src/dcg.js', 'src/standard-library.js', 'src/lib/eyeprolog.pl', 'src/lib/lists.pl', 'src/playground-worker.js']) {
879
+ for (const filename of ['src/iso.js', 'src/dcg.js', 'src/standard-library.js', 'src/lib/eyeprolog.pl', 'src/lib/lists.pl', 'src/lib/prologue.pl', 'src/playground-worker.js']) {
852
880
  assertEqual(fs.existsSync(path.join(packageRoot, filename)), true, `${filename} exists`);
853
881
  assertIncludes(book, filename, `book documents ${filename}`);
854
882
  }
@@ -1287,10 +1315,10 @@ open(X) :- candidate(X), \\+ closed(X).
1287
1315
  assertEqual(Boolean(registry.get('phrase', 2)), true, 'Part 3 phrase/2 exists');
1288
1316
  assertEqual(Boolean(registry.get('phrase', 3)), true, 'Part 3 phrase/3 exists');
1289
1317
  assertEqual(registeredNativeEyePrologLibraryNames().length, 0, 'public native EyeProlog builtin count');
1290
- assertEqual(eyePrologPortableLibraryIndicators.length, 44, 'portable Prolog library count');
1318
+ assertEqual(eyePrologPortableLibraryIndicators.length, 46, 'portable Prolog library count');
1291
1319
  assertEqual(eyePrologNativeLibraryIndicators.length, 0, 'native host library count');
1292
1320
  assertEqual(eyePrologNativeLibraryIndicators.join(','), '', 'no EyeProlog library predicate requires host support');
1293
- assertEqual(eyePrologLibraryIndicators.length, 44, 'complete EyeProlog library surface');
1321
+ assertEqual(eyePrologLibraryIndicators.length, 46, 'complete EyeProlog library surface');
1294
1322
  assertEqual(library.get('between', 3), null, 'between/3 remains portable Prolog');
1295
1323
  assertEqual(library.get('smallest_divisor_from', 3), null, 'smallest_divisor_from/3 remains portable Prolog');
1296
1324
  assertEqual(library.get('random', 3), null, 'random/3 remains portable Prolog');
@@ -1318,6 +1346,7 @@ open(X) :- candidate(X), \\+ closed(X).
1318
1346
  assertEqual(fs.existsSync(path.join(packageRoot, 'src', 'lib', 'eyeprolog.pl')), true, 'portable module exists');
1319
1347
  assertEqual(fs.existsSync(path.join(packageRoot, 'src', 'standard-library.js')), true, 'standard module registry exists');
1320
1348
  assertEqual(fs.existsSync(path.join(packageRoot, 'src', 'lib', 'lists.pl')), true, 'lists module exists');
1349
+ assertEqual(fs.existsSync(path.join(packageRoot, 'src', 'lib', 'prologue.pl')), true, 'Prologue module exists');
1321
1350
  assertEqual(fs.existsSync(path.join(packageRoot, 'src', 'eyeprolog-autoload.js')), false, 'obsolete autoloader is absent');
1322
1351
  assertEqual(fs.existsSync(path.join(packageRoot, 'src', 'library-source.js')), false, 'duplicate source loader is absent');
1323
1352
  assertEqual(fs.existsSync(path.join(packageRoot, 'src', 'portable-library.js')), false, 'obsolete duplicate module remains absent');
@@ -5580,6 +5580,8 @@ silently changing a static program.
5580
5580
  | `integer_rounding_function` | `toward_zero` | `toward_zero` | no |
5581
5581
  | `char_conversion` | `on` | `on`, `off` | yes |
5582
5582
  | `debug` | `off` | `on`, `off` | yes |
5583
+ | `max_integer` | `unbounded` | `unbounded` | no |
5584
+ | `min_integer` | `unbounded` | `unbounded` | no |
5583
5585
  | `max_arity` | `unbounded` | `unbounded` | no |
5584
5586
  | `unknown` | `fail` | `error`, `fail`, `warning` | yes |
5585
5587
  | `double_quotes` | `chars` | `chars`, `codes`, `atom` | yes |
@@ -5735,17 +5737,20 @@ so side effects occur in Prolog execution order.
5735
5737
 
5736
5738
  ### The EyeProlog library
5737
5739
 
5738
- EyeProlog exposes **44 library predicate indicators** in addition to the 129
5739
- indicators in its isolated ISO profile. **All 44 are ordinary Prolog clauses**
5740
- across `src/lib/eyeprolog.pl` and `src/lib/lists.pl`; none
5740
+ EyeProlog exposes **46 library predicate indicators** in addition to the 129
5741
+ indicators in its isolated ISO profile. **All 46 are ordinary Prolog clauses**
5742
+ across `src/lib/eyeprolog.pl`, `src/lib/lists.pl`, and `src/lib/prologue.pl`; none
5741
5743
  is a native host predicate. The resulting
5742
- normal EyeProlog language surface is therefore **173 public predicate
5744
+ normal EyeProlog language surface is therefore **175 public predicate
5743
5745
  indicators**. Internally, the runtime registry contains only the 129 ISO
5744
5746
  definitions; the EyeProlog relations are module source clauses.
5745
5747
 
5746
- The two Prolog files declare `eyeprolog` and `lists` with `module/2`. A program
5747
- loads them explicitly with `use_module(library(eyeprolog))` and
5748
- `use_module(library(lists))`; `use_module/2` can select a smaller import list.
5748
+ The three Prolog files declare `eyeprolog`, `lists`, and `prologue` with
5749
+ `module/2`. A program loads them explicitly with
5750
+ `use_module(library(eyeprolog))`, `use_module(library(lists))`, or
5751
+ `use_module(library(prologue))`; `use_module/2` can select a smaller import
5752
+ list. The last module implements the predicates exercised by the working-draft
5753
+ Prologue quad corpus.
5749
5754
  `src/standard-library.js` only registers the module sources for Node and browser
5750
5755
  resolution and never adds clauses implicitly.
5751
5756
  The isolated ISO-only registry remains
@@ -5759,6 +5764,7 @@ private helpers and same-named predicates in different modules separate.
5759
5764
  | --- | --- |
5760
5765
  | `library(lists)` | `maplist/3`, `append/3`, `member/2`, `select/3`, `last/2`, `nth0/3`, `nth1/3`, `reverse/2`, `length/2`, `sum_list/2`, `min_list/2`, `max_list/2`, `list_to_set/2`, `countall/2` |
5761
5766
  | `library(eyeprolog)` | `uuid/3`, `difference/3`, `lt/2`, `le/2`, `gt/2`, `ge/2`, `between/3`, `smallest_divisor_from/3`, `random/3`, `matches/3`, `split/3`, `replace/4`, `lowercase/2`, `uppercase/2`, `trim/2`, `number_string/2`, `atom_string/2`, `term_string/2`, `string_concat/3`, `contains/2`, `matches/2`, `join/3`, `substring/4`, `set_nth0/4`, `take/3`, `drop/3`, `slice/4`, `sumall/3`, `aggregate_min/5`, `aggregate_max/5` |
5767
+ | `library(prologue)` | `member/2`, `append/3`, `length/2`, `between/3`, `select/3`, `succ/2`, `maplist/2` |
5762
5768
 
5763
5769
  <!-- eyeprolog-library-catalog:end -->
5764
5770
 
@@ -5885,7 +5891,7 @@ eyeprolog --goal 'answer(Kind, Value)' program.pl
5885
5891
  The portable text API uses **ISO atoms or proper lists of one-character atoms**.
5886
5892
  A generated text result defaults to an atom. Double-quoted source text uses the
5887
5893
  ISO representation selected by `double_quotes`; with the default `chars`, it is
5888
- already a proper character list accepted by this API. The 44-predicate portable
5894
+ already a proper character list accepted by this API. The 46-predicate portable
5889
5895
  library itself has no STRING or JavaScript dependency.
5890
5896
 
5891
5897
  | Predicate and principal mode | Behavior |