eyeprolog 1.3.75 → 1.3.76

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/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.3.75",
6
+ "version": "1.3.76",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
package/src/iso.js CHANGED
@@ -202,7 +202,12 @@ function difConstraint(left, right) {
202
202
  status(env) {
203
203
  if (identical(left, right, env)) return 'violated';
204
204
  const probe = env.clone();
205
- return unify(left, right, probe, { skipVariableConstraints: true }) ? 'pending' : 'entailed';
205
+ // Test structural unifiability without waking freeze/2 or other
206
+ // attributed-variable hooks in this disposable environment.
207
+ return unify(left, right, probe, {
208
+ skipVariableConstraints: true,
209
+ skipAttributeHooks: true,
210
+ }) ? 'pending' : 'entailed';
206
211
  },
207
212
  residualGoal() {
208
213
  return compound('dif', [left, right]);
@@ -218,8 +223,12 @@ function* difBuiltin({ goal, env }) {
218
223
  // In EyeProlog's finite-tree model, terms that cannot unify are already
219
224
  // provably different and need no residual constraint. Otherwise attach a
220
225
  // disequality constraint to every currently unbound variable it mentions.
226
+ // This probe must not wake goals attached to those variables.
221
227
  const probe = env.clone();
222
- if (!unify(left, right, probe, { skipVariableConstraints: true })) {
228
+ if (!unify(left, right, probe, {
229
+ skipVariableConstraints: true,
230
+ skipAttributeHooks: true,
231
+ })) {
223
232
  yield env;
224
233
  return;
225
234
  }
package/src/repl.js CHANGED
@@ -722,6 +722,12 @@ function formatAnswer(engine, state, variables, env) {
722
722
  : equality.specifier === 'xfy' ? equality.priority : equality.priority - 1;
723
723
  let generated = 0;
724
724
 
725
+ // Meta-predicate wrappers can make a query variable alias an internal fresh
726
+ // variable. Keep residual constraints tied to the query's visible name.
727
+ for (const variable of variables) {
728
+ const root = engine.deref(variable, env);
729
+ if (root.type === 'var' && !names.has(root.name)) names.set(root.name, variable.name);
730
+ }
725
731
  for (const variable of variables) collectUnboundVariables(engine, variable, env, names, () => `_${letterName(generated++)}`);
726
732
  for (const variable of variables) {
727
733
  const value = engine.deref(variable, env);
package/src/term.js CHANGED
@@ -650,6 +650,7 @@ export function unify(left, right, env, options = {}) {
650
650
  // unification: a variable cannot be bound to a term containing itself.
651
651
  // Bindings are written into the supplied Env.
652
652
  const occursCheckHandler = options.occursCheck === 'fail' ? null : env?._occursCheckHandler;
653
+ const runAttributeHooks = options.skipAttributeHooks !== true;
653
654
  // Callers may provide a proof that selected variables cannot occur in the
654
655
  // term they are about to receive. Source-level first-use analysis and a few
655
656
  // construction fast paths share this internal proof; ordinary unification
@@ -663,7 +664,7 @@ export function unify(left, right, env, options = {}) {
663
664
 
664
665
  if (a.type === VAR && b.type === VAR && a.name === b.name) continue;
665
666
  if (a.type === VAR && b.type === VAR) {
666
- if (env?._prologAttributes != null) {
667
+ if (runAttributeHooks && env?._prologAttributes != null) {
667
668
  if (!env.preparePrologAttributeUnification(a, b)) return false;
668
669
  a = deref(a, env);
669
670
  b = deref(b, env);
@@ -694,7 +695,7 @@ export function unify(left, right, env, options = {}) {
694
695
  occursCheckHandler?.(a, b, env);
695
696
  return false;
696
697
  }
697
- if (env?._prologAttributes != null && env.hasPrologAttributes(a.name)) {
698
+ if (runAttributeHooks && env?._prologAttributes != null && env.hasPrologAttributes(a.name)) {
698
699
  if (!env.preparePrologAttributeUnification(a, b)) return false;
699
700
  a = deref(a, env);
700
701
  b = deref(b, env);
@@ -712,7 +713,7 @@ export function unify(left, right, env, options = {}) {
712
713
  occursCheckHandler?.(b, a, env);
713
714
  return false;
714
715
  }
715
- if (env?._prologAttributes != null && env.hasPrologAttributes(b.name)) {
716
+ if (runAttributeHooks && env?._prologAttributes != null && env.hasPrologAttributes(b.name)) {
716
717
  if (!env.preparePrologAttributeUnification(b, a)) return false;
717
718
  b = deref(b, env);
718
719
  a = deref(a, env);
@@ -127,6 +127,17 @@ function regressionCases() {
127
127
  'scalar-fact fast matching still validates annotated variables');
128
128
  assertEqual(run('p(b).', { goal: 'dif(X,a),p(X)' }).stats.completed_goal_lists, 1,
129
129
  'scalar-fact fast matching can discharge an annotated disequality');
130
+
131
+ const freezeProgram = Program.parse(':- use_module(library(freeze), [freeze/2]).');
132
+ const freezeSolver = new Solver(freezeProgram);
133
+ const freezeDifGoal = parseGoalText('freeze(X,a),dif(X,c)');
134
+ const freezeDifAnswer = freezeSolver.solve([freezeDifGoal], new Env(), 0).next();
135
+ assertEqual(freezeDifAnswer.done, false, 'freeze/2 and dif/2 answer');
136
+ const freezeResiduals = freezeSolver.attributeResidualGoals(
137
+ freezeDifGoal.args[0].args[0], freezeDifAnswer.value,
138
+ );
139
+ assertEqual(freezeResiduals.length, 1,
140
+ 'freeze/2 residual projection terminates alongside dif/2 (issue #73)');
130
141
  },
131
142
  },
132
143
  {
@@ -190,6 +201,7 @@ check :- call_residue_vars(X in 1..3, Vs), Vs = [X].
190
201
 
191
202
  const residualFile = path.join(tmp, `atts-residual-${++tmpCounter}.pl`);
192
203
  fs.writeFileSync(residualFile, `:- use_module(library(atts)).
204
+ :- use_module(library(freeze), [freeze/2]).
193
205
  :- attribute required/1.
194
206
  attach(X,V) :- put_atts(X, required(V)).
195
207
  verify_attributes(Var, Other, Goals) :-
@@ -199,9 +211,13 @@ verify_attributes(Var, Other, Goals) :-
199
211
  same(X,X).
200
212
  attribute_goals(X) --> { get_atts(X, required(V)) }, [required(X,V)].
201
213
  `);
202
- const repl = runCli([], { input: `[${sourceAtom(residualFile)}].\nattach(X,a).\nhalt.\n` });
214
+ const repl = runCli([], {
215
+ input: `[${sourceAtom(residualFile)}].\nattach(X,a).\nfreeze(Y,a),dif(Y,c).\nhalt.\n`,
216
+ });
203
217
  assertEqual(repl.status, 0, 'attribute residual REPL status');
204
218
  assertIncludes(repl.stdout, 'required(X, a).', 'attribute_goals//1 residual projection');
219
+ assertIncludes(repl.stdout, 'dif(Y, c), freeze:freeze(Y, a).',
220
+ 'freeze/2 and dif/2 residuals share the query variable (issue #73)');
205
221
 
206
222
  const scryerShape = Program.parse(`:- module(scryer_shape, [op(700, xfx, #=), probe/0]).
207
223
  :- op(700, xfx, #=).