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.
Files changed (43) hide show
  1. package/README.md +5 -3
  2. package/examples/age.pl +7 -7
  3. package/examples/blocks-world-planning.pl +3 -3
  4. package/examples/combinatorics-findall-sort.pl +4 -4
  5. package/examples/d3-group.pl +3 -3
  6. package/examples/dijkstra-findall-sort.pl +3 -3
  7. package/examples/enigma1225.pl +1 -2
  8. package/examples/eulerian-path.pl +4 -4
  9. package/examples/four-color-map.pl +2 -2
  10. package/examples/input/odrl-dpv-healthcare-risk-ranked-rules.pl +1 -1
  11. package/examples/input/odrl-dpv-risk-ranked-rules.pl +1 -1
  12. package/examples/input/rdf12-annotated-claims-rules.pl +1 -1
  13. package/examples/odrl-dpv-healthcare-risk-ranked.pl +1 -1
  14. package/examples/odrl-dpv-risk-ranked.pl +1 -1
  15. package/examples/proof/age.pl +6 -6
  16. package/examples/proof/d3-group.pl +4 -4
  17. package/examples/proof/iso-dynamic-database.pl +1 -1
  18. package/examples/quine-mccluskey.pl +7 -7
  19. package/examples/rdf12-annotated-claims.pl +1 -1
  20. package/examples/sudoku.pl +1 -1
  21. package/examples/uuid.pl +4 -4
  22. package/package.json +1 -1
  23. package/playground.html +28 -8
  24. package/src/cli.js +1 -1
  25. package/src/explain.js +1 -1
  26. package/src/eyeprolog-autoload.js +133 -0
  27. package/src/eyeprolog-common-library.pl +44 -0
  28. package/src/eyeprolog-library.pl +113 -74
  29. package/src/index.js +2 -2
  30. package/src/playground-worker.js +2 -2
  31. package/src/solver.js +1 -1
  32. package/test/conformance/cases/builtins/extra_uuid_v4.pl +2 -2
  33. package/test/conformance/cases/lists/031_lists_aggregation_ordering.pl +1 -2
  34. package/test/conformance/cases/lists/045_sort_deduplicates_atoms.pl +2 -3
  35. package/test/conformance/cases/lists/087_sort_reverse_length.pl +2 -2
  36. package/test/conformance/cases/lists/extra_sort_iri_atoms.pl +1 -1
  37. package/test/conformance/cases/lists/sort_structured_terms.pl +1 -1
  38. package/test/run-examples.mjs +11 -19
  39. package/test/run-playground.mjs +5 -1
  40. package/test/run-regression.mjs +35 -19
  41. package/the-art-of-eyeprolog.md +37 -32
  42. package/src/library-source.js +0 -18
  43. package/src/library.js +0 -171
@@ -177,15 +177,23 @@ why(
177
177
  },
178
178
  },
179
179
  {
180
- name: 'EYEPROLOG_LOCAL_TIME fixes local_time builtin',
180
+ name: 'seeded random/3 sequence is reproducible',
181
181
  run: () => {
182
- const result = runCli(['--goal', 'local_time_answer(D)', '-'], {
183
- input: 'local_time_answer(D) :- local_time(D).\n',
184
- env: { EYEPROLOG_LOCAL_TIME: '2024-01-02' },
185
- });
186
- assertEqual(result.status, 0, 'exit status');
187
- assertEqual(result.stdout, "local_time_answer('2024-01-02').\n", 'stdout');
188
- assertEqual(result.stderr, '', 'stderr');
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-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, 117, 'ISO plus host registry size');
1011
- assertEqual(registeredNativeEyePrologLibraryNames().length, 2, 'public native EyeProlog builtin count');
1012
- assertEqual(eyePrologPortableLibraryIndicators.length, 48, 'portable Prolog library count');
1013
- assertEqual(eyePrologNativeLibraryIndicators.length, 2, 'native host library count');
1014
- assertEqual(eyePrologNativeLibraryIndicators.join(','), 'uuid/1,local_time/1', 'native host library stays minimal');
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', 1)?.deterministic, true, 'uuid/1 deterministic metadata');
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', 'library-source.js')), true, 'portable source loader exists');
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', 1), null, 'uuid/1 remains outside portable file');
1057
- assertEqual(program.findGroup('local_time', 1), null, 'local_time/1 remains outside portable file');
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
- if (!workerText.includes("from './library.js?playground=") ||
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');
@@ -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
- `sort/2`, slicing helpers, and numeric summaries. Improper lists such as
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
- `sort/2`; when derivation multiplicity matters, retain the duplicates. Making
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` is
1713
- the portable EyeProlog library: 48 public predicates written as ordinary
1714
- Prolog clauses against that ISO profile. `src/library-source.js` loads the same
1715
- file in Node and the browser, while `src/library.js` owns autoload integration
1716
- and the two unavoidable public host predicates, `uuid/1` and `local_time/1`.
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. Of those 50, **48 are ordinary Prolog
5257
- clauses** in `src/eyeprolog-library.pl`; only **`uuid/1` and `local_time/1` are
5258
- native host predicates** because ISO Prolog provides neither entropy nor a wall
5259
- clock. The resulting normal EyeProlog language surface is therefore **165
5260
- public predicate indicators**. Internally, the runtime registry has 117
5261
- definitions: 115 ISO predicates and the 2 public host predicates.
5262
-
5263
- The portable file is autoloaded once into every `Program` used with the
5264
- EyeProlog registry. `src/library-source.js` loads it from the package in Node or
5265
- through `fetch()` in the browser; `src/library.js` performs the autoload and
5266
- registers the two host predicates.
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`, `sort/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/1`, `local_time/1`, `difference/3` |
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/1` creates one fresh version 4 UUID atom. It is deterministic in
5300
- the Prolog sense: each call has exactly one solution, although the generated atom
5301
- is intentionally different between calls. The implementation uses the Web Crypto API where available and remains
5302
- portable across Node and the browser playground.
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
- | `local_time(-Date)` | The one host relation in this group. Produces the host-local calendar date as atom `'YYYY-MM-DD'`. Tests and reproducible hosts may set `EYEPROLOG_LOCAL_TIME`. |
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
- | `maplist(+Closure,+List1,?List2)` | Applies a two-argument closure pairwise; `call/3` supplies the closure arguments and supports partially applied compound closures. |
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
- | `sort(+List,-Set)` | Sorts by standard term order and removes structural duplicates. |
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 sort/2. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/combinatorics-findall-sort.pl) |
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/1` creates one version 4 UUID atom; the example validates its shape while keeping the checked answer stable. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/uuid.pl) |
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 sort/2. | [answers](https://github.com/eyereasoner/eyeprolog/blob/main/examples/output/dijkstra-findall-sort.pl) |
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) |
@@ -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
- }