eyeprolog 0.5.11 → 0.5.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 +3 -3
- package/examples/age.pl +6 -6
- package/examples/proof/age.pl +5 -5
- package/examples/proof/iso-dynamic-database.pl +1 -1
- package/examples/uuid.pl +4 -4
- package/package.json +1 -1
- package/playground.html +1 -1
- package/src/cli.js +1 -1
- package/src/explain.js +1 -1
- package/src/eyeprolog-library.js +118 -0
- package/src/eyeprolog-library.pl +68 -2
- package/src/index.js +2 -2
- package/src/playground-worker.js +1 -1
- package/src/solver.js +1 -1
- package/test/conformance/cases/builtins/extra_uuid_v4.pl +2 -2
- package/test/run-examples.mjs +11 -19
- package/test/run-playground.mjs +1 -1
- package/test/run-regression.mjs +32 -19
- package/the-art-of-eyeprolog.md +21 -21
- package/src/library-source.js +0 -18
- package/src/library.js +0 -171
package/README.md
CHANGED
|
@@ -32,9 +32,9 @@ 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
|
-
|
|
37
|
-
|
|
35
|
+
**All 50 are ordinary Prolog clauses** in `src/eyeprolog-library.pl` and are
|
|
36
|
+
autoloaded in Node and the browser. None requires host support. Portable text
|
|
37
|
+
predicates use ISO atoms or character lists; the
|
|
38
38
|
RDF tools emit lexical values as ISO atoms as well.
|
|
39
39
|
|
|
40
40
|
## 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),
|
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(
|
|
38
|
+
by(rule("age.pl", clause(5))),
|
|
39
39
|
bindings([binding("S", 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')),
|
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
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('./library.js'),
|
|
118
|
+
import('./eyeprolog-library.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 './library.js';
|
|
7
|
+
import { getEyePrologRegistry } from './eyeprolog-library.js';
|
|
8
8
|
import { Solver, nextFreshId } from './solver.js';
|
|
9
9
|
|
|
10
10
|
export function whyProof(program, goal, options = {}) {
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Load and 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
|
+
'call/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/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 libraryUrl = new URL('./eyeprolog-library.pl', import.meta.url);
|
|
68
|
+
const librarySource = await loadLibrarySource(libraryUrl);
|
|
69
|
+
const portableClauseTemplates = parseClauses(librarySource, {
|
|
70
|
+
filename: 'src/eyeprolog-library.pl',
|
|
71
|
+
sourceMetadata: true,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
async function loadLibrarySource(url) {
|
|
75
|
+
if (isNode) return fs.readFileSync(url, 'utf8');
|
|
76
|
+
const response = await fetch(url);
|
|
77
|
+
if (!response.ok) throw new Error(`could not load EyeProlog library: ${response.status}`);
|
|
78
|
+
return response.text();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function ensureEyePrologLibrary(program) {
|
|
82
|
+
if (autoloadedPrograms.has(program)) return program;
|
|
83
|
+
|
|
84
|
+
// User clauses already present in the Program stay first in clause order;
|
|
85
|
+
// the autoloaded library clauses are appended as defaults. This preserves
|
|
86
|
+
// useful source specializations such as length(numbers,N) while still making
|
|
87
|
+
// the relational length(List,N) library clauses available inside them.
|
|
88
|
+
let added = 0;
|
|
89
|
+
for (const template of portableClauseTemplates) {
|
|
90
|
+
// Clause terms are immutable; clone only the mutable indexing shell so the
|
|
91
|
+
// cached parse can be safely shared across independent Program instances.
|
|
92
|
+
const clause = {
|
|
93
|
+
...template,
|
|
94
|
+
body: template.body.slice(),
|
|
95
|
+
index: program.clauses.length,
|
|
96
|
+
eyePrologLibraryPortable: true,
|
|
97
|
+
};
|
|
98
|
+
program.clauses.push(clause);
|
|
99
|
+
program.indexClause(clause);
|
|
100
|
+
added++;
|
|
101
|
+
}
|
|
102
|
+
if (added > 0) program.markRecursivePredicates();
|
|
103
|
+
autoloadedPrograms.add(program);
|
|
104
|
+
return program;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function createEyePrologRegistry() {
|
|
108
|
+
const registry = createDefaultRegistry();
|
|
109
|
+
registry.eyePrologLibrary = true;
|
|
110
|
+
return registry;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let eyePrologRegistry = null;
|
|
114
|
+
|
|
115
|
+
export function getEyePrologRegistry() {
|
|
116
|
+
if (eyePrologRegistry == null) eyePrologRegistry = createEyePrologRegistry();
|
|
117
|
+
return eyePrologRegistry;
|
|
118
|
+
}
|
package/src/eyeprolog-library.pl
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
% This file is autoloaded for the EyeProlog runtime and is intentionally
|
|
4
4
|
% written against the project's ISO compatibility profile. Text-facing
|
|
5
5
|
% predicates accept ISO atoms or proper lists of one-character atoms; newly
|
|
6
|
-
% produced text defaults to atoms.
|
|
7
|
-
%
|
|
6
|
+
% produced text defaults to atoms. Every public predicate in this file uses
|
|
7
|
+
% only the project's ISO Prolog profile; none requires host support.
|
|
8
8
|
|
|
9
9
|
% ---------- text representation helpers ----------
|
|
10
10
|
|
|
@@ -140,6 +140,72 @@ eyeprolog__between(Low, High, Value) :-
|
|
|
140
140
|
!,
|
|
141
141
|
eyeprolog__between(Next, High, Value).
|
|
142
142
|
|
|
143
|
+
% A Park-Miller generator with explicit state. Threading Seed into the next
|
|
144
|
+
% call makes a sequence reproducible without mutable runtime state. Schrage's
|
|
145
|
+
% method keeps every intermediate integer within the exact 32-bit range.
|
|
146
|
+
random(Seed0, Value, Seed) :-
|
|
147
|
+
integer(Seed0),
|
|
148
|
+
eyeprolog__random_normalize_seed(Seed0, Normalized),
|
|
149
|
+
High is Normalized // 44488,
|
|
150
|
+
Low is Normalized mod 44488,
|
|
151
|
+
Candidate is 48271 * Low - 3399 * High,
|
|
152
|
+
eyeprolog__random_wrap(Candidate, Seed),
|
|
153
|
+
Value is (Seed - 1) / 2147483646.
|
|
154
|
+
|
|
155
|
+
eyeprolog__random_normalize_seed(Seed, 1) :-
|
|
156
|
+
0 is Seed mod 2147483647,
|
|
157
|
+
!.
|
|
158
|
+
eyeprolog__random_normalize_seed(Seed, Normalized) :-
|
|
159
|
+
Normalized is Seed mod 2147483647.
|
|
160
|
+
|
|
161
|
+
eyeprolog__random_wrap(Candidate, Candidate) :- Candidate > 0, !.
|
|
162
|
+
eyeprolog__random_wrap(Candidate, Seed) :- Seed is Candidate + 2147483647.
|
|
163
|
+
|
|
164
|
+
% Seed-threaded UUID version 4 generation. The version and variant nibbles are
|
|
165
|
+
% fixed by RFC 9562; all other nibbles come from random/3. Reusing Seed0
|
|
166
|
+
% reproduces the same UUID, while threading Seed produces a deterministic
|
|
167
|
+
% sequence of different UUIDs.
|
|
168
|
+
uuid(Seed0, UUID, Seed) :-
|
|
169
|
+
eyeprolog__uuid_hex(8, Seed0, Group1, Seed1),
|
|
170
|
+
eyeprolog__uuid_hex(4, Seed1, Group2, Seed2),
|
|
171
|
+
eyeprolog__uuid_hex(3, Seed2, Group3, Seed3),
|
|
172
|
+
random(Seed3, _, Seed4),
|
|
173
|
+
VariantValue is 8 + Seed4 mod 4,
|
|
174
|
+
eyeprolog__hex_digit(VariantValue, Variant),
|
|
175
|
+
eyeprolog__uuid_hex(3, Seed4, Group4, Seed5),
|
|
176
|
+
eyeprolog__uuid_hex(12, Seed5, Group5, Seed),
|
|
177
|
+
append(Group1, ['-'|Tail1], Chars),
|
|
178
|
+
append(Group2, ['-','4'|Tail2], Tail1),
|
|
179
|
+
append(Group3, ['-',Variant|Tail3], Tail2),
|
|
180
|
+
append(Group4, ['-'|Group5], Tail3),
|
|
181
|
+
atom_chars(UUID, Chars).
|
|
182
|
+
|
|
183
|
+
eyeprolog__uuid_hex(0, Seed, [], Seed).
|
|
184
|
+
eyeprolog__uuid_hex(Count, Seed0, [Digit|Digits], Seed) :-
|
|
185
|
+
Count > 0,
|
|
186
|
+
random(Seed0, _, Seed1),
|
|
187
|
+
Value is Seed1 mod 16,
|
|
188
|
+
eyeprolog__hex_digit(Value, Digit),
|
|
189
|
+
NextCount is Count - 1,
|
|
190
|
+
eyeprolog__uuid_hex(NextCount, Seed1, Digits, Seed).
|
|
191
|
+
|
|
192
|
+
eyeprolog__hex_digit(0, '0').
|
|
193
|
+
eyeprolog__hex_digit(1, '1').
|
|
194
|
+
eyeprolog__hex_digit(2, '2').
|
|
195
|
+
eyeprolog__hex_digit(3, '3').
|
|
196
|
+
eyeprolog__hex_digit(4, '4').
|
|
197
|
+
eyeprolog__hex_digit(5, '5').
|
|
198
|
+
eyeprolog__hex_digit(6, '6').
|
|
199
|
+
eyeprolog__hex_digit(7, '7').
|
|
200
|
+
eyeprolog__hex_digit(8, '8').
|
|
201
|
+
eyeprolog__hex_digit(9, '9').
|
|
202
|
+
eyeprolog__hex_digit(10, a).
|
|
203
|
+
eyeprolog__hex_digit(11, b).
|
|
204
|
+
eyeprolog__hex_digit(12, c).
|
|
205
|
+
eyeprolog__hex_digit(13, d).
|
|
206
|
+
eyeprolog__hex_digit(14, e).
|
|
207
|
+
eyeprolog__hex_digit(15, f).
|
|
208
|
+
|
|
143
209
|
smallest_divisor_from(N, Start, Divisor) :-
|
|
144
210
|
N >= 0,
|
|
145
211
|
Start > 0,
|
package/src/index.js
CHANGED
|
@@ -18,7 +18,7 @@ export {
|
|
|
18
18
|
eyePrologLibraryIndicators,
|
|
19
19
|
eyePrologNativeLibraryIndicators,
|
|
20
20
|
eyePrologPortableLibraryIndicators,
|
|
21
|
-
} from './library.js';
|
|
21
|
+
} from './eyeprolog-library.js';
|
|
22
22
|
export { StreamManager } from './io.js';
|
|
23
23
|
|
|
24
24
|
import { ATOM, COMPOUND, VAR, Env, copyResolved, termIsGround, termToString } from './term.js';
|
|
@@ -26,7 +26,7 @@ import { Program } from './program.js';
|
|
|
26
26
|
import { Solver } from './solver.js';
|
|
27
27
|
import { whyNoProof, whyProof } from './explain.js';
|
|
28
28
|
import { HaltSignal, PrologError } from './iso.js';
|
|
29
|
-
import { getEyePrologRegistry } from './library.js';
|
|
29
|
+
import { getEyePrologRegistry } from './eyeprolog-library.js';
|
|
30
30
|
import { parseGoalText } from './parser.js';
|
|
31
31
|
|
|
32
32
|
export function run(source, options = {}) {
|
package/src/playground-worker.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Keep this module free of Node-only imports: it is fetched directly by the
|
|
3
3
|
// browser and is also exercised by test/run-playground.mjs.
|
|
4
4
|
import { run } from './index.js?playground=20260803c';
|
|
5
|
-
import { createEyePrologRegistry } from './library.js?playground=20260803c';
|
|
5
|
+
import { createEyePrologRegistry } from './eyeprolog-library.js?playground=20260803c';
|
|
6
6
|
|
|
7
7
|
const registry = createEyePrologRegistry();
|
|
8
8
|
|
package/src/solver.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
numberTerm, numberTextFromDouble, termIsGround, termToString, unify, variantTerms,
|
|
6
6
|
} from './term.js';
|
|
7
7
|
import { PrologError } from './iso.js';
|
|
8
|
-
import { ensureEyePrologLibrary, getEyePrologRegistry } from './library.js';
|
|
8
|
+
import { ensureEyePrologLibrary, getEyePrologRegistry } from './eyeprolog-library.js';
|
|
9
9
|
import { selectClauseCandidates, selectClauseCandidatesForValues, selectGroundClauseCandidates } from './program.js';
|
|
10
10
|
import { StreamManager } from './io.js';
|
|
11
11
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
% uuid/
|
|
1
|
+
% uuid/3 deterministically creates a canonical version 4 UUID atom from a seed.
|
|
2
2
|
answer(uuid_v4, true) :-
|
|
3
|
-
uuid(UUID),
|
|
3
|
+
uuid(1, UUID, _),
|
|
4
4
|
atom(UUID),
|
|
5
5
|
atom_length(UUID, 36),
|
|
6
6
|
sub_atom(UUID, 8, 1, 27, '-'),
|
package/test/run-examples.mjs
CHANGED
|
@@ -14,7 +14,6 @@ const packageRoot = path.resolve(root, '..');
|
|
|
14
14
|
const examplesDir = path.join(packageRoot, 'examples');
|
|
15
15
|
const expectedDir = path.join(examplesDir, 'output');
|
|
16
16
|
const expectedProofDir = path.join(examplesDir, 'proof');
|
|
17
|
-
const fixedExampleDate = '2026-05-30';
|
|
18
17
|
|
|
19
18
|
export const proofExamples = [
|
|
20
19
|
'access-control-policy.pl',
|
|
@@ -124,25 +123,18 @@ function runProofExample(name) {
|
|
|
124
123
|
}
|
|
125
124
|
|
|
126
125
|
function runProgramExample(programFile, filename, options) {
|
|
127
|
-
const
|
|
128
|
-
|
|
126
|
+
const text = fs.readFileSync(programFile, 'utf8');
|
|
127
|
+
const expectedExit = text.match(/^%\s*expect-exit:\s*(\d+)\s*$/m);
|
|
128
|
+
const program = Program.parseSources([{ text, filename }], {
|
|
129
|
+
sourceMetadata: options.proof,
|
|
130
|
+
});
|
|
129
131
|
try {
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const result = run(program, { ...options, goals: goalsInProgramOrder(program, text) });
|
|
137
|
-
if (expectedExit) throw new Error(`${filename} expected exit ${expectedExit[1]}, but reasoning succeeded`);
|
|
138
|
-
return result.stdout;
|
|
139
|
-
} catch (error) {
|
|
140
|
-
if (expectedExit && error?.code === Number(expectedExit[1])) return error.stdout ?? '';
|
|
141
|
-
throw error;
|
|
142
|
-
}
|
|
143
|
-
} finally {
|
|
144
|
-
if (oldLocalTime == null) delete process.env.EYEPROLOG_LOCAL_TIME;
|
|
145
|
-
else process.env.EYEPROLOG_LOCAL_TIME = oldLocalTime;
|
|
132
|
+
const result = run(program, { ...options, goals: goalsInProgramOrder(program, text) });
|
|
133
|
+
if (expectedExit) throw new Error(`${filename} expected exit ${expectedExit[1]}, but reasoning succeeded`);
|
|
134
|
+
return result.stdout;
|
|
135
|
+
} catch (error) {
|
|
136
|
+
if (expectedExit && error?.code === Number(expectedExit[1])) return error.stdout ?? '';
|
|
137
|
+
throw error;
|
|
146
138
|
}
|
|
147
139
|
}
|
|
148
140
|
|
package/test/run-playground.mjs
CHANGED
|
@@ -94,7 +94,7 @@ export async function runPlayground(reporter = new TestReporter()) {
|
|
|
94
94
|
await withStaticServer(async (baseUrl) => {
|
|
95
95
|
const modules = await crawlModuleGraph(new URL('src/playground-worker.js?playground=test', baseUrl));
|
|
96
96
|
assert(modules.size >= 10, `expected a substantial worker module graph, got ${modules.size}`);
|
|
97
|
-
assert([...modules].some((url) => url.includes('/src/library.js')), 'EyeProlog library missing from worker graph');
|
|
97
|
+
assert([...modules].some((url) => url.includes('/src/eyeprolog-library.js')), 'EyeProlog library missing from worker graph');
|
|
98
98
|
assert([...modules].some((url) => url.includes('/src/solver.js')), 'solver missing from worker graph');
|
|
99
99
|
});
|
|
100
100
|
});
|
package/test/run-regression.mjs
CHANGED
|
@@ -177,15 +177,23 @@ why(
|
|
|
177
177
|
},
|
|
178
178
|
},
|
|
179
179
|
{
|
|
180
|
-
name: '
|
|
180
|
+
name: 'seeded random/3 sequence is reproducible',
|
|
181
181
|
run: () => {
|
|
182
|
-
const result =
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
assertEqual(result.
|
|
187
|
-
|
|
188
|
-
|
|
182
|
+
const result = run(
|
|
183
|
+
'seeded(A, B, C, Seeds) :- random(1, A, S1), random(S1, B, S2), random(1, C, S3), Seeds = [S1, S2, S3].\n',
|
|
184
|
+
{ goal: 'seeded(A, B, C, Seeds)' },
|
|
185
|
+
);
|
|
186
|
+
assertEqual(result.stdout, 'seeded(0.00002247747035927835, 0.085032448717423201, 0.00002247747035927835, [48271, 182605794, 48271]).\n', 'stdout');
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
name: 'seeded uuid/3 sequence is reproducible',
|
|
191
|
+
run: () => {
|
|
192
|
+
const result = run(
|
|
193
|
+
'seeded_uuid(U1, U2, true) :- uuid(1, U1, S1), uuid(1, U1, _), uuid(S1, U2, _), U1 \\= U2.\n',
|
|
194
|
+
{ goal: 'seeded_uuid(U1, U2, Same)' },
|
|
195
|
+
);
|
|
196
|
+
assertEqual(result.stdout, "seeded_uuid('f26d1319-3f3f-4bd9-b92f-f414794a43b5', '4be874d3-166b-4107-b0dc-9c53074b3de1', true).\n", 'stdout');
|
|
189
197
|
},
|
|
190
198
|
},
|
|
191
199
|
{
|
|
@@ -576,7 +584,7 @@ function documentationSyncCases() {
|
|
|
576
584
|
'[Book — *The Art of EyeProlog*](https://eyereasoner.github.io/eyeprolog/the-art-of-eyeprolog)',
|
|
577
585
|
'README links to the book',
|
|
578
586
|
);
|
|
579
|
-
for (const filename of ['src/iso.js', 'src/library.js', 'src/playground-worker.js']) {
|
|
587
|
+
for (const filename of ['src/iso.js', 'src/eyeprolog-library.js', 'src/playground-worker.js']) {
|
|
580
588
|
assertEqual(fs.existsSync(path.join(packageRoot, filename)), true, `${filename} exists`);
|
|
581
589
|
assertIncludes(book, filename, `book documents ${filename}`);
|
|
582
590
|
}
|
|
@@ -1007,19 +1015,21 @@ open(X) :- candidate(X), \\+ closed(X).
|
|
|
1007
1015
|
assertEqual(Boolean(registry.get('is', 2)), true, 'ISO is/2 exists');
|
|
1008
1016
|
assertEqual(Boolean(registry.get('append', 3)), false, 'append/3 is not ISO core');
|
|
1009
1017
|
assertEqual(library.eyePrologLibrary, true, 'complete registry marker');
|
|
1010
|
-
assertEqual(library.defs.size,
|
|
1011
|
-
assertEqual(registeredNativeEyePrologLibraryNames().length,
|
|
1012
|
-
assertEqual(eyePrologPortableLibraryIndicators.length,
|
|
1013
|
-
assertEqual(eyePrologNativeLibraryIndicators.length,
|
|
1014
|
-
assertEqual(eyePrologNativeLibraryIndicators.join(','), '
|
|
1018
|
+
assertEqual(library.defs.size, 115, 'EyeProlog registry contains only ISO host definitions');
|
|
1019
|
+
assertEqual(registeredNativeEyePrologLibraryNames().length, 0, 'public native EyeProlog builtin count');
|
|
1020
|
+
assertEqual(eyePrologPortableLibraryIndicators.length, 50, 'portable Prolog library count');
|
|
1021
|
+
assertEqual(eyePrologNativeLibraryIndicators.length, 0, 'native host library count');
|
|
1022
|
+
assertEqual(eyePrologNativeLibraryIndicators.join(','), '', 'no EyeProlog library predicate requires host support');
|
|
1015
1023
|
assertEqual(eyePrologLibraryIndicators.length, 50, 'complete EyeProlog library surface');
|
|
1016
1024
|
assertEqual(library.get('between', 3), null, 'between/3 remains portable Prolog');
|
|
1017
1025
|
assertEqual(library.get('smallest_divisor_from', 3), null, 'smallest_divisor_from/3 remains portable Prolog');
|
|
1026
|
+
assertEqual(library.get('random', 3), null, 'random/3 remains portable Prolog');
|
|
1027
|
+
assertEqual(library.get('local_time', 1), null, 'local_time/1 is not a host builtin');
|
|
1018
1028
|
assertEqual(library.get('eyeprolog__string_atom', 2), null, 'private string adapter is absent');
|
|
1019
1029
|
assertEqual(library.get('append', 3), null, 'append/3 moved to portable Prolog');
|
|
1020
1030
|
assertEqual(library.get('maplist', 3), null, 'maplist/3 moved to portable Prolog');
|
|
1021
1031
|
assertEqual(library.get('matches', 3), null, 'matches/3 moved to portable Prolog');
|
|
1022
|
-
assertEqual(library.get('uuid',
|
|
1032
|
+
assertEqual(library.get('uuid', 3), null, 'uuid/3 remains portable Prolog');
|
|
1023
1033
|
for (const [name, arity] of [['not_member', 2], ['head', 2], ['rest', 2], ['min', 3], ['max', 3]]) {
|
|
1024
1034
|
assertEqual(library.get(name, arity), null, `${name}/${arity} removed from library`);
|
|
1025
1035
|
}
|
|
@@ -1037,9 +1047,11 @@ open(X) :- candidate(X), \\+ closed(X).
|
|
|
1037
1047
|
assertEqual(betweenGenerator.cutRecursive, true, 'portable between generator has deterministic recursive control');
|
|
1038
1048
|
assertEqual(betweenGenerator.tabled, false, 'portable between generator avoids suffix answer tables');
|
|
1039
1049
|
assertEqual(fs.existsSync(path.join(packageRoot, 'src', 'eyeprolog-library.pl')), true, 'portable Prolog source exists');
|
|
1040
|
-
assertEqual(fs.existsSync(path.join(packageRoot, 'src', 'library
|
|
1050
|
+
assertEqual(fs.existsSync(path.join(packageRoot, 'src', 'eyeprolog-library.js')), true, 'portable source loader exists');
|
|
1051
|
+
assertEqual(fs.existsSync(path.join(packageRoot, 'src', 'library-source.js')), false, 'duplicate source loader is absent');
|
|
1041
1052
|
assertEqual(fs.existsSync(path.join(packageRoot, 'src', 'portable-library.js')), false, 'obsolete duplicate module remains absent');
|
|
1042
1053
|
assertEqual(run(program, { goal: 'answer(X)' }).stdout, 'answer([a, b]).\n', 'autoloaded append execution');
|
|
1054
|
+
assertEqual(Boolean(program.findGroup('random', 3)), true, 'random/3 is autoloaded as a clause group');
|
|
1043
1055
|
},
|
|
1044
1056
|
},
|
|
1045
1057
|
{
|
|
@@ -1053,8 +1065,9 @@ portable_check(A, B, C) :- lowercase('HELLO', A), replace('banana', 'na', 'NA',
|
|
|
1053
1065
|
const goal = parseGoalText('portable_check(A, B, C)');
|
|
1054
1066
|
const answers = [...solver.solve([goal], new Env(), 0)].map((env) => termToString(goal, env, true));
|
|
1055
1067
|
assertEqual(answers.join('\n'), "portable_check(hello, baNANA, [x, y])", 'ISO-only portable execution');
|
|
1056
|
-
assertEqual(program.findGroup('uuid',
|
|
1057
|
-
assertEqual(program.findGroup('
|
|
1068
|
+
assertEqual(Boolean(program.findGroup('uuid', 3)), true, 'uuid/3 is implemented in the portable file');
|
|
1069
|
+
assertEqual(program.findGroup('uuid', 1), null, 'obsolete uuid/1 is absent');
|
|
1070
|
+
assertEqual(program.findGroup('local_time', 1), null, 'local_time/1 is absent from the library');
|
|
1058
1071
|
},
|
|
1059
1072
|
},
|
|
1060
1073
|
{
|
|
@@ -1699,7 +1712,7 @@ function playgroundStaticIssues() {
|
|
|
1699
1712
|
if (!html.includes("new URL('./src/playground-worker.js?playground=")) issues.push('playground must cache-bust its dedicated module worker');
|
|
1700
1713
|
if (!html.includes("new Worker(workerUrl, { type: 'module' })")) issues.push('playground must launch the dedicated module worker');
|
|
1701
1714
|
const workerText = fs.readFileSync(path.join(packageRoot, 'src', 'playground-worker.js'), 'utf8');
|
|
1702
|
-
if (!workerText.includes("from './library.js?playground=") ||
|
|
1715
|
+
if (!workerText.includes("from './eyeprolog-library.js?playground=") ||
|
|
1703
1716
|
!workerText.includes('createEyePrologRegistry') ||
|
|
1704
1717
|
!workerText.includes('executePlaygroundRequest')) {
|
|
1705
1718
|
issues.push('playground worker must install the EyeProlog library registry');
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -1710,10 +1710,10 @@ truncate search; it does not prove that no further answer exists.
|
|
|
1710
1710
|
|
|
1711
1711
|
The source layout mirrors the language boundary. `src/iso.js` contains the
|
|
1712
1712
|
isolated ISO processor predicates and registry. `src/eyeprolog-library.pl` is
|
|
1713
|
-
the portable EyeProlog library:
|
|
1714
|
-
Prolog clauses against that ISO profile.
|
|
1715
|
-
file in Node and the browser
|
|
1716
|
-
and the
|
|
1713
|
+
the portable EyeProlog library: 50 public predicates written as ordinary
|
|
1714
|
+
Prolog clauses against that ISO profile. Its paired
|
|
1715
|
+
`src/eyeprolog-library.js` module loads the Prolog file in Node and the browser
|
|
1716
|
+
and owns the small autoload integration layer.
|
|
1717
1717
|
The RDF tools emit IRI and literal lexical values as ISO atoms, matching the
|
|
1718
1718
|
portable text API without a host representation adapter.
|
|
1719
1719
|
|
|
@@ -5253,17 +5253,15 @@ so side effects occur in Prolog execution order.
|
|
|
5253
5253
|
### The EyeProlog library
|
|
5254
5254
|
|
|
5255
5255
|
EyeProlog exposes **50 library predicate indicators** in addition to the 115
|
|
5256
|
-
indicators in its isolated ISO profile.
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
definitions: 115 ISO predicates and the 2 public host predicates.
|
|
5256
|
+
indicators in its isolated ISO profile. **All 50 are ordinary Prolog clauses**
|
|
5257
|
+
in `src/eyeprolog-library.pl`; none is a native host predicate. The resulting
|
|
5258
|
+
normal EyeProlog language surface is therefore **165 public predicate
|
|
5259
|
+
indicators**. Internally, the runtime registry contains only the 115 ISO
|
|
5260
|
+
definitions; the EyeProlog relations are autoloaded source clauses.
|
|
5262
5261
|
|
|
5263
5262
|
The portable file is autoloaded once into every `Program` used with the
|
|
5264
|
-
EyeProlog registry. `src/library
|
|
5265
|
-
through `fetch()` in the browser
|
|
5266
|
-
registers the two host predicates.
|
|
5263
|
+
EyeProlog registry. `src/eyeprolog-library.js` loads it from the package in Node
|
|
5264
|
+
or through `fetch()` in the browser and performs the autoload.
|
|
5267
5265
|
The isolated ISO-only registry remains
|
|
5268
5266
|
available through `createDefaultRegistry()` and `getDefaultRegistry()` for
|
|
5269
5267
|
conformance work and advanced embedders. Source clauses sharing a portable
|
|
@@ -5283,11 +5281,11 @@ standard fallback relation.
|
|
|
5283
5281
|
| `join/3`, `substring/4` |
|
|
5284
5282
|
| `countall/2`, `sumall/3` |
|
|
5285
5283
|
| `aggregate_min/5`, `aggregate_max/5` |
|
|
5286
|
-
| `between/3`, `smallest_divisor_from/3` |
|
|
5284
|
+
| `between/3`, `smallest_divisor_from/3`, `random/3` |
|
|
5287
5285
|
| `maplist/3` |
|
|
5288
5286
|
| `acos/2`, `asin/2`, `atan2/3`, `tan/2` |
|
|
5289
5287
|
| `lt/2`, `le/2`, `gt/2`, `ge/2` |
|
|
5290
|
-
| `uuid/
|
|
5288
|
+
| `uuid/3`, `difference/3` |
|
|
5291
5289
|
| `matches/3` |
|
|
5292
5290
|
| `call/3` |
|
|
5293
5291
|
| `split/3`, `replace/4` |
|
|
@@ -5296,10 +5294,11 @@ standard fallback relation.
|
|
|
5296
5294
|
|
|
5297
5295
|
<!-- eyeprolog-library-catalog:end -->
|
|
5298
5296
|
|
|
5299
|
-
`uuid
|
|
5300
|
-
the
|
|
5301
|
-
|
|
5302
|
-
|
|
5297
|
+
`uuid(+Seed0,-UUID,-Seed)` creates a version 4 UUID atom using `random/3`.
|
|
5298
|
+
Passing the returned seed to the next call produces the next UUID; restarting
|
|
5299
|
+
with the same integer seed reproduces the same sequence exactly. This explicit
|
|
5300
|
+
state replaces hidden host entropy and behaves identically in Node and the
|
|
5301
|
+
browser playground.
|
|
5303
5302
|
|
|
5304
5303
|
On the command line, the EyeProlog library is already present:
|
|
5305
5304
|
|
|
@@ -5335,13 +5334,14 @@ the corresponding predicate.
|
|
|
5335
5334
|
| --- | --- |
|
|
5336
5335
|
| `tan/2`, `asin/2`, `acos/2`, `atan2/3` | Floating-point functions not supplied as evaluable functions by ISO/IEC 13211-1. |
|
|
5337
5336
|
| `lt(+A,+B)`, `le(+A,+B)`, `gt(+A,+B)`, `ge(+A,+B)` | Compare integers exactly, finite numeric text numerically, `PnYnMnD` duration text component-wise, and other lexical values by string order. These differ from ISO arithmetic comparison and standard term order. |
|
|
5338
|
-
| `
|
|
5337
|
+
| `random(+Seed0,-Value,-Seed)` | Portable Park-Miller generator with explicit state. `Value` is in `[0,1)`; pass the returned integer `Seed` to the next call. The same initial integer seed always reproduces the same sequence. |
|
|
5339
5338
|
| `difference(+End,+Start,-Duration)` | Portable Prolog. Computes a nonnegative calendar difference between ISO date atoms/character lists and returns atom `'PnYnMnD'`. Invalid dates or an end before the start fail. |
|
|
5340
5339
|
|
|
5341
5340
|
```eyeprolog
|
|
5342
5341
|
answer(square, S) :- (S is 12 * 12).
|
|
5343
5342
|
answer(day_count, N) :- between(3, 5, N).
|
|
5344
5343
|
answer(age, D) :- difference('2026-07-28', '2020-05-20', D).
|
|
5344
|
+
answer(random_pair, [A,B]) :- random(42, A, S), random(S, B, _).
|
|
5345
5345
|
eyeprolog --goal 'answer(Kind, Value)' program.pl
|
|
5346
5346
|
```
|
|
5347
5347
|
|
|
@@ -5778,7 +5778,7 @@ studies.
|
|
|
5778
5778
|
| [Socket Age](https://github.com/eyereasoner/eyeprolog/blob/main/examples/socket-age.pl) | Socket and plug declarations for an age-reasoning module with registry, policy, and clock providers. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/socket-age.pl) · [proof](https://github.com/eyereasoner/eyeprolog/blob/main/examples/proof/socket-age.pl) |
|
|
5779
5779
|
| [Socket Family](https://github.com/eyereasoner/eyeprolog/blob/main/examples/socket-family.pl) | Socket and plug declarations for a family-reasoning module that expects a `parent/2` provider. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/socket-family.pl) · [proof](https://github.com/eyereasoner/eyeprolog/blob/main/examples/proof/socket-family.pl) |
|
|
5780
5780
|
| [Socrates](https://github.com/eyereasoner/eyeprolog/blob/main/examples/socrates.pl) | A fact and one rule turn the classical syllogism into a ground derivation. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/socrates.pl) · [proof](https://github.com/eyereasoner/eyeprolog/blob/main/examples/proof/socrates.pl) |
|
|
5781
|
-
| [UUID](https://github.com/eyereasoner/eyeprolog/blob/main/examples/uuid.pl) | `uuid/
|
|
5781
|
+
| [UUID](https://github.com/eyereasoner/eyeprolog/blob/main/examples/uuid.pl) | `uuid/3` reproducibly creates one version 4 UUID atom from explicit random state; the example validates its canonical shape. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/uuid.pl) |
|
|
5782
5782
|
| [Witch](https://github.com/eyereasoner/eyeprolog/blob/main/examples/witch.pl) | Burn the witch, adapted from Eyeling's examples/witch.n3. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/witch.pl) · [proof](https://github.com/eyereasoner/eyeprolog/blob/main/examples/proof/witch.pl) |
|
|
5783
5783
|
|
|
5784
5784
|
Suggested path: Socrates → Age → Ancestor → Derived rule → Reusable built-ins.
|
package/src/library-source.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
// Load the portable EyeProlog Prolog-library source from the package assets.
|
|
2
|
-
// eyeprolog-library.pl is the source of truth for all 48 portable public
|
|
3
|
-
// library predicates.
|
|
4
|
-
import { fs, isNode } from './platform.js';
|
|
5
|
-
|
|
6
|
-
const portableLibraryUrl = new URL('./eyeprolog-library.pl', import.meta.url);
|
|
7
|
-
|
|
8
|
-
async function loadSource(url, label) {
|
|
9
|
-
if (isNode) return fs.readFileSync(url, 'utf8');
|
|
10
|
-
const response = await fetch(url);
|
|
11
|
-
if (!response.ok) throw new Error(`could not load ${label}: ${response.status}`);
|
|
12
|
-
return response.text();
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const eyePrologPortableLibrarySource = await loadSource(
|
|
16
|
-
portableLibraryUrl,
|
|
17
|
-
'EyeProlog portable library',
|
|
18
|
-
);
|
package/src/library.js
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
// EyeProlog library integration.
|
|
2
|
-
//
|
|
3
|
-
// 48 of the 50 public library predicates are implemented in plain Prolog in
|
|
4
|
-
// eyeprolog-library.pl and autoloaded into every Solver that uses the EyeProlog
|
|
5
|
-
// registry. Only uuid/1 and local_time/1 remain native because ISO Prolog has
|
|
6
|
-
// no standard entropy or wall-clock primitive.
|
|
7
|
-
import {
|
|
8
|
-
atom,
|
|
9
|
-
unify,
|
|
10
|
-
} from './term.js';
|
|
11
|
-
import {
|
|
12
|
-
BuiltinRegistry,
|
|
13
|
-
isoBuiltins,
|
|
14
|
-
} from './iso.js';
|
|
15
|
-
import { parseClauses } from './parser.js';
|
|
16
|
-
import { eyePrologPortableLibrarySource } from './library-source.js';
|
|
17
|
-
|
|
18
|
-
export const eyePrologNativeLibraryIndicators = Object.freeze([
|
|
19
|
-
'uuid/1',
|
|
20
|
-
'local_time/1',
|
|
21
|
-
]);
|
|
22
|
-
|
|
23
|
-
export const eyePrologPortableLibraryIndicators = Object.freeze([
|
|
24
|
-
'difference/3',
|
|
25
|
-
'call/3',
|
|
26
|
-
'maplist/3',
|
|
27
|
-
'tan/2',
|
|
28
|
-
'asin/2',
|
|
29
|
-
'acos/2',
|
|
30
|
-
'atan2/3',
|
|
31
|
-
'lt/2',
|
|
32
|
-
'gt/2',
|
|
33
|
-
'le/2',
|
|
34
|
-
'ge/2',
|
|
35
|
-
'between/3',
|
|
36
|
-
'smallest_divisor_from/3',
|
|
37
|
-
'matches/3',
|
|
38
|
-
'split/3',
|
|
39
|
-
'replace/4',
|
|
40
|
-
'lowercase/2',
|
|
41
|
-
'uppercase/2',
|
|
42
|
-
'trim/2',
|
|
43
|
-
'number_string/2',
|
|
44
|
-
'atom_string/2',
|
|
45
|
-
'term_string/2',
|
|
46
|
-
'append/3',
|
|
47
|
-
'string_concat/3',
|
|
48
|
-
'contains/2',
|
|
49
|
-
'matches/2',
|
|
50
|
-
'join/3',
|
|
51
|
-
'substring/4',
|
|
52
|
-
'member/2',
|
|
53
|
-
'select/3',
|
|
54
|
-
'last/2',
|
|
55
|
-
'nth0/3',
|
|
56
|
-
'nth1/3',
|
|
57
|
-
'set_nth0/4',
|
|
58
|
-
'take/3',
|
|
59
|
-
'drop/3',
|
|
60
|
-
'slice/4',
|
|
61
|
-
'reverse/2',
|
|
62
|
-
'length/2',
|
|
63
|
-
'sum_list/2',
|
|
64
|
-
'min_list/2',
|
|
65
|
-
'max_list/2',
|
|
66
|
-
'list_to_set/2',
|
|
67
|
-
'sort/2',
|
|
68
|
-
'countall/2',
|
|
69
|
-
'sumall/3',
|
|
70
|
-
'aggregate_min/5',
|
|
71
|
-
'aggregate_max/5',
|
|
72
|
-
]);
|
|
73
|
-
|
|
74
|
-
export const eyePrologLibraryIndicators = Object.freeze([
|
|
75
|
-
...eyePrologNativeLibraryIndicators,
|
|
76
|
-
...eyePrologPortableLibraryIndicators,
|
|
77
|
-
]);
|
|
78
|
-
|
|
79
|
-
const portableIndicatorSet = new Set(eyePrologPortableLibraryIndicators);
|
|
80
|
-
const autoloadedPrograms = new WeakSet();
|
|
81
|
-
const portableClauseTemplates = parseClauses(eyePrologPortableLibrarySource, {
|
|
82
|
-
filename: 'src/eyeprolog-library.pl',
|
|
83
|
-
sourceMetadata: true,
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
export function ensureEyePrologLibrary(program) {
|
|
88
|
-
if (autoloadedPrograms.has(program)) return program;
|
|
89
|
-
|
|
90
|
-
// User clauses already present in the Program stay first in clause order;
|
|
91
|
-
// the autoloaded library clauses are appended as defaults. This preserves
|
|
92
|
-
// useful source specializations such as length(numbers,N) while still making
|
|
93
|
-
// the relational length(List,N) library clauses available inside them.
|
|
94
|
-
let added = 0;
|
|
95
|
-
for (const template of portableClauseTemplates) {
|
|
96
|
-
// Clause terms are immutable; clone only the mutable indexing shell so the
|
|
97
|
-
// cached parse can be safely shared across independent Program instances.
|
|
98
|
-
const clause = {
|
|
99
|
-
...template,
|
|
100
|
-
body: template.body.slice(),
|
|
101
|
-
index: program.clauses.length,
|
|
102
|
-
eyePrologLibraryPortable: true,
|
|
103
|
-
};
|
|
104
|
-
program.clauses.push(clause);
|
|
105
|
-
program.indexClause(clause);
|
|
106
|
-
added++;
|
|
107
|
-
}
|
|
108
|
-
if (added > 0) program.markRecursivePredicates();
|
|
109
|
-
autoloadedPrograms.add(program);
|
|
110
|
-
return program;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function createEyePrologRegistry() {
|
|
114
|
-
const registry = new BuiltinRegistry();
|
|
115
|
-
registry.eyePrologLibrary = true;
|
|
116
|
-
|
|
117
|
-
registry.add('uuid', 1, uuidBuiltin, {
|
|
118
|
-
deterministic: true,
|
|
119
|
-
eyePrologLibrary: true,
|
|
120
|
-
});
|
|
121
|
-
registry.add('local_time', 1, localTimeBuiltin, {
|
|
122
|
-
deterministic: true,
|
|
123
|
-
eyePrologLibrary: true,
|
|
124
|
-
});
|
|
125
|
-
// ISO definitions take precedence where names overlap and remain identifiable
|
|
126
|
-
// as ISO rather than EyeProlog-library predicates.
|
|
127
|
-
isoBuiltins.register(registry);
|
|
128
|
-
return registry;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
let eyePrologRegistry = null;
|
|
132
|
-
|
|
133
|
-
export function getEyePrologRegistry() {
|
|
134
|
-
if (eyePrologRegistry == null) eyePrologRegistry = createEyePrologRegistry();
|
|
135
|
-
return eyePrologRegistry;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function* uuidBuiltin({ goal, env }) {
|
|
139
|
-
const next = env.clone();
|
|
140
|
-
if (unify(goal.args[0], atom(randomUuidV4()), next)) yield next;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function* localTimeBuiltin({ goal, env }) {
|
|
144
|
-
const next = env.clone();
|
|
145
|
-
if (unify(goal.args[0], atom(localDateText()), next)) yield next;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function randomUuidV4() {
|
|
149
|
-
const cryptoApi = globalThis.crypto;
|
|
150
|
-
if (typeof cryptoApi?.randomUUID === 'function') return cryptoApi.randomUUID();
|
|
151
|
-
const bytes = new Uint8Array(16);
|
|
152
|
-
if (typeof cryptoApi?.getRandomValues === 'function') {
|
|
153
|
-
cryptoApi.getRandomValues(bytes);
|
|
154
|
-
} else {
|
|
155
|
-
for (let index = 0; index < bytes.length; index++) bytes[index] = Math.floor(Math.random() * 256);
|
|
156
|
-
}
|
|
157
|
-
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
|
158
|
-
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
159
|
-
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0'));
|
|
160
|
-
return `${hex.slice(0, 4).join('')}-${hex.slice(4, 6).join('')}-${hex.slice(6, 8).join('')}-${hex.slice(8, 10).join('')}-${hex.slice(10).join('')}`;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function localDateText() {
|
|
164
|
-
const fixed = typeof process !== 'undefined' ? process.env?.EYEPROLOG_LOCAL_TIME : null;
|
|
165
|
-
if (fixed) return fixed;
|
|
166
|
-
const now = new Date();
|
|
167
|
-
const yyyy = now.getFullYear();
|
|
168
|
-
const mm = String(now.getMonth() + 1).padStart(2, '0');
|
|
169
|
-
const dd = String(now.getDate()).padStart(2, '0');
|
|
170
|
-
return `${yyyy}-${mm}-${dd}`;
|
|
171
|
-
}
|