eyeprolog 1.3.68 → 1.3.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -159,15 +159,17 @@ variable representatives and ordinary Prolog backtracking, and the REPL projects
159
159
  module `attribute_goals//1` hooks as residual goals. See
160
160
  [`examples/attributed-variables.pl`](examples/attributed-variables.pl).
161
161
 
162
- The second migration stage toward reusing Markus Triska's MIT-licensed
162
+ The next two migration stages toward reusing Markus Triska's MIT-licensed
163
163
  [Scryer `library(clpz)`](https://github.com/mthom/scryer-prolog/blob/master/src/lib/clpz.pl)
164
- is now in place as well. Normal-profile loading supports user-defined
164
+ are now in place as well. Normal-profile loading supports user-defined
165
165
  `term_expansion/2` and `goal_expansion/2`, clause-list generation, and
166
- `expand_term/2` for processor DCG lowering, including the qualified hook shape
167
- used by Scryer libraries. The current CLP(Z) implementation remains active while
168
- the remaining Scryer support-library surface is brought across. The staged plan
169
- and exact compatibility boundary are documented in
170
- [`src/CLPZ-MIGRATION.md`](src/CLPZ-MIGRATION.md).
166
+ `expand_term/2` for processor DCG lowering. EyeProlog also ships the support
167
+ modules used by Scryer's CLP(Z) source (`assoc`, `pairs`, `between`, `dcgs`,
168
+ `terms`, `error`, `si`, `freeze`, `arithmetic`, `debug`, and `format`) together
169
+ with generic backtrackable blackboard primitives. The current CLP(Z) engine
170
+ remains active while the upstream solver itself is integrated and checked for
171
+ behavioral and performance parity. The staged plan and exact compatibility
172
+ boundary are documented in [`src/CLPZ-MIGRATION.md`](src/CLPZ-MIGRATION.md).
171
173
 
172
174
  Recursion through negation is explicit. EyeProlog provides `tnot/1` for
173
175
  well-founded negation over finite, range-restricted, function-free Datalog
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.3.68",
6
+ "version": "1.3.69",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
@@ -48,6 +48,13 @@ needed by Scryer's `library(atts)` and `library(clpz)`. The current CLP(Z)-speci
48
48
  `Env._clpz` store remains transitional until the Scryer Prolog implementation can
49
49
  replace it without losing test or performance parity; see `src/CLPZ-MIGRATION.md`.
50
50
 
51
+ `Env` also owns the small generic backtrackable blackboard used by Scryer-style
52
+ libraries. Blackboard maps are shared by clones and copied only on write, so
53
+ `bb_b_put/2` follows ordinary Prolog backtracking without any CLP(Z)-specific
54
+ state in the VM. `scryer-compat.js` exposes the private host bridge used by
55
+ `library(debug)`; the rest of the Stage 3 compatibility surface lives as Prolog
56
+ modules under `src/lib/`.
57
+
51
58
  `source-expansion.js` is the explicit compile-time execution boundary. When an
52
59
  already-loaded `term_expansion/2` or `goal_expansion/2` hook exists, program
53
60
  preparation invokes it with a fresh bounded `Solver` against the partially built
@@ -55,17 +55,35 @@ The regression corpus covers clause-list generation, variable-sharing goal
55
55
  expansion, qualified hook lexical context, and the Scryer pattern where a
56
56
  `term_expansion/2` hook calls `expand_term/2` to generate a DCG predicate.
57
57
 
58
- ## What still prevents a drop-in Scryer `clpz.pl`
58
+ ## Stage 3: Scryer support-library surface
59
59
 
60
- The remaining compatibility work is the supporting Scryer library surface
61
- (`assoc`, `pairs`, `between`, `dcgs`, `terms`, `error`, `si`, `freeze`,
62
- `arithmetic`, `debug`, and `format`) at the interfaces expected by that source,
63
- plus any small builtin gaps exposed while loading those modules. The generated
64
- predicates and custom Duo DCG `++>` expansion are no longer VM/compiler
65
- blockers: Scryer's own `term_expansion/2` can now perform that lowering.
60
+ This stage is implemented. EyeProlog now registers the library names imported by
61
+ Scryer's current `clpz.pl`: `assoc`, `pairs`, `between`, `dcgs`, `terms`,
62
+ `error`, `si`, `freeze`, `arithmetic`, `debug`, and `format`. The implementations
63
+ are deliberately small compatibility layers where CLP(Z) needs only a subset of
64
+ the upstream module, while observable predicate contracts stay at the Prolog
65
+ library boundary. In particular, `library(freeze)` is implemented with the same
66
+ attributed-variable `verify_attributes/3` protocol introduced in Stage 1.
66
67
 
67
- These are reusable library compatibility gaps, not reasons to add more
68
- CLP(Z)-specific code to `term.js` or `solver.js`.
68
+ Two remaining Scryer runtime facilities are generic rather than constraint
69
+ specific. `Env` now carries a copy-on-write backtrackable blackboard, exposed to
70
+ Prolog through `bb_get/2` and `bb_b_put/2` compatibility wrappers, and
71
+ `library(terms)` provides `copy_term_nat/2` using EyeProlog's attribute-free
72
+ logical copy semantics. Regression coverage exercises the support modules,
73
+ blackboard rollback, attribute-free copying, and the exact import/attribute
74
+ prelude used by upstream CLP(Z).
75
+
76
+ ## What still prevents replacing `src/clpz.js`
77
+
78
+ The VM/compiler and named support-library blockers are now closed. The remaining
79
+ work is direct upstream-source integration: vendor a pinned Scryer `clpz.pl`,
80
+ apply only the small compatibility patches exposed by loading the complete file,
81
+ and run it side-by-side against EyeProlog's existing CLP(Z) corpus plus selected
82
+ Scryer regressions. Only after behavioral and performance parity should the
83
+ `eyeprolog__clpz_*` adapters and `Env._clpz` store be removed.
84
+
85
+ This boundary is intentional: Stage 3 does not claim that the complete upstream
86
+ solver is active merely because its dependency prelude loads.
69
87
 
70
88
  ## Migration rule
71
89
 
@@ -0,0 +1,36 @@
1
+ /** Integer arithmetic helpers used by Scryer's CLP(Z) source. */
2
+
3
+ :- module(arithmetic, [lsb/2, msb/2, popcount/2]).
4
+
5
+ :- use_module(library(error), [must_be/2, domain_error/3]).
6
+
7
+ lsb(X, N) :-
8
+ must_be(integer, X),
9
+ ( X < 1 -> domain_error(not_less_than_one, X, lsb/2)
10
+ ; Low is X /\ (-X), arithmetic__msb(Low, -1, N)
11
+ ).
12
+
13
+ msb(X, N) :-
14
+ must_be(integer, X),
15
+ ( X < 1 -> domain_error(not_less_than_one, X, msb/2)
16
+ ; Shifted is X >> 1, arithmetic__msb(Shifted, 0, N)
17
+ ).
18
+
19
+ arithmetic__msb(0, N, N) :- !.
20
+ arithmetic__msb(X, N0, N) :-
21
+ X1 is X >> 1,
22
+ N1 is N0 + 1,
23
+ arithmetic__msb(X1, N1, N).
24
+
25
+ popcount(X, Count) :-
26
+ must_be(integer, X),
27
+ ( X < 0 -> domain_error(not_less_than_zero, X, popcount/2)
28
+ ; arithmetic__popcount(X, 0, Count)
29
+ ).
30
+
31
+ arithmetic__popcount(0, Count, Count) :- !.
32
+ arithmetic__popcount(X, Count0, Count) :-
33
+ Bit is X /\ 1,
34
+ Count1 is Count0 + Bit,
35
+ X1 is X >> 1,
36
+ arithmetic__popcount(X1, Count1, Count).
@@ -0,0 +1,42 @@
1
+ /** Minimal Scryer-compatible association maps.
2
+
3
+ The public operations used by library(clpz) are represented as a sorted
4
+ list wrapped in assoc/1. The representation is intentionally private: the
5
+ predicates preserve the observable assoc contracts while keeping the
6
+ compatibility layer small and declarative.
7
+ */
8
+
9
+ :- module(assoc, [
10
+ empty_assoc/1,
11
+ assoc_to_list/2,
12
+ get_assoc/3,
13
+ put_assoc/4
14
+ ]).
15
+
16
+ empty_assoc(assoc([])).
17
+
18
+ assoc_to_list(assoc(Pairs), Pairs).
19
+
20
+ get_assoc(Key, assoc(Pairs), Value) :-
21
+ assoc__get(Pairs, Key, Value).
22
+
23
+ assoc__get([K-V|Pairs], Key, Value) :-
24
+ compare(Order, Key, K),
25
+ assoc__get(Order, Key, Value, V, Pairs).
26
+
27
+ assoc__get(=, _, Value, Value, _).
28
+ assoc__get(>, Key, Value, _, Pairs) :-
29
+ assoc__get(Pairs, Key, Value).
30
+
31
+ put_assoc(Key, assoc(Pairs0), Value, assoc(Pairs)) :-
32
+ assoc__put(Pairs0, Key, Value, Pairs).
33
+
34
+ assoc__put([], Key, Value, [Key-Value]).
35
+ assoc__put([K-V|Pairs0], Key, Value, Pairs) :-
36
+ compare(Order, Key, K),
37
+ assoc__put(Order, Key, Value, K, V, Pairs0, Pairs).
38
+
39
+ assoc__put(=, Key, Value, _, _, Pairs, [Key-Value|Pairs]).
40
+ assoc__put(<, Key, Value, K, V, Pairs, [Key-Value,K-V|Pairs]).
41
+ assoc__put(>, Key, Value, K, V, Pairs0, [K-V|Pairs]) :-
42
+ assoc__put(Pairs0, Key, Value, Pairs).
@@ -0,0 +1,54 @@
1
+ /** Scryer-compatible integer generators needed by CLP(Z). */
2
+
3
+ :- module(between, [between/3, gen_int/1, gen_nat/1, numlist/2, numlist/3, repeat/1]).
4
+
5
+ :- use_module(library(error), [must_be/2, can_be/2]).
6
+
7
+ between(Lower, Upper, X) :-
8
+ must_be(integer, Lower),
9
+ must_be(integer, Upper),
10
+ can_be(integer, X),
11
+ ( nonvar(X) -> Lower =< X, X =< Upper
12
+ ; Lower =< Upper, between__from(Lower, Upper, X)
13
+ ).
14
+
15
+ between__from(Lower, Upper, Lower).
16
+ between__from(Lower, Upper, X) :-
17
+ Lower < Upper,
18
+ Next is Lower + 1,
19
+ between__from(Next, Upper, X).
20
+
21
+ gen_nat(N) :-
22
+ can_be(integer, N),
23
+ ( var(N) -> between__nats(0, N) ; N >= 0 ).
24
+
25
+ between__nats(N, N).
26
+ between__nats(N0, N) :- N1 is N0 + 1, between__nats(N1, N).
27
+
28
+ gen_int(N) :-
29
+ can_be(integer, N),
30
+ ( var(N) -> between__ints(0, N) ; true ).
31
+
32
+ between__ints(0, 0).
33
+ between__ints(I, I) :- I > 0.
34
+ between__ints(I, N) :- I > 0, N is -I.
35
+ between__ints(I0, N) :- I1 is I0 + 1, between__ints(I1, N).
36
+
37
+ repeat(N) :-
38
+ must_be(integer, N), N > 0,
39
+ between__repeat(N).
40
+ between__repeat(_).
41
+ between__repeat(N0) :- N0 > 1, N is N0 - 1, between__repeat(N).
42
+
43
+ numlist(Upper, List) :- numlist(1, Upper, List).
44
+
45
+ numlist(Lower, Upper, List) :-
46
+ must_be(integer, Lower),
47
+ must_be(integer, Upper),
48
+ between__numlist(Lower, Upper, List).
49
+
50
+ between__numlist(Lower, Upper, []) :- Lower > Upper, !.
51
+ between__numlist(Lower, Upper, [Lower|Rest]) :-
52
+ Lower =< Upper,
53
+ Next is Lower + 1,
54
+ between__numlist(Next, Upper, Rest).
@@ -0,0 +1,23 @@
1
+ /** Scryer-compatible DCG helpers.
2
+
3
+ EyeProlog already provides phrase/2-3 and ISO-style grammar expansion in the
4
+ runtime. This module supplies the sequence combinators used by Scryer's
5
+ declarative libraries and exports the built-in phrase predicates under the
6
+ expected module name.
7
+ */
8
+
9
+ :- module(dcgs, [
10
+ phrase/2,
11
+ phrase/3,
12
+ seq//1,
13
+ seqq//1
14
+ ]).
15
+
16
+ seq([]) --> [].
17
+ seq([Element|Elements]) --> [Element], seq(Elements).
18
+
19
+ seqq([]) --> [].
20
+ seqq([Elements|Sequences]) --> seq(Elements), seqq(Sequences).
21
+
22
+ '...' --> [].
23
+ '...' --> [_], '...'.
@@ -0,0 +1,17 @@
1
+ /** Compatibility hooks for Scryer libraries.
2
+
3
+ CLP(Z) only needs the backtrackable blackboard operations; the debugging
4
+ predicates are deliberately harmless no-ops unless EyeProlog grows a debug
5
+ topic UI later.
6
+ */
7
+
8
+ :- module(debug, [debug/1, debug/3, nodebug/1, bb_get/2, bb_b_put/2]).
9
+
10
+ :- meta_predicate(debug(+, +, +)).
11
+
12
+ debug(_).
13
+ debug(_, _, _).
14
+ nodebug(_).
15
+
16
+ bb_get(Key, Value) :- eyeprolog__bb_get(Key, Value).
17
+ bb_b_put(Key, Value) :- eyeprolog__bb_b_put(Key, Value).
@@ -0,0 +1,123 @@
1
+ /** Small Scryer-compatible error/type layer used by reusable libraries. */
2
+
3
+ :- module(error, [
4
+ must_be/2,
5
+ can_be/2,
6
+ instantiation_error/0,
7
+ instantiation_error/1,
8
+ domain_error/2,
9
+ domain_error/3,
10
+ type_error/2,
11
+ type_error/3,
12
+ representation_error/1,
13
+ resource_error/1,
14
+ call_with_error_context/2
15
+ ]).
16
+
17
+ :- meta_predicate(call_with_error_context(0, +)).
18
+
19
+ must_be(Type, Term) :-
20
+ ( var(Type) -> instantiation_error(must_be/2)
21
+ ; error__must_be(Type, Term)
22
+ ).
23
+
24
+ error__must_be(integer, Term) :- !,
25
+ ( var(Term) -> instantiation_error(must_be/2)
26
+ ; integer(Term) -> true
27
+ ; type_error(integer, Term, must_be/2)
28
+ ).
29
+ error__must_be(atom, Term) :- !,
30
+ ( var(Term) -> instantiation_error(must_be/2)
31
+ ; atom(Term) -> true
32
+ ; type_error(atom, Term, must_be/2)
33
+ ).
34
+ error__must_be(number, Term) :- !,
35
+ ( var(Term) -> instantiation_error(must_be/2)
36
+ ; number(Term) -> true
37
+ ; type_error(number, Term, must_be/2)
38
+ ).
39
+ error__must_be(var, Term) :- !,
40
+ ( var(Term) -> true
41
+ ; throw(error(uninstantiation_error(Term), must_be/2))
42
+ ).
43
+ error__must_be(ground, Term) :- !,
44
+ ( ground(Term) -> true ; instantiation_error(must_be/2) ).
45
+ error__must_be(acyclic, Term) :- !,
46
+ ( acyclic_term(Term) -> true ; type_error(acyclic_term, Term, must_be/2) ).
47
+ error__must_be(list, Term) :- !,
48
+ error__proper_list(Term).
49
+ error__must_be(list(Type), Term) :- !,
50
+ error__proper_list_of(Term, Type).
51
+ error__must_be(pair, Term) :- !,
52
+ ( var(Term) -> instantiation_error(must_be/2)
53
+ ; Term = _-_ -> true
54
+ ; type_error(pair, Term, must_be/2)
55
+ ).
56
+ error__must_be(not_less_than_zero, Term) :- !,
57
+ must_be(integer, Term),
58
+ ( Term >= 0 -> true ; domain_error(not_less_than_zero, Term, must_be/2) ).
59
+ error__must_be(Type, Term) :-
60
+ ( var(Term) -> instantiation_error(must_be/2)
61
+ ; type_error(Type, Term, must_be/2)
62
+ ).
63
+
64
+ error__proper_list([]).
65
+ error__proper_list([_|Tail]) :- !, error__proper_list(Tail).
66
+ error__proper_list(Term) :-
67
+ ( var(Term) -> instantiation_error(must_be/2)
68
+ ; type_error(list, Term, must_be/2)
69
+ ).
70
+
71
+ error__proper_list_of([], _).
72
+ error__proper_list_of([Head|Tail], Type) :- !,
73
+ must_be(Type, Head),
74
+ error__proper_list_of(Tail, Type).
75
+ error__proper_list_of(Term, _) :-
76
+ ( var(Term) -> instantiation_error(must_be/2)
77
+ ; type_error(list, Term, must_be/2)
78
+ ).
79
+
80
+ can_be(Type, Term) :-
81
+ ( var(Type) -> instantiation_error(can_be/2)
82
+ ; var(Term) -> true
83
+ ; error__can_be(Type, Term)
84
+ ).
85
+
86
+ error__can_be(integer, Term) :- !,
87
+ ( integer(Term) -> true ; type_error(integer, Term, can_be/2) ).
88
+ error__can_be(atom, Term) :- !,
89
+ ( atom(Term) -> true ; type_error(atom, Term, can_be/2) ).
90
+ error__can_be(number, Term) :- !,
91
+ ( number(Term) -> true ; type_error(number, Term, can_be/2) ).
92
+ error__can_be(list, Term) :- !, error__partial_list(Term).
93
+ error__can_be(list(Type), Term) :- !, error__partial_list_of(Term, Type).
94
+ error__can_be(not_less_than_zero, Term) :- !,
95
+ ( integer(Term) ->
96
+ ( Term >= 0 -> true ; domain_error(not_less_than_zero, Term, can_be/2) )
97
+ ; type_error(integer, Term, can_be/2)
98
+ ).
99
+ error__can_be(Type, Term) :- error__must_be(Type, Term).
100
+
101
+ error__partial_list(Term) :- var(Term), !.
102
+ error__partial_list([]) :- !.
103
+ error__partial_list([_|Tail]) :- !, error__partial_list(Tail).
104
+ error__partial_list(Term) :- type_error(list, Term, can_be/2).
105
+
106
+ error__partial_list_of(Term, _) :- var(Term), !.
107
+ error__partial_list_of([], _) :- !.
108
+ error__partial_list_of([Head|Tail], Type) :- !,
109
+ can_be(Type, Head),
110
+ error__partial_list_of(Tail, Type).
111
+ error__partial_list_of(Term, _) :- type_error(list, Term, can_be/2).
112
+
113
+ instantiation_error :- throw(error(instantiation_error, [])).
114
+ instantiation_error(Context) :- throw(error(instantiation_error, Context)).
115
+ domain_error(Type, Term) :- throw(error(domain_error(Type, Term), [])).
116
+ domain_error(Type, Term, Context) :- throw(error(domain_error(Type, Term), Context)).
117
+ type_error(Type, Term) :- throw(error(type_error(Type, Term), [])).
118
+ type_error(Type, Term, Context) :- throw(error(type_error(Type, Term), Context)).
119
+ representation_error(Flag) :- throw(error(representation_error(Flag), [])).
120
+ resource_error(Resource) :- throw(error(resource_error(Resource), [])).
121
+
122
+ call_with_error_context(Goal, Pair) :-
123
+ catch(Goal, error(Error, Context), throw(error(Error, [Pair|Context]))).
@@ -0,0 +1,35 @@
1
+ /** Small format/2 compatibility layer covering the controls used by CLP(Z). */
2
+
3
+ :- module(format, [format/2]).
4
+
5
+ format(Template, Arguments) :-
6
+ format__chars(Template, Chars),
7
+ format__emit(Chars, Arguments).
8
+
9
+ format__chars(Template, Chars) :-
10
+ atom(Template), !,
11
+ atom_chars(Template, Chars).
12
+ format__chars(Chars, Chars).
13
+
14
+ format__emit([], []).
15
+ format__emit(['~','~'|Chars], Args) :- !,
16
+ put_char('~'),
17
+ format__emit(Chars, Args).
18
+ format__emit(['~',n|Chars], Args) :- !,
19
+ nl,
20
+ format__emit(Chars, Args).
21
+ format__emit(['~',w|Chars], [Arg|Args]) :- !,
22
+ write(Arg),
23
+ format__emit(Chars, Args).
24
+ format__emit(['~',q|Chars], [Arg|Args]) :- !,
25
+ writeq(Arg),
26
+ format__emit(Chars, Args).
27
+ format__emit(['~',a|Chars], [Arg|Args]) :- !,
28
+ write(Arg),
29
+ format__emit(Chars, Args).
30
+ format__emit(['~',d|Chars], [Arg|Args]) :- !,
31
+ write(Arg),
32
+ format__emit(Chars, Args).
33
+ format__emit([Char|Chars], Args) :-
34
+ put_char(Char),
35
+ format__emit(Chars, Args).
@@ -0,0 +1,28 @@
1
+ /** Attributed-variable implementation of Scryer-compatible freeze/2. */
2
+
3
+ :- module(freeze, [freeze/2]).
4
+
5
+ :- use_module(library(atts)).
6
+ :- meta_predicate(freeze(-, 0)).
7
+ :- attribute frozen/1.
8
+
9
+ verify_attributes(Var, Other, Goals) :-
10
+ get_atts(Var, frozen(FrozenA)), !,
11
+ ( var(Other) ->
12
+ ( get_atts(Other, frozen(FrozenB)) ->
13
+ put_atts(Other, frozen((FrozenB,FrozenA)))
14
+ ; put_atts(Other, frozen(FrozenA))
15
+ ),
16
+ Goals = []
17
+ ; Goals = [FrozenA]
18
+ ).
19
+ verify_attributes(_, _, []).
20
+
21
+ freeze(X, Goal) :-
22
+ put_atts(Fresh, frozen(Goal)),
23
+ Fresh = X.
24
+
25
+ attribute_goals(Var) -->
26
+ { get_atts(Var, frozen(Goals)),
27
+ put_atts(Var, -frozen(_)) },
28
+ [freeze:freeze(Var, Goals)].
@@ -0,0 +1,36 @@
1
+ /** Scryer-compatible pair helpers used by CLP(Z). */
2
+
3
+ :- module(pairs, [
4
+ pairs_keys_values/3,
5
+ pairs_keys/2,
6
+ pairs_values/2,
7
+ group_pairs_by_key/2,
8
+ map_list_to_pairs/3
9
+ ]).
10
+
11
+ :- meta_predicate(map_list_to_pairs(2, ?, ?)).
12
+
13
+ pairs_keys_values([], [], []).
14
+ pairs_keys_values([A-B|ABs], [A|As], [B|Bs]) :-
15
+ pairs_keys_values(ABs, As, Bs).
16
+
17
+ pairs_keys(Pairs, Keys) :- pairs_keys_values(Pairs, Keys, _).
18
+ pairs_values(Pairs, Values) :- pairs_keys_values(Pairs, _, Values).
19
+
20
+ map_list_to_pairs(Pred, List, Pairs) :-
21
+ pairs__map_list_to_pairs(List, Pred, Pairs).
22
+
23
+ pairs__map_list_to_pairs([], _, []).
24
+ pairs__map_list_to_pairs([Head|Tail0], Pred, [Key-Head|Tail]) :-
25
+ call(Pred, Head, Key),
26
+ pairs__map_list_to_pairs(Tail0, Pred, Tail).
27
+
28
+ group_pairs_by_key([], []).
29
+ group_pairs_by_key([K-V|Pairs0], [K-[V|Values]|Pairs]) :-
30
+ pairs__same_key(K, Pairs0, Values, Pairs1),
31
+ group_pairs_by_key(Pairs1, Pairs).
32
+
33
+ pairs__same_key(K0, [K1-V|Pairs0], [V|Values], Pairs) :-
34
+ K0 == K1, !,
35
+ pairs__same_key(K0, Pairs0, Values, Pairs).
36
+ pairs__same_key(_, Pairs, [], Pairs).
package/src/lib/si.pl ADDED
@@ -0,0 +1,62 @@
1
+ /** Sufficient-instantiation tests compatible with Scryer library(si). */
2
+
3
+ :- module(si, [
4
+ atom_si/1,
5
+ integer_si/1,
6
+ atomic_si/1,
7
+ list_si/1,
8
+ character_si/1,
9
+ term_si/1,
10
+ chars_si/1,
11
+ dif_si/2,
12
+ not_si/1,
13
+ when_si/2
14
+ ]).
15
+
16
+ :- meta_predicate(not_si(0)).
17
+ :- meta_predicate(when_si(+, 0)).
18
+
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) ).
22
+
23
+ list_si(List) :- si__list(List).
24
+ si__list([]).
25
+ si__list([_|Tail]) :- !, si__list(Tail).
26
+ si__list(Term) :-
27
+ ( var(Term) -> throw(error(instantiation_error, list_si/1)) ; fail ).
28
+
29
+ character_si(C) :-
30
+ ( var(C) -> throw(error(instantiation_error, character_si/1))
31
+ ; atom(C), atom_length(C, 1)
32
+ ).
33
+
34
+ term_si(Term) :-
35
+ ( ground(Term) -> acyclic_term(Term)
36
+ ; throw(error(instantiation_error, term_si/1))
37
+ ).
38
+
39
+ chars_si(Chars) :-
40
+ list_si(Chars),
41
+ si__chars(Chars).
42
+ si__chars([]).
43
+ si__chars([C|Cs]) :- character_si(C), si__chars(Cs).
44
+
45
+ dif_si(X, Y) :-
46
+ X \== Y,
47
+ ( X \= Y -> true ; throw(error(instantiation_error, dif_si/2)) ).
48
+
49
+ not_si(Goal) :- term_si(Goal), \+ Goal.
50
+
51
+ when_si(Condition, Goal) :-
52
+ ( si__condition(Condition) ->
53
+ ( call(Condition) -> call(Goal)
54
+ ; throw(error(instantiation_error, when_si/2)) )
55
+ ; throw(error(domain_error(when_condition_si, Condition), when_si/2))
56
+ ).
57
+
58
+ si__condition(Condition) :- var(Condition), !, throw(error(instantiation_error, when_si/2)).
59
+ si__condition(ground(_)).
60
+ si__condition(nonvar(_)).
61
+ si__condition((A,B)) :- si__condition(A), si__condition(B).
62
+ si__condition((A;B)) :- si__condition(A), si__condition(B).
@@ -0,0 +1,18 @@
1
+ /** Term helpers compatible with the Scryer library surface used by CLP(Z). */
2
+
3
+ :- module(terms, [numbervars/3, copy_term_nat/2]).
4
+
5
+ :- use_module(library(error), [must_be/2, can_be/2]).
6
+
7
+ copy_term_nat(Term, Copy) :- copy_term(Term, Copy).
8
+
9
+ numbervars(Term, N0, N) :-
10
+ must_be(integer, N0),
11
+ can_be(integer, N),
12
+ term_variables(Term, Variables),
13
+ terms__numbervars(Variables, N0, N).
14
+
15
+ terms__numbervars([], N, N).
16
+ terms__numbervars(['$VAR'(N0)|Variables], N0, N) :-
17
+ N1 is N0 + 1,
18
+ terms__numbervars(Variables, N1, N).
@@ -0,0 +1,39 @@
1
+ // Small host primitives needed by Scryer-style declarative libraries.
2
+ //
3
+ // Keep these facilities generic: they are not part of the CLP(Z) engine. The
4
+ // backtrackable blackboard lives in Env so ordinary Prolog choice points undo
5
+ // writes automatically.
6
+
7
+ import { ATOM, NUMBER, STRING, deref, unify } from './term.js';
8
+ import { PrologError } from './errors.js';
9
+
10
+ function blackboardKey(term, env) {
11
+ const value = deref(term, env);
12
+ if (value.type === ATOM) return `a:${value.name}`;
13
+ if (value.type === NUMBER) return `n:${value.name}`;
14
+ if (value.type === STRING) return `s:${value.name}`;
15
+ if (value.type === 'var') throw new PrologError('instantiation_error');
16
+ throw new PrologError('type_error(atomic)', value);
17
+ }
18
+
19
+ function* bbGetBuiltin({ goal, env }) {
20
+ const key = blackboardKey(goal.args[0], env);
21
+ const stored = env.getBacktrackableBlackboard(key);
22
+ if (stored === undefined) return;
23
+ const next = env.clone();
24
+ if (unify(goal.args[1], stored, next)) yield next;
25
+ }
26
+
27
+ function* bbPutBuiltin({ goal, env }) {
28
+ const next = env.clone();
29
+ const key = blackboardKey(goal.args[0], next);
30
+ next.putBacktrackableBlackboard(key, deref(goal.args[1], next));
31
+ yield next;
32
+ }
33
+
34
+ export const scryerCompatibilityBuiltins = {
35
+ register(registry) {
36
+ registry.add('eyeprolog__bb_get', 2, bbGetBuiltin, { eyePrologLibrary: true });
37
+ registry.add('eyeprolog__bb_b_put', 2, bbPutBuiltin, { deterministic: true, eyePrologLibrary: true });
38
+ },
39
+ };
@@ -6,23 +6,35 @@ import { PrologError, createDefaultRegistry, eyePrologLibraryBuiltins } from './
6
6
  import { clpzBuiltins } from './clpz.js';
7
7
  import { attsBuiltins } from './atts.js';
8
8
  import { expansionBuiltins } from './expansion-builtins.js';
9
+ import { scryerCompatibilityBuiltins } from './scryer-compat.js';
9
10
  import { registerCleanupBuiltins } from './cleanup.js';
10
11
  import { fs, isNode, memoryStatistics } from './platform.js';
11
12
  import { ATOM, VAR, atom, deref, numberTerm, unify } from './term.js';
12
13
 
13
14
  const moduleFiles = Object.freeze({
14
15
  aggregate: 'aggregate.pl',
16
+ arithmetic: 'arithmetic.pl',
17
+ assoc: 'assoc.pl',
15
18
  atts: 'atts.pl',
19
+ between: 'between.pl',
16
20
  clpz: 'clpz.pl',
17
21
  comparison: 'comparison.pl',
18
22
  dates: 'dates.pl',
23
+ dcgs: 'dcgs.pl',
24
+ debug: 'debug.pl',
25
+ error: 'error.pl',
26
+ format: 'format.pl',
27
+ freeze: 'freeze.pl',
19
28
  iso_ext: 'iso_ext.pl',
20
29
  lambda: 'lambda.pl',
21
30
  lists: 'lists.pl',
31
+ pairs: 'pairs.pl',
22
32
  primes: 'primes.pl',
23
33
  prologue: 'prologue.pl',
24
34
  random: 'random.pl',
35
+ si: 'si.pl',
25
36
  strings: 'strings.pl',
37
+ terms: 'terms.pl',
26
38
  uuid: 'uuid.pl',
27
39
  });
28
40
 
@@ -190,6 +202,7 @@ export function createEyePrologRegistry() {
190
202
  eyePrologLibraryBuiltins.register(registry);
191
203
  attsBuiltins.register(registry);
192
204
  expansionBuiltins.register(registry);
205
+ scryerCompatibilityBuiltins.register(registry);
193
206
  clpzBuiltins.register(registry);
194
207
  registry.eyePrologLibrary = true;
195
208
  return registry;
package/src/term.js CHANGED
@@ -109,6 +109,9 @@ export class Env {
109
109
  this._prologAttributes = null;
110
110
  this._attributeHookRunner = null;
111
111
  this._pendingAttributeGoals = null;
112
+ // Backtrackable blackboard entries used by Scryer-compatible libraries.
113
+ // Values are logical terms and the map is copy-on-write across Env clones.
114
+ this._backtrackableBlackboard = null;
112
115
  this._clpz = null;
113
116
  this._occursCheckHandler = null;
114
117
  this._localVariables = null;
@@ -127,6 +130,7 @@ export class Env {
127
130
  clone._prologAttributes = this._prologAttributes;
128
131
  clone._attributeHookRunner = this._attributeHookRunner;
129
132
  clone._pendingAttributeGoals = this._pendingAttributeGoals;
133
+ clone._backtrackableBlackboard = this._backtrackableBlackboard;
130
134
  clone._clpz = this._clpz;
131
135
  clone._occursCheckHandler = this._occursCheckHandler;
132
136
  clone._localVariables = this._localVariables;
@@ -148,12 +152,21 @@ export class Env {
148
152
  this._variableAnnotations = other._variableAnnotations;
149
153
  this._prologAttributes = other._prologAttributes;
150
154
  this._pendingAttributeGoals = other._pendingAttributeGoals;
155
+ this._backtrackableBlackboard = other._backtrackableBlackboard;
151
156
  this._clpz = other._clpz;
152
157
  // Execution callbacks belong to the Solver driving this Env, not to the
153
158
  // logical branch being adopted from an inner attribute-hook call.
154
159
  this._localVariables = other._localVariables;
155
160
  return this;
156
161
  }
162
+ getBacktrackableBlackboard(key) {
163
+ return this._backtrackableBlackboard?.get(key);
164
+ }
165
+ putBacktrackableBlackboard(key, value) {
166
+ const next = new Map(this._backtrackableBlackboard ?? []);
167
+ next.set(key, value);
168
+ this._backtrackableBlackboard = next;
169
+ }
157
170
  hasLocalVariables() {
158
171
  return this._localVariables != null && this._localVariables.size !== 0;
159
172
  }
@@ -229,6 +229,77 @@ check :- old(X), X = ok.
229
229
  'a user hook defined by a library module executes in its lexical module');
230
230
  },
231
231
  },
232
+ {
233
+ name: 'Scryer CLP(Z) support libraries provide the dependency surface',
234
+ run: () => {
235
+ const source = `:- module(scryer_support, [snapshot/6, blackboard_rollback/1, copy_drops_attrs/0]).
236
+ :- use_module(library(assoc)).
237
+ :- use_module(library(pairs)).
238
+ :- use_module(library(between)).
239
+ :- use_module(library(dcgs)).
240
+ :- use_module(library(terms)).
241
+ :- use_module(library(error)).
242
+ :- use_module(library(si)).
243
+ :- use_module(library(freeze)).
244
+ :- use_module(library(arithmetic)).
245
+ :- use_module(library(debug)).
246
+ :- use_module(library(format)).
247
+ :- use_module(library(atts)).
248
+ snapshot(Pairs, Keys, Values, Ns, Bits, Wake) :-
249
+ empty_assoc(A0),
250
+ put_assoc(2, A0, b, A1),
251
+ put_assoc(1, A1, a, A2),
252
+ assoc_to_list(A2, Pairs),
253
+ pairs_keys_values(Pairs, Keys, Values),
254
+ numlist(1, 3, Ns),
255
+ list_si(Ns),
256
+ arithmetic:popcount(13, Bits),
257
+ freeze(X, Wake = awake),
258
+ X = go.
259
+ blackboard_rollback(Value) :-
260
+ bb_b_put(k, base),
261
+ ( bb_b_put(k, temporary), fail ; bb_get(k, Value) ).
262
+ copy_drops_attrs :-
263
+ put_attr(X, probe, value),
264
+ copy_term_nat(X, Copy),
265
+ term_attributed_variables(Copy, []).
266
+ `;
267
+ const snapshot = run(source, { goal: 'scryer_support:snapshot(Pairs,Keys,Values,Ns,Bits,Wake)' });
268
+ assertIncludes(snapshot.stdout,
269
+ 'scryer_support:snapshot([1 - a, 2 - b], [1, 2], "ab", [1, 2, 3], 3, awake).',
270
+ 'assoc, pairs, between, si, freeze, and arithmetic compatibility');
271
+ assertIncludes(run(source, { goal: 'scryer_support:blackboard_rollback(V)' }).stdout,
272
+ 'scryer_support:blackboard_rollback(base).',
273
+ 'backtrackable blackboard writes roll back with Env branches');
274
+ assertIncludes(run(source, { goal: 'scryer_support:copy_drops_attrs' }).stdout,
275
+ 'scryer_support:copy_drops_attrs.',
276
+ 'copy_term_nat/2 omits attributed-variable state');
277
+ assertEqual(run(source, { goal: 'debug:bb_get(missing,_V)' }).stats.completed_goal_lists, 0,
278
+ 'bb_get/2 is semidet for an absent key');
279
+ const prelude = `:- module(scryer_clpz_prelude, [probe/0]).
280
+ :- use_module(library(assoc)).
281
+ :- use_module(library(pairs)).
282
+ :- use_module(library(between)).
283
+ :- use_module(library(lists)).
284
+ :- use_module(library(atts)).
285
+ :- use_module(library(iso_ext)).
286
+ :- use_module(library(dcgs)).
287
+ :- use_module(library(terms)).
288
+ :- use_module(library(error), [domain_error/3, type_error/3, can_be/2]).
289
+ :- use_module(library(si)).
290
+ :- use_module(library(freeze)).
291
+ :- use_module(library(arithmetic)).
292
+ :- use_module(library(debug)).
293
+ :- use_module(library(format)).
294
+ :- attribute clpz/1, clpz_aux/1, clpz_relation/1, edges/1, flow/1,
295
+ parent/1, free/1, queue/2, disabled/0.
296
+ probe :- empty_assoc(A), copy_term_nat(A, _), bb_b_put(current, ok), bb_get(current, ok).
297
+ `;
298
+ assertIncludes(run(prelude, { goal: 'scryer_clpz_prelude:probe' }).stdout,
299
+ 'scryer_clpz_prelude:probe.',
300
+ 'the dependency and attribute prelude used by upstream clpz.pl loads unchanged');
301
+ },
302
+ },
232
303
  {
233
304
  name: 'write_term variable_names/1 errors preserve the instantiated option culprit (issue #69)',
234
305
  run: () => {
@@ -4829,7 +4900,7 @@ answer(ok) :-
4829
4900
  assertEqual(Boolean(registry.get('is', 2)), true, 'ISO is/2 exists');
4830
4901
  assertEqual(Boolean(registry.get('append', 3)), false, 'append/3 is not ISO core');
4831
4902
  assertEqual(library.eyePrologLibrary, true, 'complete registry marker');
4832
- assertEqual(library.defs.size, 173, 'EyeProlog registry contains ISO definitions, cleanup controls, observability extensions, WFS tnot/1, and private library adapters');
4903
+ assertEqual(library.defs.size, 175, 'EyeProlog registry contains ISO definitions, cleanup controls, observability extensions, WFS tnot/1, and private library adapters');
4833
4904
  assertEqual(Boolean(registry.get('phrase', 2)), true, 'Part 3 phrase/2 exists');
4834
4905
  assertEqual(Boolean(registry.get('phrase', 3)), true, 'Part 3 phrase/3 exists');
4835
4906
  assertEqual(registry.get('statistics', 0), null, 'statistics/0 is absent from the ISO registry');
@@ -4872,6 +4943,8 @@ answer(ok) :-
4872
4943
  assertEqual(registry.get('put_atts', 2), null, 'put_atts/2 is absent from the ISO registry');
4873
4944
  assertEqual(Boolean(library.get('put_atts', 2)), true, 'put_atts/2 is registered for attributed-variable libraries');
4874
4945
  assertEqual(Boolean(library.get('$put_to_attr_list', 3)), true, 'private attribute-list adapter is registered');
4946
+ assertEqual(Boolean(library.get('eyeprolog__bb_get', 2)), true, 'private backtrackable blackboard reader is registered');
4947
+ assertEqual(Boolean(library.get('eyeprolog__bb_b_put', 2)), true, 'private backtrackable blackboard writer is registered');
4875
4948
  assertEqual(library.get('between', 3), null, 'between/3 remains portable Prolog');
4876
4949
  assertEqual(library.get('smallest_divisor_from', 3), null, 'smallest_divisor_from/3 remains portable Prolog');
4877
4950
  assertEqual(library.get('random', 3), null, 'random/3 remains portable Prolog');
@@ -6427,9 +6427,12 @@ is intended to support staged reuse of Markus Triska's MIT-licensed
6427
6427
  The generic compile-time expansion blocker is now closed: normal-profile source
6428
6428
  loading runs module-local and `user` `term_expansion/2` and `goal_expansion/2`
6429
6429
  hooks, accepts clause-list expansions, preserves surrounding variable sharing,
6430
- and provides `expand_term/2` for processor DCG lowering. This is sufficient for
6431
- Scryer's generated-predicate and Duo-DCG expansion strategy. The remaining
6432
- blocker is the supporting Scryer library surface used by that source; the
6430
+ and provides `expand_term/2` for processor DCG lowering. EyeProlog now also
6431
+ registers the Scryer support modules needed by that source (`assoc`, `pairs`,
6432
+ `between`, `dcgs`, `terms`, `error`, `si`, `freeze`, `arithmetic`, `debug`, and
6433
+ `format`) and supplies a generic copy-on-write backtrackable blackboard. The
6434
+ remaining migration step is to vendor and load a pinned upstream `clpz.pl`, then
6435
+ establish behavioral and performance parity before removing the JS kernel. The
6433
6436
  migration state is tracked in [`src/CLPZ-MIGRATION.md`](src/CLPZ-MIGRATION.md).
6434
6437
  Until parity is reached, the JS kernel remains the active implementation so
6435
6438
  existing CLP(Z) behavior does not regress.