eyeprolog 1.5.62 → 1.5.65

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 (38) hide show
  1. package/README.md +3 -2
  2. package/examples/cross-organization-data-sharing.pl +1 -1
  3. package/examples/data/README.md +1 -1
  4. package/examples/data/symbiotic-knowledge-graph-rules.pl +3 -3
  5. package/examples/deck/introduction-to-eyeprolog.md +1 -1
  6. package/examples/deck/symbiotic-knowledge-graphs.md +6 -6
  7. package/examples/dpv-odrl-purpose-mapping.pl +2 -2
  8. package/examples/explainable-ev-depot-configuration.pl +1 -1
  9. package/examples/odrl-dpv-fpv-trust-flow.pl +2 -2
  10. package/examples/odrl-dpv-healthcare-risk-ranked.pl +2 -2
  11. package/examples/odrl-dpv-risk-ranked.pl +2 -2
  12. package/examples/odrl-policy-advanced.pl +2 -2
  13. package/examples/odrl-policy.pl +2 -2
  14. package/examples/operational-incident-response.pl +1 -1
  15. package/examples/output/symbiotic-knowledge-graph.pl +2 -2
  16. package/examples/rdf12-annotated-claims.pl +2 -2
  17. package/examples/rdf12-annotation.pl +2 -2
  18. package/examples/rdf12-directional-language.pl +2 -2
  19. package/examples/rdf12-nested-triple-term.pl +2 -2
  20. package/examples/rdf12-trig-graph-join.pl +2 -2
  21. package/examples/rdf12-trig-named-graph.pl +2 -2
  22. package/examples/rdf12-trig-triple-term.pl +2 -2
  23. package/examples/rdf12-triple-term.pl +2 -2
  24. package/examples/sbom-vulnerability-response.pl +1 -1
  25. package/examples/scientific-evidence-graph.pl +1 -1
  26. package/examples/symbiotic-knowledge-graph.pl +4 -4
  27. package/package.json +1 -1
  28. package/src/lib/error.pl +11 -8
  29. package/src/lib/si.pl +48 -10
  30. package/src/library-autoload-index.js +1 -0
  31. package/src/standard-library.js +1 -1
  32. package/test/regression/cases-api.mjs +2 -2
  33. package/test/regression/cases-documentation-sync.mjs +4 -4
  34. package/test/regression/cases-regression.mjs +76 -0
  35. package/the-art-of-eyeprolog.md +428 -424
  36. package/tools/generate-predicate-reference.mjs +1 -1
  37. package/tools/predicate-reference.json +10 -1
  38. package/why-eyeprolog.md +1 -1
package/README.md CHANGED
@@ -69,14 +69,15 @@ printf 'human(socrates).\nmortal(X) :- human(X).\n' |
69
69
  - [Example decks](examples/deck/README.md) — explainable RDF/Prolog scenarios with reproducible roundtrips
70
70
  - [Introduction to EyeProlog](https://eyereasoner.github.io/eyeprolog/examples/deck/introduction-to-eyeprolog) — short presentation deck for first-time audiences
71
71
  - [Symbiotic Knowledge Graphs](https://eyereasoner.github.io/eyeprolog/examples/deck/symbiotic-knowledge-graphs) — RDF ↔ Prolog heatwave-response demo for human/AI/KG co-evolution
72
- - [rdf-prolog-roundtrip](https://github.com/eyereasoner/rdf-prolog-roundtrip) — standalone RDF 1.2 ↔ ISO Prolog bridge used by the RDF examples
72
+ - [rdf-prolog-interchange](https://github.com/eyereasoner/rdf-prolog-interchange) — standalone RDF 1.2 ↔ ISO Prolog bridge used by the RDF examples
73
+ - [RDF and Prolog: Two Standards-Based Legs](https://github.com/eyereasoner/rdf-prolog-interchange/blob/main/why-rdf-prolog.md) — why W3C RDF and ISO Prolog form the foundation
73
74
  - [ISO conformance review](test/conformance/ISO-COMPLIANCE.md) — supported Part 1 profile
74
75
  - [Latest Neumerkel conformity](test/conformance/NEUMERKEL-LATEST.md) — tracked result from the current live upstream inventory
75
76
  - [Conformance report](conformance-report.md) — generated executable conformance status, local corpus summary, and known deviations
76
77
  - [OpenRuleBench](openrulebench/README.md) — portable benchmark profile
77
78
  ## RDF, Prolog, and symbiotic knowledge graphs
78
79
 
79
- EyeProlog can sit behind an RDF knowledge graph without inventing a private graph representation. [`rdf-prolog-roundtrip`](https://github.com/eyereasoner/rdf-prolog-roundtrip) converts RDF 1.2 datasets to ordinary `rdf(Subject, Predicate, Object, Graph)` facts, EyeProlog applies portable rules, and ground `rdf/4` results can be converted back to RDF.
80
+ EyeProlog can sit behind an RDF knowledge graph without inventing a private graph representation. [`rdf-prolog-interchange`](https://github.com/eyereasoner/rdf-prolog-interchange) converts RDF 1.2 datasets to ordinary `rdf(Subject, Predicate, Object, Graph)` facts, EyeProlog applies portable rules, and ground `rdf/4` results can be converted back to RDF.
80
81
 
81
82
  The checked [Symbiotic Knowledge Graphs example](examples/symbiotic-knowledge-graph.pl) uses named graphs and RDF 1.2 triple terms to distinguish trusted knowledge, AI-proposed statements, and human review. Its [wide-audience companion](https://eyereasoner.github.io/eyeprolog/examples/deck/symbiotic-knowledge-graphs) explains why this is a useful present-day software model for human/AI/KG co-evolution: RDF supplies shared semantic memory, Prolog supplies explicit deliberation, AI supplies new hypotheses, and people remain participants in meaning and judgment.
82
83
 
@@ -1,4 +1,4 @@
1
- % Generated by rdf-prolog-roundtrip.
1
+ % Generated by rdf-prolog-interchange.
2
2
 
3
3
  % RDF terms use rdf(Subject, Predicate, Object, Graph).
4
4
 
@@ -4,7 +4,7 @@ This directory contains source and materialized data that support runnable examp
4
4
 
5
5
  ## Symbiotic Knowledge Graphs
6
6
 
7
- The heatwave-response example uses [`rdf-prolog-roundtrip`](https://github.com/eyereasoner/rdf-prolog-roundtrip) as its RDF boundary:
7
+ The heatwave-response example uses [`rdf-prolog-interchange`](https://github.com/eyereasoner/rdf-prolog-interchange) as its RDF boundary:
8
8
 
9
9
  ```text
10
10
  symbiotic-knowledge-graph-input.nq
@@ -1,6 +1,6 @@
1
1
  % Symbiotic knowledge graph rules for EyeProlog.
2
2
  %
3
- % The source rdf/4 facts above are generated by rdf-prolog-roundtrip from
3
+ % The source rdf/4 facts above are generated by rdf-prolog-interchange from
4
4
  % symbiotic-knowledge-graph-input.nq. RDF stays the interchange and storage
5
5
  % model; these rules provide the explicit reasoning and governance layer.
6
6
 
@@ -317,10 +317,10 @@ knowledge_exchange_fact(prolog_to_rdf, materialized_ground_rdf4).
317
317
 
318
318
  pipeline_step(N, Description) :- pipeline_step_fact(N, Description).
319
319
  pipeline_step_fact(1, 'RDF 1.2 N-Quads: named graphs keep source and governance boundaries explicit').
320
- pipeline_step_fact(2, 'rdf-prolog-roundtrip: RDF becomes ordinary rdf/4 Prolog facts without a solver').
320
+ pipeline_step_fact(2, 'rdf-prolog-interchange: RDF becomes ordinary rdf/4 Prolog facts without a solver').
321
321
  pipeline_step_fact(3, 'EyeProlog: ISO Prolog rules validate candidates, apply review policy, and derive actions').
322
322
  pipeline_step_fact(4, 'EyeProlog: result_rdf/4 materializes accepted knowledge and decisions as ground RDF-shaped facts').
323
- pipeline_step_fact(5, 'rdf-prolog-roundtrip: ground rdf/4 facts become RDF again for publication or federation').
323
+ pipeline_step_fact(5, 'rdf-prolog-interchange: ground rdf/4 facts become RDF again for publication or federation').
324
324
 
325
325
  cognitive_parallel(Prolog, Human) :- cognitive_parallel_fact(Prolog, Human).
326
326
  cognitive_parallel_fact(fact, remembered_assertion).
@@ -166,7 +166,7 @@ Boolean constraints are compact when the domain is “on/off”, “selected/not
166
166
 
167
167
  ## RDF and knowledge graphs
168
168
 
169
- EyeProlog pairs naturally with RDF through `rdf-prolog-roundtrip`:
169
+ EyeProlog pairs naturally with RDF through `rdf-prolog-interchange`:
170
170
 
171
171
  ```text
172
172
  RDF dataset
@@ -10,7 +10,7 @@ The demonstration does not claim that EyeProlog is a brain-computer interface. I
10
10
 
11
11
  The architecture uses two small projects with deliberately separate jobs:
12
12
 
13
- - [`rdf-prolog-roundtrip`](https://github.com/eyereasoner/rdf-prolog-roundtrip) moves RDF 1.2 datasets to and from ordinary `rdf/4` Prolog facts. It contains no Prolog solver.
13
+ - [`rdf-prolog-interchange`](https://github.com/eyereasoner/rdf-prolog-interchange) moves RDF 1.2 datasets to and from ordinary `rdf/4` Prolog facts. It contains no Prolog solver.
14
14
  - [EyeProlog](https://github.com/eyereasoner/eyeprolog) reasons over those facts with explicit ISO Prolog rules.
15
15
 
16
16
  That separation is the central design choice. **RDF remains the shared interchange and publication layer; Prolog becomes the transparent reasoning layer.**
@@ -105,7 +105,7 @@ p2 confidencePercent 93
105
105
 
106
106
  The embedded triple is a **statement being talked about**. It is not yet an asserted operational fact.
107
107
 
108
- After `rdf-prolog-roundtrip`, the same structure is an ordinary Prolog term:
108
+ After `rdf-prolog-interchange`, the same structure is an ordinary Prolog term:
109
109
 
110
110
  ```prolog
111
111
  rdf(
@@ -246,10 +246,10 @@ That closeness makes it easier for a person to enter the machine's reasoning loo
246
246
 
247
247
  Without RDF, the Prolog example could be dismissed as a hand-written toy knowledge base.
248
248
 
249
- With `rdf-prolog-roundtrip`, the roles are clearer:
249
+ With `rdf-prolog-interchange`, the roles are clearer:
250
250
 
251
251
  - RDF provides Web identifiers, graph boundaries, literals, RDF 1.2 triple terms, and interoperable publication;
252
- - `rdf-prolog-roundtrip` preserves those structures as ordinary ISO Prolog terms;
252
+ - `rdf-prolog-interchange` preserves those structures as ordinary ISO Prolog terms;
253
253
  - EyeProlog applies explicit rules and explores alternatives;
254
254
  - materialized conclusions return to RDF.
255
255
 
@@ -279,7 +279,7 @@ A direct neural interface would change the input/output channel. It would not re
279
279
 
280
280
  ## Reproduce the roundtrip
281
281
 
282
- Assuming `rdf-prolog-roundtrip` and EyeProlog are available on `PATH`:
282
+ Assuming `rdf-prolog-interchange` and EyeProlog are available on `PATH`:
283
283
 
284
284
  ```sh
285
285
  rdf-to-prolog \
@@ -327,5 +327,5 @@ The deepest reason Prolog fits this vision is not nostalgia or syntax. It is tha
327
327
  ## References
328
328
 
329
329
  - Ruben Taelman, [*Symbiotic Knowledge Graphs: A Vision for Semantic Brain-Computer Interfaces*](https://rubensworks.github.io/article-iswc2026-vision-symbiotic-knowledge-graphs/).
330
- - [`eyereasoner/rdf-prolog-roundtrip`](https://github.com/eyereasoner/rdf-prolog-roundtrip) — standalone RDF 1.2 ↔ ISO Prolog roundtripping toolkit.
330
+ - [`eyereasoner/rdf-prolog-interchange`](https://github.com/eyereasoner/rdf-prolog-interchange) — standalone RDF 1.2 ↔ ISO Prolog roundtripping toolkit.
331
331
  - [`eyereasoner/eyeprolog`](https://github.com/eyereasoner/eyeprolog) — ISO Prolog reasoning and proof engine.
@@ -1,7 +1,7 @@
1
- % Adapted from rdf-prolog-roundtrip.
1
+ % Adapted from rdf-prolog-interchange.
2
2
  % This standalone program combines generated RDF input with its ISO Prolog rules.
3
3
 
4
- % Generated by rdf-prolog-roundtrip.
4
+ % Generated by rdf-prolog-interchange.
5
5
 
6
6
  % RDF terms use rdf(Subject, Predicate, Object, Graph).
7
7
 
@@ -1,4 +1,4 @@
1
- % Generated by rdf-prolog-roundtrip.
1
+ % Generated by rdf-prolog-interchange.
2
2
 
3
3
  % RDF terms use rdf(Subject, Predicate, Object, Graph).
4
4
 
@@ -1,7 +1,7 @@
1
- % Adapted from rdf-prolog-roundtrip.
1
+ % Adapted from rdf-prolog-interchange.
2
2
  % This standalone program combines generated RDF input with its ISO Prolog rules.
3
3
 
4
- % Generated by rdf-prolog-roundtrip.
4
+ % Generated by rdf-prolog-interchange.
5
5
 
6
6
  % RDF terms use rdf(Subject, Predicate, Object, Graph).
7
7
 
@@ -1,7 +1,7 @@
1
- % Adapted from rdf-prolog-roundtrip.
1
+ % Adapted from rdf-prolog-interchange.
2
2
  % This standalone program combines generated RDF input with its ISO Prolog rules.
3
3
 
4
- % Generated by rdf-prolog-roundtrip.
4
+ % Generated by rdf-prolog-interchange.
5
5
 
6
6
  % RDF terms use rdf(Subject, Predicate, Object, Graph).
7
7
 
@@ -1,7 +1,7 @@
1
- % Adapted from rdf-prolog-roundtrip.
1
+ % Adapted from rdf-prolog-interchange.
2
2
  % This standalone program combines generated RDF input with its ISO Prolog rules.
3
3
 
4
- % Generated by rdf-prolog-roundtrip.
4
+ % Generated by rdf-prolog-interchange.
5
5
 
6
6
  % RDF terms use rdf(Subject, Predicate, Object, Graph).
7
7
 
@@ -1,7 +1,7 @@
1
- % Adapted from rdf-prolog-roundtrip.
1
+ % Adapted from rdf-prolog-interchange.
2
2
  % This standalone program combines generated RDF input with its ISO Prolog rules.
3
3
 
4
- % Generated by rdf-prolog-roundtrip.
4
+ % Generated by rdf-prolog-interchange.
5
5
 
6
6
  % RDF terms use rdf(Subject, Predicate, Object, Graph).
7
7
 
@@ -1,7 +1,7 @@
1
- % Adapted from rdf-prolog-roundtrip.
1
+ % Adapted from rdf-prolog-interchange.
2
2
  % This standalone program combines generated RDF input with its ISO Prolog rules.
3
3
 
4
- % Generated by rdf-prolog-roundtrip.
4
+ % Generated by rdf-prolog-interchange.
5
5
 
6
6
  % RDF terms use rdf(Subject, Predicate, Object, Graph).
7
7
 
@@ -1,4 +1,4 @@
1
- % Generated by rdf-prolog-roundtrip.
1
+ % Generated by rdf-prolog-interchange.
2
2
 
3
3
  % RDF terms use rdf(Subject, Predicate, Object, Graph).
4
4
 
@@ -29,10 +29,10 @@ knowledge_exchange(machine_to_human, recommendation(after_review, riverside, ope
29
29
  knowledge_exchange(rdf_to_prolog, ordinary_rdf4_facts).
30
30
  knowledge_exchange(prolog_to_rdf, materialized_ground_rdf4).
31
31
  pipeline_step(1, 'RDF 1.2 N-Quads: named graphs keep source and governance boundaries explicit').
32
- pipeline_step(2, 'rdf-prolog-roundtrip: RDF becomes ordinary rdf/4 Prolog facts without a solver').
32
+ pipeline_step(2, 'rdf-prolog-interchange: RDF becomes ordinary rdf/4 Prolog facts without a solver').
33
33
  pipeline_step(3, 'EyeProlog: ISO Prolog rules validate candidates, apply review policy, and derive actions').
34
34
  pipeline_step(4, 'EyeProlog: result_rdf/4 materializes accepted knowledge and decisions as ground RDF-shaped facts').
35
- pipeline_step(5, 'rdf-prolog-roundtrip: ground rdf/4 facts become RDF again for publication or federation').
35
+ pipeline_step(5, 'rdf-prolog-interchange: ground rdf/4 facts become RDF again for publication or federation').
36
36
  cognitive_parallel(fact, remembered_assertion).
37
37
  cognitive_parallel(rule, reusable_generalization).
38
38
  cognitive_parallel(query, explicit_question).
@@ -1,7 +1,7 @@
1
- % Adapted from rdf-prolog-roundtrip.
1
+ % Adapted from rdf-prolog-interchange.
2
2
  % This standalone program combines generated RDF input with its ISO Prolog rules.
3
3
 
4
- % Generated by rdf-prolog-roundtrip.
4
+ % Generated by rdf-prolog-interchange.
5
5
 
6
6
  % RDF terms use rdf(Subject, Predicate, Object, Graph).
7
7
 
@@ -1,7 +1,7 @@
1
- % Adapted from rdf-prolog-roundtrip.
1
+ % Adapted from rdf-prolog-interchange.
2
2
  % This standalone program combines generated RDF input with its ISO Prolog rules.
3
3
 
4
- % Generated by rdf-prolog-roundtrip.
4
+ % Generated by rdf-prolog-interchange.
5
5
 
6
6
  % RDF terms use rdf(Subject, Predicate, Object, Graph).
7
7
 
@@ -1,7 +1,7 @@
1
- % Adapted from rdf-prolog-roundtrip.
1
+ % Adapted from rdf-prolog-interchange.
2
2
  % This standalone program combines generated RDF input with its ISO Prolog rules.
3
3
 
4
- % Generated by rdf-prolog-roundtrip.
4
+ % Generated by rdf-prolog-interchange.
5
5
 
6
6
  % RDF terms use rdf(Subject, Predicate, Object, Graph).
7
7
 
@@ -1,7 +1,7 @@
1
- % Adapted from rdf-prolog-roundtrip.
1
+ % Adapted from rdf-prolog-interchange.
2
2
  % This standalone program combines generated RDF input with its ISO Prolog rules.
3
3
 
4
- % Generated by rdf-prolog-roundtrip.
4
+ % Generated by rdf-prolog-interchange.
5
5
 
6
6
  % RDF terms use rdf(Subject, Predicate, Object, Graph).
7
7
 
@@ -1,7 +1,7 @@
1
- % Adapted from rdf-prolog-roundtrip.
1
+ % Adapted from rdf-prolog-interchange.
2
2
  % This standalone program combines generated RDF input with its ISO Prolog rules.
3
3
 
4
- % Generated by rdf-prolog-roundtrip.
4
+ % Generated by rdf-prolog-interchange.
5
5
 
6
6
  % RDF terms use rdf(Subject, Predicate, Object, Graph).
7
7
 
@@ -1,7 +1,7 @@
1
- % Adapted from rdf-prolog-roundtrip.
1
+ % Adapted from rdf-prolog-interchange.
2
2
  % This standalone program combines generated RDF input with its ISO Prolog rules.
3
3
 
4
- % Generated by rdf-prolog-roundtrip.
4
+ % Generated by rdf-prolog-interchange.
5
5
 
6
6
  % RDF terms use rdf(Subject, Predicate, Object, Graph).
7
7
 
@@ -1,7 +1,7 @@
1
- % Adapted from rdf-prolog-roundtrip.
1
+ % Adapted from rdf-prolog-interchange.
2
2
  % This standalone program combines generated RDF input with its ISO Prolog rules.
3
3
 
4
- % Generated by rdf-prolog-roundtrip.
4
+ % Generated by rdf-prolog-interchange.
5
5
 
6
6
  % RDF terms use rdf(Subject, Predicate, Object, Graph).
7
7
 
@@ -1,7 +1,7 @@
1
- % Adapted from rdf-prolog-roundtrip.
1
+ % Adapted from rdf-prolog-interchange.
2
2
  % This standalone program combines generated RDF input with its ISO Prolog rules.
3
3
 
4
- % Generated by rdf-prolog-roundtrip.
4
+ % Generated by rdf-prolog-interchange.
5
5
 
6
6
  % RDF terms use rdf(Subject, Predicate, Object, Graph).
7
7
 
@@ -1,4 +1,4 @@
1
- % Generated by rdf-prolog-roundtrip.
1
+ % Generated by rdf-prolog-interchange.
2
2
 
3
3
  % RDF terms use rdf(Subject, Predicate, Object, Graph).
4
4
 
@@ -1,4 +1,4 @@
1
- % Generated by rdf-prolog-roundtrip.
1
+ % Generated by rdf-prolog-interchange.
2
2
 
3
3
  % RDF terms use rdf(Subject, Predicate, Object, Graph).
4
4
 
@@ -1,4 +1,4 @@
1
- % Generated by rdf-prolog-roundtrip.
1
+ % Generated by rdf-prolog-interchange.
2
2
 
3
3
  % RDF terms use rdf(Subject, Predicate, Object, Graph).
4
4
 
@@ -63,7 +63,7 @@ rdf(iri('https://example.org/city/review/r3'), iri('https://example.org/vocab/no
63
63
  % Rules
64
64
  % Symbiotic knowledge graph rules for EyeProlog.
65
65
  %
66
- % The source rdf/4 facts above are generated by rdf-prolog-roundtrip from
66
+ % The source rdf/4 facts above are generated by rdf-prolog-interchange from
67
67
  % symbiotic-knowledge-graph-input.nq. RDF stays the interchange and storage
68
68
  % model; these rules provide the explicit reasoning and governance layer.
69
69
 
@@ -380,10 +380,10 @@ knowledge_exchange_fact(prolog_to_rdf, materialized_ground_rdf4).
380
380
 
381
381
  pipeline_step(N, Description) :- pipeline_step_fact(N, Description).
382
382
  pipeline_step_fact(1, 'RDF 1.2 N-Quads: named graphs keep source and governance boundaries explicit').
383
- pipeline_step_fact(2, 'rdf-prolog-roundtrip: RDF becomes ordinary rdf/4 Prolog facts without a solver').
383
+ pipeline_step_fact(2, 'rdf-prolog-interchange: RDF becomes ordinary rdf/4 Prolog facts without a solver').
384
384
  pipeline_step_fact(3, 'EyeProlog: ISO Prolog rules validate candidates, apply review policy, and derive actions').
385
385
  pipeline_step_fact(4, 'EyeProlog: result_rdf/4 materializes accepted knowledge and decisions as ground RDF-shaped facts').
386
- pipeline_step_fact(5, 'rdf-prolog-roundtrip: ground rdf/4 facts become RDF again for publication or federation').
386
+ pipeline_step_fact(5, 'rdf-prolog-interchange: ground rdf/4 facts become RDF again for publication or federation').
387
387
 
388
388
  cognitive_parallel(Prolog, Human) :- cognitive_parallel_fact(Prolog, Human).
389
389
  cognitive_parallel_fact(fact, remembered_assertion).
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.5.62",
6
+ "version": "1.5.65",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
package/src/lib/error.pl CHANGED
@@ -143,12 +143,15 @@ call_with_error_context(Goal, Pair) :-
143
143
  ( copy_term(Pair, Element),
144
144
  throw(error(Error, [Element|Context])) )).
145
145
 
146
- % The element must be a pair (issue #99). This cannot go through must_be/2:
147
- % must_be/2 declares its own context with call_with_error_context/2, so
148
- % checking with must_be/2 here would recurse. The reported context matches
149
- % what must_be(pair, _) would have produced.
146
+ % The element must be a pair (issue #99). The test is inlined rather than
147
+ % delegated to must_be(pair, _) so that the wrapper stays independent of the
148
+ % rest of this module. The reported context matches what must_be(pair, _)
149
+ % produces.
150
+ % Written as indexed clauses rather than an if-then-else: this runs on every
151
+ % call_with_error_context/2 call, and an if-then-else costs several times what
152
+ % first-argument clause selection does.
153
+ error__require_pair(Pair) :- var(Pair), !,
154
+ throw(error(instantiation_error, [predicate-must_be/2])).
155
+ error__require_pair(_-_) :- !.
150
156
  error__require_pair(Pair) :-
151
- ( var(Pair) -> throw(error(instantiation_error, [predicate-must_be/2]))
152
- ; Pair = _-_ -> true
153
- ; throw(error(type_error(pair, Pair), [predicate-must_be/2]))
154
- ).
157
+ throw(error(type_error(pair, Pair), [predicate-must_be/2])).
package/src/lib/si.pl CHANGED
@@ -8,6 +8,7 @@
8
8
  character_si/1,
9
9
  term_si/1,
10
10
  chars_si/1,
11
+ compare_si/3,
11
12
  dif_si/2,
12
13
  not_si/1,
13
14
  when_si/2
@@ -16,24 +17,24 @@
16
17
  :- meta_predicate(not_si(0)).
17
18
  :- meta_predicate(when_si(+, 0)).
18
19
 
19
- atom_si(A) :- ( var(A) -> throw(error(instantiation_error, atom_si/1)) ; atom(A) ).
20
- integer_si(I) :- ( var(I) -> throw(error(instantiation_error, integer_si/1)) ; integer(I) ).
21
- atomic_si(A) :- ( var(A) -> throw(error(instantiation_error, atomic_si/1)) ; atomic(A) ).
20
+ atom_si(A) :- ( var(A) -> throw(error(instantiation_error, [predicate-atom_si/1])) ; atom(A) ).
21
+ integer_si(I) :- ( var(I) -> throw(error(instantiation_error, [predicate-integer_si/1])) ; integer(I) ).
22
+ atomic_si(A) :- ( var(A) -> throw(error(instantiation_error, [predicate-atomic_si/1])) ; atomic(A) ).
22
23
 
23
24
  list_si(List) :- si__list(List).
25
+ si__list(Term) :-
26
+ var(Term), !, throw(error(instantiation_error, [predicate-list_si/1])).
24
27
  si__list([]).
25
28
  si__list([_|Tail]) :- !, si__list(Tail).
26
- si__list(Term) :-
27
- ( var(Term) -> throw(error(instantiation_error, list_si/1)) ; fail ).
28
29
 
29
30
  character_si(C) :-
30
- ( var(C) -> throw(error(instantiation_error, character_si/1))
31
+ ( var(C) -> throw(error(instantiation_error, [predicate-character_si/1]))
31
32
  ; atom(C), atom_length(C, 1)
32
33
  ).
33
34
 
34
35
  term_si(Term) :-
35
36
  ( ground(Term) -> acyclic_term(Term)
36
- ; throw(error(instantiation_error, term_si/1))
37
+ ; throw(error(instantiation_error, [predicate-term_si/1]))
37
38
  ).
38
39
 
39
40
  chars_si(Chars) :-
@@ -44,19 +45,56 @@ si__chars([C|Cs]) :- character_si(C), si__chars(Cs).
44
45
 
45
46
  dif_si(X, Y) :-
46
47
  X \== Y,
47
- ( X \= Y -> true ; throw(error(instantiation_error, dif_si/2)) ).
48
+ ( X \= Y -> true ; throw(error(instantiation_error, [predicate-dif_si/2])) ).
48
49
 
49
50
  not_si(Goal) :- term_si(Goal), \+ Goal.
50
51
 
51
52
  when_si(Condition, Goal) :-
52
53
  ( si__condition(Condition) ->
53
54
  ( call(Condition) -> call(Goal)
54
- ; throw(error(instantiation_error, when_si/2)) )
55
+ ; throw(error(instantiation_error, [predicate-when_si/2])) )
55
56
  ; throw(error(domain_error(when_condition_si, Condition), when_si/2))
56
57
  ).
57
58
 
58
- si__condition(Condition) :- var(Condition), !, throw(error(instantiation_error, when_si/2)).
59
+ si__condition(Condition) :- var(Condition), !, throw(error(instantiation_error, [predicate-when_si/2])).
59
60
  si__condition(ground(_)).
60
61
  si__condition(nonvar(_)).
61
62
  si__condition((A,B)) :- si__condition(A), si__condition(B).
62
63
  si__condition((A;B)) :- si__condition(A), si__condition(B).
64
+
65
+ % compare_si(Order, A, B) holds when compare(Order, A, B) holds for *every*
66
+ % instance of A and B, and raises an instantiation_error otherwise. The point
67
+ % is to raise it as rarely as possible: an unbound variable only matters when
68
+ % it is what decides the ordering. So f(X) @< g(Y) is decided by the functor
69
+ % names, f(X,a) @< f(X,b) by the second arguments, and X == X gives (=), while
70
+ % f(a) vs f(Y) and X vs 1 are genuinely undecided.
71
+ compare_si(Order, A, B) :-
72
+ ( A == B
73
+ -> Order = (=)
74
+ ; si__order_decided(A, B)
75
+ -> compare(Order, A, B)
76
+ ; throw(error(instantiation_error, [predicate-compare_si/3]))
77
+ ).
78
+
79
+ % Both arguments are known to be non-identical here.
80
+ si__order_decided(A, B) :- ( var(A) ; var(B) ), !, fail.
81
+ si__order_decided(A, B) :-
82
+ compound(A), compound(B), !,
83
+ functor(A, NameA, ArityA),
84
+ functor(B, NameB, ArityB),
85
+ ( ArityA =\= ArityB -> true
86
+ ; NameA \== NameB -> true
87
+ ; A =.. [_|ArgsA],
88
+ B =.. [_|ArgsB],
89
+ si__args_decided(ArgsA, ArgsB)
90
+ ).
91
+ % Otherwise both are non-variables and at least one is atomic, so either the
92
+ % type order or the values themselves settle it and no instantiation can
93
+ % change the outcome.
94
+ si__order_decided(_, _).
95
+
96
+ si__args_decided([A|As], [B|Bs]) :-
97
+ ( A == B
98
+ -> si__args_decided(As, Bs)
99
+ ; si__order_decided(A, B)
100
+ ).
@@ -90,6 +90,7 @@ export const eyePrologLibraryAutoload = Object.freeze({
90
90
  "chars_utf8bytes/2": "charsio",
91
91
  "circuit/1": "clpz",
92
92
  "clpz_t/2": "clpz",
93
+ "compare_si/3": "si",
93
94
  "complement/2": "ugraphs",
94
95
  "compose/3": "ugraphs",
95
96
  "cond_t/3": "reif",
@@ -178,7 +178,7 @@ export const eyePrologPortableLibraryIndicators = Object.freeze([
178
178
  ',/3', ';/3', '=/3', 'cond_t/3', 'dif/3', 'if_/3', 'memberd_t/3',
179
179
  'tfilter/3', 'tmember/2', 'tmember_t/3', 'tpartition/4',
180
180
  'atom_si/1', 'integer_si/1', 'atomic_si/1', 'list_si/1', 'character_si/1',
181
- 'term_si/1', 'chars_si/1', 'dif_si/2', 'not_si/1', 'when_si/2',
181
+ 'term_si/1', 'chars_si/1', 'compare_si/3', 'dif_si/2', 'not_si/1', 'when_si/2',
182
182
  'matches/3', 'split/3', 'replace/4', 'lowercase/2', 'uppercase/2', 'trim/2',
183
183
  'number_string/2', 'atom_string/2', 'term_string/2', 'string_concat/3',
184
184
  'contains/2', 'matches/2', 'join/3', 'substring/4',
@@ -1331,7 +1331,7 @@ answer(ok) :-
1331
1331
  assertEqual(registry.get('setup_call_cleanup', 3), null, 'setup_call_cleanup/3 is absent from the ISO registry');
1332
1332
  assertEqual(Boolean(library.get('setup_call_cleanup', 3)), true, 'setup_call_cleanup/3 is an EyeProlog cleanup control');
1333
1333
  assertEqual(registeredNativeEyePrologLibraryNames().length, 122, 'public host-supported EyeProlog library count');
1334
- assertEqual(eyePrologPortableLibraryIndicators.length, 281, 'portable Prolog library count');
1334
+ assertEqual(eyePrologPortableLibraryIndicators.length, 282, 'portable Prolog library count');
1335
1335
  assertEqual(eyePrologInteropLibraryIndicators.length, 221, 'cross-implementation interop profile count');
1336
1336
  assertEqual(eyePrologInteropLibraryModules.length, 27, 'common explicit library module profile count');
1337
1337
  assertEqual(eyePrologInteropAutoload['member/2'], 'lists', 'member/2 canonical autoload');
@@ -1349,7 +1349,7 @@ answer(ok) :-
1349
1349
  assertEqual(eyePrologNativeLibraryIndicators.length, 122, 'host-supported library count');
1350
1350
  assertEqual(eyePrologNativeLibraryIndicators.includes('call_nth/2'), true, 'call_nth/2 remains classified as host-supported');
1351
1351
  assertEqual(eyePrologNativeLibraryIndicators.includes('random/1'), true, 'stateful random/1 is classified as host-supported');
1352
- assertEqual(eyePrologLibraryIndicators.length, 403, 'complete non-ISO EyeProlog library surface');
1352
+ assertEqual(eyePrologLibraryIndicators.length, 404, 'complete non-ISO EyeProlog library surface');
1353
1353
  assertEqual(registry.get('eyeprolog__call_nth', 2), null, 'private call_nth adapter is absent from ISO registry');
1354
1354
  assertEqual(Boolean(library.get('eyeprolog__call_nth', 2)), true, 'private call_nth adapter is registered for EyeProlog');
1355
1355
  assertEqual(Boolean(library.get('eyeprolog__call_residue_vars', 2)), true, 'private call_residue_vars adapter is registered for EyeProlog');
@@ -61,15 +61,15 @@ export function documentationSyncCases() {
61
61
  encoding: 'utf8',
62
62
  });
63
63
  assertEqual(result.status, 0, 'exit status');
64
- assertIncludes(result.stdout, 'predicate reference is up to date (532 predicates)', 'stdout');
64
+ assertIncludes(result.stdout, 'predicate reference is up to date (533 predicates)', 'stdout');
65
65
  assertEqual(result.stderr, '', 'stderr');
66
66
 
67
67
  const book = fs.readFileSync(path.join(packageRoot, 'the-art-of-eyeprolog.md'), 'utf8');
68
68
  const section = between(book, '<!-- eyeprolog-predicate-reference:start -->', '<!-- eyeprolog-predicate-reference:end -->');
69
69
  assertEqual(section.split('\n').some((line) => line.trimStart().startsWith('|')), false, 'predicate reference avoids wide table markup');
70
- assertEqual((section.match(/^- \*\*`/gm) ?? []).length, 532, 'one reference entry per predicate');
71
- assertEqual((section.match(/<a id="predicate-reference-\d{4}"><\/a>/g) ?? []).length, 532, 'one explicit anchor per predicate');
72
- assertEqual((section.match(/\]\(#predicate-reference-\d{4}\)/g) ?? []).length, 532, 'one direct index link per predicate');
70
+ assertEqual((section.match(/^- \*\*`/gm) ?? []).length, 533, 'one reference entry per predicate');
71
+ assertEqual((section.match(/<a id="predicate-reference-\d{4}"><\/a>/g) ?? []).length, 533, 'one explicit anchor per predicate');
72
+ assertEqual((section.match(/\]\(#predicate-reference-\d{4}\)/g) ?? []).length, 533, 'one direct index link per predicate');
73
73
  assertNotIncludes(section, '[Symbols](#predicate-reference-symbols)', 'predicate index does not rely on renderer-generated group anchors');
74
74
 
75
75
  const chapter = between(book, '## 39. Predicate reference', '## 40. Running EyeProlog: command line and corpus');
@@ -4528,6 +4528,82 @@ answer(Result) :- countdown(2048, Result), Result = 2048.
4528
4528
  if (elapsed >= 20000) throw new Error(`20k must_be/2 calls took ${elapsed}ms`);
4529
4529
  },
4530
4530
  },
4531
+ {
4532
+ // The pair check runs on every call_with_error_context/2 call, so it is
4533
+ // written as indexed clauses. An if-then-else costs several times what
4534
+ // clause selection does, and this guard used to dominate the wrapper.
4535
+ name: 'call_with_error_context/2 pair check stays off the hot path',
4536
+ run: () => {
4537
+ const input = '%% goal: answer(X)\np(X) :- integer(X).\n' +
4538
+ 'loop(0) :- !.\nloop(N) :- call_with_error_context(p(N), c-1), M is N - 1, loop(M).\n' +
4539
+ 'answer(ok) :- loop(20000).\n';
4540
+ const started = Date.now();
4541
+ const result = runCli(['-'], { input });
4542
+ const elapsed = Date.now() - started;
4543
+ assertEqual(result.status, 0, `exit status; stderr=${result.stderr}`);
4544
+ assertIncludes(result.stdout, 'answer(ok)', 'loop completes');
4545
+ if (elapsed >= 20000) throw new Error(`20k wrapped calls took ${elapsed}ms`);
4546
+ },
4547
+ },
4548
+ {
4549
+ name: 'sufficient-instantiation list checks reject unknown tails without binding elements',
4550
+ run: () => {
4551
+ const input = ':- use_module(library(si)).\n%% goal: answer(X)\n' +
4552
+ 'answer(ok) :- ' +
4553
+ 'catch(list_si(X), error(instantiation_error,_), C1 = caught), C1 == caught, var(X), ' +
4554
+ 'catch(list_si([a|T]), error(instantiation_error,_), C2 = caught), C2 == caught, var(T), ' +
4555
+ 'catch(chars_si(Cs), error(instantiation_error,_), C3 = caught), C3 == caught, var(Cs), ' +
4556
+ 'catch(chars_si([a|Tail]), error(instantiation_error,_), C4 = caught), C4 == caught, var(Tail), ' +
4557
+ 'list_si([]), list_si([Element]), var(Element), chars_si([a,b]), ' +
4558
+ '\\+ list_si([a|bad]), \\+ chars_si([ab]).\n';
4559
+ const result = runCli(['-'], { input });
4560
+ assertEqual(result.status, 0, `exit status; stderr=${result.stderr}`);
4561
+ assertIncludes(result.stdout, 'answer(ok)', 'list checks preserve sufficient instantiation');
4562
+ },
4563
+ },
4564
+ {
4565
+ // Issue #100: compare_si/3 decides the standard order only when no
4566
+ // instantiation could change it, and raises instantiation_error as
4567
+ // rarely as possible.
4568
+ name: 'compare_si/3 decides the order only when instantiation cannot change it',
4569
+ run: () => {
4570
+ const decided = [
4571
+ ['compare_si(O, 1, a)', '<'],
4572
+ ['compare_si(O, X, X)', '='],
4573
+ ['compare_si(O, f(X), g(Y))', '<'],
4574
+ ['compare_si(O, f(X,a), f(X,b))', '<'],
4575
+ ['compare_si(O, f(a,b), f(a,b,c))', '<'],
4576
+ ['compare_si(O, [a|X], [b|Y])', '<'],
4577
+ ['compare_si(O, 1, 1.0)', '>'],
4578
+ ];
4579
+ for (const [goal, order] of decided) {
4580
+ const input = ':- use_module(library(si)).\n%% goal: answer(X)\n' +
4581
+ `answer(O) :- ${goal}.\n`;
4582
+ const result = runCli(['-'], { input });
4583
+ assertEqual(result.status, 0, `${goal} status; stderr=${result.stderr}`);
4584
+ assertIncludes(result.stdout, `answer(${order})`, `${goal} order`);
4585
+ }
4586
+ for (const goal of ['compare_si(O, X, 1)', 'compare_si(O, f(a), f(Y))',
4587
+ 'compare_si(O, X, Y)', 'compare_si(O, [a|X], [a|Y])']) {
4588
+ const input = ':- use_module(library(si)).\n%% goal: answer(X)\n' +
4589
+ `answer(E) :- catch(${goal}, error(E,_), true).\n`;
4590
+ const result = runCli(['-'], { input });
4591
+ assertEqual(result.status, 0, `${goal} status; stderr=${result.stderr}`);
4592
+ assertIncludes(result.stdout, 'answer(instantiation_error)', `${goal} is undecided`);
4593
+ }
4594
+ },
4595
+ },
4596
+ {
4597
+ // compare_si/3 must not bind anything while deciding.
4598
+ name: 'compare_si/3 leaves its arguments unbound',
4599
+ run: () => {
4600
+ const input = ':- use_module(library(si)).\n%% goal: answer(X)\n' +
4601
+ 'answer(ok) :- X = f(Y), compare_si(<, X, g(1)), var(Y).\n';
4602
+ const result = runCli(['-'], { input });
4603
+ assertEqual(result.status, 0, `exit status; stderr=${result.stderr}`);
4604
+ assertIncludes(result.stdout, 'answer(ok)', 'no bindings made');
4605
+ },
4606
+ },
4531
4607
  {
4532
4608
  // Issue #99: the context element must be a pair, as in Scryer and
4533
4609
  // Trealla, and predicate contexts use the predicate-F/A convention.