eyeprolog 1.3.66 → 1.3.68

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
@@ -147,6 +147,28 @@ are removed, and violated constraints make unification fail. The interactive
147
147
  top level includes pending `dif/2` goals in residual answers. See
148
148
  [`examples/dif-constraints.pl`](examples/dif-constraints.pl).
149
149
 
150
+ ### Attributed variables and the CLP(Z) migration
151
+
152
+ `library(atts)` now exposes Scryer-style attributed-variable operations on the
153
+ same persistent `Env` machinery introduced for `dif/2`: `put_atts/2`,
154
+ `get_atts/2`, `put_attr/3`, `get_attr/3`, `del_attr/2`, and
155
+ `term_attributed_variables/2`. `:- attribute ...` declarations are accepted,
156
+ `verify_attributes/3` is invoked before an attributed binding is committed, and
157
+ the hook's returned goals run after that binding. Attribute state follows
158
+ variable representatives and ordinary Prolog backtracking, and the REPL projects
159
+ module `attribute_goals//1` hooks as residual goals. See
160
+ [`examples/attributed-variables.pl`](examples/attributed-variables.pl).
161
+
162
+ The second migration stage toward reusing Markus Triska's MIT-licensed
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
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).
171
+
150
172
  Recursion through negation is explicit. EyeProlog provides `tnot/1` for
151
173
  well-founded negation over finite, range-restricted, function-free Datalog
152
174
  components. Ordinary `\+/1` remains negation-as-failure and is not silently
@@ -275,13 +297,14 @@ be continued without rebuilding a full clause-resolution frame for every
275
297
  suffix. Open and remainder-producing uses still retain the ordinary DCG
276
298
  solutions and backtracking behavior.
277
299
 
278
- EyeProlog also adds 129 public library predicate indicators to its 129-entry ISO
279
- profile. **88 are implemented entirely as ordinary Prolog clauses** in focused
280
- modules under `src/lib/`; the remaining control predicates and finite-domain
281
- `library(clpz)` kernel use backtrackable host support.
300
+ EyeProlog also adds 135 public library predicate indicators to its 129-entry ISO
301
+ profile. **89 are implemented entirely as ordinary Prolog clauses** in focused
302
+ modules under `src/lib/`; the remaining control predicates, attributed-variable
303
+ primitives, and the transitional finite-domain `library(clpz)` kernel use
304
+ backtrackable host support.
282
305
  They are ordinary Prolog modules using EyeProlog's documented module compatibility surface, loaded explicitly by purpose, such as
283
- `library(lists)`, `library(lambda)`, `library(strings)`, `library(aggregate)`, or
284
- `library(clpz)`.
306
+ `library(lists)`, `library(lambda)`, `library(strings)`, `library(aggregate)`,
307
+ `library(atts)`, or `library(clpz)`.
285
308
  Portable text
286
309
  predicates use ISO atoms or character lists. The old catch-all
287
310
  `library(eyeprolog)` module is no longer needed.
@@ -0,0 +1,40 @@
1
+ % Scryer-style attributed variables on EyeProlog's annotated-variable runtime.
2
+ %
3
+ % The attribute declaration is intentionally the same shape used by
4
+ % library(atts) clients such as Scryer's CLP(Z). verify_attributes/3 runs before
5
+ % an attributed variable is bound; goals it returns run after the binding.
6
+
7
+ :- use_module(library(atts)).
8
+ :- attribute required/1.
9
+
10
+ %% goal: accepts_required_value(ok)
11
+ %% goal: alias_preserves_attribute(ok)
12
+
13
+ attach_required(Variable, Value) :-
14
+ put_atts(Variable, required(Value)).
15
+
16
+ verify_attributes(Variable, Other, Goals) :-
17
+ ( get_atts(Variable, required(Value)) ->
18
+ ( var(Other) ->
19
+ put_atts(Other, required(Value)),
20
+ Goals = []
21
+ ; Goals = [required_value(Other, Value)] )
22
+ ; Goals = [] ).
23
+
24
+ required_value(Value, Value).
25
+
26
+ accepts_required_value(ok) :-
27
+ attach_required(X, 7),
28
+ X = 7.
29
+
30
+ alias_preserves_attribute(ok) :-
31
+ attach_required(X, ready),
32
+ X = Y,
33
+ get_atts(Y, required(ready)),
34
+ Y = ready.
35
+
36
+ % The REPL uses the conventional attribute_goals//1 hook to project remaining
37
+ % attributes as residual goals.
38
+ attribute_goals(X) -->
39
+ { get_atts(X, required(Value)) },
40
+ [required(X, Value)].
@@ -0,0 +1,2 @@
1
+ accepts_required_value(ok).
2
+ alias_preserves_attribute(ok).
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.3.66",
6
+ "version": "1.3.68",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
package/playground.html CHANGED
@@ -446,6 +446,7 @@
446
446
  "ancestor",
447
447
  "animal",
448
448
  "annotation",
449
+ "attributed-variables",
449
450
  "auroracare",
450
451
  "backward",
451
452
  "basic-monadic",
@@ -7,15 +7,15 @@ higher-level frontends.
7
7
 
8
8
  1. **Kernel representation and syntax** — `term.js`, `number-value.js`,
9
9
  `syntax-scan.js`, `parser.js`, `write.js`, `errors.js`.
10
- 2. **Program preparation** — `program.js` plus `program-analysis.js` and
11
- `program-indexing.js`. Static recursion/Datalog/WFS classification lives in
10
+ 2. **Program preparation** — `program.js`, `source-expansion.js`, plus
11
+ `program-analysis.js` and `program-indexing.js`. Static recursion/Datalog/WFS classification lives in
12
12
  `program-analysis.js`; compact clauses and candidate indexes live in
13
13
  `program-indexing.js`.
14
14
  3. **Execution** — `solver.js`, `cleanup.js`, `io.js`, `datalog.js`, `wfs.js`,
15
15
  `clpz.js`. `cleanup.js` owns lifecycle-aware disposal of protected builtin
16
16
  iterators and registers the normal-profile cleanup controls without making
17
17
  `solver.js` depend back on the language registry.
18
- 4. **Language services** — `iso.js`, `iso-arithmetic.js`, `dcg.js`,
18
+ 4. **Language services** — `iso.js`, `iso-arithmetic.js`, `dcg.js`, `atts.js`,
19
19
  `standard-library.js`, and `src/lib/`.
20
20
  5. **Frontends/tools** — `execute.js`, `repl.js`, `cli.js`, `quads.js`,
21
21
  `explain.js`, and the playground worker.
@@ -36,7 +36,27 @@ Annotations are persistent across `Env.clone()` and therefore backtrack with the
36
36
  substitution. Language services may attach immutable constraint descriptors,
37
37
  but the unconstrained unification hot path only performs a null check; descriptor
38
38
  validation and reindexing run only in environments that actually contain
39
- annotations. `dif/2` is the first user of this mechanism.
39
+ annotations. `dif/2` is the first descriptor-based user of this mechanism.
40
+
41
+ `atts.js` layers Prolog-visible attributed variables over that same persistent
42
+ environment. Per-module attributes follow the current variable representative.
43
+ When an attributed variable is about to be bound, the solver runs that module's
44
+ `verify_attributes/3` against the still-unbound representative; only a successful
45
+ hook permits the binding, and any goals returned in the third argument are queued
46
+ immediately after it. This pre-bind/post-bind split is the compatibility boundary
47
+ needed by Scryer's `library(atts)` and `library(clpz)`. The current CLP(Z)-specific
48
+ `Env._clpz` store remains transitional until the Scryer Prolog implementation can
49
+ replace it without losing test or performance parity; see `src/CLPZ-MIGRATION.md`.
50
+
51
+ `source-expansion.js` is the explicit compile-time execution boundary. When an
52
+ already-loaded `term_expansion/2` or `goal_expansion/2` hook exists, program
53
+ preparation invokes it with a fresh bounded `Solver` against the partially built
54
+ program. `solver.js` therefore imports clause-selection primitives directly from
55
+ `program-indexing.js` rather than importing the `program.js` facade. This is the
56
+ one deliberate preparation-to-execution service edge and keeps the JavaScript
57
+ import graph acyclic while avoiding a second meta-interpreter solely for source
58
+ expansion. `expansion-builtins.js` provides the lower-level `expand_term/2` DCG
59
+ service without depending on `Solver`.
40
60
 
41
61
  The JavaScript runtime stays flat directly under `src/`; the existing `src/lib/`
42
62
  contains Prolog library sources rather than JavaScript runtime modules. The architecture
@@ -0,0 +1,81 @@
1
+ # CLP(Z) migration toward Scryer's Prolog implementation
2
+
3
+ EyeProlog's current `library(clpz)` has a Prolog API in `src/lib/clpz.pl` and a
4
+ CLP(Z)-specific propagation store in `src/clpz.js`. The long-term target is to
5
+ reuse Markus Triska's MIT-licensed [`clpz.pl` from Scryer
6
+ Prolog](https://github.com/mthom/scryer-prolog/blob/master/src/lib/clpz.pl) and
7
+ keep the JavaScript runtime responsible for generic Prolog VM facilities rather
8
+ than a second finite-domain solver.
9
+
10
+ ## Stage 1: attributed-variable substrate
11
+
12
+ This stage is implemented. `Env` carries persistent per-module Prolog
13
+ attributes in addition to the generic constraint descriptors introduced for
14
+ `dif/2`. `src/atts.js` and `library(atts)` expose the operations required by the
15
+ Scryer style: `put_atts/2`, `get_atts/2`, `put_attr/3`, `get_attr/3`,
16
+ `del_attr/2`, and `term_attributed_variables/2`. The parser accepts the
17
+ `attribute` declaration operator, Scryer-style comma-separated
18
+ `:- attribute a/1, b/0.` directives, and `op/3` entries in module export lists.
19
+
20
+ The important unification protocol is also implemented. Before an attributed
21
+ variable is bound to a nonvariable, or before two variables carrying attributes
22
+ for the same owner module are aliased, the solver invokes the owner's
23
+ `verify_attributes/3`. A failed hook rejects the unification. Goals returned in
24
+ the third argument are queued only after the binding succeeds. Aliasing an
25
+ attributed variable with a plain variable transfers the persistent attribute map
26
+ to the surviving representative, which is equivalent to Scryer's CLP(Z)
27
+ `verify_attributes/3` transfer branch and avoids a recursive hook call while
28
+ entering the hook itself.
29
+
30
+ The substrate is covered by regressions for lookup, alias transfer, conflicting
31
+ attributes, post-binding goals, backtracking, `term_attributed_variables/2`, and
32
+ REPL projection through the conventional module `attribute_goals//1` hook.
33
+ `examples/attributed-variables.pl` is the runnable source-level example.
34
+
35
+ ## Stage 2: source expansion substrate
36
+
37
+ This stage is implemented. Normal-profile source loading now invokes already
38
+ loaded module-local and `user` `term_expansion/2` and `goal_expansion/2` hooks
39
+ left-to-right. A term expansion may yield one clause or a list of clauses, and
40
+ `expand_term/2` exposes the processor's DCG lowering so a hook can generate the
41
+ ordinary clauses used by Scryer's CLP(Z) parser generators. Goal expansion is
42
+ recursive across control constructs and preserves sharing with variables in the
43
+ surrounding clause.
44
+
45
+ Module-qualified hook clauses such as `user:goal_expansion/2` are also compiled
46
+ with the correct split between procedure ownership and lexical body context. A
47
+ hook defined from a library module therefore lives in `user` while its
48
+ unqualified helper calls still resolve in the defining library. The compiler
49
+ executes hooks with a fresh bounded solver against the partially built program;
50
+ `solver.js` imports clause-selection helpers directly from `program-indexing.js`,
51
+ so this compile-time service remains acyclic. Strict ISO Part 1 mode does not run
52
+ these extension hooks.
53
+
54
+ The regression corpus covers clause-list generation, variable-sharing goal
55
+ expansion, qualified hook lexical context, and the Scryer pattern where a
56
+ `term_expansion/2` hook calls `expand_term/2` to generate a DCG predicate.
57
+
58
+ ## What still prevents a drop-in Scryer `clpz.pl`
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.
66
+
67
+ These are reusable library compatibility gaps, not reasons to add more
68
+ CLP(Z)-specific code to `term.js` or `solver.js`.
69
+
70
+ ## Migration rule
71
+
72
+ `src/clpz.js` remains the active implementation until an upstream-based Prolog
73
+ solver passes the existing EyeProlog CLP(Z) corpus and the selected Scryer CLP(Z)
74
+ regressions. During the transition it is the behavioral reference, not the
75
+ architecture to extend. New generic VM work should go into attributed variables,
76
+ term/goal expansion, or reusable library support where possible.
77
+
78
+ Once the upstream source loads and reaches parity, remove the corresponding
79
+ `eyeprolog__clpz_*` adapters and the `_clpz` store in one or more reviewable
80
+ steps. Keep the upstream copyright and MIT license notice intact and keep local
81
+ patches small enough that future Scryer updates can be diffed mechanically.
package/src/atts.js ADDED
@@ -0,0 +1,224 @@
1
+ // Generic attributed-variable bridge for Prolog libraries.
2
+ //
3
+ // The storage is owned by Env so attributes backtrack with substitutions. This
4
+ // module exposes the small atts interface used by Scryer libraries. Attribute
5
+ // verification itself is scheduled by term.js and executed by Solver before a
6
+ // binding is committed; goals returned by verify_attributes/3 run immediately
7
+ // after the binding.
8
+
9
+ import {
10
+ ATOM, COMPOUND, VAR, atom, compound, deref, listFromItems,
11
+ properListItems, unify, variable,
12
+ } from './term.js';
13
+ import { PrologError } from './errors.js';
14
+
15
+ function callerModule(goal) {
16
+ return goal.module ?? 'user';
17
+ }
18
+
19
+ function attributeSignature(term) {
20
+ if (term.type === ATOM) return { name: term.name, arity: 0 };
21
+ if (term.type === COMPOUND) return { name: term.name, arity: term.arity };
22
+ return null;
23
+ }
24
+
25
+ function normalizedAttribute(term, env) {
26
+ let resolved = deref(term, env);
27
+ let mode = 'put';
28
+ if (resolved.type === COMPOUND && resolved.arity === 1 && (resolved.name === '+' || resolved.name === '-')) {
29
+ mode = resolved.name === '-' ? 'delete' : 'put';
30
+ resolved = deref(resolved.args[0], env);
31
+ }
32
+ if (resolved.type === VAR) throw new PrologError('instantiation_error');
33
+ const signature = attributeSignature(resolved);
34
+ if (signature == null) throw new PrologError('type_error(callable)', resolved);
35
+ return { mode, attribute: resolved, ...signature };
36
+ }
37
+
38
+ function putOne(next, variableTerm, owner, attributeTerm) {
39
+ const variableValue = deref(variableTerm, next);
40
+ if (variableValue.type !== VAR) throw new PrologError('uninstantiation_error', variableValue);
41
+ const normalized = normalizedAttribute(attributeTerm, next);
42
+ if (normalized.mode === 'delete') {
43
+ next.deletePrologAttribute(variableValue.name, owner, normalized.name, normalized.arity);
44
+ return;
45
+ }
46
+ next.putPrologAttribute(variableValue.name, owner, normalized.attribute);
47
+ }
48
+
49
+ function* putAttsBuiltin({ goal, env }) {
50
+ const next = env.clone();
51
+ const owner = callerModule(goal);
52
+ const variableTerm = deref(goal.args[0], next);
53
+ if (variableTerm.type !== VAR) throw new PrologError('uninstantiation_error', variableTerm);
54
+ const list = properListItems(goal.args[1], next);
55
+ if (list != null) {
56
+ for (const attribute of list) putOne(next, variableTerm, owner, attribute);
57
+ } else {
58
+ putOne(next, variableTerm, owner, goal.args[1]);
59
+ }
60
+ yield next;
61
+ }
62
+
63
+ function* getAttsBuiltin({ goal, env }) {
64
+ const variableTerm = deref(goal.args[0], env);
65
+ if (variableTerm.type !== VAR) throw new PrologError('uninstantiation_error', variableTerm);
66
+ const owner = callerModule(goal);
67
+ const pattern = deref(goal.args[1], env);
68
+ const attributes = env.prologAttributes(variableTerm.name, owner);
69
+ if (pattern.type === VAR) {
70
+ if (attributes.length === 0) return;
71
+ const next = env.clone();
72
+ if (unify(goal.args[1], listFromItems(attributes), next)) yield next;
73
+ return;
74
+ }
75
+
76
+ let mode = 'present';
77
+ let inner = pattern;
78
+ if (pattern.type === COMPOUND && pattern.arity === 1 && (pattern.name === '+' || pattern.name === '-')) {
79
+ mode = pattern.name === '-' ? 'absent' : 'present';
80
+ inner = deref(pattern.args[0], env);
81
+ }
82
+ if (inner.type === VAR) throw new PrologError('instantiation_error');
83
+ const signature = attributeSignature(inner);
84
+ if (signature == null) throw new PrologError('type_error(callable)', inner);
85
+ const stored = env.getPrologAttribute(variableTerm.name, owner, signature.name, signature.arity);
86
+ if (mode === 'absent') {
87
+ if (stored == null) yield env.clone();
88
+ return;
89
+ }
90
+ if (stored == null) return;
91
+ const next = env.clone();
92
+ if (unify(inner, stored, next, { skipVariableConstraints: true })) yield next;
93
+ }
94
+
95
+ function attributeName(term, env) {
96
+ const resolved = deref(term, env);
97
+ if (resolved.type === VAR) throw new PrologError('instantiation_error');
98
+ if (resolved.type !== ATOM) throw new PrologError('type_error(atom)', resolved);
99
+ return resolved.name;
100
+ }
101
+
102
+ function* putAttrBuiltin({ goal, env }) {
103
+ const next = env.clone();
104
+ const owner = callerModule(goal);
105
+ const variableTerm = deref(goal.args[0], next);
106
+ if (variableTerm.type !== VAR) throw new PrologError('uninstantiation_error', variableTerm);
107
+ const name = attributeName(goal.args[1], next);
108
+ next.putPrologAttribute(variableTerm.name, owner, compound(name, [goal.args[2]]));
109
+ yield next;
110
+ }
111
+
112
+ function* getAttrBuiltin({ goal, env }) {
113
+ const variableTerm = deref(goal.args[0], env);
114
+ if (variableTerm.type !== VAR) return;
115
+ const owner = callerModule(goal);
116
+ const name = attributeName(goal.args[1], env);
117
+ const stored = env.getPrologAttribute(variableTerm.name, owner, name, 1);
118
+ if (stored == null) return;
119
+ const next = env.clone();
120
+ if (unify(goal.args[2], stored.args[0], next, { skipVariableConstraints: true })) yield next;
121
+ }
122
+
123
+ function* delAttrBuiltin({ goal, env }) {
124
+ const next = env.clone();
125
+ const variableTerm = deref(goal.args[0], next);
126
+ if (variableTerm.type !== VAR) { yield next; return; }
127
+ const owner = callerModule(goal);
128
+ const name = attributeName(goal.args[1], next);
129
+ next.deletePrologAttribute(variableTerm.name, owner, name, null);
130
+ yield next;
131
+ }
132
+
133
+ function collectTermVariables(term, env) {
134
+ const result = [];
135
+ const seen = new Set();
136
+ const stack = [term];
137
+ while (stack.length) {
138
+ const current = deref(stack.pop(), env);
139
+ if (current.type === VAR) {
140
+ if (!seen.has(current.name)) {
141
+ seen.add(current.name);
142
+ result.push(current);
143
+ }
144
+ continue;
145
+ }
146
+ if (current.type === COMPOUND) {
147
+ for (let i = current.arity - 1; i >= 0; i--) stack.push(current.args[i]);
148
+ }
149
+ }
150
+ return result;
151
+ }
152
+
153
+ function* termAttributedVariablesBuiltin({ goal, env }) {
154
+ const variables = collectTermVariables(goal.args[0], env).filter((term) => env.hasPrologAttributes(term.name));
155
+ const next = env.clone();
156
+ if (unify(goal.args[1], listFromItems(variables), next)) yield next;
157
+ }
158
+
159
+ function* getAttrListBuiltin({ goal, env }) {
160
+ const variableTerm = deref(goal.args[0], env);
161
+ if (variableTerm.type !== VAR) return;
162
+ const entries = env.prologAttributes(variableTerm.name);
163
+ if (entries.length === 0) return;
164
+ const encoded = entries.map(({ module, attribute }) => compound(':', [atom(module), attribute]));
165
+ const next = env.clone();
166
+ if (unify(goal.args[1], listFromItems(encoded), next)) yield next;
167
+ }
168
+
169
+ function* getFromAttrListBuiltin({ goal, env }) {
170
+ const variableTerm = deref(goal.args[0], env);
171
+ if (variableTerm.type !== VAR) return;
172
+ const moduleTerm = deref(goal.args[1], env);
173
+ if (moduleTerm.type === VAR) throw new PrologError('instantiation_error');
174
+ if (moduleTerm.type !== ATOM) throw new PrologError('type_error(atom)', moduleTerm);
175
+ const pattern = deref(goal.args[2], env);
176
+ if (pattern.type === VAR) throw new PrologError('instantiation_error');
177
+ const signature = attributeSignature(pattern);
178
+ if (signature == null) throw new PrologError('type_error(callable)', pattern);
179
+ const stored = env.getPrologAttribute(variableTerm.name, moduleTerm.name, signature.name, signature.arity);
180
+ if (stored == null) return;
181
+ const next = env.clone();
182
+ if (unify(pattern, stored, next, { skipVariableConstraints: true })) yield next;
183
+ }
184
+
185
+ function* putToAttrListBuiltin({ goal, env }) {
186
+ const next = env.clone();
187
+ const variableTerm = deref(goal.args[0], next);
188
+ if (variableTerm.type !== VAR) throw new PrologError('uninstantiation_error', variableTerm);
189
+ const moduleTerm = deref(goal.args[1], next);
190
+ if (moduleTerm.type === VAR) throw new PrologError('instantiation_error');
191
+ if (moduleTerm.type !== ATOM) throw new PrologError('type_error(atom)', moduleTerm);
192
+ const normalized = normalizedAttribute(goal.args[2], next);
193
+ next.putPrologAttribute(variableTerm.name, moduleTerm.name, normalized.attribute);
194
+ yield next;
195
+ }
196
+
197
+ function* delFromAttrListBuiltin({ goal, env }) {
198
+ const next = env.clone();
199
+ const variableTerm = deref(goal.args[0], next);
200
+ if (variableTerm.type !== VAR) { yield next; return; }
201
+ const moduleTerm = deref(goal.args[1], next);
202
+ if (moduleTerm.type === VAR) throw new PrologError('instantiation_error');
203
+ if (moduleTerm.type !== ATOM) throw new PrologError('type_error(atom)', moduleTerm);
204
+ const normalized = normalizedAttribute(goal.args[2], next);
205
+ next.deletePrologAttribute(variableTerm.name, moduleTerm.name, normalized.name, normalized.arity);
206
+ yield next;
207
+ }
208
+
209
+ export const attsBuiltins = {
210
+ register(registry) {
211
+ registry.add('put_atts', 2, putAttsBuiltin, { deterministic: true, eyePrologLibrary: true });
212
+ registry.add('get_atts', 2, getAttsBuiltin, { deterministic: true, eyePrologLibrary: true });
213
+ // Scryer's CLP(Z) sources use these through goal expansion. EyeProlog
214
+ // provides them directly so the source does not depend on goal_expansion/2.
215
+ registry.add('put_attr', 3, putAttrBuiltin, { deterministic: true, eyePrologLibrary: true });
216
+ registry.add('get_attr', 3, getAttrBuiltin, { deterministic: true, eyePrologLibrary: true });
217
+ registry.add('del_attr', 2, delAttrBuiltin, { deterministic: true, eyePrologLibrary: true });
218
+ registry.add('$term_attributed_variables', 2, termAttributedVariablesBuiltin, { deterministic: true, eyePrologLibrary: true });
219
+ registry.add('$get_attr_list', 2, getAttrListBuiltin, { deterministic: true, eyePrologLibrary: true });
220
+ registry.add('$get_from_attr_list', 3, getFromAttrListBuiltin, { deterministic: true, eyePrologLibrary: true });
221
+ registry.add('$put_to_attr_list', 3, putToAttrListBuiltin, { deterministic: true, eyePrologLibrary: true });
222
+ registry.add('$del_from_attr_list', 3, delFromAttrListBuiltin, { deterministic: true, eyePrologLibrary: true });
223
+ },
224
+ };
@@ -0,0 +1,99 @@
1
+ // Generic source-expansion helpers exposed to Prolog code.
2
+ //
3
+ // expand_term/2 provides the system part of source expansion that libraries
4
+ // such as Scryer's CLP(Z) use while generating predicates. User-defined
5
+ // term_expansion/2 and goal_expansion/2 are invoked by program preparation in
6
+ // source-expansion.js; keeping this builtin independent of Solver avoids an
7
+ // import cycle through the standard-library registry.
8
+
9
+ import {
10
+ ATOM, COMPOUND, Env, atom, compound, deref, flattenConjunction, unify,
11
+ } from './term.js';
12
+ import { expandDcgRuleClause } from './dcg.js';
13
+ import { PrologError } from './errors.js';
14
+
15
+ function conjunctionFromGoals(goals) {
16
+ if (goals.length === 0) return atom('true');
17
+ let body = goals[goals.length - 1];
18
+ for (let index = goals.length - 2; index >= 0; index--) {
19
+ body = compound(',', [goals[index], body]);
20
+ }
21
+ return body;
22
+ }
23
+
24
+ export function clauseToSourceTerm(clause) {
25
+ if (clause == null || clause.kind === 'quad') return null;
26
+ if (!Array.isArray(clause.body) || clause.body.length === 0) return clause.head;
27
+ return compound(':-', [clause.head, conjunctionFromGoals(clause.body)]);
28
+ }
29
+
30
+ export function sourceTermToClauses(term, env = new Env()) {
31
+ term = deref(term, env);
32
+ const list = sourceTermList(term, env);
33
+ if (list != null) {
34
+ const clauses = [];
35
+ for (const item of list) clauses.push(...sourceTermToClauses(item, env));
36
+ return clauses;
37
+ }
38
+ if (term.type === COMPOUND && term.name === ':-' && term.arity === 2) {
39
+ const head = deref(term.args[0], env);
40
+ const body = deref(term.args[1], env);
41
+ return [{ head, body: body.type === ATOM && body.name === 'true' ? [] : flattenConjunction(body) }];
42
+ }
43
+ if (term.type === COMPOUND && term.name === ':-' && term.arity === 1) {
44
+ return [{ head: term, body: [] }];
45
+ }
46
+ if (term.type === COMPOUND && term.name === '?-' && term.arity >= 1) {
47
+ return [{ head: term, body: [] }];
48
+ }
49
+ if (term.type !== ATOM && term.type !== COMPOUND) {
50
+ throw new PrologError('type_error(callable)', term);
51
+ }
52
+ return [{ head: term, body: [] }];
53
+ }
54
+
55
+ function sourceTermList(term, env) {
56
+ const items = [];
57
+ const seen = new Set();
58
+ let cursor = deref(term, env);
59
+ while (cursor.type === COMPOUND && cursor.name === '.' && cursor.arity === 2) {
60
+ if (seen.has(cursor)) throw new PrologError('type_error(list)', term);
61
+ seen.add(cursor);
62
+ items.push(deref(cursor.args[0], env));
63
+ cursor = deref(cursor.args[1], env);
64
+ }
65
+ if (cursor.type === ATOM && cursor.name === '[]') return items;
66
+ return null;
67
+ }
68
+
69
+ export function systemExpandTerm(term, module = 'user') {
70
+ const resolved = deref(term, new Env());
71
+ if (resolved.type === COMPOUND && resolved.name === '-->' && resolved.arity === 2) {
72
+ const expanded = expandDcgRuleClause({ head: resolved, body: [] }, module);
73
+ if (expanded == null) return resolved;
74
+ return clauseToSourceTerm(expanded);
75
+ }
76
+ return resolved;
77
+ }
78
+
79
+ function* expandTermBuiltin({ goal, env }) {
80
+ const input = deref(goal.args[0], env);
81
+ if (input.type === 'var') throw new PrologError('instantiation_error');
82
+ const expanded = systemExpandTerm(input, goal.module ?? 'user');
83
+ const next = env.clone();
84
+ if (unify(goal.args[1], expanded, next)) yield next;
85
+ }
86
+
87
+ function* expandGoalBuiltin({ goal, env }) {
88
+ const input = deref(goal.args[0], env);
89
+ if (input.type === 'var') throw new PrologError('instantiation_error');
90
+ const next = env.clone();
91
+ if (unify(goal.args[1], input, next)) yield next;
92
+ }
93
+
94
+ export const expansionBuiltins = {
95
+ register(registry) {
96
+ registry.add('expand_term', 2, expandTermBuiltin, { deterministic: true, eyePrologLibrary: true });
97
+ registry.add('expand_goal', 2, expandGoalBuiltin, { deterministic: true, eyePrologLibrary: true });
98
+ },
99
+ };
@@ -0,0 +1,20 @@
1
+ /* Attributed variables compatibility layer.
2
+
3
+ The public surface follows Scryer Prolog library(atts). EyeProlog keeps the
4
+ actual attribute store and verify_attributes/3 scheduling in the generic
5
+ annotated-variable runtime, so libraries can use put_atts/2 and get_atts/2
6
+ without code-generation term expansion.
7
+ */
8
+
9
+ :- module(atts, [
10
+ put_atts/2,
11
+ get_atts/2,
12
+ put_attr/3,
13
+ get_attr/3,
14
+ del_attr/2,
15
+ term_attributed_variables/2
16
+ ]).
17
+ :- op(1199, fx, attribute).
18
+
19
+ term_attributed_variables(Term, Variables) :-
20
+ '$term_attributed_variables'(Term, Variables).