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/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/
|
|
587
|
+
for (const filename of ['src/iso.js', 'src/eyeprolog-autoload.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,24 +1047,30 @@ 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', '
|
|
1050
|
+
assertEqual(fs.existsSync(path.join(packageRoot, 'src', 'eyeprolog-autoload.js')), true, 'portable source autoloader exists');
|
|
1051
|
+
assertEqual(fs.existsSync(path.join(packageRoot, 'src', 'eyeprolog-common-library.pl')), true, 'pure-Prolog common library exists');
|
|
1052
|
+
assertEqual(fs.existsSync(path.join(packageRoot, 'src', 'library-source.js')), false, 'duplicate source loader is absent');
|
|
1041
1053
|
assertEqual(fs.existsSync(path.join(packageRoot, 'src', 'portable-library.js')), false, 'obsolete duplicate module remains absent');
|
|
1042
1054
|
assertEqual(run(program, { goal: 'answer(X)' }).stdout, 'answer([a, b]).\n', 'autoloaded append execution');
|
|
1055
|
+
assertEqual(Boolean(program.findGroup('random', 3)), true, 'random/3 is autoloaded as a clause group');
|
|
1043
1056
|
},
|
|
1044
1057
|
},
|
|
1045
1058
|
{
|
|
1046
1059
|
name: 'portable library executes against the ISO-only registry',
|
|
1047
1060
|
run: () => {
|
|
1048
1061
|
const portableSource = fs.readFileSync(path.join(packageRoot, 'src', 'eyeprolog-library.pl'), 'utf8');
|
|
1062
|
+
const commonSource = fs.readFileSync(path.join(packageRoot, 'src', 'eyeprolog-common-library.pl'), 'utf8');
|
|
1049
1063
|
const program = Program.parse(`${portableSource}
|
|
1064
|
+
${commonSource}
|
|
1050
1065
|
portable_check(A, B, C) :- lowercase('HELLO', A), replace('banana', 'na', 'NA', B), append([x], [y], C).
|
|
1051
1066
|
`);
|
|
1052
1067
|
const solver = new Solver(program, { registry: createDefaultRegistry() });
|
|
1053
1068
|
const goal = parseGoalText('portable_check(A, B, C)');
|
|
1054
1069
|
const answers = [...solver.solve([goal], new Env(), 0)].map((env) => termToString(goal, env, true));
|
|
1055
1070
|
assertEqual(answers.join('\n'), "portable_check(hello, baNANA, [x, y])", 'ISO-only portable execution');
|
|
1056
|
-
assertEqual(program.findGroup('uuid',
|
|
1057
|
-
assertEqual(program.findGroup('
|
|
1071
|
+
assertEqual(Boolean(program.findGroup('uuid', 3)), true, 'uuid/3 is implemented in the portable file');
|
|
1072
|
+
assertEqual(program.findGroup('uuid', 1), null, 'obsolete uuid/1 is absent');
|
|
1073
|
+
assertEqual(program.findGroup('local_time', 1), null, 'local_time/1 is absent from the library');
|
|
1058
1074
|
},
|
|
1059
1075
|
},
|
|
1060
1076
|
{
|
|
@@ -1699,7 +1715,7 @@ function playgroundStaticIssues() {
|
|
|
1699
1715
|
if (!html.includes("new URL('./src/playground-worker.js?playground=")) issues.push('playground must cache-bust its dedicated module worker');
|
|
1700
1716
|
if (!html.includes("new Worker(workerUrl, { type: 'module' })")) issues.push('playground must launch the dedicated module worker');
|
|
1701
1717
|
const workerText = fs.readFileSync(path.join(packageRoot, 'src', 'playground-worker.js'), 'utf8');
|
|
1702
|
-
|
|
1718
|
+
if (!workerText.includes("from './index.js?playground=") ||
|
|
1703
1719
|
!workerText.includes('createEyePrologRegistry') ||
|
|
1704
1720
|
!workerText.includes('executePlaygroundRequest')) {
|
|
1705
1721
|
issues.push('playground worker must install the EyeProlog library registry');
|
package/the-art-of-eyeprolog.md
CHANGED
|
@@ -907,7 +907,7 @@ reverse_go([X | Xs], Acc, Reversed) :-
|
|
|
907
907
|
|
|
908
908
|
No mutation occurs; every call receives a new term. EyeProlog also includes
|
|
909
909
|
`member/2`, `append/3`, `select/3`, `nth0/3`, `reverse/2`, `length/2`,
|
|
910
|
-
`
|
|
910
|
+
`sort_unique/2`, slicing helpers, and numeric summaries. Improper lists such as
|
|
911
911
|
`[a | Tail]` are valid terms, but operations requiring a proper finite list
|
|
912
912
|
fail unless the tail is `[]`.
|
|
913
913
|
|
|
@@ -1109,7 +1109,7 @@ Choose a collector from the question, not from convenience:
|
|
|
1109
1109
|
Counting solutions is not necessarily counting distinct domain objects: two
|
|
1110
1110
|
proofs may resolve the visible value in the same way. When identity matters,
|
|
1111
1111
|
collect the identifying template and deliberately canonicalize it with
|
|
1112
|
-
`
|
|
1112
|
+
`sort_unique/2`; when derivation multiplicity matters, retain the duplicates. Making
|
|
1113
1113
|
that decision explicit prevents a database-style summary from silently
|
|
1114
1114
|
changing the question.
|
|
1115
1115
|
|
|
@@ -1709,11 +1709,12 @@ truncate search; it does not prove that no further answer exists.
|
|
|
1709
1709
|
### Implementation boundary
|
|
1710
1710
|
|
|
1711
1711
|
The source layout mirrors the language boundary. `src/iso.js` contains the
|
|
1712
|
-
isolated ISO processor predicates and registry. `src/eyeprolog-library.pl`
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1712
|
+
isolated ISO processor predicates and registry. `src/eyeprolog-library.pl`
|
|
1713
|
+
contains collision-free, self-contained portable extensions.
|
|
1714
|
+
`src/eyeprolog-common-library.pl` supplies common list and aggregation
|
|
1715
|
+
predicates that Trealla, Scryer, and many other systems already provide. The
|
|
1716
|
+
paired `src/eyeprolog-autoload.js` module autoloads both pure-Prolog files in
|
|
1717
|
+
Node and the browser.
|
|
1717
1718
|
The RDF tools emit IRI and literal lexical values as ISO atoms, matching the
|
|
1718
1719
|
portable text API without a host representation adapter.
|
|
1719
1720
|
|
|
@@ -5253,17 +5254,18 @@ so side effects occur in Prolog execution order.
|
|
|
5253
5254
|
### The EyeProlog library
|
|
5254
5255
|
|
|
5255
5256
|
EyeProlog exposes **50 library predicate indicators** in addition to the 115
|
|
5256
|
-
indicators in its isolated ISO profile.
|
|
5257
|
-
|
|
5258
|
-
native host
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
definitions
|
|
5262
|
-
|
|
5263
|
-
The
|
|
5264
|
-
EyeProlog registry. `src/
|
|
5265
|
-
through `fetch()` in the browser
|
|
5266
|
-
|
|
5257
|
+
indicators in its isolated ISO profile. **All 50 are ordinary Prolog clauses**
|
|
5258
|
+
across `src/eyeprolog-library.pl` and `src/eyeprolog-common-library.pl`; none
|
|
5259
|
+
is a native host predicate. The resulting
|
|
5260
|
+
normal EyeProlog language surface is therefore **165 public predicate
|
|
5261
|
+
indicators**. Internally, the runtime registry contains only the 115 ISO
|
|
5262
|
+
definitions; the EyeProlog relations are autoloaded source clauses.
|
|
5263
|
+
|
|
5264
|
+
The two Prolog files are autoloaded once into every `Program` used with the
|
|
5265
|
+
EyeProlog registry. `src/eyeprolog-autoload.js` loads them from the package in
|
|
5266
|
+
Node or through `fetch()` in the browser and performs the autoload. External
|
|
5267
|
+
Prolog systems load only `src/eyeprolog-library.pl`; this avoids redefining
|
|
5268
|
+
their protected or preloaded list predicates.
|
|
5267
5269
|
The isolated ISO-only registry remains
|
|
5268
5270
|
available through `createDefaultRegistry()` and `getDefaultRegistry()` for
|
|
5269
5271
|
conformance work and advanced embedders. Source clauses sharing a portable
|
|
@@ -5278,28 +5280,29 @@ standard fallback relation.
|
|
|
5278
5280
|
| `nth0/3`, `nth1/3`, `set_nth0/4`, `take/3`, `drop/3`, `slice/4` |
|
|
5279
5281
|
| `reverse/2`, `length/2`, `sum_list/2` |
|
|
5280
5282
|
| `min_list/2`, `max_list/2` |
|
|
5281
|
-
| `list_to_set/2`, `
|
|
5283
|
+
| `list_to_set/2`, `sort_unique/2` |
|
|
5282
5284
|
| `string_concat/3`, `contains/2`, `matches/2` |
|
|
5283
5285
|
| `join/3`, `substring/4` |
|
|
5284
5286
|
| `countall/2`, `sumall/3` |
|
|
5285
5287
|
| `aggregate_min/5`, `aggregate_max/5` |
|
|
5286
|
-
| `between/3`, `smallest_divisor_from/3` |
|
|
5288
|
+
| `between/3`, `smallest_divisor_from/3`, `random/3` |
|
|
5289
|
+
| `apply/3` |
|
|
5287
5290
|
| `maplist/3` |
|
|
5288
5291
|
| `acos/2`, `asin/2`, `atan2/3`, `tan/2` |
|
|
5289
5292
|
| `lt/2`, `le/2`, `gt/2`, `ge/2` |
|
|
5290
|
-
| `uuid/
|
|
5293
|
+
| `uuid/3`, `difference/3` |
|
|
5291
5294
|
| `matches/3` |
|
|
5292
|
-
| `call/3` |
|
|
5293
5295
|
| `split/3`, `replace/4` |
|
|
5294
5296
|
| `lowercase/2`, `uppercase/2`, `trim/2` |
|
|
5295
5297
|
| `number_string/2`, `atom_string/2`, `term_string/2` |
|
|
5296
5298
|
|
|
5297
5299
|
<!-- eyeprolog-library-catalog:end -->
|
|
5298
5300
|
|
|
5299
|
-
`uuid
|
|
5300
|
-
the
|
|
5301
|
-
|
|
5302
|
-
|
|
5301
|
+
`uuid(+Seed0,-UUID,-Seed)` creates a version 4 UUID atom using `random/3`.
|
|
5302
|
+
Passing the returned seed to the next call produces the next UUID; restarting
|
|
5303
|
+
with the same integer seed reproduces the same sequence exactly. This explicit
|
|
5304
|
+
state replaces hidden host entropy and behaves identically in Node and the
|
|
5305
|
+
browser playground.
|
|
5303
5306
|
|
|
5304
5307
|
On the command line, the EyeProlog library is already present:
|
|
5305
5308
|
|
|
@@ -5335,13 +5338,14 @@ the corresponding predicate.
|
|
|
5335
5338
|
| --- | --- |
|
|
5336
5339
|
| `tan/2`, `asin/2`, `acos/2`, `atan2/3` | Floating-point functions not supplied as evaluable functions by ISO/IEC 13211-1. |
|
|
5337
5340
|
| `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
|
-
| `
|
|
5341
|
+
| `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
5342
|
| `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
5343
|
|
|
5341
5344
|
```eyeprolog
|
|
5342
5345
|
answer(square, S) :- (S is 12 * 12).
|
|
5343
5346
|
answer(day_count, N) :- between(3, 5, N).
|
|
5344
5347
|
answer(age, D) :- difference('2026-07-28', '2020-05-20', D).
|
|
5348
|
+
answer(random_pair, [A,B]) :- random(42, A, S), random(S, B, _).
|
|
5345
5349
|
eyeprolog --goal 'answer(Kind, Value)' program.pl
|
|
5346
5350
|
```
|
|
5347
5351
|
|
|
@@ -5374,7 +5378,8 @@ zero-based, nonnegative safe integers.
|
|
|
5374
5378
|
| `\+ member(+Item,+List)` | Succeeds only when `Item` does not unify with any member. Use it after binding the item and list. |
|
|
5375
5379
|
| `nth0(?Index,+List,?Item)` | Checks a bound zero-based index or enumerates indexes and their items. |
|
|
5376
5380
|
| `nth1(?Index,+List,?Item)` | Checks a bound one-based index or enumerates one-based indexes and items. |
|
|
5377
|
-
| `
|
|
5381
|
+
| `apply(+Closure,+A,+B)` | Extends a closure with two arguments using ISO `=../2`, then invokes it through ISO `call/1`. |
|
|
5382
|
+
| `maplist(+Closure,+List1,?List2)` | Applies a two-argument closure pairwise through `apply/3`; partially applied compound closures are supported. |
|
|
5378
5383
|
| `[Head|Tail] = List` | Decomposes a nonempty list directly with ISO unification; no library wrapper is needed. |
|
|
5379
5384
|
| `set_nth0(+Index,+List,+Item,-NewList)` | Replaces one existing position without mutating the input list. |
|
|
5380
5385
|
| `last(+List,?Last)` | Returns the final element of a nonempty proper list. |
|
|
@@ -5385,7 +5390,7 @@ zero-based, nonnegative safe integers.
|
|
|
5385
5390
|
| `sum_list(+List,-Sum)` | Sums numeric elements with ISO `is/2`. The empty sum is `0`; invalid arithmetic raises the corresponding ISO error. |
|
|
5386
5391
|
| `min_list(+List,-Min)`, `max_list(+List,-Max)` | Select by EyeProlog term order, not numeric coercion. Empty lists fail. |
|
|
5387
5392
|
| `list_to_set(+List,-Set)` | Removes later structural duplicates while preserving first-occurrence order. |
|
|
5388
|
-
| `
|
|
5393
|
+
| `sort_unique(+List,-Set)` | Sorts by standard term order and removes structural duplicates without colliding with engines that protect `sort/2`. |
|
|
5389
5394
|
|
|
5390
5395
|
```eyeprolog
|
|
5391
5396
|
answer(split, pair(Prefix, Suffix)) :-
|
|
@@ -5739,7 +5744,7 @@ mode at a time.
|
|
|
5739
5744
|
|
|
5740
5745
|
| Program | Standard facility | Checked answer |
|
|
5741
5746
|
| --- | --- | --- |
|
|
5742
|
-
| [Combinatorics Findall Sort](https://github.com/eyereasoner/eyeprolog/blob/main/examples/combinatorics-findall-sort.pl) | Eyelet-inspired combinations example using findall/3 and
|
|
5747
|
+
| [Combinatorics Findall Sort](https://github.com/eyereasoner/eyeprolog/blob/main/examples/combinatorics-findall-sort.pl) | Eyelet-inspired combinations example using findall/3 and `sort_unique/2`. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/combinatorics-findall-sort.pl) |
|
|
5743
5748
|
| [Floating Point](https://github.com/eyereasoner/eyeprolog/blob/main/examples/floating-point.pl) | Floating-point arithmetic and comparisons. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/floating-point.pl) · [proof](https://github.com/eyereasoner/eyeprolog/blob/main/examples/proof/floating-point.pl) |
|
|
5744
5749
|
| [Atomic conversion](https://github.com/eyereasoner/eyeprolog/blob/main/examples/iso-atomic-conversion.pl) | Atom splitting, character atoms, Unicode codes, and numeric parsing. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/iso-atomic-conversion.pl) |
|
|
5745
5750
|
| [Control and errors](https://github.com/eyereasoner/eyeprolog/blob/main/examples/iso-control-and-errors.pl) | `call/1`, `once/1`, cut, if-then-else, `throw/1`, and `catch/3`. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/iso-control-and-errors.pl) |
|
|
@@ -5778,7 +5783,7 @@ studies.
|
|
|
5778
5783
|
| [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
5784
|
| [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
5785
|
| [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/
|
|
5786
|
+
| [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
5787
|
| [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
5788
|
|
|
5784
5789
|
Suggested path: Socrates → Age → Ancestor → Derived rule → Reusable built-ins.
|
|
@@ -5817,7 +5822,7 @@ finite search space, and which constraint removes which branches?
|
|
|
5817
5822
|
|
|
5818
5823
|
| Program | Search design | Checked answer |
|
|
5819
5824
|
| --- | --- | --- |
|
|
5820
|
-
| [Dijkstra Findall Sort](https://github.com/eyereasoner/eyeprolog/blob/main/examples/dijkstra-findall-sort.pl) | Eyelet-inspired Dijkstra example using findall/3 and
|
|
5825
|
+
| [Dijkstra Findall Sort](https://github.com/eyereasoner/eyeprolog/blob/main/examples/dijkstra-findall-sort.pl) | Eyelet-inspired Dijkstra example using findall/3 and `sort_unique/2`. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/dijkstra-findall-sort.pl) |
|
|
5821
5826
|
| [Dijkstra](https://github.com/eyereasoner/eyeprolog/blob/main/examples/dijkstra.pl) | Weighted path enumeration adapted from Eyeling dijkstra.n3. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/dijkstra.pl) |
|
|
5822
5827
|
| [DONALD + GERALD = ROBERT](https://github.com/eyereasoner/eyeprolog/blob/main/examples/donald-gerald-robert.pl) | All ten decimal digits are assigned to ten distinct letters. Right-to-left carry propagation cuts a naive 10! search space to one solution. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/donald-gerald-robert.pl) |
|
|
5823
5828
|
| [Enigma1225](https://github.com/eyereasoner/eyeprolog/blob/main/examples/enigma1225.pl) | New Scientist Enigma 1225, retaining the best board in one pass with `aggregate_max/5`. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/enigma1225.pl) |
|
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
|
-
}
|