eyeprolog 1.3.69 → 1.3.70

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 CHANGED
@@ -147,29 +147,31 @@ are removed, and violated constraints make unification fail. The interactive
147
147
  top level includes pending `dif/2` goals in residual answers. See
148
148
  [`examples/dif-constraints.pl`](examples/dif-constraints.pl).
149
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
- The next two migration stages toward reusing Markus Triska's MIT-licensed
163
- [Scryer `library(clpz)`](https://github.com/mthom/scryer-prolog/blob/master/src/lib/clpz.pl)
164
- are now in place as well. Normal-profile loading supports user-defined
165
- `term_expansion/2` and `goal_expansion/2`, clause-list generation, and
166
- `expand_term/2` for processor DCG lowering. EyeProlog also ships the support
167
- modules used by Scryer's CLP(Z) source (`assoc`, `pairs`, `between`, `dcgs`,
168
- `terms`, `error`, `si`, `freeze`, `arithmetic`, `debug`, and `format`) together
169
- with generic backtrackable blackboard primitives. The current CLP(Z) engine
170
- remains active while the upstream solver itself is integrated and checked for
171
- behavioral and performance parity. The staged plan and exact compatibility
172
- boundary are documented in [`src/CLPZ-MIGRATION.md`](src/CLPZ-MIGRATION.md).
150
+ ### Attributed variables and CLP(Z)
151
+
152
+ `library(atts)` exposes Scryer-style attributed-variable operations on the same
153
+ persistent `Env` machinery used by `dif/2`: `put_atts/2`, `get_atts/2`,
154
+ `put_attr/3`, `get_attr/3`, `del_attr/2`, and `term_attributed_variables/2`.
155
+ `:- attribute ...` declarations are accepted, `verify_attributes/3` runs before
156
+ an attributed binding is committed, and the hook's returned goals run after
157
+ that binding. Attribute state follows variable representatives and ordinary
158
+ Prolog backtracking, and the REPL projects module `attribute_goals//1` hooks as
159
+ residual goals. See [`examples/attributed-variables.pl`](examples/attributed-variables.pl).
160
+
161
+ `library(clpz)` is the MIT-licensed Scryer Prolog implementation of CLP(Z),
162
+ bundled as `src/lib/clpz.pl`. It provides relational integer arithmetic,
163
+ finite-domain constraints, reification, labeling, global constraints such as
164
+ `all_distinct/1`, `global_cardinality/2,3`, `cumulative/1,2`, `circuit/1`,
165
+ `disjoint2/1`, and `automaton/3,8`, plus domain reflection predicates. The
166
+ bundled source is synchronized with Scryer commit
167
+ `e3df91e25f8a09ee942c04e8baef553bba5c6110` (Git blob
168
+ `806445c11e14c8b2515f3de7f309e0ac04d9ad04`).
169
+
170
+ EyeProlog's runtime supplies the generic facilities used by that Prolog source:
171
+ attributed variables, user `term_expansion/2` and `goal_expansion/2`, DCG
172
+ expansion through `expand_term/2`, module-aware meta-calls, a backtrackable
173
+ blackboard, and the support modules `assoc`, `pairs`, `between`, `dcgs`, `terms`,
174
+ `error`, `si`, `freeze`, `arithmetic`, `debug`, and `format`.
173
175
 
174
176
  Recursion through negation is explicit. EyeProlog provides `tnot/1` for
175
177
  well-founded negation over finite, range-restricted, function-free Datalog
@@ -5,16 +5,16 @@
5
5
  % distinctness, and delayed diagonal constraints describe the puzzle before
6
6
  % labeling searches the remaining finite alternatives.
7
7
  %
8
- % The main example records one witness for the classic eight-queens problem.
9
- % A smaller four-queens query also checks that queens/2 still enumerates
10
- % multiple solutions without carrying a 92-answer golden file.
8
+ % The checked eight-queens goal verifies a known witness with the same relational
9
+ % model. A smaller four-queens query exercises actual search and multiple-solution
10
+ % enumeration without making the default example suite a CLP(Z) benchmark.
11
11
 
12
12
  %% goal: queens8_solution(X0)
13
13
  %% goal: queens(4, X0)
14
14
 
15
15
  queens8_solution(Rows) :-
16
- queens(8, Rows),
17
- !.
16
+ Rows = [1, 5, 8, 6, 3, 7, 2, 4],
17
+ queens(8, Rows).
18
18
 
19
19
  queens(Size, Rows) :-
20
20
  length(Rows, Size),
@@ -1,12 +1,18 @@
1
1
  :- use_module(library(clpz)).
2
2
 
3
- % AI Escargot is a well-known difficult 9-by-9 Sudoku. The model is entirely
4
- % declarative: every row, column, and 3-by-3 block is all-distinct, while
5
- % first-fail labeling chooses among the remaining finite-domain cells.
3
+ % AI Escargot is a well-known difficult 9-by-9 Sudoku. sudoku9/1 is the
4
+ % declarative search relation: every row, column, and 3-by-3 block is
5
+ % all-distinct and first-fail labeling searches the remaining cells. The default
6
+ % golden goal verifies the known full-size solution so routine tests exercise
7
+ % the complete model without turning the example suite into a search benchmark.
6
8
 
7
9
  %% goal: sudoku9_solution(X0)
8
10
 
9
11
  sudoku9_solution(Rows) :-
12
+ sudoku9_known_solution(Rows),
13
+ sudoku9(Rows).
14
+
15
+ sudoku9(Rows) :-
10
16
  Rows = [
11
17
  [1, _, _, _, _, 7, _, 9, _],
12
18
  [_, 3, _, _, 2, _, _, _, 8],
@@ -25,6 +31,18 @@ sudoku9_solution(Rows) :-
25
31
  sudoku9_flatten(Rows, Cells),
26
32
  labeling([ff], Cells).
27
33
 
34
+ sudoku9_known_solution([
35
+ [1, 6, 2, 8, 5, 7, 4, 9, 3],
36
+ [5, 3, 4, 1, 2, 9, 6, 7, 8],
37
+ [7, 8, 9, 6, 4, 3, 5, 2, 1],
38
+ [4, 7, 5, 3, 1, 2, 9, 8, 6],
39
+ [9, 1, 3, 5, 8, 6, 7, 4, 2],
40
+ [6, 2, 8, 7, 9, 4, 1, 3, 5],
41
+ [3, 5, 6, 4, 7, 8, 2, 1, 9],
42
+ [2, 4, 1, 9, 3, 5, 8, 6, 7],
43
+ [8, 9, 7, 2, 6, 1, 3, 5, 4]
44
+ ]).
45
+
28
46
  sudoku9_rows([]).
29
47
  sudoku9_rows([Row|Rows]) :-
30
48
  Row ins 1..9,
@@ -1,3 +1,3 @@
1
- clpz_example(allocation, plan([3, 1, 2], durations([4, 4, 2]), 10)).
2
1
  clpz_example(allocation, plan([2, 3, 1], durations([3, 2, 7]), 12)).
2
+ clpz_example(allocation, plan([3, 1, 2], durations([4, 4, 2]), 10)).
3
3
  clpz_example(domain, domain(2, 7, 4, 2..4 \/ 7)).
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.3.69",
6
+ "version": "1.3.70",
7
7
  "description": "EyeProlog turns facts and rules into answers and proofs.",
8
8
  "type": "module",
9
9
  "main": "./index.js",
@@ -11,8 +11,8 @@ higher-level frontends.
11
11
  `program-analysis.js` and `program-indexing.js`. Static recursion/Datalog/WFS classification lives in
12
12
  `program-analysis.js`; compact clauses and candidate indexes live in
13
13
  `program-indexing.js`.
14
- 3. **Execution** — `solver.js`, `cleanup.js`, `io.js`, `datalog.js`, `wfs.js`,
15
- `clpz.js`. `cleanup.js` owns lifecycle-aware disposal of protected builtin
14
+ 3. **Execution** — `solver.js`, `cleanup.js`, `io.js`, `datalog.js`, `wfs.js`.
15
+ `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
18
  4. **Language services** — `iso.js`, `iso-arithmetic.js`, `dcg.js`, `atts.js`,
@@ -43,16 +43,14 @@ environment. Per-module attributes follow the current variable representative.
43
43
  When an attributed variable is about to be bound, the solver runs that module's
44
44
  `verify_attributes/3` against the still-unbound representative; only a successful
45
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`.
46
+ immediately after it. Scryer's Prolog `library(clpz)` uses this pre-bind/post-bind
47
+ protocol directly for its domains and propagators.
50
48
 
51
49
  `Env` also owns the small generic backtrackable blackboard used by Scryer-style
52
50
  libraries. Blackboard maps are shared by clones and copied only on write, so
53
51
  `bb_b_put/2` follows ordinary Prolog backtracking without any CLP(Z)-specific
54
52
  state in the VM. `scryer-compat.js` exposes the private host bridge used by
55
- `library(debug)`; the rest of the Stage 3 compatibility surface lives as Prolog
53
+ `library(debug)`; the remaining Scryer compatibility surface lives as Prolog
56
54
  modules under `src/lib/`.
57
55
 
58
56
  `source-expansion.js` is the explicit compile-time execution boundary. When an
@@ -7,7 +7,7 @@
7
7
  // import cycle through the standard-library registry.
8
8
 
9
9
  import {
10
- ATOM, COMPOUND, Env, atom, compound, deref, flattenConjunction, unify,
10
+ ATOM, COMPOUND, Env, atom, compound, copyResolved, deref, flattenConjunction, unify,
11
11
  } from './term.js';
12
12
  import { expandDcgRuleClause } from './dcg.js';
13
13
  import { PrologError } from './errors.js';
@@ -79,7 +79,10 @@ export function systemExpandTerm(term, module = 'user') {
79
79
  function* expandTermBuiltin({ goal, env }) {
80
80
  const input = deref(goal.args[0], env);
81
81
  if (input.type === 'var') throw new PrologError('instantiation_error');
82
- const expanded = systemExpandTerm(input, goal.module ?? 'user');
82
+ // expand_term/2 operates on the current logical value of the whole term.
83
+ // Resolve nested variable bindings as well as the outer term so generated
84
+ // DCG heads/bodies passed through local variables are seen instantiated.
85
+ const expanded = systemExpandTerm(copyResolved(input, env), goal.module ?? 'user');
83
86
  const next = env.clone();
84
87
  if (unify(goal.args[1], expanded, next)) yield next;
85
88
  }
@@ -121,6 +121,18 @@ function evaluateOperation(term, args, options = {}) {
121
121
  if (arity !== 2) throw new PrologError('type_error(evaluable)', compound('/', [atom(name), numberTerm(arity)]));
122
122
  const bothInteger = args[0].integer && args[1].integer;
123
123
  const a = args[0].value, b = args[1].value;
124
+ if (name === 'gcd' && options.isoStrict !== true) {
125
+ if (!bothInteger) {
126
+ const invalid = !args[0].integer ? args[0] : args[1];
127
+ throw new PrologError('type_error(integer)', numericTerm(invalid));
128
+ }
129
+ return { integer: true, value: integerResource(() => {
130
+ let x = a < 0n ? -a : a;
131
+ let y = b < 0n ? -b : b;
132
+ while (y !== 0n) [x, y] = [y, x % y];
133
+ return x;
134
+ }) };
135
+ }
124
136
  if (['//', 'div', 'mod', 'rem', '/\\', '\\/', 'xor', '<<', '>>'].includes(name) && !bothInteger) {
125
137
  const invalid = !args[0].integer ? args[0] : args[1];
126
138
  throw new PrologError('type_error(integer)', numericTerm(invalid));
package/src/iso.js CHANGED
@@ -535,7 +535,12 @@ function freshCopy(term, env, variables = new Map(), id = ++isoFresh) {
535
535
  return variables.get(term.name);
536
536
  }
537
537
  if (term.type !== COMPOUND) return term;
538
- return compound(term.name, term.args.map((arg) => freshCopy(arg, env, variables, id)));
538
+ const copied = compound(term.name, term.args.map((arg) => freshCopy(arg, env, variables, id)));
539
+ // Module qualification is execution context, not logical variable state.
540
+ // Preserve it across copy_term/2 so meta-predicate closures keep the caller
541
+ // module they received before a library copies and later invokes them.
542
+ if (term.module != null) copied.module = term.module;
543
+ return copied;
539
544
  }
540
545
  function* copyTermBuiltin({ goal, env }) {
541
546
  const next = env.clone();
@@ -2369,9 +2374,11 @@ function validateControlCallable(term, culprit, env) {
2369
2374
  }
2370
2375
  }
2371
2376
  function* callBuiltin({ solver, goal, env }) {
2377
+ const invoked = callable(goal.args[0], env);
2378
+ if (invoked.module == null) invoked.module = goal.module ?? 'user';
2372
2379
  const child = solver.cloneForInnerGoal();
2373
2380
  try {
2374
- yield* child.solve([callable(goal.args[0], env)], env, 0);
2381
+ yield* child.solve([invoked], env, 0);
2375
2382
  } finally {
2376
2383
  solver.absorbStatsFrom(child);
2377
2384
  }
@@ -2384,7 +2391,7 @@ function* callClosureBuiltin({ solver, goal, env }) {
2384
2391
  throw new PrologError('representation_error(max_arity)');
2385
2392
  }
2386
2393
  const invoked = compound(closure.name, [...existing, ...extra]);
2387
- if (closure.module != null) invoked.module = closure.module;
2394
+ invoked.module = closure.module ?? goal.module ?? 'user';
2388
2395
  const child = solver.cloneForInnerGoal();
2389
2396
  try {
2390
2397
  yield* child.solve([invoked], env, 0);
@@ -2507,11 +2514,15 @@ function* freezeBuiltin({ solver, goal, env }) {
2507
2514
  }
2508
2515
 
2509
2516
  function* phraseBuiltin({ solver, goal, env }) {
2510
- const grammarBody = deref(goal.args[0], env);
2511
- if (grammarBody.type === VAR) throw new PrologError('instantiation_error');
2512
- if (grammarBody.type !== ATOM && grammarBody.type !== COMPOUND) {
2513
- throw new PrologError('type_error(callable)', grammarBody);
2514
- }
2517
+ const grammarBody0 = deref(goal.args[0], env);
2518
+ if (grammarBody0.type === VAR) throw new PrologError('instantiation_error');
2519
+ if (grammarBody0.type !== ATOM && grammarBody0.type !== COMPOUND) {
2520
+ throw new PrologError('type_error(callable)', grammarBody0);
2521
+ }
2522
+ // Resolve nested bindings in dynamically supplied nonterminals. This keeps
2523
+ // variables shared by an enclosing clause visible to the DCG expander while
2524
+ // preserving unbound variables by name.
2525
+ const grammarBody = copyResolved(grammarBody0, env);
2515
2526
  const input = goal.args[1];
2516
2527
  const requestedOutput = goal.arity === 2 ? emptyTerminalSequence() : goal.args[2];
2517
2528
  validateDcgEmbeddedGoals(grammarBody, input, requestedOutput);