eyeprolog 1.3.15 → 1.3.16

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.15",
6
+ "version": "1.3.16",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
package/src/solver.js CHANGED
@@ -513,6 +513,23 @@ export class Solver {
513
513
  break;
514
514
  }
515
515
 
516
+ const betweenIterator = bundledBetweenIterator(this, group, goal, env);
517
+ if (betweenIterator != null) {
518
+ const firstResult = betweenIterator.next();
519
+ if (firstResult.done) break;
520
+ stack.push({
521
+ kind: 'resumeBuiltin',
522
+ iterator: betweenIterator,
523
+ goals: rest,
524
+ depth: depth + 1,
525
+ active,
526
+ });
527
+ goals = rest;
528
+ env = firstResult.value;
529
+ depth++;
530
+ continue;
531
+ }
532
+
516
533
  const memberIterator = bundledMemberIterator(this, group, goal, env);
517
534
  if (memberIterator != null) {
518
535
  const firstResult = memberIterator.next();
@@ -1216,6 +1233,53 @@ function pushWfsAnswerFrames(stack, model, group, goal, rest, env, depth, active
1216
1233
  }
1217
1234
  }
1218
1235
 
1236
+ function bundledBetweenIterator(solver, group, goal, env) {
1237
+ if (solver.registry.eyePrologLibrary !== true ||
1238
+ group.module !== 'prologue' || group.name !== 'between' || group.arity !== 3 ||
1239
+ group.bundledLibrary !== true || group.clauses.length !== 1) {
1240
+ return null;
1241
+ }
1242
+
1243
+ // The portable Prologue definition is intentionally kept as the semantic
1244
+ // source of between/3. Its recursive helper, however, carries the output
1245
+ // variable through one fresh clause instance per integer. With persistent
1246
+ // environments that builds a growing variable-alias chain, so dereferencing
1247
+ // the generated value in the caller repeatedly revisits all earlier frames.
1248
+ // Enumerate the canonical bundled relation directly while leaving user
1249
+ // definitions and non-EyeProlog registries on the ordinary Prolog path.
1250
+ return bundledBetweenSolutions(solver, goal, env);
1251
+ }
1252
+
1253
+ function requireBetweenInteger(term, env) {
1254
+ const value = deref(term, env);
1255
+ if (value.type === VAR) throw new PrologError('instantiation_error');
1256
+ if (value.type !== NUMBER || !isDecimalInteger(value.name)) {
1257
+ throw new PrologError('type_error(integer)', value);
1258
+ }
1259
+ return BigInt(value.name);
1260
+ }
1261
+
1262
+ function* bundledBetweenSolutions(solver, goal, env) {
1263
+ const lower = requireBetweenInteger(goal.args[0], env);
1264
+ const upper = requireBetweenInteger(goal.args[1], env);
1265
+ const requested = deref(goal.args[2], env);
1266
+
1267
+ if (requested.type !== VAR) {
1268
+ if (requested.type !== NUMBER || !isDecimalInteger(requested.name)) {
1269
+ throw new PrologError('type_error(integer)', requested);
1270
+ }
1271
+ const value = BigInt(requested.name);
1272
+ if (value >= lower && value <= upper) yield env;
1273
+ return;
1274
+ }
1275
+
1276
+ for (let value = lower; value <= upper; value++) {
1277
+ const next = env.clone();
1278
+ solver.stats.unify_calls++;
1279
+ if (unify(goal.args[2], numberTerm(value), next)) yield next;
1280
+ }
1281
+ }
1282
+
1219
1283
  function bundledMemberIterator(solver, group, goal, env) {
1220
1284
  if (solver.registry.eyePrologLibrary !== true ||
1221
1285
  !['lists', 'prologue'].includes(group.module) || group.name !== 'member' || group.arity !== 2 ||
@@ -2398,6 +2398,22 @@ child.stdin.write(\`consult(${consultedAtom}).\\n\`);
2398
2398
  assertEqual(result.stderr, '', 'stderr');
2399
2399
  },
2400
2400
  },
2401
+ {
2402
+ name: 'between/3 generated values avoid recursive environment chains (issue #52)',
2403
+ run: () => {
2404
+ const goalText = 'between(1, 1024, X), X < 0';
2405
+ const program = Program.parse('', { autoloadGoals: [goalText] });
2406
+ const solver = new Solver(program, { registry: getEyePrologRegistry() });
2407
+ const goal = parseGoalText(goalText, {
2408
+ operatorDefinitions: [...solver.program.operators.values()],
2409
+ });
2410
+ let answers = 0;
2411
+ for (const _env of solver.solve([goal], new Env(), 0)) answers++;
2412
+ assertEqual(answers, 0, 'positive generated values fail X < 0');
2413
+ assertEqual(solver.stats.unify_calls, 1024, 'one output unification per generated integer');
2414
+ assertEqual(solver.stats.max_depth <= 4, true, 'generation stays at bounded solver depth');
2415
+ },
2416
+ },
2401
2417
  {
2402
2418
  name: 'library(lists) length/2 stays relational and call_nth/2 autoloads (issue #28)',
2403
2419
  run: () => {