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 +29 -6
- package/examples/attributed-variables.pl +40 -0
- package/examples/output/attributed-variables.pl +2 -0
- package/package.json +1 -1
- package/playground.html +1 -0
- package/src/ARCHITECTURE.md +24 -4
- package/src/CLPZ-MIGRATION.md +81 -0
- package/src/atts.js +224 -0
- package/src/expansion-builtins.js +99 -0
- package/src/lib/atts.pl +20 -0
- package/src/parser.js +47 -6
- package/src/program.js +87 -8
- package/src/repl.js +18 -0
- package/src/solver.js +81 -3
- package/src/source-expansion.js +173 -0
- package/src/standard-library.js +7 -1
- package/src/term.js +197 -0
- package/test/run-regression.mjs +110 -7
- package/the-art-of-eyeprolog.md +30 -10
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
|
|
279
|
-
profile. **
|
|
280
|
-
modules under `src/lib/`; the remaining control predicates
|
|
281
|
-
`library(clpz)` kernel use
|
|
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)`,
|
|
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)].
|
package/package.json
CHANGED
package/playground.html
CHANGED
package/src/ARCHITECTURE.md
CHANGED
|
@@ -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
|
|
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
|
+
};
|
package/src/lib/atts.pl
ADDED
|
@@ -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).
|