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/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
 
@@ -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