eyeprolog 1.1.9 → 1.1.11
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 +35 -10
- package/package.json +1 -1
- package/src/lib/prologue.pl +87 -0
- package/src/solver.js +2 -0
- package/src/standard-library.js +3 -2
- package/test/conformance/expected/iso/exceptions_and_flags.pl +1 -1
- package/test/fixtures/README.md +13 -0
- package/test/fixtures/prologue_quad.pl +134 -0
- package/test/fixtures/prologue_quad_runner.pl +2 -0
- package/test/run-regression.mjs +56 -3
- package/the-art-of-eyeprolog.md +38 -8
package/README.md
CHANGED
|
@@ -21,11 +21,22 @@ examples, proofs, conformance, and implementation.
|
|
|
21
21
|
|
|
22
22
|
## Quick start
|
|
23
23
|
|
|
24
|
-
EyeProlog requires Node.js 18 or newer.
|
|
24
|
+
EyeProlog requires Node.js 18 or newer. Check the active runtime before
|
|
25
|
+
installing:
|
|
25
26
|
|
|
26
27
|
```sh
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
node --version
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
If it reports an older release, upgrade through a Node version manager or the
|
|
32
|
+
[official Node.js download](https://nodejs.org/en/download) and check again.
|
|
33
|
+
Distribution packages can provide an older Node.js even on a current operating
|
|
34
|
+
system.
|
|
35
|
+
|
|
36
|
+
Run EyeProlog without a global installation:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
npx --yes eyeprolog
|
|
29
40
|
?- use_module(library(lists)).
|
|
30
41
|
true.
|
|
31
42
|
?- member(X, [prolog, logic]).
|
|
@@ -34,11 +45,25 @@ eyeprolog
|
|
|
34
45
|
?- halt.
|
|
35
46
|
```
|
|
36
47
|
|
|
48
|
+
For a persistent `eyeprolog` command without administrator access, install it
|
|
49
|
+
under a user-owned prefix and put that prefix's `bin` directory on `PATH`:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
npm install --global --prefix "$HOME/.local" eyeprolog
|
|
53
|
+
export PATH="$HOME/.local/bin:$PATH"
|
|
54
|
+
eyeprolog
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Add the `PATH` export to your shell startup file to keep it across sessions.
|
|
58
|
+
Do not use `sudo npm install`; npm's
|
|
59
|
+
[EACCES guidance](https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally/)
|
|
60
|
+
also recommends a Node version manager or a user-owned npm prefix.
|
|
61
|
+
|
|
37
62
|
For a non-interactive run:
|
|
38
63
|
|
|
39
64
|
```sh
|
|
40
65
|
printf 'human(socrates).\nmortal(X) :- human(X).\n' |
|
|
41
|
-
eyeprolog --proof --goal 'mortal(socrates)' -
|
|
66
|
+
npx --yes eyeprolog --proof --goal 'mortal(socrates)' -
|
|
42
67
|
```
|
|
43
68
|
|
|
44
69
|
Programs may declare their default queries with `%% goal:` comments.
|
|
@@ -66,12 +91,12 @@ noun --> [world] | [prolog].
|
|
|
66
91
|
%% goal: phrase(sentence, Words)
|
|
67
92
|
```
|
|
68
93
|
|
|
69
|
-
EyeProlog also adds
|
|
70
|
-
**All
|
|
71
|
-
`src/lib/lists.pl`. They are ISO/IEC 13211-2 modules,
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
predicates use ISO atoms or character lists.
|
|
94
|
+
EyeProlog also adds 46 public library predicates to its 129-entry ISO registry.
|
|
95
|
+
**All 46 are ordinary Prolog clauses** in `src/lib/eyeprolog.pl`,
|
|
96
|
+
`src/lib/lists.pl`, and `src/lib/prologue.pl`. They are ISO/IEC 13211-2 modules,
|
|
97
|
+
loaded explicitly with `use_module(library(eyeprolog))`,
|
|
98
|
+
`use_module(library(lists))`, or `use_module(library(prologue))`. None requires
|
|
99
|
+
host support. Portable text predicates use ISO atoms or character lists.
|
|
75
100
|
|
|
76
101
|
## Development
|
|
77
102
|
|
package/package.json
CHANGED
|
@@ -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 }],
|
package/src/standard-library.js
CHANGED
|
@@ -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)]).
|
package/test/fixtures/README.md
CHANGED
|
@@ -8,3 +8,16 @@
|
|
|
8
8
|
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
|
+
|
|
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,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.
|
package/test/run-regression.mjs
CHANGED
|
@@ -322,6 +322,21 @@ c4 ?- call((!;1)).
|
|
|
322
322
|
assertEqual(result.stdout, 'quads: 58 run, 58 passed, 0 failed.\n', 'quad report');
|
|
323
323
|
},
|
|
324
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
|
+
},
|
|
325
340
|
{
|
|
326
341
|
name: 'runQuads rejects malformed answer substitutions',
|
|
327
342
|
run: () => {
|
|
@@ -501,6 +516,27 @@ c4 ?- call((!;1)).
|
|
|
501
516
|
assertEqual(result.stderr, '', 'stderr');
|
|
502
517
|
},
|
|
503
518
|
},
|
|
519
|
+
{
|
|
520
|
+
name: 'npm can install the CLI under a user-owned prefix',
|
|
521
|
+
run: () => {
|
|
522
|
+
const prefix = path.join(tmp, `npm-prefix-${++tmpCounter}`);
|
|
523
|
+
const installed = spawnSync('npm', [
|
|
524
|
+
'install', '--global', '--prefix', prefix, '--loglevel=silent', '--no-audit', '--no-fund', '.',
|
|
525
|
+
], {
|
|
526
|
+
cwd: packageRoot,
|
|
527
|
+
encoding: 'utf8',
|
|
528
|
+
env: { ...process.env, npm_config_update_notifier: 'false' },
|
|
529
|
+
});
|
|
530
|
+
assertEqual(installed.status, 0, 'install exit status');
|
|
531
|
+
const executable = process.platform === 'win32'
|
|
532
|
+
? path.join(prefix, 'eyeprolog.cmd')
|
|
533
|
+
: path.join(prefix, 'bin', 'eyeprolog');
|
|
534
|
+
const result = spawnSync(executable, ['--version'], { encoding: 'utf8' });
|
|
535
|
+
assertEqual(result.status, 0, 'installed CLI exit status');
|
|
536
|
+
assertEqual(result.stdout, `eyeprolog ${pkg.version}\n`, 'installed CLI stdout');
|
|
537
|
+
assertEqual(result.stderr, '', 'installed CLI stderr');
|
|
538
|
+
},
|
|
539
|
+
},
|
|
504
540
|
{
|
|
505
541
|
name: 'stdin input is accepted',
|
|
506
542
|
run: () => {
|
|
@@ -861,7 +897,7 @@ function documentationSyncCases() {
|
|
|
861
897
|
'<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"',
|
|
862
898
|
'README cover links to the book',
|
|
863
899
|
);
|
|
864
|
-
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']) {
|
|
900
|
+
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']) {
|
|
865
901
|
assertEqual(fs.existsSync(path.join(packageRoot, filename)), true, `${filename} exists`);
|
|
866
902
|
assertIncludes(book, filename, `book documents ${filename}`);
|
|
867
903
|
}
|
|
@@ -964,6 +1000,22 @@ function documentationSyncCases() {
|
|
|
964
1000
|
assertArrayEqual(misleadingDependencyInstallDocs(), [], 'misleading dependency install docs');
|
|
965
1001
|
},
|
|
966
1002
|
},
|
|
1003
|
+
{
|
|
1004
|
+
name: 'installation docs avoid unsupported Node and global npm permission traps',
|
|
1005
|
+
run: () => {
|
|
1006
|
+
assertEqual(pkg.engines?.node, '>=18', 'supported Node range');
|
|
1007
|
+
for (const filename of ['README.md', 'the-art-of-eyeprolog.md']) {
|
|
1008
|
+
const text = fs.readFileSync(path.join(packageRoot, filename), 'utf8');
|
|
1009
|
+
assertIncludes(text, 'node --version', `${filename} checks Node version`);
|
|
1010
|
+
assertIncludes(text, 'npx --yes eyeprolog', `${filename} offers a non-global launch`);
|
|
1011
|
+
assertIncludes(text, 'npm install --global --prefix "$HOME/.local" eyeprolog', `${filename} uses a user prefix`);
|
|
1012
|
+
assertIncludes(text, 'https://nodejs.org/en/download', `${filename} links Node upgrades`);
|
|
1013
|
+
assertIncludes(text, 'https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally/', `${filename} links npm EACCES guidance`);
|
|
1014
|
+
assertEqual(text.includes('sudo npm install'), true, `${filename} explicitly warns against sudo npm`);
|
|
1015
|
+
assertEqual(/^\s*sudo npm install/m.test(text), false, `${filename} never recommends sudo npm`);
|
|
1016
|
+
}
|
|
1017
|
+
},
|
|
1018
|
+
},
|
|
967
1019
|
];
|
|
968
1020
|
}
|
|
969
1021
|
|
|
@@ -1300,10 +1352,10 @@ open(X) :- candidate(X), \\+ closed(X).
|
|
|
1300
1352
|
assertEqual(Boolean(registry.get('phrase', 2)), true, 'Part 3 phrase/2 exists');
|
|
1301
1353
|
assertEqual(Boolean(registry.get('phrase', 3)), true, 'Part 3 phrase/3 exists');
|
|
1302
1354
|
assertEqual(registeredNativeEyePrologLibraryNames().length, 0, 'public native EyeProlog builtin count');
|
|
1303
|
-
assertEqual(eyePrologPortableLibraryIndicators.length,
|
|
1355
|
+
assertEqual(eyePrologPortableLibraryIndicators.length, 46, 'portable Prolog library count');
|
|
1304
1356
|
assertEqual(eyePrologNativeLibraryIndicators.length, 0, 'native host library count');
|
|
1305
1357
|
assertEqual(eyePrologNativeLibraryIndicators.join(','), '', 'no EyeProlog library predicate requires host support');
|
|
1306
|
-
assertEqual(eyePrologLibraryIndicators.length,
|
|
1358
|
+
assertEqual(eyePrologLibraryIndicators.length, 46, 'complete EyeProlog library surface');
|
|
1307
1359
|
assertEqual(library.get('between', 3), null, 'between/3 remains portable Prolog');
|
|
1308
1360
|
assertEqual(library.get('smallest_divisor_from', 3), null, 'smallest_divisor_from/3 remains portable Prolog');
|
|
1309
1361
|
assertEqual(library.get('random', 3), null, 'random/3 remains portable Prolog');
|
|
@@ -1331,6 +1383,7 @@ open(X) :- candidate(X), \\+ closed(X).
|
|
|
1331
1383
|
assertEqual(fs.existsSync(path.join(packageRoot, 'src', 'lib', 'eyeprolog.pl')), true, 'portable module exists');
|
|
1332
1384
|
assertEqual(fs.existsSync(path.join(packageRoot, 'src', 'standard-library.js')), true, 'standard module registry exists');
|
|
1333
1385
|
assertEqual(fs.existsSync(path.join(packageRoot, 'src', 'lib', 'lists.pl')), true, 'lists module exists');
|
|
1386
|
+
assertEqual(fs.existsSync(path.join(packageRoot, 'src', 'lib', 'prologue.pl')), true, 'Prologue module exists');
|
|
1334
1387
|
assertEqual(fs.existsSync(path.join(packageRoot, 'src', 'eyeprolog-autoload.js')), false, 'obsolete autoloader is absent');
|
|
1335
1388
|
assertEqual(fs.existsSync(path.join(packageRoot, 'src', 'library-source.js')), false, 'duplicate source loader is absent');
|
|
1336
1389
|
assertEqual(fs.existsSync(path.join(packageRoot, 'src', 'portable-library.js')), false, 'obsolete duplicate module remains absent');
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -79,6 +79,30 @@ Then ask for the derivations:
|
|
|
79
79
|
node bin/eyeprolog.js --proof examples/socrates.pl
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
+
To use the published package, first verify that `node --version` reports Node.js
|
|
83
|
+
18 or newer. Upgrade an older runtime through a Node version manager or the
|
|
84
|
+
[official Node.js download](https://nodejs.org/en/download). A current Linux
|
|
85
|
+
distribution can still expose an older Node.js package.
|
|
86
|
+
|
|
87
|
+
The package can be launched without a global installation:
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
npx --yes eyeprolog
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
For a persistent command without administrator access, install into a
|
|
94
|
+
user-owned prefix:
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
npm install --global --prefix "$HOME/.local" eyeprolog
|
|
98
|
+
export PATH="$HOME/.local/bin:$PATH"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Persist the `PATH` export in the appropriate shell startup file. Do not use
|
|
102
|
+
`sudo npm install`; npm's
|
|
103
|
+
[EACCES guidance](https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally/)
|
|
104
|
+
recommends a Node version manager or a user-owned npm prefix.
|
|
105
|
+
|
|
82
106
|
Readers who do not want to install anything can begin in the
|
|
83
107
|
[browser playground](https://eyereasoner.github.io/eyeprolog/playground). Paste
|
|
84
108
|
the source of `examples/socrates.pl` into the editor and run it. The playground
|
|
@@ -5580,6 +5604,8 @@ silently changing a static program.
|
|
|
5580
5604
|
| `integer_rounding_function` | `toward_zero` | `toward_zero` | no |
|
|
5581
5605
|
| `char_conversion` | `on` | `on`, `off` | yes |
|
|
5582
5606
|
| `debug` | `off` | `on`, `off` | yes |
|
|
5607
|
+
| `max_integer` | `unbounded` | `unbounded` | no |
|
|
5608
|
+
| `min_integer` | `unbounded` | `unbounded` | no |
|
|
5583
5609
|
| `max_arity` | `unbounded` | `unbounded` | no |
|
|
5584
5610
|
| `unknown` | `fail` | `error`, `fail`, `warning` | yes |
|
|
5585
5611
|
| `double_quotes` | `chars` | `chars`, `codes`, `atom` | yes |
|
|
@@ -5735,17 +5761,20 @@ so side effects occur in Prolog execution order.
|
|
|
5735
5761
|
|
|
5736
5762
|
### The EyeProlog library
|
|
5737
5763
|
|
|
5738
|
-
EyeProlog exposes **
|
|
5739
|
-
indicators in its isolated ISO profile. **All
|
|
5740
|
-
across `src/lib/eyeprolog.pl` and `src/lib/
|
|
5764
|
+
EyeProlog exposes **46 library predicate indicators** in addition to the 129
|
|
5765
|
+
indicators in its isolated ISO profile. **All 46 are ordinary Prolog clauses**
|
|
5766
|
+
across `src/lib/eyeprolog.pl`, `src/lib/lists.pl`, and `src/lib/prologue.pl`; none
|
|
5741
5767
|
is a native host predicate. The resulting
|
|
5742
|
-
normal EyeProlog language surface is therefore **
|
|
5768
|
+
normal EyeProlog language surface is therefore **175 public predicate
|
|
5743
5769
|
indicators**. Internally, the runtime registry contains only the 129 ISO
|
|
5744
5770
|
definitions; the EyeProlog relations are module source clauses.
|
|
5745
5771
|
|
|
5746
|
-
The
|
|
5747
|
-
loads them explicitly with
|
|
5748
|
-
`use_module(library(
|
|
5772
|
+
The three Prolog files declare `eyeprolog`, `lists`, and `prologue` with
|
|
5773
|
+
`module/2`. A program loads them explicitly with
|
|
5774
|
+
`use_module(library(eyeprolog))`, `use_module(library(lists))`, or
|
|
5775
|
+
`use_module(library(prologue))`; `use_module/2` can select a smaller import
|
|
5776
|
+
list. The last module implements the predicates exercised by the working-draft
|
|
5777
|
+
Prologue quad corpus.
|
|
5749
5778
|
`src/standard-library.js` only registers the module sources for Node and browser
|
|
5750
5779
|
resolution and never adds clauses implicitly.
|
|
5751
5780
|
The isolated ISO-only registry remains
|
|
@@ -5759,6 +5788,7 @@ private helpers and same-named predicates in different modules separate.
|
|
|
5759
5788
|
| --- | --- |
|
|
5760
5789
|
| `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
5790
|
| `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` |
|
|
5791
|
+
| `library(prologue)` | `member/2`, `append/3`, `length/2`, `between/3`, `select/3`, `succ/2`, `maplist/2` |
|
|
5762
5792
|
|
|
5763
5793
|
<!-- eyeprolog-library-catalog:end -->
|
|
5764
5794
|
|
|
@@ -5885,7 +5915,7 @@ eyeprolog --goal 'answer(Kind, Value)' program.pl
|
|
|
5885
5915
|
The portable text API uses **ISO atoms or proper lists of one-character atoms**.
|
|
5886
5916
|
A generated text result defaults to an atom. Double-quoted source text uses the
|
|
5887
5917
|
ISO representation selected by `double_quotes`; with the default `chars`, it is
|
|
5888
|
-
already a proper character list accepted by this API. The
|
|
5918
|
+
already a proper character list accepted by this API. The 46-predicate portable
|
|
5889
5919
|
library itself has no STRING or JavaScript dependency.
|
|
5890
5920
|
|
|
5891
5921
|
| Predicate and principal mode | Behavior |
|