eyeling 1.23.2 → 1.23.4
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/HANDBOOK.md +18 -6
- package/dist/browser/eyeling.browser.js +19 -479
- package/examples/builtin/queens.js +141 -0
- package/{lib/builtin-sudoku.js → examples/builtin/sudoku.js} +15 -0
- package/examples/output/queens.txt +21 -0
- package/examples/queens.n3 +20 -0
- package/examples/sudoku.n3 +7 -1
- package/eyeling.js +20 -476
- package/lib/engine.js +14 -7
- package/lib/entry.js +5 -0
- package/package.json +1 -1
- package/test/examples.test.js +33 -10
- package/test/playground.test.js +45 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Example-specific builtin module for examples/queens.n3.
|
|
4
|
+
//
|
|
5
|
+
// The N3 file keeps the example interface declarative:
|
|
6
|
+
// (16 0) queens:render ?Text
|
|
7
|
+
// (16 0) queens:count ?Count
|
|
8
|
+
//
|
|
9
|
+
// This JavaScript module supplies the intentionally specialized hot loop. The
|
|
10
|
+
// solver is the same 32-bit bit-mask kernel used in the standalone queens.js
|
|
11
|
+
// benchmark: columns and diagonals are represented as integer masks, so a whole
|
|
12
|
+
// row's legal moves are computed with a few bitwise operations.
|
|
13
|
+
//
|
|
14
|
+
// Why use a builtin instead of pure N3 rules?
|
|
15
|
+
// * A pure N3 generator for 16-Queens would create an enormous search tree.
|
|
16
|
+
// * The builtin keeps the example useful for performance demonstrations.
|
|
17
|
+
// * It also shows the intended pattern for expensive domain-specific kernels:
|
|
18
|
+
// put the tight computation in a custom builtin, then let N3 describe how
|
|
19
|
+
// the result is connected to the rest of the knowledge graph.
|
|
20
|
+
//
|
|
21
|
+
module.exports = ({ registerBuiltin, internLiteral, unifyTerm, applySubstTerm, parseNumericLiteralInfo, terms }) => {
|
|
22
|
+
const { ListTerm } = terms;
|
|
23
|
+
const NS = 'http://example.org/queens#';
|
|
24
|
+
|
|
25
|
+
// Cache by "N/MAX_PRINT" because the same N3 run may ask for both the count
|
|
26
|
+
// and the rendered report. Without this, queens:count and queens:render would
|
|
27
|
+
// solve the same board twice.
|
|
28
|
+
const resultCache = new Map();
|
|
29
|
+
|
|
30
|
+
function integerValue(term) {
|
|
31
|
+
const info = parseNumericLiteralInfo(term);
|
|
32
|
+
if (!info) return null;
|
|
33
|
+
if (info.kind === 'bigint') {
|
|
34
|
+
const n = Number(info.value);
|
|
35
|
+
return Number.isSafeInteger(n) ? n : null;
|
|
36
|
+
}
|
|
37
|
+
if (info.kind === 'number' && Number.isInteger(info.value)) return info.value;
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function solveNQueens(n, maxPrint) {
|
|
42
|
+
if (!Number.isInteger(n) || n <= 0 || n > 31) {
|
|
43
|
+
throw new RangeError('queens:count expects 1 <= N <= 31');
|
|
44
|
+
}
|
|
45
|
+
if (!Number.isInteger(maxPrint) || maxPrint < 0) {
|
|
46
|
+
throw new RangeError('queens:count expects MAX_PRINT >= 0');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// JavaScript bitwise operators work on signed 32-bit integers. N <= 31 is
|
|
50
|
+
// therefore the safe range for this compact benchmark implementation.
|
|
51
|
+
const allColumns = Math.pow(2, n) - 1;
|
|
52
|
+
const board = new Array(n).fill(-1);
|
|
53
|
+
const printed = [];
|
|
54
|
+
let count = 0;
|
|
55
|
+
|
|
56
|
+
function boardText() {
|
|
57
|
+
const lines = [];
|
|
58
|
+
for (let row = 0; row < n; row++) {
|
|
59
|
+
const cells = [];
|
|
60
|
+
for (let col = 0; col < n; col++) cells.push(col === board[row] ? 'Q' : '.');
|
|
61
|
+
lines.push(cells.join(' '));
|
|
62
|
+
}
|
|
63
|
+
lines.push(`As column positions by row: [${board.map((col) => col + 1).join(', ')}]`);
|
|
64
|
+
return lines.join('\n');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function search(row, columns, diagLeft, diagRight) {
|
|
68
|
+
if (row === n) {
|
|
69
|
+
count++;
|
|
70
|
+
if (count <= maxPrint) printed.push(`Solution ${count}:\n${boardText()}`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// All legal columns for this row in one expression.
|
|
75
|
+
let available = allColumns & ~(columns | diagLeft | diagRight);
|
|
76
|
+
while (available !== 0) {
|
|
77
|
+
// Pick and clear the lowest set bit.
|
|
78
|
+
const position = available & -available;
|
|
79
|
+
available ^= position;
|
|
80
|
+
|
|
81
|
+
board[row] = Math.clz32(position) ^ 31;
|
|
82
|
+
search(row + 1, columns | position, (diagLeft | position) << 1, (diagRight | position) >> 1);
|
|
83
|
+
board[row] = -1;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
search(0, 0, 0, 0);
|
|
88
|
+
return { count, printed };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
registerBuiltin(NS + 'count', ({ goal, subst }) => {
|
|
92
|
+
const subject = applySubstTerm(goal.s, subst);
|
|
93
|
+
if (!(subject instanceof ListTerm) || subject.elems.length !== 2) return [];
|
|
94
|
+
|
|
95
|
+
const n = integerValue(applySubstTerm(subject.elems[0], subst));
|
|
96
|
+
const maxPrint = integerValue(applySubstTerm(subject.elems[1], subst));
|
|
97
|
+
if (n == null || maxPrint == null) return [];
|
|
98
|
+
|
|
99
|
+
const key = `${n}/${maxPrint}`;
|
|
100
|
+
let result = resultCache.get(key);
|
|
101
|
+
if (!result) {
|
|
102
|
+
result = solveNQueens(n, maxPrint);
|
|
103
|
+
resultCache.set(key, result);
|
|
104
|
+
}
|
|
105
|
+
const { count } = result;
|
|
106
|
+
const lit = internLiteral(String(count));
|
|
107
|
+
const next = unifyTerm(goal.o, lit, subst);
|
|
108
|
+
return next ? [next] : [];
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
registerBuiltin(NS + 'render', ({ goal, subst }) => {
|
|
112
|
+
const subject = applySubstTerm(goal.s, subst);
|
|
113
|
+
if (!(subject instanceof ListTerm) || subject.elems.length !== 2) return [];
|
|
114
|
+
|
|
115
|
+
const n = integerValue(applySubstTerm(subject.elems[0], subst));
|
|
116
|
+
const maxPrint = integerValue(applySubstTerm(subject.elems[1], subst));
|
|
117
|
+
if (n == null || maxPrint == null) return [];
|
|
118
|
+
|
|
119
|
+
const key = `${n}/${maxPrint}`;
|
|
120
|
+
let result = resultCache.get(key);
|
|
121
|
+
if (!result) {
|
|
122
|
+
result = solveNQueens(n, maxPrint);
|
|
123
|
+
resultCache.set(key, result);
|
|
124
|
+
}
|
|
125
|
+
const { count, printed } = result;
|
|
126
|
+
const body = [
|
|
127
|
+
`Solving ${n}-Queens...`,
|
|
128
|
+
`Printing at most ${maxPrint} solution(s).`,
|
|
129
|
+
'',
|
|
130
|
+
...printed,
|
|
131
|
+
...(printed.length ? [''] : []),
|
|
132
|
+
`Total solutions for ${n}-Queens: ${count}`,
|
|
133
|
+
'',
|
|
134
|
+
].join('\n');
|
|
135
|
+
|
|
136
|
+
// Eyeling string literals are represented by their quoted lexical form.
|
|
137
|
+
const lit = internLiteral(JSON.stringify(body));
|
|
138
|
+
const next = unifyTerm(goal.o, lit, subst);
|
|
139
|
+
return next ? [next] : [];
|
|
140
|
+
});
|
|
141
|
+
};
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
// Example-specific builtin module for examples/sudoku.n3.
|
|
4
|
+
//
|
|
5
|
+
// The N3 file keeps the Sudoku report declarative: it asks predicates in the
|
|
6
|
+
// http://example.org/sudoku-builtin# namespace for facts such as the normalized
|
|
7
|
+
// puzzle, solution, move counts, and validation checks.
|
|
8
|
+
//
|
|
9
|
+
// This JavaScript module supplies the specialized Sudoku search/verification
|
|
10
|
+
// kernel. It is loaded by the examples test runner in the same uniform way as
|
|
11
|
+
// examples/builtin/queens.js:
|
|
12
|
+
//
|
|
13
|
+
// node eyeling.js --builtin examples/builtin/sudoku.js examples/sudoku.n3
|
|
14
|
+
//
|
|
15
|
+
// Keeping this under examples/builtin/ makes the example self-contained and
|
|
16
|
+
// avoids registering example-specific predicates in the core runtime.
|
|
17
|
+
|
|
3
18
|
module.exports = function registerSudokuBuiltins(api) {
|
|
4
19
|
const { registerBuiltin, internLiteral, termToJsString, unifyTerm, terms } = api;
|
|
5
20
|
const { Var } = terms;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Solving 14-Queens...
|
|
2
|
+
Printing at most 1 solution(s).
|
|
3
|
+
|
|
4
|
+
Solution 1:
|
|
5
|
+
Q . . . . . . . . . . . . .
|
|
6
|
+
. . Q . . . . . . . . . . .
|
|
7
|
+
. . . . Q . . . . . . . . .
|
|
8
|
+
. . . . . . Q . . . . . . .
|
|
9
|
+
. . . . . . . . . . . Q . .
|
|
10
|
+
. . . . . . . . . Q . . . .
|
|
11
|
+
. . . . . . . . . . . . Q .
|
|
12
|
+
. . . Q . . . . . . . . . .
|
|
13
|
+
. . . . . . . . . . . . . Q
|
|
14
|
+
. . . . . . . . Q . . . . .
|
|
15
|
+
. Q . . . . . . . . . . . .
|
|
16
|
+
. . . . . Q . . . . . . . .
|
|
17
|
+
. . . . . . . Q . . . . . .
|
|
18
|
+
. . . . . . . . . . Q . . .
|
|
19
|
+
As column positions by row: [1, 3, 5, 7, 12, 10, 13, 4, 14, 9, 2, 6, 8, 11]
|
|
20
|
+
|
|
21
|
+
Total solutions for 14-Queens: 365596
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# N-Queens benchmark wrapper for Eyeling.
|
|
2
|
+
#
|
|
3
|
+
# Run:
|
|
4
|
+
# node eyeling.js --builtin examples/builtin/queens.js examples/queens.n3
|
|
5
|
+
#
|
|
6
|
+
# The tight bit-mask search kernel lives in examples/builtin/queens.js.
|
|
7
|
+
# Pure N3 can express N-Queens declaratively, but a 16x16 exhaustive count is
|
|
8
|
+
# not a practical pure-N3 benchmark for the current engine.
|
|
9
|
+
|
|
10
|
+
@prefix : <http://example.org/queens#>.
|
|
11
|
+
@prefix log: <http://www.w3.org/2000/10/swap/log#>.
|
|
12
|
+
|
|
13
|
+
:run :n 14; :maxPrint 1.
|
|
14
|
+
|
|
15
|
+
{
|
|
16
|
+
:run :n ?N; :maxPrint ?MaxPrint.
|
|
17
|
+
(?N ?MaxPrint) :render ?Report.
|
|
18
|
+
} log:query {
|
|
19
|
+
:answer log:outputString ?Report.
|
|
20
|
+
}.
|
package/examples/sudoku.n3
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
# ==================================================
|
|
2
2
|
# sudoku.n3
|
|
3
3
|
#
|
|
4
|
-
# A
|
|
4
|
+
# A Sudoku solver and report generator.
|
|
5
|
+
#
|
|
6
|
+
# Run:
|
|
7
|
+
# node eyeling.js --builtin examples/builtin/sudoku.js examples/sudoku.n3
|
|
8
|
+
#
|
|
9
|
+
# The specialized solver lives in examples/builtin/sudoku.js. The N3 below
|
|
10
|
+
# describes the report and validation checks around that solver.
|
|
5
11
|
#
|
|
6
12
|
# Edit :case :puzzle to solve another puzzle.
|
|
7
13
|
# Accepted blanks: 0, ., _
|