eyeprolog 1.6.27 → 1.6.28
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 +1 -1
- package/src/ARCHITECTURE.md +4 -1
- package/src/datalog-common.js +14 -0
- package/src/datalog.js +22 -12
- package/test/regression/cases-white-box.mjs +39 -0
package/package.json
CHANGED
package/src/ARCHITECTURE.md
CHANGED
|
@@ -21,7 +21,10 @@ higher-level frontends.
|
|
|
21
21
|
`datalog-common.js` owns what the two Datalog evaluators share: scalar row
|
|
22
22
|
keying and the per-argument candidate-bucket narrowing both index their
|
|
23
23
|
relations with. Their fixpoint algorithms and relation storage stay in their
|
|
24
|
-
own modules.
|
|
24
|
+
own modules. Positive Datalog compiles clause variables to numeric slots
|
|
25
|
+
and joins through array bindings; WFS retains its persistent Map bindings.
|
|
26
|
+
Candidate selection has a direct entry point for each binding representation
|
|
27
|
+
so their inner loops do not dispatch through a binding-store abstraction.
|
|
25
28
|
`cleanup.js` owns lifecycle-aware disposal of protected builtin
|
|
26
29
|
iterators and registers the normal-profile cleanup controls without making
|
|
27
30
|
`solver.js` depend back on the language registry.
|
package/src/datalog-common.js
CHANGED
|
@@ -91,3 +91,17 @@ export function selectCandidateIndexes(indexes, args, bindings) {
|
|
|
91
91
|
}
|
|
92
92
|
return selected;
|
|
93
93
|
}
|
|
94
|
+
|
|
95
|
+
// Positive Datalog compiles clause variables to numeric slots. Keep its hot
|
|
96
|
+
// path direct rather than introducing a binding-store dispatch in each lookup.
|
|
97
|
+
export function selectCandidateIndexesForSlots(indexes, args, bindings) {
|
|
98
|
+
let selected = null;
|
|
99
|
+
for (let i = 0; i < args.length; i++) {
|
|
100
|
+
const value = args[i].type === VAR ? bindings[args[i].slot] : args[i];
|
|
101
|
+
if (value == null) continue;
|
|
102
|
+
const bucket = indexes[i].get(scalarKey(value)) ?? EMPTY_ARRAY;
|
|
103
|
+
if (selected == null || bucket.length < selected.length) selected = bucket;
|
|
104
|
+
if (selected.length === 0) break;
|
|
105
|
+
}
|
|
106
|
+
return selected;
|
|
107
|
+
}
|
package/src/datalog.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
predicateKey,
|
|
15
15
|
sameScalar,
|
|
16
16
|
scalarKey,
|
|
17
|
-
|
|
17
|
+
selectCandidateIndexesForSlots,
|
|
18
18
|
} from './datalog-common.js';
|
|
19
19
|
|
|
20
20
|
class DatalogRelation {
|
|
@@ -61,7 +61,7 @@ class DatalogRelation {
|
|
|
61
61
|
const tuple = new Array(args.length);
|
|
62
62
|
const parts = new Array(args.length);
|
|
63
63
|
for (let i = 0; i < args.length; i++) {
|
|
64
|
-
const value = args[i].type === VAR ? bindings
|
|
64
|
+
const value = args[i].type === VAR ? bindings[args[i].slot] : args[i];
|
|
65
65
|
if (value == null || value.type === VAR) return null;
|
|
66
66
|
tuple[i] = value;
|
|
67
67
|
parts[i] = scalarKey(value);
|
|
@@ -87,7 +87,7 @@ class DatalogRelation {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
candidateIndexes(args, bindings) {
|
|
90
|
-
return
|
|
90
|
+
return selectCandidateIndexesForSlots(this.indexes, args, bindings);
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
@@ -106,11 +106,21 @@ function compileProgram(program, rootGroup) {
|
|
|
106
106
|
for (const group of groups) {
|
|
107
107
|
const headKey = predicateKey(group.module, group.name, group.arity);
|
|
108
108
|
for (const clause of group.clauses) {
|
|
109
|
-
const headArgs = clause.head.args ?? EMPTY_ARRAY;
|
|
110
109
|
if (clause.body.length === 0) continue;
|
|
111
110
|
const body = clause.body.map((goal) => directLiteral(goal, group.module));
|
|
112
111
|
if (body.some((literal) => literal == null)) continue;
|
|
113
|
-
|
|
112
|
+
// Variable names matter only within this source clause. Compile them to
|
|
113
|
+
// array slots once so each join can bind and undo without Map lookups.
|
|
114
|
+
const variables = new Map();
|
|
115
|
+
const compileArgument = (arg) => {
|
|
116
|
+
if (arg.type !== VAR) return arg;
|
|
117
|
+
let slot = variables.get(arg.name);
|
|
118
|
+
if (slot == null) variables.set(arg.name, slot = variables.size);
|
|
119
|
+
return { type: VAR, slot };
|
|
120
|
+
};
|
|
121
|
+
const headArgs = (clause.head.args ?? EMPTY_ARRAY).map(compileArgument);
|
|
122
|
+
for (const literal of body) literal.args = literal.args.map(compileArgument);
|
|
123
|
+
const rule = { headKey, headArgs, body, variableCount: variables.size };
|
|
114
124
|
rules.push(rule);
|
|
115
125
|
for (let index = 0; index < body.length; index++) {
|
|
116
126
|
const key = body[index].key;
|
|
@@ -130,20 +140,20 @@ function matchTupleMutable(args, tuple, bindings) {
|
|
|
130
140
|
for (let i = 0; i < args.length; i++) {
|
|
131
141
|
const pattern = args[i];
|
|
132
142
|
if (pattern.type === VAR) {
|
|
133
|
-
const current = bindings
|
|
143
|
+
const current = bindings[pattern.slot];
|
|
134
144
|
if (current != null) {
|
|
135
145
|
if (!sameScalar(current, tuple[i])) {
|
|
136
|
-
|
|
146
|
+
undoBindings(bindings, added);
|
|
137
147
|
return null;
|
|
138
148
|
}
|
|
139
149
|
} else {
|
|
140
|
-
bindings
|
|
141
|
-
added.push(pattern.
|
|
150
|
+
bindings[pattern.slot] = tuple[i];
|
|
151
|
+
added.push(pattern.slot);
|
|
142
152
|
}
|
|
143
153
|
continue;
|
|
144
154
|
}
|
|
145
155
|
if (!sameScalar(pattern, tuple[i])) {
|
|
146
|
-
|
|
156
|
+
undoBindings(bindings, added);
|
|
147
157
|
return null;
|
|
148
158
|
}
|
|
149
159
|
}
|
|
@@ -151,7 +161,7 @@ function matchTupleMutable(args, tuple, bindings) {
|
|
|
151
161
|
}
|
|
152
162
|
|
|
153
163
|
function undoBindings(bindings, added) {
|
|
154
|
-
for (let i = added.length - 1; i >= 0; i--) bindings
|
|
164
|
+
for (let i = added.length - 1; i >= 0; i--) bindings[added[i]] = undefined;
|
|
155
165
|
}
|
|
156
166
|
|
|
157
167
|
|
|
@@ -234,7 +244,7 @@ export function evaluatePositiveDatalog(program, rootGroup) {
|
|
|
234
244
|
const triggerEntries = compiled.triggers.get(event.key) ?? EMPTY_ARRAY;
|
|
235
245
|
for (const { rule, literalIndex } of triggerEntries) {
|
|
236
246
|
const fixed = rule.body[literalIndex];
|
|
237
|
-
const bindings = new
|
|
247
|
+
const bindings = new Array(rule.variableCount);
|
|
238
248
|
if (!matchTupleMutable(fixed.args, event.tuple, bindings)) continue;
|
|
239
249
|
const remaining = [];
|
|
240
250
|
for (let i = 0; i < rule.body.length; i++) if (i !== literalIndex) remaining.push(i);
|
|
@@ -10,6 +10,7 @@ import { compareTerms } from '../../src/term.js';
|
|
|
10
10
|
import { checkProofDocument, verdict } from '../../src/check-proof.js';
|
|
11
11
|
import { formatTermForWrite } from '../../src/write.js';
|
|
12
12
|
import { selectClauseCandidates } from '../../src/program.js';
|
|
13
|
+
import { evaluatePositiveDatalog, relationForDatalogGroup } from '../../src/datalog.js';
|
|
13
14
|
import { assertEqual, assertIncludes, assertNotIncludes } from '../test-style.mjs';
|
|
14
15
|
import { goalsFromSource } from '../goal-metadata.mjs';
|
|
15
16
|
import {
|
|
@@ -770,6 +771,44 @@ export function whiteBoxCases() {
|
|
|
770
771
|
assertEqual(ground.stats.datalog_evaluations, 0, 'ground query keeps the ordinary indexed chain path');
|
|
771
772
|
},
|
|
772
773
|
},
|
|
774
|
+
{
|
|
775
|
+
name: 'Datalog joins preserve clause-local variables and repeated arguments',
|
|
776
|
+
run: () => {
|
|
777
|
+
const program = Program.parse(`
|
|
778
|
+
link(a,b,red).
|
|
779
|
+
link(b,c,red).
|
|
780
|
+
link(c,d,red).
|
|
781
|
+
link(x,x,red).
|
|
782
|
+
link(a,b,blue).
|
|
783
|
+
link(c,c,blue).
|
|
784
|
+
link(a,c,blue).
|
|
785
|
+
reach(X,Y,C) :- link(X,Y,C).
|
|
786
|
+
reach(X,Y,C) :- link(X,Z,C), reach(Z,Y,C).
|
|
787
|
+
back(Y,X) :- reach(X,Y,red).
|
|
788
|
+
back(X,Y) :- reach(X,Y,blue).
|
|
789
|
+
diagonal(X) :- reach(X,X,C).
|
|
790
|
+
marked(tag,X) :- reach(X,X,red).
|
|
791
|
+
wide(X,marker,Y) :- reach(X,Y,red).
|
|
792
|
+
ready :- diagonal(x).
|
|
793
|
+
summary :- ready, back(X,Y), marked(tag,Z), wide(X,marker,Y).
|
|
794
|
+
`);
|
|
795
|
+
const model = evaluatePositiveDatalog(program, program.findGroup('summary', 0));
|
|
796
|
+
const checkRows = (name, arity, expected) => {
|
|
797
|
+
const relation = relationForDatalogGroup(model, program.findGroup(name, arity));
|
|
798
|
+
const actual = relation.rows.map((row) => row.map((value) => value.name).join(':')).sort();
|
|
799
|
+
assertEqual(actual.join('|'), expected.sort().join('|'), `${name}/${arity} rows`);
|
|
800
|
+
};
|
|
801
|
+
checkRows('back', 2, ['b:a', 'c:b', 'd:c', 'x:x', 'c:a', 'd:a', 'd:b', 'a:b', 'c:c', 'a:c']);
|
|
802
|
+
checkRows('diagonal', 1, ['c', 'x']);
|
|
803
|
+
checkRows('marked', 2, ['tag:x']);
|
|
804
|
+
checkRows('wide', 3, [
|
|
805
|
+
'a:marker:b', 'b:marker:c', 'c:marker:d', 'x:marker:x',
|
|
806
|
+
'a:marker:c', 'a:marker:d', 'b:marker:d',
|
|
807
|
+
]);
|
|
808
|
+
checkRows('ready', 0, ['']);
|
|
809
|
+
checkRows('summary', 0, ['']);
|
|
810
|
+
},
|
|
811
|
+
},
|
|
773
812
|
{
|
|
774
813
|
name: 'compact finite Datalog planning preserves lazy clause terms',
|
|
775
814
|
run: () => {
|