eyeprolog 0.5.11 → 0.5.13
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 +5 -3
- package/examples/age.pl +7 -7
- package/examples/blocks-world-planning.pl +3 -3
- package/examples/combinatorics-findall-sort.pl +4 -4
- package/examples/d3-group.pl +3 -3
- package/examples/dijkstra-findall-sort.pl +3 -3
- package/examples/enigma1225.pl +1 -2
- package/examples/eulerian-path.pl +4 -4
- package/examples/four-color-map.pl +2 -2
- package/examples/input/odrl-dpv-healthcare-risk-ranked-rules.pl +1 -1
- package/examples/input/odrl-dpv-risk-ranked-rules.pl +1 -1
- package/examples/input/rdf12-annotated-claims-rules.pl +1 -1
- package/examples/odrl-dpv-healthcare-risk-ranked.pl +1 -1
- package/examples/odrl-dpv-risk-ranked.pl +1 -1
- package/examples/proof/age.pl +6 -6
- package/examples/proof/d3-group.pl +4 -4
- package/examples/proof/iso-dynamic-database.pl +1 -1
- package/examples/quine-mccluskey.pl +7 -7
- package/examples/rdf12-annotated-claims.pl +1 -1
- package/examples/sudoku.pl +1 -1
- package/examples/uuid.pl +4 -4
- package/package.json +1 -1
- package/playground.html +28 -8
- package/src/cli.js +1 -1
- package/src/explain.js +1 -1
- package/src/eyeprolog-autoload.js +133 -0
- package/src/eyeprolog-common-library.pl +44 -0
- package/src/eyeprolog-library.pl +113 -74
- package/src/index.js +2 -2
- package/src/playground-worker.js +2 -2
- package/src/solver.js +1 -1
- package/test/conformance/cases/builtins/extra_uuid_v4.pl +2 -2
- package/test/conformance/cases/lists/031_lists_aggregation_ordering.pl +1 -2
- package/test/conformance/cases/lists/045_sort_deduplicates_atoms.pl +2 -3
- package/test/conformance/cases/lists/087_sort_reverse_length.pl +2 -2
- package/test/conformance/cases/lists/extra_sort_iri_atoms.pl +1 -1
- package/test/conformance/cases/lists/sort_structured_terms.pl +1 -1
- package/test/run-examples.mjs +11 -19
- package/test/run-playground.mjs +5 -1
- package/test/run-regression.mjs +35 -19
- package/the-art-of-eyeprolog.md +37 -32
- package/src/library-source.js +0 -18
- package/src/library.js +0 -171
package/README.md
CHANGED
|
@@ -32,9 +32,11 @@ Programs may declare their default queries with `%% goal:` comments.
|
|
|
32
32
|
## Portable library
|
|
33
33
|
|
|
34
34
|
EyeProlog adds 50 public library predicates to its 115-predicate ISO profile.
|
|
35
|
-
**
|
|
36
|
-
are autoloaded in Node and the
|
|
37
|
-
|
|
35
|
+
**All 50 are ordinary Prolog clauses** across `src/eyeprolog-library.pl` and
|
|
36
|
+
`src/eyeprolog-common-library.pl`; both are autoloaded in Node and the
|
|
37
|
+
browser. None requires host support. Other Prolog systems should load only
|
|
38
|
+
`eyeprolog-library.pl`, which avoids redefining their common list predicates.
|
|
39
|
+
Portable text predicates use ISO atoms or character lists; the
|
|
38
40
|
RDF tools emit lexical values as ISO atoms as well.
|
|
39
41
|
|
|
40
42
|
## RDF 1.2
|
package/examples/age.pl
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
% Age checker adapted from Eyeling.
|
|
2
|
-
% The example combines date literals, ISO-8601 duration values,
|
|
3
|
-
% difference/3, and duration comparison.
|
|
4
|
-
%
|
|
5
|
-
% check rather than a precomputed constant.
|
|
2
|
+
% The example combines date literals, ISO-8601 duration values, an explicit
|
|
3
|
+
% local_time/1 fact, difference/3, and duration comparison. Declaring the date
|
|
4
|
+
% as scenario data keeps the result reproducible across hosts and runs.
|
|
6
5
|
|
|
7
6
|
%% goal: birthDay(X0, X1)
|
|
8
7
|
|
|
@@ -15,9 +14,10 @@
|
|
|
15
14
|
|
|
16
15
|
birthDay(patH, '1944-08-21').
|
|
17
16
|
duration(check, 'P80Y').
|
|
17
|
+
local_time('2026-05-30').
|
|
18
18
|
|
|
19
|
-
% A person is above a duration if the local date minus the birthday
|
|
20
|
-
% than that duration.
|
|
19
|
+
% A person is above a duration if the declared local date minus the birthday
|
|
20
|
+
% is greater than that duration.
|
|
21
21
|
ageAbove(S, A) :-
|
|
22
22
|
birthDay(S, B),
|
|
23
23
|
duration(check, A),
|
|
@@ -27,4 +27,4 @@ ageAbove(S, A) :-
|
|
|
27
27
|
|
|
28
28
|
% Test mirroring the Eyeling example.
|
|
29
29
|
holds_result(test, true) :-
|
|
30
|
-
ageAbove(
|
|
30
|
+
ageAbove(_, 'P80Y').
|
|
@@ -48,7 +48,7 @@ move(State, move(Block, From, To), Newstate) :-
|
|
|
48
48
|
(Block \= To),
|
|
49
49
|
(From \= To),
|
|
50
50
|
select(on(Block, From), State, Rest),
|
|
51
|
-
|
|
51
|
+
sort_unique([on(Block, To)|Rest], Newstate).
|
|
52
52
|
|
|
53
53
|
plan(State, Goal, 0, _visited, [], State) :-
|
|
54
54
|
(State = Goal).
|
|
@@ -63,8 +63,8 @@ plan(State, Goal, Depth, Visited, [Move|Moves], Final) :-
|
|
|
63
63
|
five_move_plan(Moves, Final) :-
|
|
64
64
|
initial(Start),
|
|
65
65
|
goal(Goal),
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
sort_unique(Start, Sortedstart),
|
|
67
|
+
sort_unique(Goal, Sortedgoal),
|
|
68
68
|
plan(Sortedstart, Sortedgoal, 5, [Sortedstart], Moves, Final).
|
|
69
69
|
|
|
70
70
|
status(blocks_world, planned) :-
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
% Eyelet-inspired combinations example using findall/3 and
|
|
1
|
+
% Eyelet-inspired combinations example using findall/3 and sort_unique/2.
|
|
2
2
|
%
|
|
3
3
|
% combination/3 generates the same subset in several selection orders. findall/3
|
|
4
|
-
% collects those candidates, and
|
|
4
|
+
% collects those candidates, and sort_unique/2 canonicalizes the list so each unordered
|
|
5
5
|
% 3-combination of five items is reported once.
|
|
6
6
|
%% goal: combinations(X0, X1)
|
|
7
7
|
|
|
@@ -24,12 +24,12 @@ combination(I, Items, Combination) :-
|
|
|
24
24
|
select(Item, Items, Remaining),
|
|
25
25
|
(J is I - 1),
|
|
26
26
|
combination(J, Remaining, Partial),
|
|
27
|
-
|
|
27
|
+
sort_unique([Item | Partial], Combination).
|
|
28
28
|
|
|
29
29
|
% findall collects all generation orders; sort canonicalizes and deduplicates.
|
|
30
30
|
unique_combinations(K, Items, Unique) :-
|
|
31
31
|
findall(C, combination(K, Items, C), All),
|
|
32
|
-
|
|
32
|
+
sort_unique(All, Unique).
|
|
33
33
|
|
|
34
34
|
combinations(combinations_5_choose_3, Unique) :-
|
|
35
35
|
unique_combinations(3, [1, 2, 3, 4, 5], Unique).
|
package/examples/d3-group.pl
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
% Eyelet-inspired D3 group example using findall/3 and
|
|
1
|
+
% Eyelet-inspired D3 group example using findall/3 and sort_unique/2.
|
|
2
2
|
% The six facts are the symmetries of an equilateral triangle, with compose/3 as
|
|
3
3
|
% the Cayley table and inverse/2 as the inverse relation. Candidate subsets are
|
|
4
4
|
% generated as subsequences, then filtered for subgroup closure.
|
|
@@ -72,7 +72,7 @@ subsequence([_head | Tail], Rest) :-
|
|
|
72
72
|
|
|
73
73
|
all_symmetries(Symmetries) :-
|
|
74
74
|
findall(X, symmetry(X), Raw),
|
|
75
|
-
|
|
75
|
+
sort_unique(Raw, Symmetries).
|
|
76
76
|
|
|
77
77
|
% A valid subgroup is closed under both composition and inverse.
|
|
78
78
|
closed_under_composition(Group) :-
|
|
@@ -90,7 +90,7 @@ valid_group(Group) :-
|
|
|
90
90
|
|
|
91
91
|
all_subgroups(Groups) :-
|
|
92
92
|
findall(G, valid_group(G), Raw),
|
|
93
|
-
|
|
93
|
+
sort_unique(Raw, Groups).
|
|
94
94
|
|
|
95
95
|
subgroups(d3_group, Groups) :-
|
|
96
96
|
all_subgroups(Groups).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
% Eyelet-inspired Dijkstra example using findall/3 and
|
|
1
|
+
% Eyelet-inspired Dijkstra example using findall/3 and sort_unique/2.
|
|
2
2
|
% The priority queue is represented as sorted list entries [Cost, Node | Path].
|
|
3
3
|
% Each expansion collects unvisited neighbors with findall/3, appends them to
|
|
4
|
-
% the frontier, and uses
|
|
4
|
+
% the frontier, and uses sort_unique/2 so the cheapest frontier entry is processed next.
|
|
5
5
|
|
|
6
6
|
%% goal: shortestPath(X0, X1)
|
|
7
7
|
|
|
@@ -34,7 +34,7 @@ dijkstra_queue([[Cost, Node | Path] | Queue], Goal, Visited, Resultpath, Resultc
|
|
|
34
34
|
(edge(Node, Neighbor, Weight), \+ member(Neighbor, Visited), (Newcost is Cost + Weight)),
|
|
35
35
|
Neighbors),
|
|
36
36
|
append(Queue, Neighbors, Newqueue),
|
|
37
|
-
|
|
37
|
+
sort_unique(Newqueue, Sortedqueue),
|
|
38
38
|
dijkstra_queue(Sortedqueue, Goal, [Node | Visited], Resultpath, Resultcost).
|
|
39
39
|
|
|
40
40
|
shortestPath(dijkstra_findall_sort, Path) :-
|
package/examples/enigma1225.pl
CHANGED
|
@@ -89,7 +89,7 @@ eval_matrix(Matrix, FreqSorted) :-
|
|
|
89
89
|
setof(E, member(E, Entries), Set),
|
|
90
90
|
maplist(count_var(Entries), Set, Multiplicities),
|
|
91
91
|
zip(Multiplicities, Set, Frequencies),
|
|
92
|
-
|
|
92
|
+
sort_unique(Frequencies, FreqSorted),
|
|
93
93
|
maplist(snd, FreqSorted, VarsSorted),
|
|
94
94
|
length(VarsSorted, NVars),
|
|
95
95
|
from_to(1, NVars, VarsSorted).
|
|
@@ -237,4 +237,3 @@ lists_reform([[A|B]|C], [A|D], [B|E]) :-
|
|
|
237
237
|
|
|
238
238
|
% query
|
|
239
239
|
%% goal: enigma1225(8, _)
|
|
240
|
-
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
% Eyelet-inspired Eulerian path example using findall/3 and
|
|
1
|
+
% Eyelet-inspired Eulerian path example using findall/3 and sort_unique/2.
|
|
2
2
|
%
|
|
3
3
|
% The graph is undirected; edges have identifiers so the trail consumes each
|
|
4
4
|
% physical edge exactly once even when vertices are revisited. The remaining
|
|
@@ -49,15 +49,15 @@ odd_degree(V) :-
|
|
|
49
49
|
|
|
50
50
|
odd_vertices(Odds) :-
|
|
51
51
|
findall(V, odd_degree(V), Raw),
|
|
52
|
-
|
|
52
|
+
sort_unique(Raw, Odds).
|
|
53
53
|
|
|
54
54
|
all_edges(Edges) :-
|
|
55
55
|
findall(E, edge(E, _a, _b), Raw),
|
|
56
|
-
|
|
56
|
+
sort_unique(Raw, Edges).
|
|
57
57
|
|
|
58
58
|
vertices(Vertices) :-
|
|
59
59
|
findall(V, vertex(V), Raw),
|
|
60
|
-
|
|
60
|
+
sort_unique(Raw, Vertices).
|
|
61
61
|
|
|
62
62
|
eulerian_start(Start) :-
|
|
63
63
|
odd_vertices([Start, _end]).
|
|
@@ -102,13 +102,13 @@ border_count(36).
|
|
|
102
102
|
|
|
103
103
|
all_countries_coloured(map_eu) :-
|
|
104
104
|
findall(Country, valid_assignment(Country), Countries),
|
|
105
|
-
|
|
105
|
+
sort_unique(Countries, Uniquecountries),
|
|
106
106
|
length(Uniquecountries, Count),
|
|
107
107
|
country_count(Count).
|
|
108
108
|
|
|
109
109
|
all_borders_checked(map_eu) :-
|
|
110
110
|
findall([A, B], border_colours_differ(A, B), Borders),
|
|
111
|
-
|
|
111
|
+
sort_unique(Borders, Uniqueborders),
|
|
112
112
|
length(Uniqueborders, Count),
|
|
113
113
|
border_count(Count).
|
|
114
114
|
|
package/examples/proof/age.pl
CHANGED
|
@@ -3,7 +3,7 @@ why(
|
|
|
3
3
|
ageAbove(patH, 'P80Y'),
|
|
4
4
|
proof(
|
|
5
5
|
goal(ageAbove(patH, 'P80Y')),
|
|
6
|
-
by(rule("age.pl", clause(
|
|
6
|
+
by(rule("age.pl", clause(4))),
|
|
7
7
|
bindings([binding("S", patH), binding("A", 'P80Y'), binding("B", '1944-08-21'), binding("D", '2026-05-30'), binding("F", 'P81Y9M9D')]),
|
|
8
8
|
uses([
|
|
9
9
|
proof(
|
|
@@ -16,7 +16,7 @@ why(
|
|
|
16
16
|
),
|
|
17
17
|
proof(
|
|
18
18
|
goal(local_time('2026-05-30')),
|
|
19
|
-
by(
|
|
19
|
+
by(fact("age.pl", clause(3)))
|
|
20
20
|
),
|
|
21
21
|
proof(
|
|
22
22
|
goal(difference('2026-05-30', '1944-08-21', 'P81Y9M9D')),
|
|
@@ -35,12 +35,12 @@ why(
|
|
|
35
35
|
holds_result(test, true),
|
|
36
36
|
proof(
|
|
37
37
|
goal(holds_result(test, true)),
|
|
38
|
-
by(rule("age.pl", clause(
|
|
39
|
-
bindings([binding("
|
|
38
|
+
by(rule("age.pl", clause(5))),
|
|
39
|
+
bindings([binding("__anon0", patH)]),
|
|
40
40
|
uses([
|
|
41
41
|
proof(
|
|
42
42
|
goal(ageAbove(patH, 'P80Y')),
|
|
43
|
-
by(rule("age.pl", clause(
|
|
43
|
+
by(rule("age.pl", clause(4))),
|
|
44
44
|
bindings([binding("S", patH), binding("A", 'P80Y'), binding("B", '1944-08-21'), binding("D", '2026-05-30'), binding("F", 'P81Y9M9D')]),
|
|
45
45
|
uses([
|
|
46
46
|
proof(
|
|
@@ -53,7 +53,7 @@ why(
|
|
|
53
53
|
),
|
|
54
54
|
proof(
|
|
55
55
|
goal(local_time('2026-05-30')),
|
|
56
|
-
by(
|
|
56
|
+
by(fact("age.pl", clause(3)))
|
|
57
57
|
),
|
|
58
58
|
proof(
|
|
59
59
|
goal(difference('2026-05-30', '1944-08-21', 'P81Y9M9D')),
|
|
@@ -16,8 +16,8 @@ why(
|
|
|
16
16
|
by(builtin(findall, 3))
|
|
17
17
|
),
|
|
18
18
|
proof(
|
|
19
|
-
goal(
|
|
20
|
-
by(library(
|
|
19
|
+
goal(sort_unique([], [])),
|
|
20
|
+
by(library(sort_unique, 2))
|
|
21
21
|
)
|
|
22
22
|
])
|
|
23
23
|
)
|
|
@@ -43,8 +43,8 @@ why(
|
|
|
43
43
|
by(builtin(findall, 3))
|
|
44
44
|
),
|
|
45
45
|
proof(
|
|
46
|
-
goal(
|
|
47
|
-
by(library(
|
|
46
|
+
goal(sort_unique([], [])),
|
|
47
|
+
by(library(sort_unique, 2))
|
|
48
48
|
)
|
|
49
49
|
])
|
|
50
50
|
),
|
|
@@ -75,7 +75,7 @@ check(4, 'Canonical Tie-Breaking (Lexicographical First)', Status) :-
|
|
|
75
75
|
find_combination(Size, Primes, C),
|
|
76
76
|
covers_all(C, Minterms)
|
|
77
77
|
), Alternatives),
|
|
78
|
-
|
|
78
|
+
sort_unique(Alternatives, [Best|_]),
|
|
79
79
|
equality_status(Cover, Best, Status).
|
|
80
80
|
|
|
81
81
|
check(5, 'Consistency (Solution is subset of Primes)', Status) :-
|
|
@@ -166,9 +166,9 @@ compute_primes(Primes) :-
|
|
|
166
166
|
findall(B, (minterm(M), int_to_bits(M, B)), Mts),
|
|
167
167
|
findall(B, (dont_care(D), int_to_bits(D, B)), Dcs),
|
|
168
168
|
append(Mts, Dcs, All),
|
|
169
|
-
|
|
169
|
+
sort_unique(All, Init),
|
|
170
170
|
generate_loop(Init, [], RawPrimes),
|
|
171
|
-
|
|
171
|
+
sort_unique(RawPrimes, Primes).
|
|
172
172
|
|
|
173
173
|
generate_loop(Group, Acc, Final) :-
|
|
174
174
|
findall(Nx-[P1,P2], (
|
|
@@ -176,8 +176,8 @@ generate_loop(Group, Acc, Final) :-
|
|
|
176
176
|
), Pairs),
|
|
177
177
|
findall(N, member(N-_, Pairs), NextRaw),
|
|
178
178
|
findall(P, (member(_-Pars, Pairs), member(P, Pars)), UsedRaw),
|
|
179
|
-
|
|
180
|
-
|
|
179
|
+
sort_unique(NextRaw, NextGen),
|
|
180
|
+
sort_unique(UsedRaw, Used),
|
|
181
181
|
findall(P, (member(P, Group), \+ member(P, Used)), New),
|
|
182
182
|
append(Acc, New, AccUpd),
|
|
183
183
|
(NextGen = [] -> Final = AccUpd ; generate_loop(NextGen, AccUpd, Final)).
|
|
@@ -187,13 +187,13 @@ solve_minimal_cover(Primes, MinimalCover) :-
|
|
|
187
187
|
% 1. Essentials
|
|
188
188
|
findall(M-Ps, (member(M, Minterms), findall(P, (member(P, Primes), covers_int(P, M)), Ps)), Chart),
|
|
189
189
|
findall(P, member(_-[P], Chart), EssRaw),
|
|
190
|
-
|
|
190
|
+
sort_unique(EssRaw, Essentials),
|
|
191
191
|
% 2. Remaining
|
|
192
192
|
remaining_minterms(Minterms, Essentials, RemMts),
|
|
193
193
|
subtract_list(Primes, Essentials, NonEss),
|
|
194
194
|
(RemMts = [] -> BestRest = [] ; find_smallest_subset(NonEss, RemMts, BestRest)),
|
|
195
195
|
append(Essentials, BestRest, Total),
|
|
196
|
-
|
|
196
|
+
sort_unique(Total, MinimalCover).
|
|
197
197
|
|
|
198
198
|
remaining_minterms([], _, []).
|
|
199
199
|
remaining_minterms([M|Ms], Essentials, Rest) :-
|
package/examples/sudoku.pl
CHANGED
|
@@ -46,7 +46,7 @@ best_choice(Grid, Row, Column, Candidates) :-
|
|
|
46
46
|
),
|
|
47
47
|
Choices
|
|
48
48
|
),
|
|
49
|
-
|
|
49
|
+
sort_unique(Choices, [choice(_Count, Row, Column, Candidates)|_]).
|
|
50
50
|
|
|
51
51
|
empty_cell(Grid, Row, Column) :-
|
|
52
52
|
between(0, 8, Row),
|
package/examples/uuid.pl
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
% uuid/
|
|
1
|
+
% uuid/3 creates a version 4 UUID atom from explicit random state.
|
|
2
2
|
%
|
|
3
|
-
% The
|
|
4
|
-
%
|
|
3
|
+
% The same initial seed reproduces the same UUID across runs. Threading the
|
|
4
|
+
% returned seed into another call produces the next UUID in the sequence.
|
|
5
5
|
|
|
6
6
|
%% goal: uuid_example(Result)
|
|
7
7
|
|
|
8
8
|
uuid_example(true) :-
|
|
9
|
-
uuid(UUID),
|
|
9
|
+
uuid(20260807, UUID, _),
|
|
10
10
|
atom(UUID),
|
|
11
11
|
atom_length(UUID, 36),
|
|
12
12
|
sub_atom(UUID, 8, 1, 27, '-'),
|
package/package.json
CHANGED
package/playground.html
CHANGED
|
@@ -655,6 +655,7 @@
|
|
|
655
655
|
"aggregate_max",
|
|
656
656
|
"aggregate_min",
|
|
657
657
|
"append",
|
|
658
|
+
"apply",
|
|
658
659
|
"arg",
|
|
659
660
|
"asin",
|
|
660
661
|
"atan2",
|
|
@@ -681,7 +682,6 @@
|
|
|
681
682
|
"le",
|
|
682
683
|
"length",
|
|
683
684
|
"list_to_set",
|
|
684
|
-
"local_time",
|
|
685
685
|
"log",
|
|
686
686
|
"lowercase",
|
|
687
687
|
"lt",
|
|
@@ -699,6 +699,7 @@
|
|
|
699
699
|
"number_string",
|
|
700
700
|
"once",
|
|
701
701
|
"pow",
|
|
702
|
+
"random",
|
|
702
703
|
"replace",
|
|
703
704
|
"reverse",
|
|
704
705
|
"rounded",
|
|
@@ -707,7 +708,7 @@
|
|
|
707
708
|
"sin",
|
|
708
709
|
"slice",
|
|
709
710
|
"smallest_divisor_from",
|
|
710
|
-
"
|
|
711
|
+
"sort_unique",
|
|
711
712
|
"split",
|
|
712
713
|
"sqrt",
|
|
713
714
|
"string_concat",
|
|
@@ -744,6 +745,7 @@
|
|
|
744
745
|
let backgroundSource = '';
|
|
745
746
|
let backgroundName = '';
|
|
746
747
|
let activeWorker = null;
|
|
748
|
+
let activeWorkerStartupTimer = null;
|
|
747
749
|
let renderToken = 0;
|
|
748
750
|
let syntaxErrorLine = null;
|
|
749
751
|
let sourceReference = { kind: 'custom' };
|
|
@@ -957,9 +959,29 @@
|
|
|
957
959
|
runButton.disabled = true;
|
|
958
960
|
stopButton.disabled = false;
|
|
959
961
|
|
|
960
|
-
const workerUrl = new URL('./src/playground-worker.js?playground=
|
|
962
|
+
const workerUrl = new URL('./src/playground-worker.js?playground=20260807b', location.href);
|
|
961
963
|
activeWorker = new Worker(workerUrl, { type: 'module' });
|
|
962
|
-
|
|
964
|
+
const worker = activeWorker;
|
|
965
|
+
const request = {
|
|
966
|
+
source: combinedSource(),
|
|
967
|
+
options: { goals: selectedGoals(), proof: proof.checked, stats: stats.checked },
|
|
968
|
+
};
|
|
969
|
+
activeWorkerStartupTimer = setTimeout(() => {
|
|
970
|
+
if (activeWorker !== worker) return;
|
|
971
|
+
finishRun({
|
|
972
|
+
ok: false,
|
|
973
|
+
error: 'The reasoning worker did not finish loading. Reload the page to refresh cached EyeProlog assets.',
|
|
974
|
+
});
|
|
975
|
+
}, 10000);
|
|
976
|
+
activeWorker.onmessage = (event) => {
|
|
977
|
+
if (event.data?.type === 'ready') {
|
|
978
|
+
clearTimeout(activeWorkerStartupTimer);
|
|
979
|
+
activeWorkerStartupTimer = null;
|
|
980
|
+
worker.postMessage(request);
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
finishRun(event.data);
|
|
984
|
+
};
|
|
963
985
|
activeWorker.onerror = (event) => {
|
|
964
986
|
const location = event.filename
|
|
965
987
|
? ` (${event.filename}${event.lineno ? `:${event.lineno}${event.colno ? `:${event.colno}` : ''}` : ''})`
|
|
@@ -974,10 +996,6 @@
|
|
|
974
996
|
ok: false,
|
|
975
997
|
error: 'The reasoning worker returned an unreadable message.',
|
|
976
998
|
});
|
|
977
|
-
activeWorker.postMessage({
|
|
978
|
-
source: combinedSource(),
|
|
979
|
-
options: { goals: selectedGoals(), proof: proof.checked, stats: stats.checked },
|
|
980
|
-
});
|
|
981
999
|
}
|
|
982
1000
|
|
|
983
1001
|
function combinedSource() {
|
|
@@ -1072,6 +1090,8 @@
|
|
|
1072
1090
|
}
|
|
1073
1091
|
|
|
1074
1092
|
function cleanupWorker() {
|
|
1093
|
+
if (activeWorkerStartupTimer != null) clearTimeout(activeWorkerStartupTimer);
|
|
1094
|
+
activeWorkerStartupTimer = null;
|
|
1075
1095
|
if (activeWorker) activeWorker.terminate();
|
|
1076
1096
|
activeWorker = null;
|
|
1077
1097
|
}
|
package/src/cli.js
CHANGED
|
@@ -115,7 +115,7 @@ async function loadEngine() {
|
|
|
115
115
|
import('./program.js'),
|
|
116
116
|
import('./solver.js'),
|
|
117
117
|
import('./iso.js'),
|
|
118
|
-
import('./
|
|
118
|
+
import('./eyeprolog-autoload.js'),
|
|
119
119
|
]);
|
|
120
120
|
engineModule = { ...term, ...parser, ...program, ...solver, ...iso, ...library };
|
|
121
121
|
}
|
package/src/explain.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// human-readable and machine-readable.
|
|
5
5
|
import { ATOM, COMPOUND, Env, Term, VAR, deref, flattenConjunction, freshTerm, termToString, unify, variantTerms } from './term.js';
|
|
6
6
|
import { selectClauseCandidates } from './program.js';
|
|
7
|
-
import { getEyePrologRegistry } from './
|
|
7
|
+
import { getEyePrologRegistry } from './eyeprolog-autoload.js';
|
|
8
8
|
import { Solver, nextFreshId } from './solver.js';
|
|
9
9
|
|
|
10
10
|
export function whyProof(program, goal, options = {}) {
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// Autoload the pure-Prolog EyeProlog library in Node and the browser.
|
|
2
|
+
import { createDefaultRegistry } from './iso.js';
|
|
3
|
+
import { parseClauses } from './parser.js';
|
|
4
|
+
import { fs, isNode } from './platform.js';
|
|
5
|
+
|
|
6
|
+
export const eyePrologNativeLibraryIndicators = Object.freeze([]);
|
|
7
|
+
|
|
8
|
+
export const eyePrologPortableLibraryIndicators = Object.freeze([
|
|
9
|
+
'uuid/3',
|
|
10
|
+
'difference/3',
|
|
11
|
+
'apply/3',
|
|
12
|
+
'maplist/3',
|
|
13
|
+
'tan/2',
|
|
14
|
+
'asin/2',
|
|
15
|
+
'acos/2',
|
|
16
|
+
'atan2/3',
|
|
17
|
+
'lt/2',
|
|
18
|
+
'gt/2',
|
|
19
|
+
'le/2',
|
|
20
|
+
'ge/2',
|
|
21
|
+
'between/3',
|
|
22
|
+
'smallest_divisor_from/3',
|
|
23
|
+
'random/3',
|
|
24
|
+
'matches/3',
|
|
25
|
+
'split/3',
|
|
26
|
+
'replace/4',
|
|
27
|
+
'lowercase/2',
|
|
28
|
+
'uppercase/2',
|
|
29
|
+
'trim/2',
|
|
30
|
+
'number_string/2',
|
|
31
|
+
'atom_string/2',
|
|
32
|
+
'term_string/2',
|
|
33
|
+
'append/3',
|
|
34
|
+
'string_concat/3',
|
|
35
|
+
'contains/2',
|
|
36
|
+
'matches/2',
|
|
37
|
+
'join/3',
|
|
38
|
+
'substring/4',
|
|
39
|
+
'member/2',
|
|
40
|
+
'select/3',
|
|
41
|
+
'last/2',
|
|
42
|
+
'nth0/3',
|
|
43
|
+
'nth1/3',
|
|
44
|
+
'set_nth0/4',
|
|
45
|
+
'take/3',
|
|
46
|
+
'drop/3',
|
|
47
|
+
'slice/4',
|
|
48
|
+
'reverse/2',
|
|
49
|
+
'length/2',
|
|
50
|
+
'sum_list/2',
|
|
51
|
+
'min_list/2',
|
|
52
|
+
'max_list/2',
|
|
53
|
+
'list_to_set/2',
|
|
54
|
+
'sort_unique/2',
|
|
55
|
+
'countall/2',
|
|
56
|
+
'sumall/3',
|
|
57
|
+
'aggregate_min/5',
|
|
58
|
+
'aggregate_max/5',
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
export const eyePrologLibraryIndicators = Object.freeze([
|
|
62
|
+
...eyePrologNativeLibraryIndicators,
|
|
63
|
+
...eyePrologPortableLibraryIndicators,
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
const autoloadedPrograms = new WeakSet();
|
|
67
|
+
const libraryFiles = [
|
|
68
|
+
'eyeprolog-library.pl',
|
|
69
|
+
'eyeprolog-common-library.pl',
|
|
70
|
+
];
|
|
71
|
+
const libraryCacheKey = isNode
|
|
72
|
+
? null
|
|
73
|
+
: (new URL(import.meta.url).searchParams.get('playground') ?? '20260807b');
|
|
74
|
+
const librarySources = await Promise.all(libraryFiles.map(async (filename) => ({
|
|
75
|
+
filename,
|
|
76
|
+
source: await loadLibrarySource(libraryFileUrl(filename)),
|
|
77
|
+
})));
|
|
78
|
+
const portableClauseTemplates = librarySources.flatMap(({ filename, source }) => parseClauses(source, {
|
|
79
|
+
filename: `src/${filename}`,
|
|
80
|
+
sourceMetadata: true,
|
|
81
|
+
}));
|
|
82
|
+
|
|
83
|
+
async function loadLibrarySource(url) {
|
|
84
|
+
if (isNode) return fs.readFileSync(url, 'utf8');
|
|
85
|
+
const response = await fetch(url);
|
|
86
|
+
if (!response.ok) throw new Error(`could not load EyeProlog library: ${response.status}`);
|
|
87
|
+
return response.text();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function libraryFileUrl(filename) {
|
|
91
|
+
const url = new URL(`./${filename}`, import.meta.url);
|
|
92
|
+
if (!isNode && libraryCacheKey) url.searchParams.set('playground', libraryCacheKey);
|
|
93
|
+
return url;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function ensureEyePrologLibrary(program) {
|
|
97
|
+
if (autoloadedPrograms.has(program)) return program;
|
|
98
|
+
|
|
99
|
+
// User clauses already present in the Program stay first in clause order;
|
|
100
|
+
// the autoloaded library clauses are appended as defaults. This preserves
|
|
101
|
+
// useful source specializations such as length(numbers,N) while still making
|
|
102
|
+
// the relational length(List,N) library clauses available inside them.
|
|
103
|
+
let added = 0;
|
|
104
|
+
for (const template of portableClauseTemplates) {
|
|
105
|
+
// Clause terms are immutable; clone only the mutable indexing shell so the
|
|
106
|
+
// cached parse can be safely shared across independent Program instances.
|
|
107
|
+
const clause = {
|
|
108
|
+
...template,
|
|
109
|
+
body: template.body.slice(),
|
|
110
|
+
index: program.clauses.length,
|
|
111
|
+
eyePrologLibraryPortable: true,
|
|
112
|
+
};
|
|
113
|
+
program.clauses.push(clause);
|
|
114
|
+
program.indexClause(clause);
|
|
115
|
+
added++;
|
|
116
|
+
}
|
|
117
|
+
if (added > 0) program.markRecursivePredicates();
|
|
118
|
+
autoloadedPrograms.add(program);
|
|
119
|
+
return program;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function createEyePrologRegistry() {
|
|
123
|
+
const registry = createDefaultRegistry();
|
|
124
|
+
registry.eyePrologLibrary = true;
|
|
125
|
+
return registry;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let eyePrologRegistry = null;
|
|
129
|
+
|
|
130
|
+
export function getEyePrologRegistry() {
|
|
131
|
+
if (eyePrologRegistry == null) eyePrologRegistry = createEyePrologRegistry();
|
|
132
|
+
return eyePrologRegistry;
|
|
133
|
+
}
|