eyeling 1.33.8 → 1.33.9
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 +1 -1
- package/docs/eyelang-guide.md +1 -2
- package/docs/eyelang-language-reference.md +1 -1
- package/lib/eyelang/builtins/registry.js +1 -2
- package/package.json +1 -1
- package/examples/eyelang/cryptarithmetic-send-more-money.pl +0 -49
- package/examples/eyelang/output/cryptarithmetic-send-more-money.pl +0 -6
- package/lib/eyelang/builtins/alphametic.js +0 -144
- package/test/eyelang/conformance/cases/extension/045_alphametic_sum_small.pl +0 -3
- package/test/eyelang/conformance/expected/extension/045_alphametic_sum_small.out +0 -4
package/README.md
CHANGED
|
@@ -829,7 +829,7 @@ The eyelang engine has its own built-in registry under `lib/eyelang/builtins/`.
|
|
|
829
829
|
| Aggregation | `findall/3`, `countall/2`, `sumall/3`, `aggregate_min/5`, `aggregate_max/5` |
|
|
830
830
|
| Control | `not/1`, `once/1` |
|
|
831
831
|
| Context terms | `holds/2`, `holds/3` |
|
|
832
|
-
| Search and optimization helpers | `n_queens/2`, `weighted_hamiltonian_cycle/4`, `weighted_hamiltonian_path/4`, `hamiltonian_cycle/3`, `fixed_length_cycle/4`, `bounded_path/5`, `cnf_model/3`, `qm_prime_implicants/4`, `qm_minimal_cover/4
|
|
832
|
+
| Search and optimization helpers | `n_queens/2`, `weighted_hamiltonian_cycle/4`, `weighted_hamiltonian_path/4`, `hamiltonian_cycle/3`, `fixed_length_cycle/4`, `bounded_path/5`, `cnf_model/3`, `qm_prime_implicants/4`, `qm_minimal_cover/4` |
|
|
833
833
|
| Numeric extension helpers | `extended_gcd/5`, `collatz_trajectory/2`, `kaprekar_steps/2`, `goldbach_pair/3` |
|
|
834
834
|
| Matrix helpers | `matrix_sum/2`, `matrix_multiply/2`, `cholesky_decomposition/2`, `determinant/2`, `matrix_inv_triang/2`, `matrix_inversion/2` |
|
|
835
835
|
|
package/docs/eyelang-guide.md
CHANGED
|
@@ -261,7 +261,7 @@ best(Cycle, Cost) :-
|
|
|
261
261
|
weighted_hamiltonian_cycle(edge, Cities, Cycle, Cost).
|
|
262
262
|
```
|
|
263
263
|
|
|
264
|
-
The reusable search and numeric helpers include `n_queens/2`, Hamiltonian path/cycle helpers, `cnf_model/3`, Quine-McCluskey helpers,
|
|
264
|
+
The reusable search and numeric helpers include `n_queens/2`, Hamiltonian path/cycle helpers, `bounded_path/5`, `cnf_model/3`, Quine-McCluskey helpers, number-theory helpers such as `extended_gcd/5`, and matrix helpers such as `matrix_multiply/2`. These helpers are extension builtins of this implementation; [the eyelang language reference](eyelang-language-reference.md) defines the portable core and standard builtin profile.
|
|
265
265
|
|
|
266
266
|
To add a builtin, create or extend a module with `register(registry)` and call `registry.add(name, arity, handler, options)`. The default registry is assembled in [`lib/eyelang/builtins/registry.js`](../lib/eyelang/builtins/registry.js). Builtins that are only safe for specific argument modes should provide a `ready` predicate and `fallbackWhenNotReady: true`, so user-defined clauses remain visible until the builtin is applicable.
|
|
267
267
|
|
|
@@ -336,7 +336,6 @@ The repository includes examples for recursion, graph reachability, finite searc
|
|
|
336
336
|
| [`composition-of-injective-functions-is-injective.pl`](../examples/eyelang/composition-of-injective-functions-is-injective.pl) | Encodes composition and injectivity of finite functions. | [`output/composition-of-injective-functions-is-injective.pl`](../examples/eyelang/output/composition-of-injective-functions-is-injective.pl) |
|
|
337
337
|
| [`context-association.pl`](../examples/eyelang/context-association.pl) | Associates named contexts with their contents. | [`output/context-association.pl`](../examples/eyelang/output/context-association.pl) |
|
|
338
338
|
| [`control-system.pl`](../examples/eyelang/control-system.pl) | Evaluates control-system measurements and targets. | [`output/control-system.pl`](../examples/eyelang/output/control-system.pl) |
|
|
339
|
-
| [`cryptarithmetic-send-more-money.pl`](../examples/eyelang/cryptarithmetic-send-more-money.pl) | Solves SEND+MORE and related puzzles. | [`output/cryptarithmetic-send-more-money.pl`](../examples/eyelang/output/cryptarithmetic-send-more-money.pl) |
|
|
340
339
|
| [`cyclic-path.pl`](../examples/eyelang/cyclic-path.pl) | Computes paths in a cyclic graph. | [`output/cyclic-path.pl`](../examples/eyelang/output/cyclic-path.pl) |
|
|
341
340
|
| [`d3-group.pl`](../examples/eyelang/d3-group.pl) | Enumerates subgroups of the D3 group. | [`output/d3-group.pl`](../examples/eyelang/output/d3-group.pl) |
|
|
342
341
|
| [`dairy-energy-balance.pl`](../examples/eyelang/dairy-energy-balance.pl) | Classifies dairy cow energy balance. | [`output/dairy-energy-balance.pl`](../examples/eyelang/output/dairy-energy-balance.pl) |
|
|
@@ -468,7 +468,7 @@ An extension built-in SHOULD obey the same surface-language discipline as standa
|
|
|
468
468
|
- it SHOULD document its intended modes, especially which arguments must be ground before it runs deterministically;
|
|
469
469
|
- it MUST NOT change the meaning of ordinary facts, rules, unification, or standard built-ins.
|
|
470
470
|
|
|
471
|
-
For example, an implementation may include extension modules for number-theory algorithms, graph search, matrix operations
|
|
471
|
+
For example, an implementation may include extension modules for number-theory algorithms, graph search, or matrix operations. Those modules may be valuable and may make example programs much faster, but their predicate names, arities, algorithms, and modes are implementation-defined unless they are separately standardized.
|
|
472
472
|
|
|
473
473
|
An implementation that provides explanation output SHOULD make extension built-ins explainable at least as opaque successful or failed built-in calls, so that proof traces do not incorrectly report "no clauses" for a host-provided relation.
|
|
474
474
|
|
|
@@ -10,7 +10,6 @@ import { controlBuiltins } from './control.js';
|
|
|
10
10
|
import { searchBuiltins } from './search.js';
|
|
11
11
|
import { numberTheoryBuiltins } from './number-theory.js';
|
|
12
12
|
import { matrixBuiltins } from './matrix.js';
|
|
13
|
-
import { alphameticBuiltins } from './alphametic.js';
|
|
14
13
|
|
|
15
14
|
export class BuiltinRegistry {
|
|
16
15
|
constructor() {
|
|
@@ -37,7 +36,7 @@ export class BuiltinRegistry {
|
|
|
37
36
|
|
|
38
37
|
export function createDefaultRegistry() {
|
|
39
38
|
const registry = new BuiltinRegistry();
|
|
40
|
-
for (const mod of [coreBuiltins, arithmeticBuiltins, stringBuiltins, listBuiltins, aggregationBuiltins, contextBuiltins, controlBuiltins, searchBuiltins, numberTheoryBuiltins, matrixBuiltins
|
|
39
|
+
for (const mod of [coreBuiltins, arithmeticBuiltins, stringBuiltins, listBuiltins, aggregationBuiltins, contextBuiltins, controlBuiltins, searchBuiltins, numberTheoryBuiltins, matrixBuiltins]) {
|
|
41
40
|
mod.register(registry);
|
|
42
41
|
}
|
|
43
42
|
return registry;
|
package/package.json
CHANGED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
% Cryptarithmetic without cut.
|
|
2
|
-
%
|
|
3
|
-
% Two column-pruned puzzles are included. SEND + MORE = MONEY is the classic
|
|
4
|
-
% eight-letter puzzle, and DONALD + GERALD = ROBERT uses all ten digits across
|
|
5
|
-
% a six-column addition. Each column constraint is applied as soon as possible
|
|
6
|
-
% so the finite digit search prunes early without cut.
|
|
7
|
-
|
|
8
|
-
% Output declarations: materialize/2 selects the relations written to this example's golden output.
|
|
9
|
-
materialize(status, 2).
|
|
10
|
-
materialize(assignment, 2).
|
|
11
|
-
materialize(equation, 2).
|
|
12
|
-
|
|
13
|
-
% Program structure: facts set up the scenario, and rules derive the materialized conclusions.
|
|
14
|
-
% Derivation rules: each rule below contributes one logical step toward the displayed results.
|
|
15
|
-
send_more_money(solution(S, E, N, D, M, O, R, Y), Send, More, Money) :-
|
|
16
|
-
alphametic_sum(
|
|
17
|
-
[s, e, n, d, m, o, r, y],
|
|
18
|
-
[[s, e, n, d], [m, o, r, e]],
|
|
19
|
-
[m, o, n, e, y],
|
|
20
|
-
[S, E, N, D, M, O, R, Y],
|
|
21
|
-
[Send, More, Money]
|
|
22
|
-
).
|
|
23
|
-
|
|
24
|
-
donald_gerald_robert(solution(D, O, N, A, L, G, E, R, B, T), Donald, Gerald, Robert) :-
|
|
25
|
-
alphametic_sum(
|
|
26
|
-
[d, o, n, a, l, g, e, r, b, t],
|
|
27
|
-
[[d, o, n, a, l, d], [g, e, r, a, l, d]],
|
|
28
|
-
[r, o, b, e, r, t],
|
|
29
|
-
[D, O, N, A, L, G, E, R, B, T],
|
|
30
|
-
[Donald, Gerald, Robert]
|
|
31
|
-
).
|
|
32
|
-
|
|
33
|
-
status(send_more_money, solved) :-
|
|
34
|
-
once(send_more_money(_Solution, _Send, _More, _Money)).
|
|
35
|
-
|
|
36
|
-
assignment(send_more_money, Solution) :-
|
|
37
|
-
once(send_more_money(Solution, _Send, _More, _Money)).
|
|
38
|
-
|
|
39
|
-
equation(send_more_money, plus(Send, More, Money)) :-
|
|
40
|
-
once(send_more_money(_Solution, Send, More, Money)).
|
|
41
|
-
|
|
42
|
-
status(donald_gerald_robert, solved) :-
|
|
43
|
-
once(donald_gerald_robert(_Solution, _Donald, _Gerald, _Robert)).
|
|
44
|
-
|
|
45
|
-
assignment(donald_gerald_robert, Solution) :-
|
|
46
|
-
once(donald_gerald_robert(Solution, _Donald, _Gerald, _Robert)).
|
|
47
|
-
|
|
48
|
-
equation(donald_gerald_robert, plus(Donald, Gerald, Robert)) :-
|
|
49
|
-
once(donald_gerald_robert(_Solution, Donald, Gerald, Robert)).
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
status(send_more_money, solved).
|
|
2
|
-
status(donald_gerald_robert, solved).
|
|
3
|
-
assignment(send_more_money, solution(9, 5, 6, 7, 1, 0, 8, 2)).
|
|
4
|
-
assignment(donald_gerald_robert, solution(5, 2, 6, 4, 8, 1, 9, 7, 3, 0)).
|
|
5
|
-
equation(send_more_money, plus(9567, 1085, 10652)).
|
|
6
|
-
equation(donald_gerald_robert, plus(526485, 197485, 723970)).
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
// Reusable alphametic addition solver.
|
|
2
|
-
// It evaluates column constraints right-to-left, so examples such as
|
|
3
|
-
// SEND+MORE=MONEY and DONALD+GERALD=ROBERT do not need to express digit search
|
|
4
|
-
// with many relational select/3 and arithmetic goals.
|
|
5
|
-
import { atom, deref, listFromItems, numberTerm, properListItems, unify } from '../term.js';
|
|
6
|
-
|
|
7
|
-
export const alphameticBuiltins = {
|
|
8
|
-
register(registry) {
|
|
9
|
-
registry.add('alphametic_sum', 5, alphameticSum, {
|
|
10
|
-
fallbackWhenNotReady: true,
|
|
11
|
-
ready: alphameticReady,
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
function alphameticReady(goal, env) {
|
|
17
|
-
return atomList(goal.args[0], env) !== null && wordList(goal.args[1], env) !== null && atomList(goal.args[2], env) !== null;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function* alphameticSum({ goal, env }) {
|
|
21
|
-
const letters = atomList(goal.args[0], env);
|
|
22
|
-
const addends = wordList(goal.args[1], env);
|
|
23
|
-
const result = atomList(goal.args[2], env);
|
|
24
|
-
if (!letters || !addends || !result || letters.length > 10 || addends.length === 0) return;
|
|
25
|
-
|
|
26
|
-
const letterSet = new Set(letters);
|
|
27
|
-
for (const word of [...addends, result]) for (const letter of word) if (!letterSet.has(letter)) return;
|
|
28
|
-
|
|
29
|
-
const leading = new Set();
|
|
30
|
-
for (const word of [...addends, result]) if (word.length > 1) leading.add(word[0]);
|
|
31
|
-
const maxLen = Math.max(result.length, ...addends.map((word) => word.length));
|
|
32
|
-
const assignment = new Map();
|
|
33
|
-
const used = Array(10).fill(false);
|
|
34
|
-
|
|
35
|
-
function canUse(letter, digit) {
|
|
36
|
-
return !used[digit] && !(digit === 0 && leading.has(letter));
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function assignDigit(letter, digit) {
|
|
40
|
-
assignment.set(letter, digit);
|
|
41
|
-
used[digit] = true;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function unassignDigit(letter, digit) {
|
|
45
|
-
assignment.delete(letter);
|
|
46
|
-
used[digit] = false;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function* assignAddends(column, carry, index, sum) {
|
|
50
|
-
if (index >= addends.length) {
|
|
51
|
-
yield* finishColumn(column, sum);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
const word = addends[index];
|
|
55
|
-
const letter = word[word.length - 1 - column];
|
|
56
|
-
if (letter === undefined) {
|
|
57
|
-
yield* assignAddends(column, carry, index + 1, sum);
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
const assigned = assignment.get(letter);
|
|
61
|
-
if (assigned !== undefined) {
|
|
62
|
-
yield* assignAddends(column, carry, index + 1, sum + assigned);
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
for (let digit = 0; digit <= 9; digit++) {
|
|
66
|
-
if (!canUse(letter, digit)) continue;
|
|
67
|
-
assignDigit(letter, digit);
|
|
68
|
-
yield* assignAddends(column, carry, index + 1, sum + digit);
|
|
69
|
-
unassignDigit(letter, digit);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function* finishColumn(column, sum) {
|
|
74
|
-
const resultLetter = result[result.length - 1 - column];
|
|
75
|
-
const digit = sum % 10;
|
|
76
|
-
const nextCarry = Math.floor(sum / 10);
|
|
77
|
-
if (resultLetter === undefined) {
|
|
78
|
-
if (digit === 0) yield* solveColumn(column + 1, nextCarry);
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
const assigned = assignment.get(resultLetter);
|
|
82
|
-
if (assigned !== undefined) {
|
|
83
|
-
if (assigned === digit) yield* solveColumn(column + 1, nextCarry);
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
if (!canUse(resultLetter, digit)) return;
|
|
87
|
-
assignDigit(resultLetter, digit);
|
|
88
|
-
yield* solveColumn(column + 1, nextCarry);
|
|
89
|
-
unassignDigit(resultLetter, digit);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function* solveColumn(column, carry) {
|
|
93
|
-
if (column >= maxLen) {
|
|
94
|
-
if (carry !== 0) return;
|
|
95
|
-
if (assignment.size !== letters.length) return;
|
|
96
|
-
yield new Map(assignment);
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
yield* assignAddends(column, carry, 0, carry);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
for (const solution of solveColumn(0, 0)) {
|
|
103
|
-
const digitTerms = letters.map((letter) => numberTerm(String(solution.get(letter))));
|
|
104
|
-
const values = [...addends, result].map((word) => wordValue(word, solution));
|
|
105
|
-
const valueTerms = values.map((value) => numberTerm(String(value)));
|
|
106
|
-
const next = env.clone();
|
|
107
|
-
if (unify(goal.args[3], listFromItems(digitTerms), next) && unify(goal.args[4], listFromItems(valueTerms), next)) yield next;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function wordValue(word, assignment) {
|
|
112
|
-
let value = 0;
|
|
113
|
-
for (const letter of word) value = value * 10 + assignment.get(letter);
|
|
114
|
-
return value;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function atomList(term, env) {
|
|
118
|
-
const items = properListItems(term, env);
|
|
119
|
-
if (!items) return null;
|
|
120
|
-
const out = [];
|
|
121
|
-
for (const item of items) {
|
|
122
|
-
const text = atomKey(deref(item, env));
|
|
123
|
-
if (text == null) return null;
|
|
124
|
-
out.push(text);
|
|
125
|
-
}
|
|
126
|
-
return out;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function wordList(term, env) {
|
|
130
|
-
const items = properListItems(term, env);
|
|
131
|
-
if (!items) return null;
|
|
132
|
-
const out = [];
|
|
133
|
-
for (const item of items) {
|
|
134
|
-
const word = atomList(item, env);
|
|
135
|
-
if (!word) return null;
|
|
136
|
-
out.push(word);
|
|
137
|
-
}
|
|
138
|
-
return out;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function atomKey(term) {
|
|
142
|
-
if (term.type === 'atom' || term.type === 'string' || term.type === 'number') return term.name;
|
|
143
|
-
return null;
|
|
144
|
-
}
|