@suss/datalog 0.20.0 → 0.21.1
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 +31 -0
- package/dist/index.cjs +129 -22
- package/dist/index.js +129 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -64,6 +64,24 @@ column gets indexed the first time something asks for it and stays up to
|
|
|
64
64
|
date after that, so a relation nobody joins on that way never gets an
|
|
65
65
|
index at all.
|
|
66
66
|
|
|
67
|
+
The body is not always walked in the order it was written. A round draws
|
|
68
|
+
one literal from the facts that arrived last round, and that list is
|
|
69
|
+
usually short, so it goes first and everything after it starts with some
|
|
70
|
+
variables already bound. Take `chain(x, z) :- chain(x, y), binds(y, z)`
|
|
71
|
+
in the round where new `binds` facts arrive. Written order asks for
|
|
72
|
+
`chain(x, y)` with nothing bound, which scans every chain fact and tries
|
|
73
|
+
each one against every new binding. Leading with the new bindings binds
|
|
74
|
+
`y`, and `chain` then comes off its index on that column. On suss's own
|
|
75
|
+
sources that one reordering took the rule from 2.4 seconds to 55
|
|
76
|
+
milliseconds.
|
|
77
|
+
|
|
78
|
+
Once the delta leads, the rest of the body follows greedily: the next
|
|
79
|
+
literal is one sharing a variable that is already bound, so it too comes
|
|
80
|
+
off an index rather than scanning. A negated literal is a filter, so it
|
|
81
|
+
is placed as soon as its variables are bound. A join produces the same
|
|
82
|
+
rows whatever order it walks in, so this changes what a round costs and
|
|
83
|
+
not what it derives.
|
|
84
|
+
|
|
67
85
|
`evaluate` picks up where it left off. Call it again with the same rules
|
|
68
86
|
after adding facts and it starts from the facts you added rather than
|
|
69
87
|
redoing the whole fixpoint. Callers that go back and forth between
|
|
@@ -243,6 +261,19 @@ companion becomes a literal in the rule body, and demand travels down
|
|
|
243
261
|
each body the same way the join binds variables. A relation nothing asks
|
|
244
262
|
for is not derived at all, so only read back the relations you listed.
|
|
245
263
|
|
|
264
|
+
The rewrite puts each body in the order demand should travel, which is
|
|
265
|
+
not always written order. At each position it takes the literal with the
|
|
266
|
+
most columns already fixed, by a constant or by a variable the head or
|
|
267
|
+
an earlier literal bound, and keeps written order between equals. Take
|
|
268
|
+
`moduleExport(m, n, v) <- reExports(m, n, m2, n2), moduleExport(m2, n2,
|
|
269
|
+
v)` asked with `v` bound. In written order the re-export comes first and
|
|
270
|
+
binds `m2` and `n2` off every re-export in the program, so the recursive
|
|
271
|
+
call wants `moduleExport` with all three columns bound: one demand fact
|
|
272
|
+
per re-export per value asked about. With `moduleExport(m2, n2, v)`
|
|
273
|
+
first, the demand stays on `v` and the re-exports come off an index on
|
|
274
|
+
`m2`. A body the head binds nothing in starts with its written first
|
|
275
|
+
literal.
|
|
276
|
+
|
|
246
277
|
This costs you two things. The companion relations are stored like any
|
|
247
278
|
other, at a few tuples per value asked about, so a caller who asks about
|
|
248
279
|
most of a program ends up deriving more rather than less. And the
|
package/dist/index.cjs
CHANGED
|
@@ -93,7 +93,8 @@ function deriveOnDemand(rules, complete) {
|
|
|
93
93
|
}
|
|
94
94
|
while (pending.length > 0) {
|
|
95
95
|
const adorned = pending.pop();
|
|
96
|
-
for (const
|
|
96
|
+
for (const written of rulesByHead.get(adorned.relation) ?? []) {
|
|
97
|
+
const r = boundFirst(written, adorned.adornment);
|
|
97
98
|
const adornments = bodyAdornments(r, adorned.adornment, derives);
|
|
98
99
|
r.body.forEach((literal, at) => {
|
|
99
100
|
const adornment = adornments[at];
|
|
@@ -129,7 +130,8 @@ function deriveOnDemand(rules, complete) {
|
|
|
129
130
|
demandDriven.add(nameOf(adorned.relation, adorned.adornment));
|
|
130
131
|
demandDriven.add(demandOn(adorned.relation, adorned.adornment));
|
|
131
132
|
}
|
|
132
|
-
for (const
|
|
133
|
+
for (const written of rulesByHead.get(adorned.relation) ?? []) {
|
|
134
|
+
const r = boundFirst(written, adorned.adornment);
|
|
133
135
|
const guard = anyBound(adorned.adornment) ? [
|
|
134
136
|
positiveLiteral(
|
|
135
137
|
demandOn(adorned.relation, adorned.adornment),
|
|
@@ -141,13 +143,14 @@ function deriveOnDemand(rules, complete) {
|
|
|
141
143
|
r.body.forEach((literal, at) => {
|
|
142
144
|
const adornment = adornments[at];
|
|
143
145
|
if (adornment !== null && anyBound(adornment)) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
146
|
+
const demand = positiveLiteral(
|
|
147
|
+
demandOn(literal.relation, adornment),
|
|
148
|
+
boundTerms(literal.terms, adornment)
|
|
149
|
+
);
|
|
150
|
+
const premises = [...guard, ...body];
|
|
151
|
+
if (!sameLiteral(premises, demand)) {
|
|
152
|
+
emit({ head: demand, body: premises });
|
|
153
|
+
}
|
|
151
154
|
}
|
|
152
155
|
body.push(
|
|
153
156
|
adornment === null ? literal : positiveLiteral(
|
|
@@ -168,6 +171,36 @@ function deriveOnDemand(rules, complete) {
|
|
|
168
171
|
demandRewrites.add(rewritten);
|
|
169
172
|
return { rules: rewritten, demandDriven: [...demandDriven] };
|
|
170
173
|
}
|
|
174
|
+
var sameLiteral = (premises, conclusion) => premises.length === 1 && JSON.stringify(premises[0]) === JSON.stringify(conclusion);
|
|
175
|
+
function boundFirst(r, headAdornment) {
|
|
176
|
+
const bound = /* @__PURE__ */ new Set();
|
|
177
|
+
r.head.terms.forEach((term, column) => {
|
|
178
|
+
if (headAdornment[column] && term.type === "variable") {
|
|
179
|
+
bound.add(term.name);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
const boundColumns = (literal) => literal.terms.filter(
|
|
183
|
+
(term) => term.type === "constant" || bound.has(term.name)
|
|
184
|
+
).length;
|
|
185
|
+
const remaining = [...r.body];
|
|
186
|
+
const body = [];
|
|
187
|
+
while (remaining.length > 0) {
|
|
188
|
+
let pick = 0;
|
|
189
|
+
for (let at = 1; at < remaining.length; at++) {
|
|
190
|
+
if (boundColumns(remaining[at]) > boundColumns(remaining[pick])) {
|
|
191
|
+
pick = at;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const [literal] = remaining.splice(pick, 1);
|
|
195
|
+
body.push(literal);
|
|
196
|
+
for (const term of literal.terms) {
|
|
197
|
+
if (term.type === "variable") {
|
|
198
|
+
bound.add(term.name);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return { head: r.head, body };
|
|
203
|
+
}
|
|
171
204
|
function bodyAdornments(r, headAdornment, derives) {
|
|
172
205
|
const bound = /* @__PURE__ */ new Set();
|
|
173
206
|
r.head.terms.forEach((term, column) => {
|
|
@@ -349,7 +382,13 @@ function formatProfile(profile) {
|
|
|
349
382
|
|
|
350
383
|
// src/tupleKey.ts
|
|
351
384
|
var atomKey = (a) => typeof a === "number" ? `n${a}:` : `s${a.length}:${a}`;
|
|
352
|
-
var tupleKey = (parts) =>
|
|
385
|
+
var tupleKey = (parts) => {
|
|
386
|
+
let key = "";
|
|
387
|
+
for (let i = 0; i < parts.length; i++) {
|
|
388
|
+
key += atomKey(parts[i]);
|
|
389
|
+
}
|
|
390
|
+
return key;
|
|
391
|
+
};
|
|
353
392
|
function tupleKeyParts(key) {
|
|
354
393
|
const parts = [];
|
|
355
394
|
let at = 0;
|
|
@@ -483,7 +522,16 @@ var notLit = (relation, ...terms) => ({
|
|
|
483
522
|
var rule = (relation, terms, body, name) => name === void 0 ? { head: { relation, terms }, body } : { head: { relation, terms }, body, name };
|
|
484
523
|
var literalName = (literal) => literal.negated ? `!${literal.relation}` : literal.relation;
|
|
485
524
|
var ruleLabel = (r) => r.name ?? `${r.head.relation} :- ${r.body.map(literalName).join(", ")}`;
|
|
486
|
-
var
|
|
525
|
+
var keys = /* @__PURE__ */ new WeakMap();
|
|
526
|
+
var keyOf2 = (tuple) => {
|
|
527
|
+
const known = keys.get(tuple);
|
|
528
|
+
if (known !== void 0) {
|
|
529
|
+
return known;
|
|
530
|
+
}
|
|
531
|
+
const key = tupleKey(tuple);
|
|
532
|
+
keys.set(tuple, key);
|
|
533
|
+
return key;
|
|
534
|
+
};
|
|
487
535
|
function tagSlots(relation) {
|
|
488
536
|
if (relation.tags === void 0) {
|
|
489
537
|
relation.tags = /* @__PURE__ */ new Map();
|
|
@@ -615,9 +663,7 @@ var Database = class {
|
|
|
615
663
|
relation.keys.delete(key);
|
|
616
664
|
relation.tags?.delete(key);
|
|
617
665
|
}
|
|
618
|
-
relation.tuples = relation.tuples.filter(
|
|
619
|
-
(tuple) => !going.has(keyOf2(tuple))
|
|
620
|
-
);
|
|
666
|
+
relation.tuples = relation.keys.size === 0 ? [] : relation.tuples.filter((tuple) => !going.has(keyOf2(tuple)));
|
|
621
667
|
relation.indexes.clear();
|
|
622
668
|
forgetFacts(this, relationName, going);
|
|
623
669
|
return going.size;
|
|
@@ -727,29 +773,90 @@ function headTuple(head, bindings) {
|
|
|
727
773
|
return bound;
|
|
728
774
|
});
|
|
729
775
|
}
|
|
776
|
+
var variablesOf = (literal) => literal.terms.filter((term) => term.type === "variable").map((term) => term.name);
|
|
777
|
+
var plans = /* @__PURE__ */ new WeakMap();
|
|
778
|
+
function bodyPlan(r, deltaIndex) {
|
|
779
|
+
let byDelta = plans.get(r);
|
|
780
|
+
if (byDelta === void 0) {
|
|
781
|
+
byDelta = /* @__PURE__ */ new Map();
|
|
782
|
+
plans.set(r, byDelta);
|
|
783
|
+
}
|
|
784
|
+
const known = byDelta.get(deltaIndex);
|
|
785
|
+
if (known !== void 0) {
|
|
786
|
+
return known;
|
|
787
|
+
}
|
|
788
|
+
const positives = r.body.map((literal, index) => literal.negated ? -1 : index).filter((index) => index !== -1);
|
|
789
|
+
const deltaAt = positives[deltaIndex] ?? -1;
|
|
790
|
+
const computed = { deltaAt, order: planBodyOrder(r, deltaAt) };
|
|
791
|
+
byDelta.set(deltaIndex, computed);
|
|
792
|
+
return computed;
|
|
793
|
+
}
|
|
794
|
+
function planBodyOrder(r, deltaAt) {
|
|
795
|
+
const natural = r.body.map((_, i) => i);
|
|
796
|
+
if (deltaAt <= 0) {
|
|
797
|
+
return natural;
|
|
798
|
+
}
|
|
799
|
+
const taken = r.body.map(() => false);
|
|
800
|
+
const bound = /* @__PURE__ */ new Set();
|
|
801
|
+
const order = [];
|
|
802
|
+
const take = (index) => {
|
|
803
|
+
taken[index] = true;
|
|
804
|
+
order.push(index);
|
|
805
|
+
for (const name of variablesOf(r.body[index])) {
|
|
806
|
+
bound.add(name);
|
|
807
|
+
}
|
|
808
|
+
};
|
|
809
|
+
const firstWhere = (want) => natural.find((index) => !taken[index] && want(index)) ?? -1;
|
|
810
|
+
const nextLiteral = () => {
|
|
811
|
+
const ready = firstWhere(
|
|
812
|
+
(i) => r.body[i].negated && variablesOf(r.body[i]).every((name) => bound.has(name))
|
|
813
|
+
);
|
|
814
|
+
if (ready !== -1) {
|
|
815
|
+
return ready;
|
|
816
|
+
}
|
|
817
|
+
const joined = firstWhere(
|
|
818
|
+
(i) => !r.body[i].negated && variablesOf(r.body[i]).some((name) => bound.has(name))
|
|
819
|
+
);
|
|
820
|
+
if (joined !== -1) {
|
|
821
|
+
return joined;
|
|
822
|
+
}
|
|
823
|
+
const disconnected = firstWhere((i) => !r.body[i].negated);
|
|
824
|
+
if (disconnected !== -1) {
|
|
825
|
+
return disconnected;
|
|
826
|
+
}
|
|
827
|
+
return firstWhere(() => true);
|
|
828
|
+
};
|
|
829
|
+
take(deltaAt);
|
|
830
|
+
while (order.length < r.body.length) {
|
|
831
|
+
take(nextLiteral());
|
|
832
|
+
}
|
|
833
|
+
return order;
|
|
834
|
+
}
|
|
730
835
|
function evaluateRule(db, deltas, r, deltaIndex) {
|
|
731
836
|
const results = [];
|
|
732
|
-
const
|
|
733
|
-
|
|
837
|
+
const { deltaAt, order } = bodyPlan(r, deltaIndex);
|
|
838
|
+
const step = (orderIndex, bindings) => {
|
|
839
|
+
if (orderIndex === order.length) {
|
|
734
840
|
results.push(headTuple(r.head, bindings));
|
|
735
841
|
return;
|
|
736
842
|
}
|
|
843
|
+
const literalIndex = order[orderIndex];
|
|
737
844
|
const literal = r.body[literalIndex];
|
|
738
845
|
if (literal.negated) {
|
|
739
846
|
if (!db.has(literal.relation, groundNegated(literal, bindings))) {
|
|
740
|
-
step(
|
|
847
|
+
step(orderIndex + 1, bindings);
|
|
741
848
|
}
|
|
742
849
|
return;
|
|
743
850
|
}
|
|
744
|
-
const source =
|
|
851
|
+
const source = literalIndex === deltaAt ? deltas.get(literal.relation) ?? [] : boundSource(db, literal, bindings);
|
|
745
852
|
for (const tuple of source) {
|
|
746
853
|
const next = unify(literal, tuple, bindings);
|
|
747
854
|
if (next !== NO_MATCH) {
|
|
748
|
-
step(
|
|
855
|
+
step(orderIndex + 1, next);
|
|
749
856
|
}
|
|
750
857
|
}
|
|
751
858
|
};
|
|
752
|
-
step(0,
|
|
859
|
+
step(0, null);
|
|
753
860
|
return results;
|
|
754
861
|
}
|
|
755
862
|
function boundSource(db, literal, bindings) {
|
|
@@ -855,7 +962,7 @@ function claimFact(db, relation, key) {
|
|
|
855
962
|
state.derived.get(relation)?.delete(key);
|
|
856
963
|
}
|
|
857
964
|
}
|
|
858
|
-
function forgetFacts(db, relation,
|
|
965
|
+
function forgetFacts(db, relation, keys2) {
|
|
859
966
|
const states = evaluated.get(db);
|
|
860
967
|
if (states === void 0) {
|
|
861
968
|
return;
|
|
@@ -864,7 +971,7 @@ function forgetFacts(db, relation, keys) {
|
|
|
864
971
|
state.marks = null;
|
|
865
972
|
const ledger = state.derived.get(relation);
|
|
866
973
|
if (ledger !== void 0) {
|
|
867
|
-
for (const key of
|
|
974
|
+
for (const key of keys2) {
|
|
868
975
|
ledger.delete(key);
|
|
869
976
|
}
|
|
870
977
|
}
|
package/dist/index.js
CHANGED
|
@@ -47,7 +47,8 @@ function deriveOnDemand(rules, complete) {
|
|
|
47
47
|
}
|
|
48
48
|
while (pending.length > 0) {
|
|
49
49
|
const adorned = pending.pop();
|
|
50
|
-
for (const
|
|
50
|
+
for (const written of rulesByHead.get(adorned.relation) ?? []) {
|
|
51
|
+
const r = boundFirst(written, adorned.adornment);
|
|
51
52
|
const adornments = bodyAdornments(r, adorned.adornment, derives);
|
|
52
53
|
r.body.forEach((literal, at) => {
|
|
53
54
|
const adornment = adornments[at];
|
|
@@ -83,7 +84,8 @@ function deriveOnDemand(rules, complete) {
|
|
|
83
84
|
demandDriven.add(nameOf(adorned.relation, adorned.adornment));
|
|
84
85
|
demandDriven.add(demandOn(adorned.relation, adorned.adornment));
|
|
85
86
|
}
|
|
86
|
-
for (const
|
|
87
|
+
for (const written of rulesByHead.get(adorned.relation) ?? []) {
|
|
88
|
+
const r = boundFirst(written, adorned.adornment);
|
|
87
89
|
const guard = anyBound(adorned.adornment) ? [
|
|
88
90
|
positiveLiteral(
|
|
89
91
|
demandOn(adorned.relation, adorned.adornment),
|
|
@@ -95,13 +97,14 @@ function deriveOnDemand(rules, complete) {
|
|
|
95
97
|
r.body.forEach((literal, at) => {
|
|
96
98
|
const adornment = adornments[at];
|
|
97
99
|
if (adornment !== null && anyBound(adornment)) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
100
|
+
const demand = positiveLiteral(
|
|
101
|
+
demandOn(literal.relation, adornment),
|
|
102
|
+
boundTerms(literal.terms, adornment)
|
|
103
|
+
);
|
|
104
|
+
const premises = [...guard, ...body];
|
|
105
|
+
if (!sameLiteral(premises, demand)) {
|
|
106
|
+
emit({ head: demand, body: premises });
|
|
107
|
+
}
|
|
105
108
|
}
|
|
106
109
|
body.push(
|
|
107
110
|
adornment === null ? literal : positiveLiteral(
|
|
@@ -122,6 +125,36 @@ function deriveOnDemand(rules, complete) {
|
|
|
122
125
|
demandRewrites.add(rewritten);
|
|
123
126
|
return { rules: rewritten, demandDriven: [...demandDriven] };
|
|
124
127
|
}
|
|
128
|
+
var sameLiteral = (premises, conclusion) => premises.length === 1 && JSON.stringify(premises[0]) === JSON.stringify(conclusion);
|
|
129
|
+
function boundFirst(r, headAdornment) {
|
|
130
|
+
const bound = /* @__PURE__ */ new Set();
|
|
131
|
+
r.head.terms.forEach((term, column) => {
|
|
132
|
+
if (headAdornment[column] && term.type === "variable") {
|
|
133
|
+
bound.add(term.name);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
const boundColumns = (literal) => literal.terms.filter(
|
|
137
|
+
(term) => term.type === "constant" || bound.has(term.name)
|
|
138
|
+
).length;
|
|
139
|
+
const remaining = [...r.body];
|
|
140
|
+
const body = [];
|
|
141
|
+
while (remaining.length > 0) {
|
|
142
|
+
let pick = 0;
|
|
143
|
+
for (let at = 1; at < remaining.length; at++) {
|
|
144
|
+
if (boundColumns(remaining[at]) > boundColumns(remaining[pick])) {
|
|
145
|
+
pick = at;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const [literal] = remaining.splice(pick, 1);
|
|
149
|
+
body.push(literal);
|
|
150
|
+
for (const term of literal.terms) {
|
|
151
|
+
if (term.type === "variable") {
|
|
152
|
+
bound.add(term.name);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return { head: r.head, body };
|
|
157
|
+
}
|
|
125
158
|
function bodyAdornments(r, headAdornment, derives) {
|
|
126
159
|
const bound = /* @__PURE__ */ new Set();
|
|
127
160
|
r.head.terms.forEach((term, column) => {
|
|
@@ -303,7 +336,13 @@ function formatProfile(profile) {
|
|
|
303
336
|
|
|
304
337
|
// src/tupleKey.ts
|
|
305
338
|
var atomKey = (a) => typeof a === "number" ? `n${a}:` : `s${a.length}:${a}`;
|
|
306
|
-
var tupleKey = (parts) =>
|
|
339
|
+
var tupleKey = (parts) => {
|
|
340
|
+
let key = "";
|
|
341
|
+
for (let i = 0; i < parts.length; i++) {
|
|
342
|
+
key += atomKey(parts[i]);
|
|
343
|
+
}
|
|
344
|
+
return key;
|
|
345
|
+
};
|
|
307
346
|
function tupleKeyParts(key) {
|
|
308
347
|
const parts = [];
|
|
309
348
|
let at = 0;
|
|
@@ -437,7 +476,16 @@ var notLit = (relation, ...terms) => ({
|
|
|
437
476
|
var rule = (relation, terms, body, name) => name === void 0 ? { head: { relation, terms }, body } : { head: { relation, terms }, body, name };
|
|
438
477
|
var literalName = (literal) => literal.negated ? `!${literal.relation}` : literal.relation;
|
|
439
478
|
var ruleLabel = (r) => r.name ?? `${r.head.relation} :- ${r.body.map(literalName).join(", ")}`;
|
|
440
|
-
var
|
|
479
|
+
var keys = /* @__PURE__ */ new WeakMap();
|
|
480
|
+
var keyOf2 = (tuple) => {
|
|
481
|
+
const known = keys.get(tuple);
|
|
482
|
+
if (known !== void 0) {
|
|
483
|
+
return known;
|
|
484
|
+
}
|
|
485
|
+
const key = tupleKey(tuple);
|
|
486
|
+
keys.set(tuple, key);
|
|
487
|
+
return key;
|
|
488
|
+
};
|
|
441
489
|
function tagSlots(relation) {
|
|
442
490
|
if (relation.tags === void 0) {
|
|
443
491
|
relation.tags = /* @__PURE__ */ new Map();
|
|
@@ -569,9 +617,7 @@ var Database = class {
|
|
|
569
617
|
relation.keys.delete(key);
|
|
570
618
|
relation.tags?.delete(key);
|
|
571
619
|
}
|
|
572
|
-
relation.tuples = relation.tuples.filter(
|
|
573
|
-
(tuple) => !going.has(keyOf2(tuple))
|
|
574
|
-
);
|
|
620
|
+
relation.tuples = relation.keys.size === 0 ? [] : relation.tuples.filter((tuple) => !going.has(keyOf2(tuple)));
|
|
575
621
|
relation.indexes.clear();
|
|
576
622
|
forgetFacts(this, relationName, going);
|
|
577
623
|
return going.size;
|
|
@@ -681,29 +727,90 @@ function headTuple(head, bindings) {
|
|
|
681
727
|
return bound;
|
|
682
728
|
});
|
|
683
729
|
}
|
|
730
|
+
var variablesOf = (literal) => literal.terms.filter((term) => term.type === "variable").map((term) => term.name);
|
|
731
|
+
var plans = /* @__PURE__ */ new WeakMap();
|
|
732
|
+
function bodyPlan(r, deltaIndex) {
|
|
733
|
+
let byDelta = plans.get(r);
|
|
734
|
+
if (byDelta === void 0) {
|
|
735
|
+
byDelta = /* @__PURE__ */ new Map();
|
|
736
|
+
plans.set(r, byDelta);
|
|
737
|
+
}
|
|
738
|
+
const known = byDelta.get(deltaIndex);
|
|
739
|
+
if (known !== void 0) {
|
|
740
|
+
return known;
|
|
741
|
+
}
|
|
742
|
+
const positives = r.body.map((literal, index) => literal.negated ? -1 : index).filter((index) => index !== -1);
|
|
743
|
+
const deltaAt = positives[deltaIndex] ?? -1;
|
|
744
|
+
const computed = { deltaAt, order: planBodyOrder(r, deltaAt) };
|
|
745
|
+
byDelta.set(deltaIndex, computed);
|
|
746
|
+
return computed;
|
|
747
|
+
}
|
|
748
|
+
function planBodyOrder(r, deltaAt) {
|
|
749
|
+
const natural = r.body.map((_, i) => i);
|
|
750
|
+
if (deltaAt <= 0) {
|
|
751
|
+
return natural;
|
|
752
|
+
}
|
|
753
|
+
const taken = r.body.map(() => false);
|
|
754
|
+
const bound = /* @__PURE__ */ new Set();
|
|
755
|
+
const order = [];
|
|
756
|
+
const take = (index) => {
|
|
757
|
+
taken[index] = true;
|
|
758
|
+
order.push(index);
|
|
759
|
+
for (const name of variablesOf(r.body[index])) {
|
|
760
|
+
bound.add(name);
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
const firstWhere = (want) => natural.find((index) => !taken[index] && want(index)) ?? -1;
|
|
764
|
+
const nextLiteral = () => {
|
|
765
|
+
const ready = firstWhere(
|
|
766
|
+
(i) => r.body[i].negated && variablesOf(r.body[i]).every((name) => bound.has(name))
|
|
767
|
+
);
|
|
768
|
+
if (ready !== -1) {
|
|
769
|
+
return ready;
|
|
770
|
+
}
|
|
771
|
+
const joined = firstWhere(
|
|
772
|
+
(i) => !r.body[i].negated && variablesOf(r.body[i]).some((name) => bound.has(name))
|
|
773
|
+
);
|
|
774
|
+
if (joined !== -1) {
|
|
775
|
+
return joined;
|
|
776
|
+
}
|
|
777
|
+
const disconnected = firstWhere((i) => !r.body[i].negated);
|
|
778
|
+
if (disconnected !== -1) {
|
|
779
|
+
return disconnected;
|
|
780
|
+
}
|
|
781
|
+
return firstWhere(() => true);
|
|
782
|
+
};
|
|
783
|
+
take(deltaAt);
|
|
784
|
+
while (order.length < r.body.length) {
|
|
785
|
+
take(nextLiteral());
|
|
786
|
+
}
|
|
787
|
+
return order;
|
|
788
|
+
}
|
|
684
789
|
function evaluateRule(db, deltas, r, deltaIndex) {
|
|
685
790
|
const results = [];
|
|
686
|
-
const
|
|
687
|
-
|
|
791
|
+
const { deltaAt, order } = bodyPlan(r, deltaIndex);
|
|
792
|
+
const step = (orderIndex, bindings) => {
|
|
793
|
+
if (orderIndex === order.length) {
|
|
688
794
|
results.push(headTuple(r.head, bindings));
|
|
689
795
|
return;
|
|
690
796
|
}
|
|
797
|
+
const literalIndex = order[orderIndex];
|
|
691
798
|
const literal = r.body[literalIndex];
|
|
692
799
|
if (literal.negated) {
|
|
693
800
|
if (!db.has(literal.relation, groundNegated(literal, bindings))) {
|
|
694
|
-
step(
|
|
801
|
+
step(orderIndex + 1, bindings);
|
|
695
802
|
}
|
|
696
803
|
return;
|
|
697
804
|
}
|
|
698
|
-
const source =
|
|
805
|
+
const source = literalIndex === deltaAt ? deltas.get(literal.relation) ?? [] : boundSource(db, literal, bindings);
|
|
699
806
|
for (const tuple of source) {
|
|
700
807
|
const next = unify(literal, tuple, bindings);
|
|
701
808
|
if (next !== NO_MATCH) {
|
|
702
|
-
step(
|
|
809
|
+
step(orderIndex + 1, next);
|
|
703
810
|
}
|
|
704
811
|
}
|
|
705
812
|
};
|
|
706
|
-
step(0,
|
|
813
|
+
step(0, null);
|
|
707
814
|
return results;
|
|
708
815
|
}
|
|
709
816
|
function boundSource(db, literal, bindings) {
|
|
@@ -809,7 +916,7 @@ function claimFact(db, relation, key) {
|
|
|
809
916
|
state.derived.get(relation)?.delete(key);
|
|
810
917
|
}
|
|
811
918
|
}
|
|
812
|
-
function forgetFacts(db, relation,
|
|
919
|
+
function forgetFacts(db, relation, keys2) {
|
|
813
920
|
const states = evaluated.get(db);
|
|
814
921
|
if (states === void 0) {
|
|
815
922
|
return;
|
|
@@ -818,7 +925,7 @@ function forgetFacts(db, relation, keys) {
|
|
|
818
925
|
state.marks = null;
|
|
819
926
|
const ledger = state.derived.get(relation);
|
|
820
927
|
if (ledger !== void 0) {
|
|
821
|
-
for (const key of
|
|
928
|
+
for (const key of keys2) {
|
|
822
929
|
ledger.delete(key);
|
|
823
930
|
}
|
|
824
931
|
}
|
package/package.json
CHANGED