eyeprolog 1.3.65 → 1.3.67
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 +44 -6
- package/examples/attributed-variables.pl +40 -0
- package/examples/dif-constraints.pl +19 -0
- package/examples/output/attributed-variables.pl +2 -0
- package/examples/output/dif-constraints.pl +2 -0
- package/package.json +1 -1
- package/playground.html +2 -0
- package/src/ARCHITECTURE.md +18 -1
- package/src/CLPZ-MIGRATION.md +61 -0
- package/src/atts.js +224 -0
- package/src/iso.js +45 -6
- package/src/lib/atts.pl +20 -0
- package/src/parser.js +21 -6
- package/src/program.js +14 -1
- package/src/repl.js +31 -0
- package/src/solver.js +83 -2
- package/src/standard-library.js +6 -2
- package/src/term.js +253 -0
- package/test/run-regression.mjs +104 -8
- package/the-art-of-eyeprolog.md +27 -10
package/README.md
CHANGED
|
@@ -130,6 +130,43 @@ local/global variable stack. `phrase/2` likewise supplies its fixed `[]`
|
|
|
130
130
|
remainder directly to the grammar; `phrase/3` retains its delayed final output
|
|
131
131
|
unification for steadfastness.
|
|
132
132
|
|
|
133
|
+
### Delayed disequality with `dif/2`
|
|
134
|
+
|
|
135
|
+
The normal EyeProlog runtime provides `dif/2` as a conforming extension;
|
|
136
|
+
`--iso-strict` remains limited to ISO/IEC 13211-1 and does not expose it.
|
|
137
|
+
Unlike `\=/2`, which only tests whether two terms are non-unifiable *now*,
|
|
138
|
+
`dif/2` can succeed with a residual disequality constraint and reject a later
|
|
139
|
+
binding that would make the terms identical. For example,
|
|
140
|
+
`dif(X, Y), X = Y` fails, while `dif(X-Y, 1-2), X = Y, Y = 1` succeeds once
|
|
141
|
+
the structural constraint becomes provably satisfied.
|
|
142
|
+
|
|
143
|
+
The implementation uses backtracking-safe annotated variables in `Env`. A
|
|
144
|
+
pending constraint is indexed by every currently unbound variable it mentions;
|
|
145
|
+
variable aliasing and later bindings reindex the annotation, entailed constraints
|
|
146
|
+
are removed, and violated constraints make unification fail. The interactive
|
|
147
|
+
top level includes pending `dif/2` goals in residual answers. See
|
|
148
|
+
[`examples/dif-constraints.pl`](examples/dif-constraints.pl).
|
|
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
|
+
This is the first 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
|
+
instead of maintaining a separate constraint solver in `src/clpz.js`. The
|
|
165
|
+
current CLP(Z) implementation remains active while compatibility gaps such as
|
|
166
|
+
Scryer's compile-time term/goal expansion and supporting libraries are closed.
|
|
167
|
+
The staged plan and exact compatibility boundary are documented in
|
|
168
|
+
[`src/CLPZ-MIGRATION.md`](src/CLPZ-MIGRATION.md).
|
|
169
|
+
|
|
133
170
|
Recursion through negation is explicit. EyeProlog provides `tnot/1` for
|
|
134
171
|
well-founded negation over finite, range-restricted, function-free Datalog
|
|
135
172
|
components. Ordinary `\+/1` remains negation-as-failure and is not silently
|
|
@@ -258,13 +295,14 @@ be continued without rebuilding a full clause-resolution frame for every
|
|
|
258
295
|
suffix. Open and remainder-producing uses still retain the ordinary DCG
|
|
259
296
|
solutions and backtracking behavior.
|
|
260
297
|
|
|
261
|
-
EyeProlog also adds
|
|
262
|
-
profile. **
|
|
263
|
-
modules under `src/lib/`; the remaining control predicates
|
|
264
|
-
`library(clpz)` kernel use
|
|
298
|
+
EyeProlog also adds 135 public library predicate indicators to its 129-entry ISO
|
|
299
|
+
profile. **89 are implemented entirely as ordinary Prolog clauses** in focused
|
|
300
|
+
modules under `src/lib/`; the remaining control predicates, attributed-variable
|
|
301
|
+
primitives, and the transitional finite-domain `library(clpz)` kernel use
|
|
302
|
+
backtrackable host support.
|
|
265
303
|
They are ordinary Prolog modules using EyeProlog's documented module compatibility surface, loaded explicitly by purpose, such as
|
|
266
|
-
`library(lists)`, `library(lambda)`, `library(strings)`, `library(aggregate)`,
|
|
267
|
-
`library(clpz)`.
|
|
304
|
+
`library(lists)`, `library(lambda)`, `library(strings)`, `library(aggregate)`,
|
|
305
|
+
`library(atts)`, or `library(clpz)`.
|
|
268
306
|
Portable text
|
|
269
307
|
predicates use ISO atoms or character lists. The old catch-all
|
|
270
308
|
`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,19 @@
|
|
|
1
|
+
% Delayed disequality constraints with dif/2.
|
|
2
|
+
%
|
|
3
|
+
% Unlike \=/2, dif/2 records a constraint when its arguments are still
|
|
4
|
+
% unifiable and rechecks that constraint after later bindings.
|
|
5
|
+
|
|
6
|
+
%% goal: allowed_pair(X, Y)
|
|
7
|
+
%% goal: specialization(X, Y)
|
|
8
|
+
|
|
9
|
+
allowed_pair(X, Y) :-
|
|
10
|
+
dif(X, Y),
|
|
11
|
+
X = left,
|
|
12
|
+
Y = right.
|
|
13
|
+
|
|
14
|
+
% The structural constraint remains pending after X=Y. Binding both to 1
|
|
15
|
+
% then proves X-Y and 1-2 different, so the constraint is discharged.
|
|
16
|
+
specialization(X, Y) :-
|
|
17
|
+
dif(X-Y, 1-2),
|
|
18
|
+
X = Y,
|
|
19
|
+
Y = 1.
|
package/package.json
CHANGED
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",
|
|
@@ -495,6 +496,7 @@
|
|
|
495
496
|
"derived-backward-rule",
|
|
496
497
|
"derived-rule",
|
|
497
498
|
"diamond-property",
|
|
499
|
+
"dif-constraints",
|
|
498
500
|
"dijkstra",
|
|
499
501
|
"dijkstra-findall-sort",
|
|
500
502
|
"dijkstra-risk-path",
|
package/src/ARCHITECTURE.md
CHANGED
|
@@ -15,7 +15,7 @@ higher-level frontends.
|
|
|
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.
|
|
@@ -31,6 +31,23 @@ kernel component into the ISO registry or a frontend. In particular,
|
|
|
31
31
|
report processor errors without importing `iso.js` and creating an
|
|
32
32
|
`iso.js <-> dcg.js` cycle.
|
|
33
33
|
|
|
34
|
+
`term.js` also owns the generic annotated-variable storage carried by `Env`.
|
|
35
|
+
Annotations are persistent across `Env.clone()` and therefore backtrack with the
|
|
36
|
+
substitution. Language services may attach immutable constraint descriptors,
|
|
37
|
+
but the unconstrained unification hot path only performs a null check; descriptor
|
|
38
|
+
validation and reindexing run only in environments that actually contain
|
|
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
|
+
|
|
34
51
|
The JavaScript runtime stays flat directly under `src/`; the existing `src/lib/`
|
|
35
52
|
contains Prolog library sources rather than JavaScript runtime modules. The architecture
|
|
36
53
|
test rejects JavaScript import cycles under `src/`. Cleanup lifecycle hooks are
|
|
@@ -0,0 +1,61 @@
|
|
|
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
|
+
## What still prevents a drop-in Scryer `clpz.pl`
|
|
36
|
+
|
|
37
|
+
The upstream solver is not only an attributed-variable client. It also relies on
|
|
38
|
+
compile-time facilities that EyeProlog does not yet provide generically:
|
|
39
|
+
|
|
40
|
+
- user-defined `term_expansion/2` and `goal_expansion/2` during module loading;
|
|
41
|
+
- the generated predicates at the end of Scryer's `clpz.pl` and its custom Duo
|
|
42
|
+
DCG `++>` expansion;
|
|
43
|
+
- the supporting Scryer library surface (`assoc`, `pairs`, `between`, `dcgs`,
|
|
44
|
+
`terms`, `error`, `si`, `freeze`, `arithmetic`, `debug`, and `format`) at the
|
|
45
|
+
interfaces expected by that source;
|
|
46
|
+
|
|
47
|
+
These are compatibility gaps, not reasons to add more CLP(Z)-specific code to
|
|
48
|
+
`term.js` or `solver.js`.
|
|
49
|
+
|
|
50
|
+
## Migration rule
|
|
51
|
+
|
|
52
|
+
`src/clpz.js` remains the active implementation until an upstream-based Prolog
|
|
53
|
+
solver passes the existing EyeProlog CLP(Z) corpus and the selected Scryer CLP(Z)
|
|
54
|
+
regressions. During the transition it is the behavioral reference, not the
|
|
55
|
+
architecture to extend. New generic VM work should go into attributed variables,
|
|
56
|
+
term/goal expansion, or reusable library support where possible.
|
|
57
|
+
|
|
58
|
+
Once the upstream source loads and reaches parity, remove the corresponding
|
|
59
|
+
`eyeprolog__clpz_*` adapters and the `_clpz` store in one or more reviewable
|
|
60
|
+
steps. Keep the upstream copyright and MIT license notice intact and keep local
|
|
61
|
+
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
|
+
};
|
package/src/iso.js
CHANGED
|
@@ -167,6 +167,7 @@ export const eyePrologLibraryBuiltins = {
|
|
|
167
167
|
registry.add('eyeprolog__call_nth', 2, callNthBuiltin, { eyePrologLibrary: true });
|
|
168
168
|
registry.add('eyeprolog__countall', 2, countAllBuiltin, { eyePrologLibrary: true });
|
|
169
169
|
registry.add('eyeprolog__freeze', 2, freezeBuiltin, { eyePrologLibrary: true });
|
|
170
|
+
registry.add('dif', 2, difBuiltin, { deterministic: true, eyePrologLibrary: true });
|
|
170
171
|
registry.add('time', 1, timeBuiltin, { eyePrologLibrary: true });
|
|
171
172
|
registry.add('eyeprolog__time', 1, timeBuiltin, { eyePrologLibrary: true });
|
|
172
173
|
},
|
|
@@ -189,6 +190,43 @@ function* nonUnification({ goal, env }) {
|
|
|
189
190
|
if (!unify(goal.args[0], goal.args[1], env.clone())) yield env;
|
|
190
191
|
}
|
|
191
192
|
|
|
193
|
+
function difConstraint(left, right) {
|
|
194
|
+
return Object.freeze({
|
|
195
|
+
kind: 'dif',
|
|
196
|
+
left,
|
|
197
|
+
right,
|
|
198
|
+
variables(env) {
|
|
199
|
+
return new Set([...termVariableNames(left, env), ...termVariableNames(right, env)]);
|
|
200
|
+
},
|
|
201
|
+
status(env) {
|
|
202
|
+
if (identical(left, right, env)) return 'violated';
|
|
203
|
+
const probe = env.clone();
|
|
204
|
+
return unify(left, right, probe, { skipVariableConstraints: true }) ? 'pending' : 'entailed';
|
|
205
|
+
},
|
|
206
|
+
residualGoal() {
|
|
207
|
+
return compound('dif', [left, right]);
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function* difBuiltin({ goal, env }) {
|
|
213
|
+
const left = goal.args[0];
|
|
214
|
+
const right = goal.args[1];
|
|
215
|
+
if (identical(left, right, env)) return;
|
|
216
|
+
|
|
217
|
+
// In EyeProlog's finite-tree model, terms that cannot unify are already
|
|
218
|
+
// provably different and need no residual constraint. Otherwise attach a
|
|
219
|
+
// disequality constraint to every currently unbound variable it mentions.
|
|
220
|
+
const probe = env.clone();
|
|
221
|
+
if (!unify(left, right, probe, { skipVariableConstraints: true })) {
|
|
222
|
+
yield env;
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const next = env.clone();
|
|
226
|
+
next.addVariableConstraint(difConstraint(left, right));
|
|
227
|
+
yield next;
|
|
228
|
+
}
|
|
229
|
+
|
|
192
230
|
function termVariableNames(term, env, names = new Set(), seen = new Set()) {
|
|
193
231
|
term = deref(term, env);
|
|
194
232
|
if (term.type === VAR) {
|
|
@@ -1646,7 +1684,7 @@ function writeOptionBoolean(value, env, option) {
|
|
|
1646
1684
|
value = deref(value, env);
|
|
1647
1685
|
if (value.type === VAR) throw new PrologError('instantiation_error');
|
|
1648
1686
|
if (value.type !== ATOM || !['true', 'false'].includes(value.name)) {
|
|
1649
|
-
throw new PrologError('domain_error(write_option)', option);
|
|
1687
|
+
throw new PrologError('domain_error(write_option)', copyResolved(option, env));
|
|
1650
1688
|
}
|
|
1651
1689
|
return value.name === 'true';
|
|
1652
1690
|
}
|
|
@@ -1657,19 +1695,20 @@ function writeVariableNames(value, env, option) {
|
|
|
1657
1695
|
const items = properListItems(value, env);
|
|
1658
1696
|
if (items == null) {
|
|
1659
1697
|
if (isPartialList(value, env)) throw new PrologError('instantiation_error');
|
|
1660
|
-
throw new PrologError('domain_error(write_option)', option);
|
|
1698
|
+
throw new PrologError('domain_error(write_option)', copyResolved(option, env));
|
|
1661
1699
|
}
|
|
1662
1700
|
const names = new Map();
|
|
1663
1701
|
for (const item of items) {
|
|
1664
1702
|
const pair = deref(item, env);
|
|
1703
|
+
if (pair.type === VAR) throw new PrologError('instantiation_error');
|
|
1665
1704
|
if (pair.type !== COMPOUND || pair.name !== '=' || pair.arity !== 2) {
|
|
1666
|
-
throw new PrologError('domain_error(write_option)', option);
|
|
1705
|
+
throw new PrologError('domain_error(write_option)', copyResolved(option, env));
|
|
1667
1706
|
}
|
|
1668
1707
|
const name = deref(pair.args[0], env);
|
|
1669
1708
|
const target = deref(pair.args[1], env);
|
|
1670
1709
|
if (name.type === VAR) throw new PrologError('instantiation_error');
|
|
1671
1710
|
if (name.type !== ATOM) {
|
|
1672
|
-
throw new PrologError('domain_error(write_option)', option);
|
|
1711
|
+
throw new PrologError('domain_error(write_option)', copyResolved(option, env));
|
|
1673
1712
|
}
|
|
1674
1713
|
// Corrigendum 3 permits any term on the right. Only variables can name a
|
|
1675
1714
|
// variable being written; retain the leftmost applicable entry.
|
|
@@ -1682,7 +1721,7 @@ function termWriteOptionsFromItems(options, env, mode = 'write_term', solver = n
|
|
|
1682
1721
|
const result = defaultTermWriteOptions(mode);
|
|
1683
1722
|
for (const option of options) {
|
|
1684
1723
|
if (option.type !== COMPOUND || option.arity !== 1) {
|
|
1685
|
-
throw new PrologError('domain_error(write_option)', option);
|
|
1724
|
+
throw new PrologError('domain_error(write_option)', copyResolved(option, env));
|
|
1686
1725
|
}
|
|
1687
1726
|
if (option.name === 'quoted') result.quoted = writeOptionBoolean(option.args[0], env, option);
|
|
1688
1727
|
else if (option.name === 'ignore_ops') result.ignoreOps = writeOptionBoolean(option.args[0], env, option);
|
|
@@ -1691,7 +1730,7 @@ function termWriteOptionsFromItems(options, env, mode = 'write_term', solver = n
|
|
|
1691
1730
|
else if (option.name === 'double_quotes' && !solver?.isoStrict) {
|
|
1692
1731
|
result.doubleQuotes = writeOptionBoolean(option.args[0], env, option);
|
|
1693
1732
|
} else {
|
|
1694
|
-
throw new PrologError('domain_error(write_option)', option);
|
|
1733
|
+
throw new PrologError('domain_error(write_option)', copyResolved(option, env));
|
|
1695
1734
|
}
|
|
1696
1735
|
}
|
|
1697
1736
|
return result;
|
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).
|
package/src/parser.js
CHANGED
|
@@ -372,9 +372,15 @@ class Parser {
|
|
|
372
372
|
if (directive.type !== COMPOUND || directive.name !== 'use_module' || ![1, 2].includes(directive.arity)) return;
|
|
373
373
|
const designation = directive.args[0];
|
|
374
374
|
if (designation?.type !== COMPOUND || designation.name !== 'library' || designation.arity !== 1 ||
|
|
375
|
-
designation.args[0]?.type !== ATOM
|
|
376
|
-
|
|
377
|
-
|
|
375
|
+
designation.args[0]?.type !== ATOM) return;
|
|
376
|
+
if (designation.args[0].name === 'clpz') {
|
|
377
|
+
for (const [priority, specifier, name] of CLPZ_OPERATOR_DEFINITIONS) {
|
|
378
|
+
this.defineOperator(priority, specifier, name);
|
|
379
|
+
}
|
|
380
|
+
} else if (designation.args[0].name === 'atts') {
|
|
381
|
+
// Scryer library(atts) exports this declaration operator. EyeProlog
|
|
382
|
+
// handles the directive directly instead of relying on term expansion.
|
|
383
|
+
this.defineOperator(1199, 'fx', 'attribute');
|
|
378
384
|
}
|
|
379
385
|
}
|
|
380
386
|
operatorTokenName(token = this.token) {
|
|
@@ -1020,13 +1026,22 @@ class Parser {
|
|
|
1020
1026
|
}
|
|
1021
1027
|
if (this.token.type === TOK.IF) {
|
|
1022
1028
|
this.advance();
|
|
1023
|
-
const
|
|
1029
|
+
const attributeSequence = !this.strictIso && this.operatorTokenName() === 'attribute';
|
|
1030
|
+
let directive = this.parseTerm(0, attributeSequence);
|
|
1031
|
+
// Scryer's library(atts) convention permits declarations such as
|
|
1032
|
+
// `:- attribute a/1, b/0.` without parentheses. At program level the
|
|
1033
|
+
// comma would otherwise sit outside the prefix term; fold it back into
|
|
1034
|
+
// the single attribute/1 directive before directive classification.
|
|
1035
|
+
if (attributeSequence && directive.type === COMPOUND && directive.name === ',' && directive.arity === 2 &&
|
|
1036
|
+
directive.args[0].type === COMPOUND && directive.args[0].name === 'attribute' && directive.args[0].arity === 1) {
|
|
1037
|
+
directive = compound('attribute', [compound(',', [directive.args[0].args[0], directive.args[1]])]);
|
|
1038
|
+
}
|
|
1024
1039
|
const coreDirective = directive.type === 'compound' && (
|
|
1025
1040
|
(['dynamic', 'multifile', 'discontiguous', 'initialization', 'include', 'ensure_loaded'].includes(directive.name) && directive.arity === 1) ||
|
|
1026
1041
|
(['char_conversion', 'set_prolog_flag'].includes(directive.name) && directive.arity === 2)
|
|
1027
1042
|
);
|
|
1028
1043
|
const extensionDirective = directive.type === 'compound' && (
|
|
1029
|
-
(['use_module', 'meta_predicate'].includes(directive.name) && directive.arity === 1) ||
|
|
1044
|
+
(['use_module', 'meta_predicate', 'attribute'].includes(directive.name) && directive.arity === 1) ||
|
|
1030
1045
|
(['module', 'use_module'].includes(directive.name) && directive.arity === 2)
|
|
1031
1046
|
);
|
|
1032
1047
|
if (this.strictIso && extensionDirective) {
|
|
@@ -1044,7 +1059,7 @@ class Parser {
|
|
|
1044
1059
|
// Module loading declarations describe the compilation unit rather
|
|
1045
1060
|
// than an executable source clause, so they do not shift proof clause
|
|
1046
1061
|
// numbers in the importing file.
|
|
1047
|
-
if (!['module', 'use_module', 'meta_predicate'].includes(directive.name)) clauseNumber++;
|
|
1062
|
+
if (!['module', 'use_module', 'meta_predicate', 'attribute'].includes(directive.name)) clauseNumber++;
|
|
1048
1063
|
if (this.sourceMetadata) clause.source = { filename: this.filename, line, clause: clauseNumber };
|
|
1049
1064
|
accept(clause);
|
|
1050
1065
|
continue;
|