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/src/program.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Program representation and clause indexing.
|
|
2
2
|
// Indexes are deliberately conservative: they speed up common scalar arguments but never replace unification as the final check.
|
|
3
|
-
import { ATOM, COMPOUND, STRING, VAR, Env, atom, compound, deref, flattenConjunction, isScalar, numberTerm, properListItems, termToString, variable } from './term.js';
|
|
3
|
+
import { ATOM, COMPOUND, NUMBER, STRING, VAR, Env, atom, compound, deref, flattenConjunction, isScalar, numberTerm, properListItems, termToString, variable } from './term.js';
|
|
4
4
|
import { formatTermForWrite } from './write.js';
|
|
5
5
|
import {
|
|
6
6
|
ISO_OPERATOR_DEFINITIONS,
|
|
@@ -1195,6 +1195,19 @@ function moduleExportIndicators(term) {
|
|
|
1195
1195
|
if (items == null) return null;
|
|
1196
1196
|
const indicators = [];
|
|
1197
1197
|
for (const item of items) {
|
|
1198
|
+
// Scryer-style modules may export operators alongside predicate
|
|
1199
|
+
// indicators, for example op(700, xfx, #=) from library(clpz).
|
|
1200
|
+
// Operator visibility while parsing bundled libraries is handled by the
|
|
1201
|
+
// parser's imported-library operator table; module import resolution only
|
|
1202
|
+
// needs procedure indicators here.
|
|
1203
|
+
if (item.type === COMPOUND && item.name === 'op' && item.arity === 3) {
|
|
1204
|
+
const [priority, specifier, names] = item.args;
|
|
1205
|
+
const operatorNames = names.type === ATOM ? [names] : properListItems(names, new Env());
|
|
1206
|
+
if (priority.type !== NUMBER || !/^\d+$/.test(priority.name) || Number(priority.name) > 1200 ||
|
|
1207
|
+
specifier.type !== ATOM || !['fx', 'fy', 'xf', 'yf', 'xfx', 'xfy', 'yfx'].includes(specifier.name) ||
|
|
1208
|
+
operatorNames == null || operatorNames.some((name) => name.type !== ATOM)) return null;
|
|
1209
|
+
continue;
|
|
1210
|
+
}
|
|
1198
1211
|
if (item.type !== COMPOUND || !['/', '//'].includes(item.name) || item.arity !== 2) return null;
|
|
1199
1212
|
const indicator = predicateIndicator(item.args[0], item.args[1]);
|
|
1200
1213
|
if (!indicator) return null;
|
package/src/repl.js
CHANGED
|
@@ -740,6 +740,37 @@ function formatAnswer(engine, state, variables, env) {
|
|
|
740
740
|
doubleQuotes: state.solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
|
|
741
741
|
})}`);
|
|
742
742
|
}
|
|
743
|
+
for (const constraint of env.variableConstraints?.() ?? []) {
|
|
744
|
+
const residual = constraint.residualGoal?.(env);
|
|
745
|
+
if (residual == null) continue;
|
|
746
|
+
collectUnboundVariables(engine, residual, env, names, () => `_${letterName(generated++)}`);
|
|
747
|
+
bindings.push(engine.formatTermForWrite(residual, env, {
|
|
748
|
+
quoted: true,
|
|
749
|
+
operators,
|
|
750
|
+
variableNames: names,
|
|
751
|
+
operatorAtomsAsArgs: true,
|
|
752
|
+
dottedGraphicAtoms: true,
|
|
753
|
+
doubleQuotes: state.solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
|
|
754
|
+
}));
|
|
755
|
+
}
|
|
756
|
+
const projectedAttributeRoots = new Set();
|
|
757
|
+
for (const variable of variables) {
|
|
758
|
+
const root = engine.deref(variable, env);
|
|
759
|
+
if (root.type !== 'var' || projectedAttributeRoots.has(root.name) || !env.hasPrologAttributes?.(root.name)) continue;
|
|
760
|
+
projectedAttributeRoots.add(root.name);
|
|
761
|
+
names.set(root.name, variable.name);
|
|
762
|
+
for (const residual of state.solver.attributeResidualGoals(root, env)) {
|
|
763
|
+
collectUnboundVariables(engine, residual, env, names, () => `_${letterName(generated++)}`);
|
|
764
|
+
bindings.push(engine.formatTermForWrite(residual, env, {
|
|
765
|
+
quoted: true,
|
|
766
|
+
operators,
|
|
767
|
+
variableNames: names,
|
|
768
|
+
operatorAtomsAsArgs: true,
|
|
769
|
+
dottedGraphicAtoms: true,
|
|
770
|
+
doubleQuotes: state.solver.prologFlags.get('double_quotes')?.value?.name ?? 'chars',
|
|
771
|
+
}));
|
|
772
|
+
}
|
|
773
|
+
}
|
|
743
774
|
return bindings.length === 0 ? 'true' : bindings.join(', ');
|
|
744
775
|
}
|
|
745
776
|
|
package/src/solver.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import {
|
|
4
4
|
ATOM, COMPOUND, NUMBER, STRING, VAR, Env, compactListLength, compactVariableList, compound, cons, copyResolved, deref, emptyList,
|
|
5
5
|
flattenConjunction, freshTerm, isCons, isDecimalInteger, isEmptyList,
|
|
6
|
-
numberTerm, numberTextFromDouble, termIsGround, termToString, unify, variable, variantTerms,
|
|
6
|
+
numberTerm, numberTextFromDouble, properListItems, termIsGround, termToString, unify, variable, variantTerms,
|
|
7
7
|
} from './term.js';
|
|
8
8
|
import { numberValueKey, sameNumberValue } from './number-value.js';
|
|
9
9
|
import { PrologError, getStrictIsoRegistry } from './iso.js';
|
|
@@ -32,6 +32,23 @@ function qualifyTerm(term, module) {
|
|
|
32
32
|
return term;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
function renameVariableInTerm(term, fromName, toName) {
|
|
36
|
+
if (fromName === toName) return term;
|
|
37
|
+
const stack = [term];
|
|
38
|
+
const seen = new Set();
|
|
39
|
+
while (stack.length) {
|
|
40
|
+
const current = stack.pop();
|
|
41
|
+
if (current == null || seen.has(current)) continue;
|
|
42
|
+
seen.add(current);
|
|
43
|
+
if (current.type === VAR) {
|
|
44
|
+
if (current.name === fromName) current.name = toName;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (current.type === COMPOUND) for (const arg of current.args) stack.push(arg);
|
|
48
|
+
}
|
|
49
|
+
return term;
|
|
50
|
+
}
|
|
51
|
+
|
|
35
52
|
export function nextFreshId() {
|
|
36
53
|
return ++freshCounter;
|
|
37
54
|
}
|
|
@@ -120,6 +137,8 @@ export class Solver {
|
|
|
120
137
|
raiseOccursCheckError(left, right, env);
|
|
121
138
|
}
|
|
122
139
|
};
|
|
140
|
+
this.attributeHookRunner = (module, attributed, other, env) =>
|
|
141
|
+
this.runAttributeHook(module, attributed, other, env);
|
|
123
142
|
this.charConversions = options.charConversions ?? new Map();
|
|
124
143
|
if (!options.prologFlags) {
|
|
125
144
|
if (['chars', 'codes', 'atom'].includes(program.doubleQuotes)) {
|
|
@@ -303,6 +322,54 @@ export class Solver {
|
|
|
303
322
|
}
|
|
304
323
|
}
|
|
305
324
|
|
|
325
|
+
runAttributeHook(module, attributed, other, env) {
|
|
326
|
+
if (this.program.findGroup('verify_attributes', 3, module) == null) return true;
|
|
327
|
+
const goalsVariable = variable(`\u0000attributeGoals${nextFreshId()}`);
|
|
328
|
+
const hook = compound('verify_attributes', [attributed, other, goalsVariable]);
|
|
329
|
+
qualifyTerm(hook, module);
|
|
330
|
+
const child = this.cloneForInnerGoal(1);
|
|
331
|
+
const iterator = child.solve([hook], env.clone(), 0);
|
|
332
|
+
const result = iterator.next();
|
|
333
|
+
try { iterator.return?.(); } catch (_) { /* best-effort iterator cleanup */ }
|
|
334
|
+
this.absorbStatsFrom(child);
|
|
335
|
+
if (result.done) return false;
|
|
336
|
+
env.adopt(result.value);
|
|
337
|
+
env.setOccursCheckHandler(this.occursCheckHandler);
|
|
338
|
+
env.setAttributeHookRunner(this.attributeHookRunner);
|
|
339
|
+
const goals = properListItems(goalsVariable, env);
|
|
340
|
+
if (goals == null) throw new PrologError('type_error(list)', deref(goalsVariable, env));
|
|
341
|
+
for (const goal of goals) qualifyTerm(goal, module);
|
|
342
|
+
env.enqueueAttributeGoals(goals);
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
attributeResidualGoals(variableTerm, env) {
|
|
347
|
+
const root = deref(variableTerm, env);
|
|
348
|
+
if (root.type !== VAR || !env.hasPrologAttributes(root.name)) return [];
|
|
349
|
+
const residuals = [];
|
|
350
|
+
for (const module of env.prologAttributeModules(root.name)) {
|
|
351
|
+
if (this.program.findGroup('attribute_goals', 3, module) == null) continue;
|
|
352
|
+
const goalsVariable = variable(`\u0000attributeResidual${nextFreshId()}`);
|
|
353
|
+
const projection = compound('attribute_goals', [root, goalsVariable, emptyList()]);
|
|
354
|
+
qualifyTerm(projection, module);
|
|
355
|
+
const child = this.cloneForInnerGoal(1);
|
|
356
|
+
const iterator = child.solve([projection], env.clone(), 0);
|
|
357
|
+
const result = iterator.next();
|
|
358
|
+
try { iterator.return?.(); } catch (_) { /* best-effort iterator cleanup */ }
|
|
359
|
+
this.absorbStatsFrom(child);
|
|
360
|
+
if (result.done) continue;
|
|
361
|
+
const goals = properListItems(goalsVariable, result.value);
|
|
362
|
+
if (goals == null) throw new PrologError('type_error(list)', deref(goalsVariable, result.value));
|
|
363
|
+
const projectedRoot = deref(root, result.value);
|
|
364
|
+
for (const goal of goals) {
|
|
365
|
+
const residual = copyResolved(goal, result.value);
|
|
366
|
+
if (projectedRoot.type === VAR) renameVariableInTerm(residual, projectedRoot.name, root.name);
|
|
367
|
+
residuals.push(residual);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return residuals;
|
|
371
|
+
}
|
|
372
|
+
|
|
306
373
|
runInitializations() {
|
|
307
374
|
if (this.program._initializationsExecuted) return;
|
|
308
375
|
for (const goal of this.program.initializations ?? []) {
|
|
@@ -321,6 +388,7 @@ export class Solver {
|
|
|
321
388
|
*solve(goals, env = new Env(), depth = 0) {
|
|
322
389
|
if (!Array.isArray(goals)) goals = [goals];
|
|
323
390
|
env.setOccursCheckHandler(this.occursCheckHandler);
|
|
391
|
+
env.setAttributeHookRunner(this.attributeHookRunner);
|
|
324
392
|
if (this.isoStrict) rejectStrictIsoStringTerms(goals, env);
|
|
325
393
|
|
|
326
394
|
const writeVariableState = this.writeVariableState;
|
|
@@ -386,6 +454,7 @@ export class Solver {
|
|
|
386
454
|
goals = frame.goals;
|
|
387
455
|
env = frame.env;
|
|
388
456
|
env.setOccursCheckHandler(this.occursCheckHandler);
|
|
457
|
+
env.setAttributeHookRunner(this.attributeHookRunner);
|
|
389
458
|
depth = frame.depth;
|
|
390
459
|
let active = frame.active;
|
|
391
460
|
|
|
@@ -408,6 +477,9 @@ export class Solver {
|
|
|
408
477
|
}
|
|
409
478
|
if (this.solutionsSeen >= this.solutionLimit) break;
|
|
410
479
|
|
|
480
|
+
const pendingAttributeGoals = env.takePendingAttributeGoals?.() ?? [];
|
|
481
|
+
if (pendingAttributeGoals.length > 0) goals = [...pendingAttributeGoals, ...goals];
|
|
482
|
+
|
|
411
483
|
const readyDelays = env.takeReadyDelays();
|
|
412
484
|
if (readyDelays.length > 0) {
|
|
413
485
|
const awakened = readyDelays.map(({ goal, module }) => {
|
|
@@ -1971,7 +2043,7 @@ function fastCountBranch(branch, env) {
|
|
|
1971
2043
|
|
|
1972
2044
|
function fastCountPureGoal(solver, goal, env) {
|
|
1973
2045
|
if (solver.isoStrict || solver.solutionLimit !== Infinity || solver.maxInferences !== Infinity ||
|
|
1974
|
-
env._clpz != null || (env._delays != null && env._delays.size !== 0)) return null;
|
|
2046
|
+
env._clpz != null || env._variableConstraints != null || env._prologAttributes != null || (env._delays != null && env._delays.size !== 0)) return null;
|
|
1975
2047
|
const budget = { remaining: 8192 };
|
|
1976
2048
|
const branches = expandPureCountGoal(solver, goal, goal.module ?? 'user', new Set(), budget);
|
|
1977
2049
|
if (branches == null) return null;
|
|
@@ -1981,6 +2053,7 @@ function fastCountPureGoal(solver, goal, env) {
|
|
|
1981
2053
|
}
|
|
1982
2054
|
|
|
1983
2055
|
function tryPushScalarFactRunFrames(stack, solver, goals, env, depth, active) {
|
|
2056
|
+
if (env._prologAttributes != null) return false;
|
|
1984
2057
|
// Consecutive scalar-fact lookups are common in data-heavy joins. Short
|
|
1985
2058
|
// joins are fastest when their continuation frames are materialized locally;
|
|
1986
2059
|
// wide joins can explode combinatorially, so they are streamed lazily.
|
|
@@ -2015,6 +2088,7 @@ function tryPushScalarFactRunFrames(stack, solver, goals, env, depth, active) {
|
|
|
2015
2088
|
if (state.index === runLength) {
|
|
2016
2089
|
const next = env.clone();
|
|
2017
2090
|
for (let i = 0; i < state.names.length; i++) next.bind(state.names[i], state.values[i]);
|
|
2091
|
+
if (next._variableConstraints != null && !next.validateVariableConstraints()) continue;
|
|
2018
2092
|
frames.push({ kind: 'goals', goals: rest, env: next, depth: state.depth, active });
|
|
2019
2093
|
if (frames.length > frameLimit) {
|
|
2020
2094
|
// Do not repeat the old bug of abandoning the optimization and then
|
|
@@ -2079,6 +2153,7 @@ function* scalarFactRunSolutions(solver, goals, groups, env, depth, active) {
|
|
|
2079
2153
|
if (state.index === goals.length) {
|
|
2080
2154
|
const next = env.clone();
|
|
2081
2155
|
for (let i = 0; i < state.names.length; i++) next.bind(state.names[i], state.values[i]);
|
|
2156
|
+
if (next._variableConstraints != null && !next.validateVariableConstraints()) continue;
|
|
2082
2157
|
yield next;
|
|
2083
2158
|
continue;
|
|
2084
2159
|
}
|
|
@@ -2162,6 +2237,11 @@ function matchScalarFactLocal(goal, head, env, names, values) {
|
|
|
2162
2237
|
}
|
|
2163
2238
|
|
|
2164
2239
|
function matchScalarFact(goal, head, env) {
|
|
2240
|
+
if (env._prologAttributes != null) {
|
|
2241
|
+
const next = env.clone();
|
|
2242
|
+
if (!unify(goal, head, next)) return null;
|
|
2243
|
+
return next;
|
|
2244
|
+
}
|
|
2165
2245
|
// A scalar ground fact has no variables to freshen and no compound structure
|
|
2166
2246
|
// to traverse. Match the goal arguments directly and clone only after the
|
|
2167
2247
|
// candidate has succeeded.
|
|
@@ -2183,6 +2263,7 @@ function matchScalarFact(goal, head, env) {
|
|
|
2183
2263
|
|
|
2184
2264
|
const next = env.clone();
|
|
2185
2265
|
for (let i = 0; i < names.length; i++) next.bind(names[i], values[i]);
|
|
2266
|
+
if (next._variableConstraints != null && !next.validateVariableConstraints()) return null;
|
|
2186
2267
|
return next;
|
|
2187
2268
|
}
|
|
2188
2269
|
|
package/src/standard-library.js
CHANGED
|
@@ -4,12 +4,14 @@
|
|
|
4
4
|
// source-level interop autoloader declared below.
|
|
5
5
|
import { PrologError, createDefaultRegistry, eyePrologLibraryBuiltins } from './iso.js';
|
|
6
6
|
import { clpzBuiltins } from './clpz.js';
|
|
7
|
+
import { attsBuiltins } from './atts.js';
|
|
7
8
|
import { registerCleanupBuiltins } from './cleanup.js';
|
|
8
9
|
import { fs, isNode, memoryStatistics } from './platform.js';
|
|
9
10
|
import { ATOM, VAR, atom, deref, numberTerm, unify } from './term.js';
|
|
10
11
|
|
|
11
12
|
const moduleFiles = Object.freeze({
|
|
12
13
|
aggregate: 'aggregate.pl',
|
|
14
|
+
atts: 'atts.pl',
|
|
13
15
|
clpz: 'clpz.pl',
|
|
14
16
|
comparison: 'comparison.pl',
|
|
15
17
|
dates: 'dates.pl',
|
|
@@ -48,7 +50,8 @@ function libraryUrl(filename) {
|
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
export const eyePrologNativeLibraryIndicators = Object.freeze([
|
|
51
|
-
'call_nth/2', 'freeze/2', 'countall/2', 'time/1',
|
|
53
|
+
'call_nth/2', 'freeze/2', 'dif/2', 'countall/2', 'time/1',
|
|
54
|
+
'put_atts/2', 'get_atts/2', 'put_attr/3', 'get_attr/3', 'del_attr/2', 'term_attributed_variables/2',
|
|
52
55
|
'#>/2', '#</2', '#>=/2', '#=</2', '#=/2', '#\\=/2', '#\\/1',
|
|
53
56
|
'#<==>/2', '#==>/2', '#<==/2', '#\\//2', '#\\/2', '#/\\/2',
|
|
54
57
|
'in/2', 'ins/2', 'all_different/1', 'all_distinct/1', 'nvalue/2', 'sum/3',
|
|
@@ -137,7 +140,7 @@ export const eyePrologInteropLibraryIndicators = Object.freeze(
|
|
|
137
140
|
// Libraries whose *name* is part of the current interop profile. A program
|
|
138
141
|
// may freely use_module/1 with these common module names; predicates in those
|
|
139
142
|
// modules outside eyePrologInteropLibraryIndicators are still diagnosed when used.
|
|
140
|
-
export const eyePrologInteropLibraryModules = Object.freeze(['lists', 'iso_ext', 'lambda']);
|
|
143
|
+
export const eyePrologInteropLibraryModules = Object.freeze(['lists', 'iso_ext', 'lambda', 'atts']);
|
|
141
144
|
|
|
142
145
|
function* tabledNegationBuiltin({ solver, goal, env }) {
|
|
143
146
|
yield* solver.solveTabledNegation(goal.args[0], env);
|
|
@@ -184,6 +187,7 @@ export function createEyePrologRegistry() {
|
|
|
184
187
|
registry.add('tnot', 1, tabledNegationBuiltin, { deterministic: true });
|
|
185
188
|
registerCleanupBuiltins(registry);
|
|
186
189
|
eyePrologLibraryBuiltins.register(registry);
|
|
190
|
+
attsBuiltins.register(registry);
|
|
187
191
|
clpzBuiltins.register(registry);
|
|
188
192
|
registry.eyePrologLibrary = true;
|
|
189
193
|
return registry;
|
package/src/term.js
CHANGED
|
@@ -97,6 +97,18 @@ export class Env {
|
|
|
97
97
|
cache: null,
|
|
98
98
|
};
|
|
99
99
|
this._delays = null;
|
|
100
|
+
// Backtracking-safe attributed-variable constraints. Constraints are
|
|
101
|
+
// immutable descriptors shared by cloned environments; the Set/Map index
|
|
102
|
+
// is copied only when a branch adds, removes, or reindexes a constraint.
|
|
103
|
+
this._variableConstraints = null;
|
|
104
|
+
this._variableAnnotations = null;
|
|
105
|
+
// Prolog-visible attributed variables. The outer map is indexed by the
|
|
106
|
+
// current unbound representative name; each representative owns module-
|
|
107
|
+
// scoped attribute terms keyed by functor/arity. Maps are copy-on-write so
|
|
108
|
+
// Env.clone() keeps ordinary Prolog backtracking constant-time.
|
|
109
|
+
this._prologAttributes = null;
|
|
110
|
+
this._attributeHookRunner = null;
|
|
111
|
+
this._pendingAttributeGoals = null;
|
|
100
112
|
this._clpz = null;
|
|
101
113
|
this._occursCheckHandler = null;
|
|
102
114
|
this._localVariables = null;
|
|
@@ -110,6 +122,11 @@ export class Env {
|
|
|
110
122
|
const clone = Object.create(Env.prototype);
|
|
111
123
|
clone._state = this._state;
|
|
112
124
|
clone._delays = this._delays;
|
|
125
|
+
clone._variableConstraints = this._variableConstraints;
|
|
126
|
+
clone._variableAnnotations = this._variableAnnotations;
|
|
127
|
+
clone._prologAttributes = this._prologAttributes;
|
|
128
|
+
clone._attributeHookRunner = this._attributeHookRunner;
|
|
129
|
+
clone._pendingAttributeGoals = this._pendingAttributeGoals;
|
|
113
130
|
clone._clpz = this._clpz;
|
|
114
131
|
clone._occursCheckHandler = this._occursCheckHandler;
|
|
115
132
|
clone._localVariables = this._localVariables;
|
|
@@ -119,6 +136,24 @@ export class Env {
|
|
|
119
136
|
this._occursCheckHandler = typeof handler === 'function' ? handler : null;
|
|
120
137
|
return this;
|
|
121
138
|
}
|
|
139
|
+
setAttributeHookRunner(handler) {
|
|
140
|
+
this._attributeHookRunner = typeof handler === 'function' ? handler : null;
|
|
141
|
+
return this;
|
|
142
|
+
}
|
|
143
|
+
adopt(other) {
|
|
144
|
+
if (!(other instanceof Env)) throw new TypeError('Env.adopt expects Env');
|
|
145
|
+
this._state = other._state;
|
|
146
|
+
this._delays = other._delays;
|
|
147
|
+
this._variableConstraints = other._variableConstraints;
|
|
148
|
+
this._variableAnnotations = other._variableAnnotations;
|
|
149
|
+
this._prologAttributes = other._prologAttributes;
|
|
150
|
+
this._pendingAttributeGoals = other._pendingAttributeGoals;
|
|
151
|
+
this._clpz = other._clpz;
|
|
152
|
+
// Execution callbacks belong to the Solver driving this Env, not to the
|
|
153
|
+
// logical branch being adopted from an inner attribute-hook call.
|
|
154
|
+
this._localVariables = other._localVariables;
|
|
155
|
+
return this;
|
|
156
|
+
}
|
|
122
157
|
hasLocalVariables() {
|
|
123
158
|
return this._localVariables != null && this._localVariables.size !== 0;
|
|
124
159
|
}
|
|
@@ -236,6 +271,198 @@ export class Env {
|
|
|
236
271
|
if (ready.length > 0) this._delays = remaining;
|
|
237
272
|
return ready;
|
|
238
273
|
}
|
|
274
|
+
_attributeRootName(name) {
|
|
275
|
+
const root = deref(variable(name), this);
|
|
276
|
+
return root.type === VAR ? root.name : null;
|
|
277
|
+
}
|
|
278
|
+
_attributeModulesForRoot(name) {
|
|
279
|
+
const root = this._attributeRootName(name);
|
|
280
|
+
return root == null ? null : (this._prologAttributes?.get(root) ?? null);
|
|
281
|
+
}
|
|
282
|
+
prologAttributes(name, module = null) {
|
|
283
|
+
const modules = this._attributeModulesForRoot(name);
|
|
284
|
+
if (modules == null) return [];
|
|
285
|
+
if (module != null) return [...(modules.get(module)?.values() ?? [])];
|
|
286
|
+
const result = [];
|
|
287
|
+
for (const [owner, attrs] of modules) {
|
|
288
|
+
for (const attribute of attrs.values()) result.push({ module: owner, attribute });
|
|
289
|
+
}
|
|
290
|
+
return result;
|
|
291
|
+
}
|
|
292
|
+
prologAttributeModules(name) {
|
|
293
|
+
const modules = this._attributeModulesForRoot(name);
|
|
294
|
+
return modules == null ? [] : [...modules.keys()];
|
|
295
|
+
}
|
|
296
|
+
getPrologAttribute(name, module, functor, arity) {
|
|
297
|
+
const modules = this._attributeModulesForRoot(name);
|
|
298
|
+
return modules?.get(module)?.get(`${functor}/${arity}`) ?? null;
|
|
299
|
+
}
|
|
300
|
+
putPrologAttribute(name, module, attribute) {
|
|
301
|
+
const root = this._attributeRootName(name);
|
|
302
|
+
if (root == null) return false;
|
|
303
|
+
const signature = `${attribute.name}/${attribute.arity}`;
|
|
304
|
+
const outer = new Map(this._prologAttributes ?? []);
|
|
305
|
+
const modules = new Map(outer.get(root) ?? []);
|
|
306
|
+
const attrs = new Map(modules.get(module) ?? []);
|
|
307
|
+
attrs.set(signature, attribute);
|
|
308
|
+
modules.set(module, attrs);
|
|
309
|
+
outer.set(root, modules);
|
|
310
|
+
this._prologAttributes = outer;
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
deletePrologAttribute(name, module, functor, arity = null) {
|
|
314
|
+
const root = this._attributeRootName(name);
|
|
315
|
+
if (root == null) return false;
|
|
316
|
+
const existingModules = this._prologAttributes?.get(root);
|
|
317
|
+
const existingAttrs = existingModules?.get(module);
|
|
318
|
+
if (existingAttrs == null) return false;
|
|
319
|
+
const attrs = new Map(existingAttrs);
|
|
320
|
+
let deleted = false;
|
|
321
|
+
if (arity == null) {
|
|
322
|
+
for (const key of [...attrs.keys()]) {
|
|
323
|
+
if (key.startsWith(`${functor}/`)) { attrs.delete(key); deleted = true; }
|
|
324
|
+
}
|
|
325
|
+
} else {
|
|
326
|
+
deleted = attrs.delete(`${functor}/${arity}`);
|
|
327
|
+
}
|
|
328
|
+
if (!deleted) return false;
|
|
329
|
+
const modules = new Map(existingModules);
|
|
330
|
+
if (attrs.size === 0) modules.delete(module); else modules.set(module, attrs);
|
|
331
|
+
const outer = new Map(this._prologAttributes);
|
|
332
|
+
if (modules.size === 0) outer.delete(root); else outer.set(root, modules);
|
|
333
|
+
this._prologAttributes = outer.size === 0 ? null : outer;
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
hasPrologAttributes(name) {
|
|
337
|
+
const modules = this._attributeModulesForRoot(name);
|
|
338
|
+
if (modules == null) return false;
|
|
339
|
+
for (const attrs of modules.values()) if (attrs.size !== 0) return true;
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
attributedVariableNames() {
|
|
343
|
+
if (this._prologAttributes == null) return [];
|
|
344
|
+
const names = [];
|
|
345
|
+
const seen = new Set();
|
|
346
|
+
for (const name of this._prologAttributes.keys()) {
|
|
347
|
+
const root = deref(variable(name), this);
|
|
348
|
+
if (root.type !== VAR || seen.has(root.name)) continue;
|
|
349
|
+
if (!this.hasPrologAttributes(root.name)) continue;
|
|
350
|
+
seen.add(root.name);
|
|
351
|
+
names.push(root.name);
|
|
352
|
+
}
|
|
353
|
+
return names;
|
|
354
|
+
}
|
|
355
|
+
enqueueAttributeGoals(goals) {
|
|
356
|
+
if (goals == null || goals.length === 0) return;
|
|
357
|
+
this._pendingAttributeGoals = [...(this._pendingAttributeGoals ?? []), ...goals];
|
|
358
|
+
}
|
|
359
|
+
takePendingAttributeGoals() {
|
|
360
|
+
if (this._pendingAttributeGoals == null || this._pendingAttributeGoals.length === 0) return [];
|
|
361
|
+
const goals = this._pendingAttributeGoals;
|
|
362
|
+
this._pendingAttributeGoals = null;
|
|
363
|
+
return goals;
|
|
364
|
+
}
|
|
365
|
+
_ownerModules(name) {
|
|
366
|
+
const modules = this._attributeModulesForRoot(name);
|
|
367
|
+
return modules == null ? [] : [...modules.keys()];
|
|
368
|
+
}
|
|
369
|
+
_targetHasOwnerModule(name, module) {
|
|
370
|
+
return this._attributeModulesForRoot(name)?.has(module) === true;
|
|
371
|
+
}
|
|
372
|
+
preparePrologAttributeUnification(variableTerm, otherTerm) {
|
|
373
|
+
if (this._prologAttributes == null || variableTerm?.type !== VAR) return true;
|
|
374
|
+
const sourceRoot = deref(variableTerm, this);
|
|
375
|
+
if (sourceRoot.type !== VAR) return true;
|
|
376
|
+
const modules = this._ownerModules(sourceRoot.name);
|
|
377
|
+
if (modules.length === 0) return true;
|
|
378
|
+
const other = deref(otherTerm, this);
|
|
379
|
+
if (other.type === VAR) {
|
|
380
|
+
if (other.name === sourceRoot.name) return true;
|
|
381
|
+
// Aliasing an attributed variable with a plain variable is just a
|
|
382
|
+
// representative change. No user-level hook is needed; final aliasing
|
|
383
|
+
// moves the attributes to the surviving representative.
|
|
384
|
+
const hooks = modules.filter((module) => this._targetHasOwnerModule(other.name, module));
|
|
385
|
+
for (const module of hooks) {
|
|
386
|
+
if (this._attributeHookRunner && !this._attributeHookRunner(module, sourceRoot, other, this)) return false;
|
|
387
|
+
}
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
for (const module of modules) {
|
|
391
|
+
if (this._attributeHookRunner && !this._attributeHookRunner(module, sourceRoot, other, this)) return false;
|
|
392
|
+
}
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
395
|
+
finalizePrologAttributeAlias(sourceName, targetName) {
|
|
396
|
+
if (this._prologAttributes == null || sourceName === targetName) return;
|
|
397
|
+
const source = this._prologAttributes.get(sourceName);
|
|
398
|
+
if (source == null) return;
|
|
399
|
+
const outer = new Map(this._prologAttributes);
|
|
400
|
+
const target = new Map(outer.get(targetName) ?? []);
|
|
401
|
+
for (const [module, sourceAttrs] of source) {
|
|
402
|
+
const attrs = new Map(target.get(module) ?? []);
|
|
403
|
+
for (const [signature, attribute] of sourceAttrs) {
|
|
404
|
+
if (!attrs.has(signature)) attrs.set(signature, attribute);
|
|
405
|
+
}
|
|
406
|
+
target.set(module, attrs);
|
|
407
|
+
}
|
|
408
|
+
outer.delete(sourceName);
|
|
409
|
+
outer.set(targetName, target);
|
|
410
|
+
this._prologAttributes = outer;
|
|
411
|
+
}
|
|
412
|
+
dropPrologAttributes(name) {
|
|
413
|
+
if (this._prologAttributes == null) return;
|
|
414
|
+
const outer = new Map(this._prologAttributes);
|
|
415
|
+
outer.delete(name);
|
|
416
|
+
this._prologAttributes = outer.size === 0 ? null : outer;
|
|
417
|
+
}
|
|
418
|
+
addVariableConstraint(constraint) {
|
|
419
|
+
if (constraint == null || typeof constraint.variables !== 'function' ||
|
|
420
|
+
typeof constraint.status !== 'function') {
|
|
421
|
+
throw new TypeError('variable constraint requires variables(env) and status(env)');
|
|
422
|
+
}
|
|
423
|
+
const constraints = new Set(this._variableConstraints ?? []);
|
|
424
|
+
constraints.add(constraint);
|
|
425
|
+
this._variableConstraints = constraints;
|
|
426
|
+
this._reindexVariableConstraints();
|
|
427
|
+
}
|
|
428
|
+
variableConstraints(kind = null) {
|
|
429
|
+
const constraints = [...(this._variableConstraints ?? [])];
|
|
430
|
+
return kind == null ? constraints : constraints.filter((constraint) => constraint.kind === kind);
|
|
431
|
+
}
|
|
432
|
+
variableAnnotations(name) {
|
|
433
|
+
const root = deref(variable(name), this);
|
|
434
|
+
if (root.type !== VAR) return [];
|
|
435
|
+
return [...(this._variableAnnotations?.get(root.name) ?? [])];
|
|
436
|
+
}
|
|
437
|
+
validateVariableConstraints() {
|
|
438
|
+
if (this._variableConstraints == null || this._variableConstraints.size === 0) return true;
|
|
439
|
+
const pending = new Set();
|
|
440
|
+
for (const constraint of this._variableConstraints) {
|
|
441
|
+
const status = constraint.status(this);
|
|
442
|
+
if (status === 'violated') return false;
|
|
443
|
+
if (status !== 'entailed') pending.add(constraint);
|
|
444
|
+
}
|
|
445
|
+
this._variableConstraints = pending.size === 0 ? null : pending;
|
|
446
|
+
this._reindexVariableConstraints();
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
449
|
+
_reindexVariableConstraints() {
|
|
450
|
+
if (this._variableConstraints == null || this._variableConstraints.size === 0) {
|
|
451
|
+
this._variableAnnotations = null;
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
const annotations = new Map();
|
|
455
|
+
for (const constraint of this._variableConstraints) {
|
|
456
|
+
for (const name of constraint.variables(this)) {
|
|
457
|
+
const root = deref(variable(name), this);
|
|
458
|
+
if (root.type !== VAR) continue;
|
|
459
|
+
const set = annotations.get(root.name) ?? new Set();
|
|
460
|
+
set.add(constraint);
|
|
461
|
+
annotations.set(root.name, set);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
this._variableAnnotations = annotations.size === 0 ? null : annotations;
|
|
465
|
+
}
|
|
239
466
|
}
|
|
240
467
|
|
|
241
468
|
export function deref(term, env) {
|
|
@@ -319,6 +546,15 @@ export function unify(left, right, env, options = {}) {
|
|
|
319
546
|
|
|
320
547
|
if (a.type === VAR && b.type === VAR && a.name === b.name) continue;
|
|
321
548
|
if (a.type === VAR && b.type === VAR) {
|
|
549
|
+
if (env?._prologAttributes != null) {
|
|
550
|
+
if (!env.preparePrologAttributeUnification(a, b)) return false;
|
|
551
|
+
a = deref(a, env);
|
|
552
|
+
b = deref(b, env);
|
|
553
|
+
if (a.type !== VAR || b.type !== VAR || a.name === b.name) {
|
|
554
|
+
stack.push([a, b]);
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
322
558
|
// For a compiler-generated DCG state handed directly to another
|
|
323
559
|
// nonterminal, keep the local caller variable as representative. Ordinary
|
|
324
560
|
// aliases retain the established direction and observable conventions.
|
|
@@ -326,9 +562,11 @@ export function unify(left, right, env, options = {}) {
|
|
|
326
562
|
a.name.startsWith('\u0000dcg') && b.name.startsWith('\u0000dcg');
|
|
327
563
|
if (aLocalDcg) {
|
|
328
564
|
markCompactVariableBound(b);
|
|
565
|
+
env?.finalizePrologAttributeAlias?.(b.name, a.name);
|
|
329
566
|
env.bind(b.name, a);
|
|
330
567
|
} else {
|
|
331
568
|
markCompactVariableBound(a);
|
|
569
|
+
env?.finalizePrologAttributeAlias?.(a.name, b.name);
|
|
332
570
|
env.bind(a.name, b);
|
|
333
571
|
}
|
|
334
572
|
continue;
|
|
@@ -339,7 +577,14 @@ export function unify(left, right, env, options = {}) {
|
|
|
339
577
|
occursCheckHandler?.(a, b, env);
|
|
340
578
|
return false;
|
|
341
579
|
}
|
|
580
|
+
if (env?._prologAttributes != null && env.hasPrologAttributes(a.name)) {
|
|
581
|
+
if (!env.preparePrologAttributeUnification(a, b)) return false;
|
|
582
|
+
a = deref(a, env);
|
|
583
|
+
b = deref(b, env);
|
|
584
|
+
if (a.type !== VAR) { stack.push([a, b]); continue; }
|
|
585
|
+
}
|
|
342
586
|
markCompactVariableBound(a);
|
|
587
|
+
env?.dropPrologAttributes?.(a.name);
|
|
343
588
|
env.bind(a.name, b);
|
|
344
589
|
if (aLocal) env.forgetLocalVariable(a.name);
|
|
345
590
|
continue;
|
|
@@ -350,7 +595,14 @@ export function unify(left, right, env, options = {}) {
|
|
|
350
595
|
occursCheckHandler?.(b, a, env);
|
|
351
596
|
return false;
|
|
352
597
|
}
|
|
598
|
+
if (env?._prologAttributes != null && env.hasPrologAttributes(b.name)) {
|
|
599
|
+
if (!env.preparePrologAttributeUnification(b, a)) return false;
|
|
600
|
+
b = deref(b, env);
|
|
601
|
+
a = deref(a, env);
|
|
602
|
+
if (b.type !== VAR) { stack.push([a, b]); continue; }
|
|
603
|
+
}
|
|
353
604
|
markCompactVariableBound(b);
|
|
605
|
+
env?.dropPrologAttributes?.(b.name);
|
|
354
606
|
env.bind(b.name, a);
|
|
355
607
|
if (bLocal) env.forgetLocalVariable(b.name);
|
|
356
608
|
continue;
|
|
@@ -373,6 +625,7 @@ export function unify(left, right, env, options = {}) {
|
|
|
373
625
|
|
|
374
626
|
return false;
|
|
375
627
|
}
|
|
628
|
+
if (options.skipVariableConstraints !== true && env?._variableConstraints != null && !env.validateVariableConstraints()) return false;
|
|
376
629
|
return true;
|
|
377
630
|
}
|
|
378
631
|
|