eyeprolog 1.1.10 → 1.1.12
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 +36 -9
- package/package.json +1 -1
- package/src/iso.js +39 -0
- package/src/lib/prologue.pl +105 -1
- package/src/standard-library.js +12 -6
- package/test/fixtures/prologue_call_nth_quad.pl +32 -0
- package/test/fixtures/prologue_call_nth_quad_runner.pl +2 -0
- package/test/fixtures/prologue_extended_quad.pl +87 -0
- package/test/fixtures/prologue_extended_quad_runner.pl +13 -0
- package/test/run-regression.mjs +77 -7
- package/the-art-of-eyeprolog.md +40 -13
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,14 @@ noun --> [world] | [prolog].
|
|
|
66
91
|
%% goal: phrase(sentence, Words)
|
|
67
92
|
```
|
|
68
93
|
|
|
69
|
-
EyeProlog also adds
|
|
70
|
-
**
|
|
71
|
-
`src/lib/lists.pl`, and `src/lib/prologue.pl
|
|
94
|
+
EyeProlog also adds 57 public library predicate indicators to its 129-entry ISO
|
|
95
|
+
profile. **56 are implemented entirely as ordinary Prolog clauses** in
|
|
96
|
+
`src/lib/eyeprolog.pl`, `src/lib/lists.pl`, and `src/lib/prologue.pl`; the
|
|
97
|
+
`call_nth/2` module clause uses one private host adapter to number solutions.
|
|
98
|
+
They are ISO/IEC 13211-2 modules,
|
|
72
99
|
loaded explicitly with `use_module(library(eyeprolog))`,
|
|
73
|
-
`use_module(library(lists))`, or `use_module(library(prologue))`.
|
|
74
|
-
|
|
100
|
+
`use_module(library(lists))`, or `use_module(library(prologue))`. Portable text
|
|
101
|
+
predicates use ISO atoms or character lists.
|
|
75
102
|
|
|
76
103
|
## Development
|
|
77
104
|
|
package/package.json
CHANGED
package/src/iso.js
CHANGED
|
@@ -164,6 +164,15 @@ export const isoBuiltins = {
|
|
|
164
164
|
}
|
|
165
165
|
};
|
|
166
166
|
|
|
167
|
+
// call_nth/2 is a Prologue library predicate with control behavior that cannot
|
|
168
|
+
// be expressed portably as ordinary Prolog clauses. Keep the public wrapper
|
|
169
|
+
// in library(prologue) and expose only this private adapter to the host registry.
|
|
170
|
+
export const eyePrologLibraryBuiltins = {
|
|
171
|
+
register(registry) {
|
|
172
|
+
registry.add('eyeprolog__call_nth', 2, callNthBuiltin, { eyePrologLibrary: true });
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
|
|
167
176
|
function* unification({ goal, env }) {
|
|
168
177
|
const next = env.clone();
|
|
169
178
|
if (unify(goal.args[0], goal.args[1], next)) yield next;
|
|
@@ -1564,6 +1573,36 @@ function* callClosureBuiltin({ solver, goal, env }) {
|
|
|
1564
1573
|
}
|
|
1565
1574
|
}
|
|
1566
1575
|
|
|
1576
|
+
function* callNthBuiltin({ solver, goal, env }) {
|
|
1577
|
+
const requestedTerm = deref(goal.args[1], env);
|
|
1578
|
+
// Zero is the one Nth value that fails before Goal is inspected.
|
|
1579
|
+
if (requestedTerm.type === NUMBER && isDecimalInteger(requestedTerm.name) && BigInt(requestedTerm.name) === 0n) return;
|
|
1580
|
+
|
|
1581
|
+
const invoked = callable(goal.args[0], env);
|
|
1582
|
+
let requested = null;
|
|
1583
|
+
if (requestedTerm.type !== VAR) {
|
|
1584
|
+
if (requestedTerm.type !== NUMBER || !isDecimalInteger(requestedTerm.name)) {
|
|
1585
|
+
throw new PrologError('type_error(integer)', requestedTerm);
|
|
1586
|
+
}
|
|
1587
|
+
requested = BigInt(requestedTerm.name);
|
|
1588
|
+
if (requested < 0n) throw new PrologError('domain_error(not_less_than_zero)', requestedTerm);
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
const child = solver.cloneForInnerGoal();
|
|
1592
|
+
let nth = 0n;
|
|
1593
|
+
try {
|
|
1594
|
+
for (const answerEnv of child.solve([invoked], env, 0)) {
|
|
1595
|
+
nth++;
|
|
1596
|
+
if (requested != null && nth < requested) continue;
|
|
1597
|
+
const next = answerEnv.clone();
|
|
1598
|
+
if (unify(goal.args[1], numberTerm(nth.toString()), next)) yield next;
|
|
1599
|
+
if (requested != null) return;
|
|
1600
|
+
}
|
|
1601
|
+
} finally {
|
|
1602
|
+
solver.absorbStatsFrom(child);
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1567
1606
|
function* phraseBuiltin({ solver, goal, env }) {
|
|
1568
1607
|
const grammarBody = deref(goal.args[0], env);
|
|
1569
1608
|
if (grammarBody.type === VAR) throw new PrologError('instantiation_error');
|
package/src/lib/prologue.pl
CHANGED
|
@@ -7,10 +7,36 @@
|
|
|
7
7
|
between/3,
|
|
8
8
|
select/3,
|
|
9
9
|
succ/2,
|
|
10
|
-
maplist/2
|
|
10
|
+
maplist/2,
|
|
11
|
+
maplist/3,
|
|
12
|
+
maplist/4,
|
|
13
|
+
maplist/5,
|
|
14
|
+
maplist/6,
|
|
15
|
+
maplist/7,
|
|
16
|
+
maplist/8,
|
|
17
|
+
nth0/3,
|
|
18
|
+
nth0/4,
|
|
19
|
+
nth1/3,
|
|
20
|
+
nth1/4,
|
|
21
|
+
call_nth/2,
|
|
22
|
+
foldl/4,
|
|
23
|
+
foldl/5,
|
|
24
|
+
foldl/6,
|
|
25
|
+
countall/2
|
|
11
26
|
]).
|
|
12
27
|
|
|
13
28
|
:- meta_predicate(maplist(1, '?')).
|
|
29
|
+
:- meta_predicate(maplist(2, '?', '?')).
|
|
30
|
+
:- meta_predicate(maplist(3, '?', '?', '?')).
|
|
31
|
+
:- meta_predicate(maplist(4, '?', '?', '?', '?')).
|
|
32
|
+
:- meta_predicate(maplist(5, '?', '?', '?', '?', '?')).
|
|
33
|
+
:- meta_predicate(maplist(6, '?', '?', '?', '?', '?', '?')).
|
|
34
|
+
:- meta_predicate(maplist(7, '?', '?', '?', '?', '?', '?', '?')).
|
|
35
|
+
:- meta_predicate(call_nth(0, '?')).
|
|
36
|
+
:- meta_predicate(foldl(3, '?', '?', '?')).
|
|
37
|
+
:- meta_predicate(foldl(4, '?', '?', '?', '?')).
|
|
38
|
+
:- meta_predicate(foldl(5, '?', '?', '?', '?', '?')).
|
|
39
|
+
:- meta_predicate(countall(0, '?')).
|
|
14
40
|
|
|
15
41
|
member(X, [X|_]).
|
|
16
42
|
member(X, [_|Xs]) :- member(X, Xs).
|
|
@@ -74,6 +100,84 @@ maplist(Closure, [X|Xs]) :-
|
|
|
74
100
|
call(Closure, X),
|
|
75
101
|
maplist(Closure, Xs).
|
|
76
102
|
|
|
103
|
+
maplist(_, [], []).
|
|
104
|
+
maplist(Closure, [A|As], [B|Bs]) :-
|
|
105
|
+
call(Closure, A, B),
|
|
106
|
+
maplist(Closure, As, Bs).
|
|
107
|
+
|
|
108
|
+
maplist(_, [], [], []).
|
|
109
|
+
maplist(Closure, [A|As], [B|Bs], [C|Cs]) :-
|
|
110
|
+
call(Closure, A, B, C),
|
|
111
|
+
maplist(Closure, As, Bs, Cs).
|
|
112
|
+
|
|
113
|
+
maplist(_, [], [], [], []).
|
|
114
|
+
maplist(Closure, [A|As], [B|Bs], [C|Cs], [D|Ds]) :-
|
|
115
|
+
call(Closure, A, B, C, D),
|
|
116
|
+
maplist(Closure, As, Bs, Cs, Ds).
|
|
117
|
+
|
|
118
|
+
maplist(_, [], [], [], [], []).
|
|
119
|
+
maplist(Closure, [A|As], [B|Bs], [C|Cs], [D|Ds], [E|Es]) :-
|
|
120
|
+
call(Closure, A, B, C, D, E),
|
|
121
|
+
maplist(Closure, As, Bs, Cs, Ds, Es).
|
|
122
|
+
|
|
123
|
+
maplist(_, [], [], [], [], [], []).
|
|
124
|
+
maplist(Closure, [A|As], [B|Bs], [C|Cs], [D|Ds], [E|Es], [F|Fs]) :-
|
|
125
|
+
call(Closure, A, B, C, D, E, F),
|
|
126
|
+
maplist(Closure, As, Bs, Cs, Ds, Es, Fs).
|
|
127
|
+
|
|
128
|
+
maplist(_, [], [], [], [], [], [], []).
|
|
129
|
+
maplist(Closure, [A|As], [B|Bs], [C|Cs], [D|Ds], [E|Es], [F|Fs], [G|Gs]) :-
|
|
130
|
+
call(Closure, A, B, C, D, E, F, G),
|
|
131
|
+
maplist(Closure, As, Bs, Cs, Ds, Es, Fs, Gs).
|
|
132
|
+
|
|
133
|
+
nth0(N, List, Elem) :- nth0(N, List, Elem, _).
|
|
134
|
+
|
|
135
|
+
nth0(N, List, Elem, Rest) :-
|
|
136
|
+
prologue__integer_or_variable(N),
|
|
137
|
+
( var(N) -> prologue__nth0(N, List, Elem, Rest)
|
|
138
|
+
; prologue__not_less_than_zero(N),
|
|
139
|
+
prologue__nth0(N, List, Elem, Rest)
|
|
140
|
+
).
|
|
141
|
+
|
|
142
|
+
prologue__nth0(0, [Elem|Rest], Elem, Rest).
|
|
143
|
+
prologue__nth0(N, [X|Xs], Elem, [X|Rest]) :-
|
|
144
|
+
var(N),
|
|
145
|
+
prologue__nth0(N0, Xs, Elem, Rest),
|
|
146
|
+
N is N0 + 1.
|
|
147
|
+
prologue__nth0(N, [X|Xs], Elem, [X|Rest]) :-
|
|
148
|
+
nonvar(N),
|
|
149
|
+
N > 0,
|
|
150
|
+
N0 is N - 1,
|
|
151
|
+
prologue__nth0(N0, Xs, Elem, Rest).
|
|
152
|
+
|
|
153
|
+
nth1(N, List, Elem) :- nth1(N, List, Elem, _).
|
|
154
|
+
|
|
155
|
+
nth1(N, List, Elem, Rest) :-
|
|
156
|
+
N \== 0,
|
|
157
|
+
nth0(N, [_|List], Elem, [_|Rest]),
|
|
158
|
+
N \== 0.
|
|
159
|
+
|
|
160
|
+
call_nth(Goal, Nth) :- eyeprolog__call_nth(Goal, Nth).
|
|
161
|
+
|
|
162
|
+
foldl(_, [], Acc, Acc).
|
|
163
|
+
foldl(Closure, [A|As], Acc0, Acc) :-
|
|
164
|
+
call(Closure, A, Acc0, Acc1),
|
|
165
|
+
foldl(Closure, As, Acc1, Acc).
|
|
166
|
+
|
|
167
|
+
foldl(_, [], [], Acc, Acc).
|
|
168
|
+
foldl(Closure, [A|As], [B|Bs], Acc0, Acc) :-
|
|
169
|
+
call(Closure, A, B, Acc0, Acc1),
|
|
170
|
+
foldl(Closure, As, Bs, Acc1, Acc).
|
|
171
|
+
|
|
172
|
+
foldl(_, [], [], [], Acc, Acc).
|
|
173
|
+
foldl(Closure, [A|As], [B|Bs], [C|Cs], Acc0, Acc) :-
|
|
174
|
+
call(Closure, A, B, C, Acc0, Acc1),
|
|
175
|
+
foldl(Closure, As, Bs, Cs, Acc1, Acc).
|
|
176
|
+
|
|
177
|
+
countall(Goal, Count) :-
|
|
178
|
+
findall(1, Goal, Ones),
|
|
179
|
+
length(Ones, Count).
|
|
180
|
+
|
|
77
181
|
prologue__integer_or_variable(X) :- var(X), !.
|
|
78
182
|
prologue__integer_or_variable(X) :- prologue__integer(X).
|
|
79
183
|
|
package/src/standard-library.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// The sources are registered here so library(Name) works in Node and browsers;
|
|
3
3
|
// unlike the former autoloader, no clauses are added unless use_module/1 or
|
|
4
4
|
// use_module/2 requests their module.
|
|
5
|
-
import { createDefaultRegistry } from './iso.js';
|
|
5
|
+
import { createDefaultRegistry, eyePrologLibraryBuiltins } from './iso.js';
|
|
6
6
|
import { fs, isNode } from './platform.js';
|
|
7
7
|
|
|
8
8
|
const moduleFiles = Object.freeze({
|
|
@@ -35,21 +35,27 @@ function libraryUrl(filename) {
|
|
|
35
35
|
return url;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
export const eyePrologNativeLibraryIndicators = Object.freeze([]);
|
|
38
|
+
export const eyePrologNativeLibraryIndicators = Object.freeze(['call_nth/2']);
|
|
39
39
|
export const eyePrologPortableLibraryIndicators = Object.freeze([
|
|
40
|
-
'uuid/3', 'difference/3', 'maplist/2', 'maplist/3', '
|
|
40
|
+
'uuid/3', 'difference/3', 'maplist/2', 'maplist/3', 'maplist/4', 'maplist/5',
|
|
41
|
+
'maplist/6', 'maplist/7', 'maplist/8', 'lt/2', 'gt/2', 'le/2', 'ge/2',
|
|
41
42
|
'between/3', 'smallest_divisor_from/3', 'random/3', 'matches/3', 'split/3',
|
|
42
43
|
'replace/4', 'lowercase/2', 'uppercase/2', 'trim/2', 'number_string/2',
|
|
43
44
|
'atom_string/2', 'term_string/2', 'append/3', 'string_concat/3', 'contains/2',
|
|
44
45
|
'matches/2', 'join/3', 'substring/4', 'member/2', 'select/3', 'last/2',
|
|
45
|
-
'nth0/3', 'nth1/3', 'set_nth0/4', 'take/3', 'drop/3', 'slice/4', 'reverse/2',
|
|
46
|
+
'nth0/3', 'nth0/4', 'nth1/3', 'nth1/4', 'set_nth0/4', 'take/3', 'drop/3', 'slice/4', 'reverse/2',
|
|
46
47
|
'length/2', 'sum_list/2', 'min_list/2', 'max_list/2', 'list_to_set/2',
|
|
47
|
-
'countall/2', 'succ/2', '
|
|
48
|
+
'countall/2', 'succ/2', 'foldl/4', 'foldl/5', 'foldl/6',
|
|
49
|
+
'sumall/3', 'aggregate_min/5', 'aggregate_max/5',
|
|
50
|
+
]);
|
|
51
|
+
export const eyePrologLibraryIndicators = Object.freeze([
|
|
52
|
+
...eyePrologPortableLibraryIndicators,
|
|
53
|
+
...eyePrologNativeLibraryIndicators,
|
|
48
54
|
]);
|
|
49
|
-
export const eyePrologLibraryIndicators = Object.freeze([...eyePrologPortableLibraryIndicators]);
|
|
50
55
|
|
|
51
56
|
export function createEyePrologRegistry() {
|
|
52
57
|
const registry = createDefaultRegistry();
|
|
58
|
+
eyePrologLibraryBuiltins.register(registry);
|
|
53
59
|
registry.eyePrologLibrary = true;
|
|
54
60
|
return registry;
|
|
55
61
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
?- call_nth(true, Nth).
|
|
2
|
+
Nth = 1.
|
|
3
|
+
?- call_nth(false, Nth).
|
|
4
|
+
false.
|
|
5
|
+
?- call_nth(repeat, Nth).
|
|
6
|
+
Nth = 1
|
|
7
|
+
; Nth = 2
|
|
8
|
+
; Nth = 3
|
|
9
|
+
; Nth = 4
|
|
10
|
+
; Nth = 5
|
|
11
|
+
; ... .
|
|
12
|
+
?- call_nth(( N = 1 ; N = 2 ), Nth).
|
|
13
|
+
N = 1, Nth = 1
|
|
14
|
+
; N = 2, Nth = 2.
|
|
15
|
+
?- call_nth(true, non_integer).
|
|
16
|
+
type_error(integer,non_integer).
|
|
17
|
+
?- call_nth(true, 1.0).
|
|
18
|
+
type_error(integer,1.0).
|
|
19
|
+
?- call_nth(true, 0).
|
|
20
|
+
false.
|
|
21
|
+
?- call_nth(repeat, 0).
|
|
22
|
+
false.
|
|
23
|
+
?- call_nth(repeat, -1).
|
|
24
|
+
domain_error(not_less_than_zero,-1).
|
|
25
|
+
?- call_nth(length(L,N), 3).
|
|
26
|
+
L = [_A,_B], N = 2.
|
|
27
|
+
?- call_nth(inex, 0).
|
|
28
|
+
false.
|
|
29
|
+
?- call_nth(1, 0).
|
|
30
|
+
false.
|
|
31
|
+
?- call_nth(V, 0).
|
|
32
|
+
false.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
% Finite examples and arity coverage for Prologue p.p.7 through p.p.11.
|
|
2
|
+
|
|
3
|
+
?- maplist(same2, [a], [a]).
|
|
4
|
+
true.
|
|
5
|
+
?- maplist(same3, [a], [a], [a]).
|
|
6
|
+
true.
|
|
7
|
+
?- maplist(same4, [a], [a], [a], [a]).
|
|
8
|
+
true.
|
|
9
|
+
?- maplist(same5, [a], [a], [a], [a], [a]).
|
|
10
|
+
true.
|
|
11
|
+
?- maplist(same6, [a], [a], [a], [a], [a], [a]).
|
|
12
|
+
true.
|
|
13
|
+
?- maplist(same7, [a], [a], [a], [a], [a], [a], [a]).
|
|
14
|
+
true.
|
|
15
|
+
|
|
16
|
+
?- nth0(1, [a,b,c], E).
|
|
17
|
+
E = b.
|
|
18
|
+
?- nth0(N, [a,b,c], E).
|
|
19
|
+
N = 0, E = a
|
|
20
|
+
; N = 1, E = b
|
|
21
|
+
; N = 2, E = c.
|
|
22
|
+
?- nth0(0, [A,B|non_list], E).
|
|
23
|
+
A = E.
|
|
24
|
+
?- nth0(2, Es, E).
|
|
25
|
+
Es = [_A,_B,E|_C].
|
|
26
|
+
?- nth0(N, Es, E).
|
|
27
|
+
N = 0, Es = [E|_A]
|
|
28
|
+
; N = 1, Es = [_A,E|_B]
|
|
29
|
+
; N = 2, Es = [_A,_B,E|_C]
|
|
30
|
+
; N = 3, Es = [_A,_B,_C,E|_D]
|
|
31
|
+
; ... .
|
|
32
|
+
?- nth0(non_integer, Es, E).
|
|
33
|
+
type_error(integer, non_integer).
|
|
34
|
+
?- nth0(-1, Es, E).
|
|
35
|
+
domain_error(not_less_than_zero, -1).
|
|
36
|
+
?- nth0(N, [[]|Es], Es).
|
|
37
|
+
N = 0, Es = []
|
|
38
|
+
; sto, loops.
|
|
39
|
+
?- nth0(1, [a,b,c], E, Es).
|
|
40
|
+
E = b, Es = [a,c].
|
|
41
|
+
?- nth1(0, Es, E).
|
|
42
|
+
false.
|
|
43
|
+
?- nth1(2, [a,b,c], E, Es).
|
|
44
|
+
E = b, Es = [a,c].
|
|
45
|
+
|
|
46
|
+
?- call_nth(N = 1, N).
|
|
47
|
+
N = 1.
|
|
48
|
+
?- call_nth(N = -1, N).
|
|
49
|
+
false.
|
|
50
|
+
?- call_nth(repeat, 1+1).
|
|
51
|
+
type_error(integer, 1+1).
|
|
52
|
+
?- call_nth(inex, 0).
|
|
53
|
+
existence_error(procedure, _), unexpected.
|
|
54
|
+
?- call_nth(G, non_integer).
|
|
55
|
+
instantiation_error.
|
|
56
|
+
?- call_nth(1, -1).
|
|
57
|
+
type_error(callable, 1).
|
|
58
|
+
|
|
59
|
+
?- foldl(append, [[1,2],[3],[4,5]], [], Xs).
|
|
60
|
+
Xs = [4,5,3,1,2].
|
|
61
|
+
?- foldl(add2, [1,2], [10,20], 0, S).
|
|
62
|
+
S = 33.
|
|
63
|
+
?- foldl(add3, [1,2], [10,20], [100,200], 0, S).
|
|
64
|
+
S = 333.
|
|
65
|
+
|
|
66
|
+
?- countall((X=1;X=2), N).
|
|
67
|
+
N = 2.
|
|
68
|
+
?- countall((true;true), N).
|
|
69
|
+
N = 2.
|
|
70
|
+
?- countall(G, N).
|
|
71
|
+
instantiation_error.
|
|
72
|
+
?- countall((length(L,5),nth0(_,L,_),nth0(_,L,_)), N).
|
|
73
|
+
N = 25.
|
|
74
|
+
?- countall(N=1, N).
|
|
75
|
+
N = 1.
|
|
76
|
+
?- countall(N=non_integer, N).
|
|
77
|
+
N = 1.
|
|
78
|
+
?- countall(false, 1).
|
|
79
|
+
false.
|
|
80
|
+
?- countall(false, -1).
|
|
81
|
+
domain_error(not_less_than_zero, -1).
|
|
82
|
+
?- countall(false, non_integer).
|
|
83
|
+
type_error(integer, non_integer).
|
|
84
|
+
?- countall(G, -1).
|
|
85
|
+
instantiation_error.
|
|
86
|
+
?- countall(1, -1).
|
|
87
|
+
type_error(callable, 1).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
:- use_module(library(prologue)).
|
|
2
|
+
|
|
3
|
+
same2(X, X).
|
|
4
|
+
same3(X, X, X).
|
|
5
|
+
same4(X, X, X, X).
|
|
6
|
+
same5(X, X, X, X, X).
|
|
7
|
+
same6(X, X, X, X, X, X).
|
|
8
|
+
same7(X, X, X, X, X, X, X).
|
|
9
|
+
|
|
10
|
+
add2(X, Y, S0, S) :- S is S0 + X + Y.
|
|
11
|
+
add3(X, Y, Z, S0, S) :- S is S0 + X + Y + Z.
|
|
12
|
+
|
|
13
|
+
:- include('prologue_extended_quad.pl').
|
package/test/run-regression.mjs
CHANGED
|
@@ -50,7 +50,7 @@ const bin = path.join(packageRoot, 'bin', 'eyeprolog.js');
|
|
|
50
50
|
const pkg = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
|
|
51
51
|
let tmp = null;
|
|
52
52
|
let tmpCounter = 0;
|
|
53
|
-
const libraryCall = /\b(?:uuid|difference|maplist|lt|gt|le|ge|between|smallest_divisor_from|random|matches|split|replace|lowercase|uppercase|trim|number_string|atom_string|term_string|append|string_concat|contains|join|substring|member|select|last|nth0|nth1|set_nth0|take|drop|slice|reverse|length|sum_list|min_list|max_list|list_to_set|countall|sumall|aggregate_min|aggregate_max)\s*\(/;
|
|
53
|
+
const libraryCall = /\b(?:uuid|difference|maplist|foldl|call_nth|lt|gt|le|ge|between|smallest_divisor_from|random|matches|split|replace|lowercase|uppercase|trim|number_string|atom_string|term_string|append|string_concat|contains|join|substring|member|select|last|nth0|nth1|set_nth0|take|drop|slice|reverse|length|sum_list|min_list|max_list|list_to_set|countall|sumall|aggregate_min|aggregate_max)\s*\(/;
|
|
54
54
|
|
|
55
55
|
function withStandardModules(source) {
|
|
56
56
|
if (!libraryCall.test(source) || source.includes('use_module(library(') || source.includes(':- module(')) return source;
|
|
@@ -337,6 +337,36 @@ c4 ?- call((!;1)).
|
|
|
337
337
|
assertEqual(result.stdout, 'quads: 33 run, 33 passed, 0 failed.\n', 'quad report');
|
|
338
338
|
},
|
|
339
339
|
},
|
|
340
|
+
{
|
|
341
|
+
name: 'runQuads passes the authoritative Prologue call_nth quad corpus',
|
|
342
|
+
run: () => {
|
|
343
|
+
const filename = path.join(testRoot, 'fixtures', 'prologue_call_nth_quad_runner.pl');
|
|
344
|
+
const source = fs.readFileSync(filename, 'utf8');
|
|
345
|
+
const result = publicApi.runQuads(Program.parseSources([{
|
|
346
|
+
text: source,
|
|
347
|
+
filename,
|
|
348
|
+
baseDir: path.dirname(filename),
|
|
349
|
+
}]));
|
|
350
|
+
assertEqual(result.total, 13, 'quad total');
|
|
351
|
+
assertEqual(result.passed, 13, 'quad passed');
|
|
352
|
+
assertEqual(result.stdout, 'quads: 13 run, 13 passed, 0 failed.\n', 'quad report');
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
name: 'runQuads covers the remaining finite Prologue examples and arities',
|
|
357
|
+
run: () => {
|
|
358
|
+
const filename = path.join(testRoot, 'fixtures', 'prologue_extended_quad_runner.pl');
|
|
359
|
+
const source = fs.readFileSync(filename, 'utf8');
|
|
360
|
+
const result = publicApi.runQuads(Program.parseSources([{
|
|
361
|
+
text: source,
|
|
362
|
+
filename,
|
|
363
|
+
baseDir: path.dirname(filename),
|
|
364
|
+
}]));
|
|
365
|
+
assertEqual(result.total, 37, 'quad total');
|
|
366
|
+
assertEqual(result.passed, 37, 'quad passed');
|
|
367
|
+
assertEqual(result.stdout, 'quads: 37 run, 37 passed, 0 failed.\n', 'quad report');
|
|
368
|
+
},
|
|
369
|
+
},
|
|
340
370
|
{
|
|
341
371
|
name: 'runQuads rejects malformed answer substitutions',
|
|
342
372
|
run: () => {
|
|
@@ -516,6 +546,27 @@ c4 ?- call((!;1)).
|
|
|
516
546
|
assertEqual(result.stderr, '', 'stderr');
|
|
517
547
|
},
|
|
518
548
|
},
|
|
549
|
+
{
|
|
550
|
+
name: 'npm can install the CLI under a user-owned prefix',
|
|
551
|
+
run: () => {
|
|
552
|
+
const prefix = path.join(tmp, `npm-prefix-${++tmpCounter}`);
|
|
553
|
+
const installed = spawnSync('npm', [
|
|
554
|
+
'install', '--global', '--prefix', prefix, '--loglevel=silent', '--no-audit', '--no-fund', '.',
|
|
555
|
+
], {
|
|
556
|
+
cwd: packageRoot,
|
|
557
|
+
encoding: 'utf8',
|
|
558
|
+
env: { ...process.env, npm_config_update_notifier: 'false' },
|
|
559
|
+
});
|
|
560
|
+
assertEqual(installed.status, 0, 'install exit status');
|
|
561
|
+
const executable = process.platform === 'win32'
|
|
562
|
+
? path.join(prefix, 'eyeprolog.cmd')
|
|
563
|
+
: path.join(prefix, 'bin', 'eyeprolog');
|
|
564
|
+
const result = spawnSync(executable, ['--version'], { encoding: 'utf8' });
|
|
565
|
+
assertEqual(result.status, 0, 'installed CLI exit status');
|
|
566
|
+
assertEqual(result.stdout, `eyeprolog ${pkg.version}\n`, 'installed CLI stdout');
|
|
567
|
+
assertEqual(result.stderr, '', 'installed CLI stderr');
|
|
568
|
+
},
|
|
569
|
+
},
|
|
519
570
|
{
|
|
520
571
|
name: 'stdin input is accepted',
|
|
521
572
|
run: () => {
|
|
@@ -979,6 +1030,22 @@ function documentationSyncCases() {
|
|
|
979
1030
|
assertArrayEqual(misleadingDependencyInstallDocs(), [], 'misleading dependency install docs');
|
|
980
1031
|
},
|
|
981
1032
|
},
|
|
1033
|
+
{
|
|
1034
|
+
name: 'installation docs avoid unsupported Node and global npm permission traps',
|
|
1035
|
+
run: () => {
|
|
1036
|
+
assertEqual(pkg.engines?.node, '>=18', 'supported Node range');
|
|
1037
|
+
for (const filename of ['README.md', 'the-art-of-eyeprolog.md']) {
|
|
1038
|
+
const text = fs.readFileSync(path.join(packageRoot, filename), 'utf8');
|
|
1039
|
+
assertIncludes(text, 'node --version', `${filename} checks Node version`);
|
|
1040
|
+
assertIncludes(text, 'npx --yes eyeprolog', `${filename} offers a non-global launch`);
|
|
1041
|
+
assertIncludes(text, 'npm install --global --prefix "$HOME/.local" eyeprolog', `${filename} uses a user prefix`);
|
|
1042
|
+
assertIncludes(text, 'https://nodejs.org/en/download', `${filename} links Node upgrades`);
|
|
1043
|
+
assertIncludes(text, 'https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally/', `${filename} links npm EACCES guidance`);
|
|
1044
|
+
assertEqual(text.includes('sudo npm install'), true, `${filename} explicitly warns against sudo npm`);
|
|
1045
|
+
assertEqual(/^\s*sudo npm install/m.test(text), false, `${filename} never recommends sudo npm`);
|
|
1046
|
+
}
|
|
1047
|
+
},
|
|
1048
|
+
},
|
|
982
1049
|
];
|
|
983
1050
|
}
|
|
984
1051
|
|
|
@@ -1311,14 +1378,17 @@ open(X) :- candidate(X), \\+ closed(X).
|
|
|
1311
1378
|
assertEqual(Boolean(registry.get('is', 2)), true, 'ISO is/2 exists');
|
|
1312
1379
|
assertEqual(Boolean(registry.get('append', 3)), false, 'append/3 is not ISO core');
|
|
1313
1380
|
assertEqual(library.eyePrologLibrary, true, 'complete registry marker');
|
|
1314
|
-
assertEqual(library.defs.size,
|
|
1381
|
+
assertEqual(library.defs.size, 130, 'EyeProlog registry contains ISO definitions and one private library adapter');
|
|
1315
1382
|
assertEqual(Boolean(registry.get('phrase', 2)), true, 'Part 3 phrase/2 exists');
|
|
1316
1383
|
assertEqual(Boolean(registry.get('phrase', 3)), true, 'Part 3 phrase/3 exists');
|
|
1317
|
-
assertEqual(registeredNativeEyePrologLibraryNames().length,
|
|
1318
|
-
assertEqual(eyePrologPortableLibraryIndicators.length,
|
|
1319
|
-
assertEqual(eyePrologNativeLibraryIndicators.length,
|
|
1320
|
-
assertEqual(eyePrologNativeLibraryIndicators.join(','), '', '
|
|
1321
|
-
assertEqual(eyePrologLibraryIndicators.length,
|
|
1384
|
+
assertEqual(registeredNativeEyePrologLibraryNames().length, 1, 'public native EyeProlog builtin count');
|
|
1385
|
+
assertEqual(eyePrologPortableLibraryIndicators.length, 56, 'portable Prolog library count');
|
|
1386
|
+
assertEqual(eyePrologNativeLibraryIndicators.length, 1, 'native host library count');
|
|
1387
|
+
assertEqual(eyePrologNativeLibraryIndicators.join(','), 'call_nth/2', 'control predicate requiring host support');
|
|
1388
|
+
assertEqual(eyePrologLibraryIndicators.length, 57, 'complete EyeProlog library surface');
|
|
1389
|
+
assertEqual(registry.get('eyeprolog__call_nth', 2), null, 'private call_nth adapter is absent from ISO registry');
|
|
1390
|
+
assertEqual(Boolean(library.get('eyeprolog__call_nth', 2)), true, 'private call_nth adapter is registered for EyeProlog');
|
|
1391
|
+
assertEqual(library.get('eyeprolog__call_nth', 2)?.eyePrologLibrary, true, 'private adapter is marked as library support');
|
|
1322
1392
|
assertEqual(library.get('between', 3), null, 'between/3 remains portable Prolog');
|
|
1323
1393
|
assertEqual(library.get('smallest_divisor_from', 3), null, 'smallest_divisor_from/3 remains portable Prolog');
|
|
1324
1394
|
assertEqual(library.get('random', 3), null, 'random/3 remains portable Prolog');
|
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
|
|
@@ -5737,22 +5761,25 @@ so side effects occur in Prolog execution order.
|
|
|
5737
5761
|
|
|
5738
5762
|
### The EyeProlog library
|
|
5739
5763
|
|
|
5740
|
-
EyeProlog exposes **
|
|
5741
|
-
indicators in its isolated ISO profile. **
|
|
5742
|
-
across `src/lib/eyeprolog.pl`, `src/lib/lists.pl`, and
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5764
|
+
EyeProlog exposes **57 library predicate indicators** in addition to the 129
|
|
5765
|
+
indicators in its isolated ISO profile. **56 are implemented entirely as
|
|
5766
|
+
ordinary Prolog clauses** across `src/lib/eyeprolog.pl`, `src/lib/lists.pl`, and
|
|
5767
|
+
`src/lib/prologue.pl`. The Prologue `call_nth/2` clause delegates its solution
|
|
5768
|
+
numbering to one private host adapter. The resulting normal EyeProlog language
|
|
5769
|
+
surface is therefore **186 public predicate indicators**. Internally, the
|
|
5770
|
+
runtime registry contains the 129 ISO definitions plus that private adapter;
|
|
5771
|
+
the remaining EyeProlog relations are module source clauses.
|
|
5747
5772
|
|
|
5748
5773
|
The three Prolog files declare `eyeprolog`, `lists`, and `prologue` with
|
|
5749
5774
|
`module/2`. A program loads them explicitly with
|
|
5750
5775
|
`use_module(library(eyeprolog))`, `use_module(library(lists))`, or
|
|
5751
5776
|
`use_module(library(prologue))`; `use_module/2` can select a smaller import
|
|
5752
|
-
list. The last module implements
|
|
5753
|
-
Prologue
|
|
5754
|
-
`
|
|
5755
|
-
|
|
5777
|
+
list. The last module implements p.p.1 through p.p.11 of the
|
|
5778
|
+
[working-draft Prologue](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/prologue),
|
|
5779
|
+
with the published `call_nth/2` quads and finite examples retained as offline
|
|
5780
|
+
regressions. `src/standard-library.js` registers the module sources and private
|
|
5781
|
+
control adapter for Node and browser resolution, and never adds clauses
|
|
5782
|
+
implicitly.
|
|
5756
5783
|
The isolated ISO-only registry remains
|
|
5757
5784
|
available through `createDefaultRegistry()` and `getDefaultRegistry()` for
|
|
5758
5785
|
conformance work and advanced embedders. Module-local predicate identity keeps
|
|
@@ -5764,7 +5791,7 @@ private helpers and same-named predicates in different modules separate.
|
|
|
5764
5791
|
| --- | --- |
|
|
5765
5792
|
| `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` |
|
|
5766
5793
|
| `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` |
|
|
5794
|
+
| `library(prologue)` | `member/2`, `append/3`, `length/2`, `between/3`, `select/3`, `succ/2`, `maplist/2`, `maplist/3`, `maplist/4`, `maplist/5`, `maplist/6`, `maplist/7`, `maplist/8`, `nth0/3`, `nth0/4`, `nth1/3`, `nth1/4`, `call_nth/2`, `foldl/4`, `foldl/5`, `foldl/6`, `countall/2` |
|
|
5768
5795
|
|
|
5769
5796
|
<!-- eyeprolog-library-catalog:end -->
|
|
5770
5797
|
|
|
@@ -5891,7 +5918,7 @@ eyeprolog --goal 'answer(Kind, Value)' program.pl
|
|
|
5891
5918
|
The portable text API uses **ISO atoms or proper lists of one-character atoms**.
|
|
5892
5919
|
A generated text result defaults to an atom. Double-quoted source text uses the
|
|
5893
5920
|
ISO representation selected by `double_quotes`; with the default `chars`, it is
|
|
5894
|
-
already a proper character list accepted by this API. The
|
|
5921
|
+
already a proper character list accepted by this API. The 56-predicate portable
|
|
5895
5922
|
library itself has no STRING or JavaScript dependency.
|
|
5896
5923
|
|
|
5897
5924
|
| Predicate and principal mode | Behavior |
|