eyeprolog 1.5.54 → 1.5.56

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 (47) hide show
  1. package/README.md +0 -1
  2. package/conformance-report.md +21 -0
  3. package/examples/combinatorics-findall-sort.pl +4 -4
  4. package/examples/d3-group.pl +37 -37
  5. package/examples/eulerian-path.pl +4 -4
  6. package/examples/graph-reachability.pl +4 -4
  7. package/examples/monkey-bananas.pl +4 -4
  8. package/examples/proof/graph-reachability.pl +8 -8
  9. package/package.json +1 -2
  10. package/src/cli.js +12 -0
  11. package/src/datalog.js +1 -1
  12. package/src/expansion-builtins.js +1 -1
  13. package/src/iso-character.js +0 -10
  14. package/src/iso.js +10 -14
  15. package/src/program-analysis.js +7 -7
  16. package/src/program-indexing.js +0 -8
  17. package/src/program.js +45 -4
  18. package/test/conformance/ISO-COMPLIANCE.md +3 -1
  19. package/test/conformance/ISO-PROLOG-TEXT-EXECUTION-MATRIX.md +1 -1
  20. package/test/fixtures/README.md +6 -4
  21. package/test/regression/cases-api.mjs +1677 -0
  22. package/test/regression/cases-documentation-sync.mjs +717 -0
  23. package/test/regression/cases-regression.mjs +5250 -0
  24. package/test/regression/cases-white-box.mjs +938 -0
  25. package/test/regression/support.mjs +770 -0
  26. package/test/run-all.mjs +0 -0
  27. package/test/run-architecture.mjs +0 -0
  28. package/test/run-book-examples.mjs +0 -0
  29. package/test/run-cleanup.mjs +0 -0
  30. package/test/run-conformance-all.mjs +0 -0
  31. package/test/run-conformance-report.mjs +29 -0
  32. package/test/run-conformance.mjs +0 -0
  33. package/test/run-examples.mjs +0 -0
  34. package/test/run-http-json.mjs +0 -0
  35. package/test/run-interop.mjs +0 -0
  36. package/test/run-iso-part2-amendment.mjs +0 -0
  37. package/test/run-iso-strict.mjs +32 -0
  38. package/test/run-neumerkel-tests.mjs +0 -0
  39. package/test/run-neumerkel.mjs +0 -0
  40. package/test/run-openrulebench.mjs +0 -0
  41. package/test/run-playground.mjs +0 -0
  42. package/test/run-regression.mjs +28 -9237
  43. package/test/run-wg17.mjs +0 -0
  44. package/the-art-of-eyeprolog.md +9 -2
  45. package/tools/extract-book-examples.mjs +0 -0
  46. package/tools/report-wg17-syntax-coverage.mjs +0 -0
  47. package/ACKNOWLEDGEMENTS.md +0 -36
package/README.md CHANGED
@@ -73,7 +73,6 @@ printf 'human(socrates).\nmortal(X) :- human(X).\n' |
73
73
  - [ISO conformance review](test/conformance/ISO-COMPLIANCE.md) — supported Part 1 profile
74
74
  - [Latest Neumerkel conformity](test/conformance/NEUMERKEL-LATEST.md) — tracked result from the current live upstream inventory
75
75
  - [Conformance report](conformance-report.md) — generated executable conformance status, local corpus summary, and known deviations
76
- - [Acknowledgements](ACKNOWLEDGEMENTS.md) — funding, standards consulted, and credits for vendored conformance corpora
77
76
  - [OpenRuleBench](openrulebench/README.md) — portable benchmark profile
78
77
  ## RDF, Prolog, and symbiotic knowledge graphs
79
78
 
@@ -70,3 +70,24 @@ runs that corpus live through the Neumerkel gate; the offline regression
70
70
  suite runs all 58 vendored quads. Neither expectations nor error matching
71
71
  are relaxed for quads 41-44.
72
72
 
73
+
74
+ ## Integer flag choice under `bounded=false`
75
+
76
+ EyeProlog reports `bounded=false` and uses arbitrary-precision integers.
77
+ It therefore associates no current value with `max_integer` or
78
+ `min_integer`, so `current_prolog_flag/2` does not enumerate them and
79
+ fails when either is named. Both flags stay registered, so
80
+ `set_prolog_flag/2` still reaches the normal non-changeable-flag errors.
81
+
82
+ This is an implementation choice, not a requirement of Part 1. Clause
83
+ 7.11.1.1 defines the `bounded` flag and does not govern
84
+ `current_prolog_flag/2` outcomes, while 7.11.1.2 and 7.11.1.3 give both
85
+ flags an implementation-defined default value unconditionally; the
86
+ `bounded` condition constrains what that value *means*, not whether the
87
+ flag exists. Two alternative readings are equally defensible: expose
88
+ implementation-defined values so the flags enumerate, or treat them as
89
+ unsupported and raise `domain_error(prolog_flag, Flag)` per 8.17.2.3 b.
90
+ EyeProlog prefers silence over inventing a largest integer that its
91
+ arithmetic does not have. The vendored Prologue corpus records the
92
+ resulting single divergence rather than patching the upstream fixture.
93
+
@@ -14,16 +14,16 @@
14
14
 
15
15
  % select/3 nondeterministically removes one item from a list; because it is an
16
16
  % ordinary rule, the example also demonstrates user-level list recursion.
17
- select(Item, [Item | Rest], Rest).
17
+ select_item(Item, [Item | Rest], Rest).
18
18
  % The recursive clause keeps the non-selected head and searches the tail.
19
- select(Item, [Head | Tail], [Head | Rest]) :-
20
- select(Item, Tail, Rest).
19
+ select_item(Item, [Head | Tail], [Head | Rest]) :-
20
+ select_item(Item, Tail, Rest).
21
21
 
22
22
  % combination/3 builds an unordered K-combination by repeated selection.
23
23
  combination(0, _items, []).
24
24
  combination(I, Items, Combination) :-
25
25
  (I > 0),
26
- select(Item, Items, Remaining),
26
+ select_item(Item, Items, Remaining),
27
27
  (J is I - 1),
28
28
  combination(J, Remaining, Partial),
29
29
  sort([Item | Partial], Combination).
@@ -21,42 +21,42 @@ symmetry(reflection_b).
21
21
  symmetry(reflection_c).
22
22
 
23
23
  % Cayley table for D3 composition.
24
- compose(identity, identity, identity).
25
- compose(identity, rotation_120, rotation_120).
26
- compose(identity, rotation_240, rotation_240).
27
- compose(identity, reflection_a, reflection_a).
28
- compose(identity, reflection_b, reflection_b).
29
- compose(identity, reflection_c, reflection_c).
30
- compose(rotation_120, identity, rotation_120).
31
- compose(rotation_120, rotation_120, rotation_240).
32
- compose(rotation_120, rotation_240, identity).
33
- compose(rotation_120, reflection_a, reflection_b).
34
- compose(rotation_120, reflection_b, reflection_c).
35
- compose(rotation_120, reflection_c, reflection_a).
36
- compose(rotation_240, identity, rotation_240).
37
- compose(rotation_240, rotation_120, identity).
38
- compose(rotation_240, rotation_240, rotation_120).
39
- compose(rotation_240, reflection_a, reflection_c).
40
- compose(rotation_240, reflection_b, reflection_a).
41
- compose(rotation_240, reflection_c, reflection_b).
42
- compose(reflection_a, identity, reflection_a).
43
- compose(reflection_a, rotation_120, reflection_c).
44
- compose(reflection_a, rotation_240, reflection_b).
45
- compose(reflection_a, reflection_a, identity).
46
- compose(reflection_a, reflection_b, rotation_240).
47
- compose(reflection_a, reflection_c, rotation_120).
48
- compose(reflection_b, identity, reflection_b).
49
- compose(reflection_b, rotation_120, reflection_a).
50
- compose(reflection_b, rotation_240, reflection_c).
51
- compose(reflection_b, reflection_a, rotation_120).
52
- compose(reflection_b, reflection_b, identity).
53
- compose(reflection_b, reflection_c, rotation_240).
54
- compose(reflection_c, identity, reflection_c).
55
- compose(reflection_c, rotation_120, reflection_b).
56
- compose(reflection_c, rotation_240, reflection_a).
57
- compose(reflection_c, reflection_a, rotation_240).
58
- compose(reflection_c, reflection_b, rotation_120).
59
- compose(reflection_c, reflection_c, identity).
24
+ compose_op(identity, identity, identity).
25
+ compose_op(identity, rotation_120, rotation_120).
26
+ compose_op(identity, rotation_240, rotation_240).
27
+ compose_op(identity, reflection_a, reflection_a).
28
+ compose_op(identity, reflection_b, reflection_b).
29
+ compose_op(identity, reflection_c, reflection_c).
30
+ compose_op(rotation_120, identity, rotation_120).
31
+ compose_op(rotation_120, rotation_120, rotation_240).
32
+ compose_op(rotation_120, rotation_240, identity).
33
+ compose_op(rotation_120, reflection_a, reflection_b).
34
+ compose_op(rotation_120, reflection_b, reflection_c).
35
+ compose_op(rotation_120, reflection_c, reflection_a).
36
+ compose_op(rotation_240, identity, rotation_240).
37
+ compose_op(rotation_240, rotation_120, identity).
38
+ compose_op(rotation_240, rotation_240, rotation_120).
39
+ compose_op(rotation_240, reflection_a, reflection_c).
40
+ compose_op(rotation_240, reflection_b, reflection_a).
41
+ compose_op(rotation_240, reflection_c, reflection_b).
42
+ compose_op(reflection_a, identity, reflection_a).
43
+ compose_op(reflection_a, rotation_120, reflection_c).
44
+ compose_op(reflection_a, rotation_240, reflection_b).
45
+ compose_op(reflection_a, reflection_a, identity).
46
+ compose_op(reflection_a, reflection_b, rotation_240).
47
+ compose_op(reflection_a, reflection_c, rotation_120).
48
+ compose_op(reflection_b, identity, reflection_b).
49
+ compose_op(reflection_b, rotation_120, reflection_a).
50
+ compose_op(reflection_b, rotation_240, reflection_c).
51
+ compose_op(reflection_b, reflection_a, rotation_120).
52
+ compose_op(reflection_b, reflection_b, identity).
53
+ compose_op(reflection_b, reflection_c, rotation_240).
54
+ compose_op(reflection_c, identity, reflection_c).
55
+ compose_op(reflection_c, rotation_120, reflection_b).
56
+ compose_op(reflection_c, rotation_240, reflection_a).
57
+ compose_op(reflection_c, reflection_a, rotation_240).
58
+ compose_op(reflection_c, reflection_b, rotation_120).
59
+ compose_op(reflection_c, reflection_c, identity).
60
60
  % Each candidate subgroup must also contain inverses.
61
61
  inverse(identity, identity).
62
62
  inverse(rotation_120, rotation_240).
@@ -78,7 +78,7 @@ all_symmetries(Symmetries) :-
78
78
 
79
79
  % A valid subgroup is closed under both composition and inverse.
80
80
  closed_under_composition(Group) :-
81
- \+ (member(X, Group), member(Y, Group), compose(X, Y, Z), \+ member(Z, Group)).
81
+ \+ (member(X, Group), member(Y, Group), compose_op(X, Y, Z), \+ member(Z, Group)).
82
82
 
83
83
  closed_under_inverse(Group) :-
84
84
  \+ (member(X, Group), inverse(X, Y), \+ member(Y, Group)).
@@ -39,9 +39,9 @@ incident(V, E) :- edge(E, _u, V).
39
39
  adjacent_by_edge(V, U, E) :- edge(E, V, U).
40
40
  adjacent_by_edge(V, U, E) :- edge(E, U, V).
41
41
 
42
- select(Item, [Item | Rest], Rest).
43
- select(Item, [Head | Tail], [Head | Rest]) :-
44
- select(Item, Tail, Rest).
42
+ select_item(Item, [Item | Rest], Rest).
43
+ select_item(Item, [Head | Tail], [Head | Rest]) :-
44
+ select_item(Item, Tail, Rest).
45
45
 
46
46
  % Eulerian paths start at an odd-degree vertex when exactly two exist.
47
47
  odd_degree(V) :-
@@ -77,7 +77,7 @@ eulerian_path(Path) :-
77
77
  dfs_euler(_current, Path, [], Path).
78
78
  dfs_euler(Current, Visited, Remaining, Path) :-
79
79
  adjacent_by_edge(Current, Next, Edge),
80
- select(Edge, Remaining, Newremaining),
80
+ select_item(Edge, Remaining, Newremaining),
81
81
  dfs_euler(Next, [Next | Visited], Newremaining, Path).
82
82
 
83
83
  oddVertices(eulerian_path_case, Odds) :-
@@ -18,14 +18,14 @@ edge(d, f).
18
18
  edge(e, f).
19
19
  edge(f, g).
20
20
 
21
- reachable(Node, Node, _visited).
22
- reachable(Start, Goal, Visited) :-
21
+ reachable_via(Node, Node, _visited).
22
+ reachable_via(Start, Goal, Visited) :-
23
23
  edge(Start, Next),
24
24
  \+ member(Next, Visited),
25
- reachable(Next, Goal, [Next|Visited]).
25
+ reachable_via(Next, Goal, [Next|Visited]).
26
26
 
27
27
  is_reachable(Start, Goal) :-
28
- reachable(Start, Goal, [Start]).
28
+ reachable_via(Start, Goal, [Start]).
29
29
 
30
30
  reachable(reachability_case, path(a, f)) :-
31
31
  is_reachable(a, f).
@@ -20,16 +20,16 @@ plan(Moves) :-
20
20
  candidate_plan(Moves),
21
21
  initial_state(I),
22
22
  goal_state(G),
23
- reachable(I, Moves, G).
23
+ reachable_state(I, Moves, G).
24
24
 
25
25
  candidate_plan([_, _, _]).
26
26
  candidate_plan([_, _, _, _]).
27
27
  candidate_plan([_, _, _, _, _]).
28
28
 
29
- reachable(S, [], S).
30
- reachable(S1, [M|L], S3) :-
29
+ reachable_state(S, [], S).
30
+ reachable_state(S1, [M|L], S3) :-
31
31
  legal_move(S1, M, S2),
32
- reachable(S2, L, S3).
32
+ reachable_state(S2, L, S3).
33
33
 
34
34
  initial_state([loc1, loc2, loc3, n, n]).
35
35
  goal_state([_, _, _, _, y]).
@@ -11,7 +11,7 @@ why(
11
11
  bindings([binding("Start", a), binding("Goal", f)]),
12
12
  uses([
13
13
  proof(
14
- goal(reachable(a, f, "a")),
14
+ goal(reachable_via(a, f, "a")),
15
15
  by(rule("graph-reachability.pl", clause(9))),
16
16
  bindings([binding("Start", a), binding("Goal", f), binding("Visited", "a"), binding("Next", b)]),
17
17
  uses([
@@ -24,7 +24,7 @@ why(
24
24
  by(builtin('\\+', 1))
25
25
  ),
26
26
  proof(
27
- goal(reachable(b, f, "ba")),
27
+ goal(reachable_via(b, f, "ba")),
28
28
  by(rule("graph-reachability.pl", clause(9))),
29
29
  bindings([binding("Start", b), binding("Goal", f), binding("Visited", "ba"), binding("Next", d)]),
30
30
  uses([
@@ -37,7 +37,7 @@ why(
37
37
  by(builtin('\\+', 1))
38
38
  ),
39
39
  proof(
40
- goal(reachable(d, f, "dba")),
40
+ goal(reachable_via(d, f, "dba")),
41
41
  by(rule("graph-reachability.pl", clause(9))),
42
42
  bindings([binding("Start", d), binding("Goal", f), binding("Visited", "dba"), binding("Next", f)]),
43
43
  uses([
@@ -50,7 +50,7 @@ why(
50
50
  by(builtin('\\+', 1))
51
51
  ),
52
52
  proof(
53
- goal(reachable(f, f, "fdba")),
53
+ goal(reachable_via(f, f, "fdba")),
54
54
  by(fact("graph-reachability.pl", clause(8))),
55
55
  bindings([binding("Node", f), binding("_visited", "fdba")])
56
56
  )
@@ -79,7 +79,7 @@ why(
79
79
  bindings([binding("Start", c), binding("Goal", g)]),
80
80
  uses([
81
81
  proof(
82
- goal(reachable(c, g, "c")),
82
+ goal(reachable_via(c, g, "c")),
83
83
  by(rule("graph-reachability.pl", clause(9))),
84
84
  bindings([binding("Start", c), binding("Goal", g), binding("Visited", "c"), binding("Next", e)]),
85
85
  uses([
@@ -92,7 +92,7 @@ why(
92
92
  by(builtin('\\+', 1))
93
93
  ),
94
94
  proof(
95
- goal(reachable(e, g, "ec")),
95
+ goal(reachable_via(e, g, "ec")),
96
96
  by(rule("graph-reachability.pl", clause(9))),
97
97
  bindings([binding("Start", e), binding("Goal", g), binding("Visited", "ec"), binding("Next", f)]),
98
98
  uses([
@@ -105,7 +105,7 @@ why(
105
105
  by(builtin('\\+', 1))
106
106
  ),
107
107
  proof(
108
- goal(reachable(f, g, "fec")),
108
+ goal(reachable_via(f, g, "fec")),
109
109
  by(rule("graph-reachability.pl", clause(9))),
110
110
  bindings([binding("Start", f), binding("Goal", g), binding("Visited", "fec"), binding("Next", g)]),
111
111
  uses([
@@ -118,7 +118,7 @@ why(
118
118
  by(builtin('\\+', 1))
119
119
  ),
120
120
  proof(
121
- goal(reachable(g, g, "gfec")),
121
+ goal(reachable_via(g, g, "gfec")),
122
122
  by(fact("graph-reachability.pl", clause(8))),
123
123
  bindings([binding("Node", g), binding("_visited", "gfec")])
124
124
  )
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.5.54",
6
+ "version": "1.5.56",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
@@ -28,7 +28,6 @@
28
28
  "files": [
29
29
  "LICENSE.md",
30
30
  "README.md",
31
- "ACKNOWLEDGEMENTS.md",
32
31
  "index.js",
33
32
  "index.d.ts",
34
33
  "playground.html",
package/src/cli.js CHANGED
@@ -196,6 +196,10 @@ export async function main(argv) {
196
196
  });
197
197
 
198
198
  const portabilityFailures = program.interopPortabilityWarnings ?? [];
199
+ // Shadowing is reported unconditionally: silently replacing a library
200
+ // predicate for the whole program is the failure mode this diagnostic exists
201
+ // to surface, so it must not depend on opting in to --warnings.
202
+ printLibraryShadowingWarnings(program);
199
203
  if (options.warnings || (options.portable && portabilityFailures.length > 0)) printWarnings(program);
200
204
  if (options.portable && portabilityFailures.length > 0) {
201
205
  process.exitCode = 1;
@@ -364,6 +368,14 @@ function printSourceWarning(warning) {
364
368
  `);
365
369
  }
366
370
 
371
+ function printLibraryShadowingWarnings(program) {
372
+ for (const warning of program.libraryShadowingWarnings ?? []) {
373
+ process.stderr.write('eyeprolog warning: user definition shadows a bundled library predicate\n');
374
+ process.stderr.write(` ${warning.indicator} replaces library(${warning.library}) ${warning.indicator} for all user code in this program\n`);
375
+ process.stderr.write(` import it explicitly with :- use_module(library(${warning.library}), [${warning.indicator}]). to make this an error\n`);
376
+ }
377
+ }
378
+
367
379
  function printWarnings(program) {
368
380
  for (const warning of program.interopPortabilityWarnings ?? []) {
369
381
  if (warning.kind === 'library') {
package/src/datalog.js CHANGED
@@ -32,7 +32,7 @@ function sameScalar(left, right) {
32
32
  return scalarKey(left) === scalarKey(right);
33
33
  }
34
34
 
35
- export class DatalogRelation {
35
+ class DatalogRelation {
36
36
  constructor(arity) {
37
37
  this.arity = arity;
38
38
  this.rows = [];
@@ -66,7 +66,7 @@ function sourceTermList(term, env) {
66
66
  return null;
67
67
  }
68
68
 
69
- export function systemExpandTerm(term, module = 'user') {
69
+ function systemExpandTerm(term, module = 'user') {
70
70
  const resolved = deref(term, new Env());
71
71
  if (resolved.type === COMPOUND && resolved.name === '-->' && resolved.arity === 2) {
72
72
  const expanded = expandDcgRuleClause({ head: resolved, body: [] }, module);
@@ -23,13 +23,3 @@ export function isStrictIsoPcsCharacter(character) {
23
23
  if (typeof character !== 'string' || Array.from(character).length !== 1) return false;
24
24
  return isStrictIsoPcsCodePoint(character.codePointAt(0));
25
25
  }
26
-
27
- // EyeProlog chooses the Unicode scalar value as the collating-sequence integer.
28
- export function strictIsoCollatingInteger(character) {
29
- return isStrictIsoPcsCharacter(character) ? character.codePointAt(0) : null;
30
- }
31
-
32
- export function assertStrictIsoPcsCharacter(character, formal = 'representation_error(character)') {
33
- if (!isStrictIsoPcsCharacter(character)) throw new CharacterRepresentationError(formal);
34
- return character;
35
- }
package/src/iso.js CHANGED
@@ -35,7 +35,7 @@ class ThrownTerm extends Error {
35
35
  const succeed = function* ({ env }) { yield env; };
36
36
  const fail = function* () {};
37
37
 
38
- export const isoBuiltins = {
38
+ const isoBuiltins = {
39
39
  register(registry) {
40
40
  registry.add('true', 0, succeed, { deterministic: true });
41
41
  registry.add('fail', 0, fail, { deterministic: true });
@@ -956,10 +956,15 @@ function* currentPrologFlagSolutions({ solver, goal, env }, state) {
956
956
  .filter(([name, definition]) => definition.value != null && (flag.type === VAR || flag.name === name));
957
957
  for (let index = 0; index < definitions.length; index++) {
958
958
  const [name, definition] = definitions[index];
959
- // ISO 7.11.1.1: when bounded=false, max_integer and min_integer have no
960
- // current value and current_prolog_flag/2 must therefore not enumerate
961
- // them. The definitions remain registered so attempts to change these
962
- // non-changeable flags still receive the normal flag error handling.
959
+ // Implementation choice: with bounded=false there is no largest integer to
960
+ // report, so max_integer and min_integer carry no current value and are not
961
+ // enumerated. ISO 7.11.1.1 defines the bounded flag and does not govern
962
+ // current_prolog_flag/2; 7.11.1.2 and 7.11.1.3 give both flags an
963
+ // implementation-defined default unconditionally, so the alternative
964
+ // reading (expose values, or raise domain_error(prolog_flag)) is equally
965
+ // available. See conformance-report.md. The definitions remain registered
966
+ // so attempts to change these non-changeable flags still receive the
967
+ // normal flag error handling.
963
968
  const next = env.clone();
964
969
  if (unify(goal.args[0], atom(name), next) && unify(goal.args[1], definition.value, next)) {
965
970
  state.pending = index + 1 < definitions.length;
@@ -1802,15 +1807,6 @@ function parseReadTermText(text, solver) {
1802
1807
  });
1803
1808
  }
1804
1809
 
1805
- export function isCompleteReadTermText(text, solver) {
1806
- try {
1807
- parseReadTermText(text, solver);
1808
- return true;
1809
- } catch (_) {
1810
- return false;
1811
- }
1812
- }
1813
-
1814
1810
  function readTermFromStream(stream, solver) {
1815
1811
  if (stream.pastEnd && stream.eofAction === 'error') {
1816
1812
  throw new PrologError('permission_error(input, past_end_of_stream)', streamHandle(stream.id));
@@ -16,13 +16,13 @@ export function componentHasNegativeEdge(start, deps, negativeEdges) {
16
16
  return negativeEdges.some(([from, to]) => component.has(from) && component.has(to));
17
17
  }
18
18
 
19
- export function compactClauseIsDirectRecursive(clause, group) {
19
+ function compactClauseIsDirectRecursive(clause, group) {
20
20
  return isCompactBinaryClause(clause) && clause.bodyName === group.name && group.arity === 2;
21
21
  }
22
22
 
23
23
 
24
24
 
25
- export function clauseIsDirectRecursive(clause, group) {
25
+ function clauseIsDirectRecursive(clause, group) {
26
26
  if (isCompactBinaryClause(clause)) return compactClauseIsDirectRecursive(clause, group);
27
27
  return clause.body.some((goal) =>
28
28
  goal.type === COMPOUND && goal.name === group.name && goal.arity === group.arity
@@ -86,7 +86,7 @@ function reachableIndexesTransposed(target, deps, candidates) {
86
86
  }
87
87
 
88
88
 
89
- export function isFiniteDatalogArgument(term) {
89
+ function isFiniteDatalogArgument(term) {
90
90
  return term?.type === VAR || term?.type === ATOM || term?.type === 'string' || term?.type === 'number';
91
91
  }
92
92
 
@@ -289,7 +289,7 @@ export function isFiniteWfsDatalogGroup(program, group, cache = new Map(), visit
289
289
  return finite;
290
290
  }
291
291
 
292
- export function collectVariables(term, output) {
292
+ function collectVariables(term, output) {
293
293
  if (!term) return;
294
294
  if (term.type === VAR) {
295
295
  output.add(term.name);
@@ -420,12 +420,12 @@ export function isPortableBetweenGenerator(group) {
420
420
  group.clauses.every((clause) => clause.eyePrologLibraryPortable === true);
421
421
  }
422
422
 
423
- export function termContainsVariable(term, name) {
423
+ function termContainsVariable(term, name) {
424
424
  if (term.type === 'var') return term.name === name;
425
425
  return term.args.some((arg) => termContainsVariable(arg, name));
426
426
  }
427
427
 
428
- export function sameClauseTerm(left, right) {
428
+ function sameClauseTerm(left, right) {
429
429
  if (left.type !== right.type || left.name !== right.name || left.args.length !== right.args.length) return false;
430
430
  return left.args.every((arg, index) => sameClauseTerm(arg, right.args[index]));
431
431
  }
@@ -446,7 +446,7 @@ export function directGoalDependencyKey(goal) {
446
446
  }
447
447
 
448
448
  // ISO 13211-1, 7.1.6.3: `V1^V2^...^Goal` has the iterated goal Goal.
449
- export function iteratedGoal(goal) {
449
+ function iteratedGoal(goal) {
450
450
  let current = goal;
451
451
  while (current?.type === COMPOUND && current.name === '^' && current.arity === 2) {
452
452
  current = current.args[1];
@@ -68,14 +68,6 @@ export function compactHeadArgName(clause, index) {
68
68
  return index === 0 ? clause.head0Name : clause.head1Name;
69
69
  }
70
70
 
71
- export function compactBodyArgType(clause, index) {
72
- return index === 0 ? clause.body0Type : clause.body1Type;
73
- }
74
-
75
- export function compactBodyArgName(clause, index) {
76
- return index === 0 ? clause.body0Name : clause.body1Name;
77
- }
78
-
79
71
  export function clauseBodyLength(clause) {
80
72
  return isCompactBinaryClause(clause) ? (clause.bodyName == null ? 0 : 1) : clause.body.length;
81
73
  }
package/src/program.js CHANGED
@@ -153,6 +153,7 @@ export class Program {
153
153
  this.autoloadedPredicates = [];
154
154
  this.libraryImports = [];
155
155
  this.interopPortabilityWarnings = [];
156
+ this.libraryShadowingWarnings = [];
156
157
  this.dynamicPredicates = new Set();
157
158
  // ISO 7.5.3 notes that a public/1 directive declaring user-defined
158
159
  // procedures to be public would be an extension. Such a procedure stays
@@ -1428,6 +1429,50 @@ function analyzeInteropPortability(program, extraGoals = []) {
1428
1429
  });
1429
1430
  }
1430
1431
  program.interopPortabilityWarnings = [...warnings.values()];
1432
+ analyzeLibraryShadowing(program);
1433
+ }
1434
+
1435
+ // A user-defined procedure replaces the bundled-library procedure of the same
1436
+ // name and arity: the library clauses are never autoloaded, so other parts of
1437
+ // the same program that expected the library behaviour just see the user's
1438
+ // clauses. Bundled library modules keep resolving their own internal calls, so
1439
+ // this only affects user code, but within user code it acts at a distance
1440
+ // across included files.
1441
+ //
1442
+ // How strictly this is treated depends on how explicit the program was, which
1443
+ // follows the graded scheme SWI-Prolog uses for the same situation:
1444
+ //
1445
+ // implicit autoload -> warning
1446
+ // use_module(library(L)) -> warning
1447
+ // use_module(library(L), [Name/Arity]) -> permission_error
1448
+ //
1449
+ // Only the last case is an outright contradiction: the program asked for that
1450
+ // exact predicate to be imported and then supplied clauses for it. The first
1451
+ // two stay warnings so that flat, module-free programs -- which load unchanged
1452
+ // on Scryer and Trealla, where the library is simply never imported -- keep
1453
+ // working. Note that ISO reserves permission_error(modify, static_procedure)
1454
+ // for built-in predicates (7.5.3); library predicates are not built-ins, so
1455
+ // this error is raised only where the program's own import list demands it.
1456
+ function analyzeLibraryShadowing(program) {
1457
+ const explicitlyImported = new Map();
1458
+ for (const entry of program.libraryImports) {
1459
+ if (entry.imports == null) continue;
1460
+ for (const indicator of entry.imports) {
1461
+ explicitlyImported.set(`${entry.targetModule}:${indicator.key}`, entry.library);
1462
+ }
1463
+ }
1464
+
1465
+ const shadowed = new Map();
1466
+ for (const group of program.groups.values()) {
1467
+ if (group.module !== 'user') continue;
1468
+ const key = `${group.name}/${group.arity}`;
1469
+ const explicitLibrary = explicitlyImported.get(`${group.module}:${key}`);
1470
+ if (explicitLibrary != null) throw staticProcedureModificationError(group.name, group.arity);
1471
+ const library = eyePrologLibraryAutoload[key];
1472
+ if (library == null || shadowed.has(key)) continue;
1473
+ shadowed.set(key, { kind: 'shadowed-library-predicate', indicator: key, library });
1474
+ }
1475
+ program.libraryShadowingWarnings = [...shadowed.values()];
1431
1476
  }
1432
1477
 
1433
1478
  function sourceOptionsFor(source, options) {
@@ -1940,7 +1985,3 @@ function predicateIndicator(name, arity) {
1940
1985
  export function makeProgram(source, options = {}) {
1941
1986
  return Program.parse(source, options);
1942
1987
  }
1943
-
1944
- export function parseSourceClauses(source, options = {}) {
1945
- return parseClauses(source, options);
1946
- }
@@ -58,7 +58,9 @@ concrete mismatches that are now part of the closed Part 1 review:
58
58
 
59
59
  - `bounded=false` no longer exposes implementation-specific `unbounded` values
60
60
  for `max_integer` or `min_integer`; the corresponding
61
- `current_prolog_flag/2` queries fail as specified by 7.11.1.1;
61
+ `current_prolog_flag/2` queries fail. Part 1 does not require that outcome
62
+ (7.11.1.1 defines the `bounded` flag and does not govern
63
+ `current_prolog_flag/2`), so it is recorded as an implementation choice;
62
64
  - preparation-time `char_conversion/2` now converts later unquoted source text
63
65
  when the `char_conversion` flag is `on`, leaves quoted characters unchanged,
64
66
  and feeds the same mapping into execution-time term input.
@@ -23,7 +23,7 @@ chosen behavior is also indexed by `ISO-IMPLEMENTATION-DEFINED.md`.
23
23
  | 7.4.2.7 `include/1` | covered | The included text is prepared at the directive position and shares operator/character/flag preparation state with its parent. |
24
24
  | 7.4.2.8 `ensure_loaded/1` | covered | A source is prepared at most once in the current load graph, including repeated references and self/top-level references. |
25
25
  | 7.4.2.9 `set_prolog_flag/2` | covered | Preparation-time flag changes affect subsequent text and are replayed into execution state; strict flag names/values/changeability remain governed by the closed 7.11 review. |
26
- | 7.4.3 source clauses | covered | Source heads/bodies are validated like program clauses, standardized static/control procedures are protected, declarations can create empty procedures, and body conversion follows 7.6.2 while preserving head/body variable identity. The built-in restriction applies in every execution mode, so consulting a clause for a built-in predicate or control construct reports the same `permission_error(modify, static_procedure)` as `assert`ing one; EyeProlog's own library and extension predicates are not standard built-ins and stay redefinable. Corpus: `error/iso/consult_redefines_builtin`. |
26
+ | 7.4.3 source clauses | covered | Source heads/bodies are validated like program clauses, standardized static/control procedures are protected, declarations can create empty procedures, and body conversion follows 7.6.2 while preserving head/body variable identity. The built-in restriction applies in every execution mode, so consulting a clause for a built-in predicate or control construct reports the same `permission_error(modify, static_procedure)` as `assert`ing one; EyeProlog's own library and extension predicates are not standard built-ins, so ISO does not protect them. They stay redefinable when they reach the program implicitly, through autoloading or a whole-module `use_module(library(L))`, but the redefinition is reported as a shadowing warning because it replaces the library predicate for all user code in the program. Naming a predicate in an explicit import list, `use_module(library(L), [Name/Arity])`, and then supplying clauses for it is a contradiction and reports `permission_error(modify, static_procedure)`. Bundled library modules keep resolving their own internal calls, so a user redefinition never changes library behaviour. Corpus: `error/iso/consult_redefines_builtin`. |
27
27
 
28
28
  The strict release test `closes ISO 7.4 Prolog-text preparation and directive
29
29
  rows` exercises the cross-text operator/character/flag state, include and
@@ -32,7 +32,9 @@ included before its examples are run.
32
32
 
33
33
  The upstream Prologue snapshot contains one `max_integer` quad that accepts an
34
34
  implementation-specific `Max = unbounded` result. EyeProlog deliberately does
35
- not patch that vendored fixture: with its ISO `bounded=false` choice, Part 1
36
- 7.11.1.1 requires `current_prolog_flag(max_integer, _)` to fail. The regression
37
- gate therefore records this single standards-driven divergence explicitly while
38
- requiring the other 32 quads to pass.
35
+ not patch that vendored fixture: with `bounded=false` it reports no value for
36
+ `max_integer`, so `current_prolog_flag(max_integer, _)` fails. Part 1 does not
37
+ mandate that outcome -- 7.11.1.1 defines the `bounded` flag and says nothing
38
+ about `current_prolog_flag/2` -- so this is an implementation choice, not a
39
+ standards requirement. The regression gate records the single divergence
40
+ explicitly while requiring the other 32 quads to pass.