@suss/datalog 0.9.0 → 0.11.0
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 +124 -0
- package/dist/index.cjs +395 -156
- package/dist/index.d.cts +192 -5
- package/dist/index.d.ts +192 -5
- package/dist/index.js +388 -155
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,147 @@
|
|
|
1
|
+
// src/onDemand.ts
|
|
2
|
+
var adornmentKey = (adornment) => adornment.map((bound) => bound ? "b" : "f").join("");
|
|
3
|
+
var anyBound = (adornment) => adornment.some(Boolean);
|
|
4
|
+
var keyOf = ({ relation, adornment }) => `${relation}/${adornmentKey(adornment)}`;
|
|
5
|
+
var boundTerms = (terms, adornment) => terms.filter((_, column) => adornment[column]);
|
|
6
|
+
var positiveLiteral = (relation, terms) => ({
|
|
7
|
+
relation,
|
|
8
|
+
terms,
|
|
9
|
+
negated: false
|
|
10
|
+
});
|
|
11
|
+
var demandRewrites = /* @__PURE__ */ new WeakSet();
|
|
12
|
+
var isDemandRewritten = (rules) => demandRewrites.has(rules);
|
|
13
|
+
function deriveOnDemand(rules, complete) {
|
|
14
|
+
const negated = rules.find((r) => r.body.some((l) => l.negated));
|
|
15
|
+
if (negated !== void 0) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
`cannot derive "${negated.head.relation}" on demand: its rule uses negation, and a relation derived only where somebody asked can make a negated literal match where it did not`
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
const rulesByHead = /* @__PURE__ */ new Map();
|
|
21
|
+
for (const r of rules) {
|
|
22
|
+
const existing = rulesByHead.get(r.head.relation);
|
|
23
|
+
if (existing === void 0) {
|
|
24
|
+
rulesByHead.set(r.head.relation, [r]);
|
|
25
|
+
} else {
|
|
26
|
+
existing.push(r);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const derives = (relation) => rulesByHead.has(relation);
|
|
30
|
+
const asked = /* @__PURE__ */ new Map();
|
|
31
|
+
const pending = [];
|
|
32
|
+
const want = (adorned) => {
|
|
33
|
+
const key = keyOf(adorned);
|
|
34
|
+
if (!asked.has(key)) {
|
|
35
|
+
asked.set(key, adorned);
|
|
36
|
+
pending.push(adorned);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
for (const relation of complete) {
|
|
40
|
+
const derivedBy = rulesByHead.get(relation);
|
|
41
|
+
if (derivedBy === void 0) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`no rule derives "${relation}", so it cannot be asked for in full`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
want({ relation, adornment: derivedBy[0].head.terms.map(() => false) });
|
|
47
|
+
}
|
|
48
|
+
while (pending.length > 0) {
|
|
49
|
+
const adorned = pending.pop();
|
|
50
|
+
for (const r of rulesByHead.get(adorned.relation) ?? []) {
|
|
51
|
+
const adornments = bodyAdornments(r, adorned.adornment, derives);
|
|
52
|
+
r.body.forEach((literal, at) => {
|
|
53
|
+
const adornment = adornments[at];
|
|
54
|
+
if (adornment !== null) {
|
|
55
|
+
want({ relation: literal.relation, adornment });
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const variantsOf = /* @__PURE__ */ new Map();
|
|
61
|
+
for (const { relation } of asked.values()) {
|
|
62
|
+
variantsOf.set(relation, (variantsOf.get(relation) ?? 0) + 1);
|
|
63
|
+
}
|
|
64
|
+
const nameOf = (relation, adornment) => {
|
|
65
|
+
if (!anyBound(adornment) || variantsOf.get(relation) === 1) {
|
|
66
|
+
return relation;
|
|
67
|
+
}
|
|
68
|
+
return `${relation}:${adornmentKey(adornment)}`;
|
|
69
|
+
};
|
|
70
|
+
const demandOn = (relation, adornment) => `wanted:${nameOf(relation, adornment)}`;
|
|
71
|
+
const rewritten = [];
|
|
72
|
+
const demandDriven = /* @__PURE__ */ new Set();
|
|
73
|
+
const already = /* @__PURE__ */ new Set();
|
|
74
|
+
const emit = (r) => {
|
|
75
|
+
const key = JSON.stringify(r);
|
|
76
|
+
if (!already.has(key)) {
|
|
77
|
+
already.add(key);
|
|
78
|
+
rewritten.push(r);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
for (const adorned of asked.values()) {
|
|
82
|
+
if (anyBound(adorned.adornment)) {
|
|
83
|
+
demandDriven.add(nameOf(adorned.relation, adorned.adornment));
|
|
84
|
+
demandDriven.add(demandOn(adorned.relation, adorned.adornment));
|
|
85
|
+
}
|
|
86
|
+
for (const r of rulesByHead.get(adorned.relation) ?? []) {
|
|
87
|
+
const guard = anyBound(adorned.adornment) ? [
|
|
88
|
+
positiveLiteral(
|
|
89
|
+
demandOn(adorned.relation, adorned.adornment),
|
|
90
|
+
boundTerms(r.head.terms, adorned.adornment)
|
|
91
|
+
)
|
|
92
|
+
] : [];
|
|
93
|
+
const adornments = bodyAdornments(r, adorned.adornment, derives);
|
|
94
|
+
const body = [];
|
|
95
|
+
r.body.forEach((literal, at) => {
|
|
96
|
+
const adornment = adornments[at];
|
|
97
|
+
if (adornment !== null && anyBound(adornment)) {
|
|
98
|
+
emit({
|
|
99
|
+
head: {
|
|
100
|
+
relation: demandOn(literal.relation, adornment),
|
|
101
|
+
terms: boundTerms(literal.terms, adornment)
|
|
102
|
+
},
|
|
103
|
+
body: [...guard, ...body]
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
body.push(
|
|
107
|
+
adornment === null ? literal : positiveLiteral(
|
|
108
|
+
nameOf(literal.relation, adornment),
|
|
109
|
+
literal.terms
|
|
110
|
+
)
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
emit({
|
|
114
|
+
head: {
|
|
115
|
+
relation: nameOf(adorned.relation, adorned.adornment),
|
|
116
|
+
terms: r.head.terms
|
|
117
|
+
},
|
|
118
|
+
body: [...guard, ...body]
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
demandRewrites.add(rewritten);
|
|
123
|
+
return { rules: rewritten, demandDriven: [...demandDriven] };
|
|
124
|
+
}
|
|
125
|
+
function bodyAdornments(r, headAdornment, derives) {
|
|
126
|
+
const bound = /* @__PURE__ */ new Set();
|
|
127
|
+
r.head.terms.forEach((term, column) => {
|
|
128
|
+
if (headAdornment[column] && term.type === "variable") {
|
|
129
|
+
bound.add(term.name);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
return r.body.map((literal) => {
|
|
133
|
+
const adornment = derives(literal.relation) ? literal.terms.map(
|
|
134
|
+
(term) => term.type === "constant" || bound.has(term.name)
|
|
135
|
+
) : null;
|
|
136
|
+
for (const term of literal.terms) {
|
|
137
|
+
if (term.type === "variable") {
|
|
138
|
+
bound.add(term.name);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return adornment;
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
1
145
|
// src/profile.ts
|
|
2
146
|
var open = [];
|
|
3
147
|
var isProfiling = () => open.length > 0;
|
|
@@ -189,145 +333,92 @@ function tupleKeyParts(key) {
|
|
|
189
333
|
return parts;
|
|
190
334
|
}
|
|
191
335
|
|
|
192
|
-
// src/
|
|
193
|
-
var
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
rulesByHead.set(r.head.relation, [r]);
|
|
214
|
-
} else {
|
|
215
|
-
existing.push(r);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
const derives = (relation) => rulesByHead.has(relation);
|
|
219
|
-
const asked = /* @__PURE__ */ new Map();
|
|
220
|
-
const pending = [];
|
|
221
|
-
const want = (adorned) => {
|
|
222
|
-
const key = keyOf(adorned);
|
|
223
|
-
if (!asked.has(key)) {
|
|
224
|
-
asked.set(key, adorned);
|
|
225
|
-
pending.push(adorned);
|
|
336
|
+
// src/confidence.ts
|
|
337
|
+
var RANK = {
|
|
338
|
+
low: 0,
|
|
339
|
+
medium: 1,
|
|
340
|
+
high: 2
|
|
341
|
+
};
|
|
342
|
+
function confidenceWith(levelOf) {
|
|
343
|
+
return {
|
|
344
|
+
asserted: "high",
|
|
345
|
+
absent: "high",
|
|
346
|
+
combine(bodyTags, derivation) {
|
|
347
|
+
let lowest = levelOf(derivation.rule) ?? "high";
|
|
348
|
+
for (const tag of bodyTags) {
|
|
349
|
+
if (RANK[tag] < RANK[lowest]) {
|
|
350
|
+
lowest = tag;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return lowest;
|
|
354
|
+
},
|
|
355
|
+
merge(stored, incoming) {
|
|
356
|
+
return RANK[incoming] > RANK[stored] ? incoming : stored;
|
|
226
357
|
}
|
|
227
358
|
};
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
359
|
+
}
|
|
360
|
+
var confidence = confidenceWith(
|
|
361
|
+
() => void 0
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
// src/witness.ts
|
|
365
|
+
var Witness = class {
|
|
366
|
+
constructor(rule2, body) {
|
|
367
|
+
this.rule = rule2;
|
|
368
|
+
this.body = body;
|
|
236
369
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
370
|
+
rule;
|
|
371
|
+
body;
|
|
372
|
+
};
|
|
373
|
+
var witnesses = {
|
|
374
|
+
asserted: "asserted",
|
|
375
|
+
absent: "absent",
|
|
376
|
+
// A witness is built from the derivation alone, and saying so lets
|
|
377
|
+
// the evaluator skip the body-tag lookups, which were most of the
|
|
378
|
+
// tagged path's cost on a large sweep.
|
|
379
|
+
ignoresBodyTags: true,
|
|
380
|
+
combine: (_bodyTags, derivation) => new Witness(derivation.rule, derivation.body),
|
|
381
|
+
merge: (stored) => stored
|
|
382
|
+
};
|
|
383
|
+
var DEFAULT_MAX_DEPTH = 128;
|
|
384
|
+
function proofOf(db, relation, tuple, options = {}) {
|
|
385
|
+
return walk(
|
|
386
|
+
db,
|
|
387
|
+
relation,
|
|
388
|
+
tuple,
|
|
389
|
+
options.maxDepth ?? DEFAULT_MAX_DEPTH,
|
|
390
|
+
/* @__PURE__ */ new Set()
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
var premiseOf = {
|
|
394
|
+
fact: (db, match, depth, path) => walk(db, match.relation, match.tuple, depth, path),
|
|
395
|
+
absence: (_db, match) => ({
|
|
396
|
+
kind: "absence",
|
|
397
|
+
relation: match.relation,
|
|
398
|
+
tuple: match.tuple
|
|
399
|
+
})
|
|
400
|
+
};
|
|
401
|
+
function walk(db, relation, tuple, depth, path) {
|
|
402
|
+
if (!db.has(relation, tuple)) {
|
|
403
|
+
return { kind: "absence", relation, tuple };
|
|
248
404
|
}
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
405
|
+
const tag = db.tagOf(relation, tuple);
|
|
406
|
+
if (!(tag instanceof Witness)) {
|
|
407
|
+
return { kind: "fact", relation, tuple };
|
|
252
408
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
return relation;
|
|
256
|
-
}
|
|
257
|
-
return `${relation}:${adornmentKey(adornment)}`;
|
|
258
|
-
};
|
|
259
|
-
const demandOn = (relation, adornment) => `wanted:${nameOf(relation, adornment)}`;
|
|
260
|
-
const rewritten = [];
|
|
261
|
-
const demandDriven = /* @__PURE__ */ new Set();
|
|
262
|
-
const already = /* @__PURE__ */ new Set();
|
|
263
|
-
const emit = (r) => {
|
|
264
|
-
const key = JSON.stringify(r);
|
|
265
|
-
if (!already.has(key)) {
|
|
266
|
-
already.add(key);
|
|
267
|
-
rewritten.push(r);
|
|
268
|
-
}
|
|
269
|
-
};
|
|
270
|
-
for (const adorned of asked.values()) {
|
|
271
|
-
if (anyBound(adorned.adornment)) {
|
|
272
|
-
demandDriven.add(nameOf(adorned.relation, adorned.adornment));
|
|
273
|
-
demandDriven.add(demandOn(adorned.relation, adorned.adornment));
|
|
274
|
-
}
|
|
275
|
-
for (const r of rulesByHead.get(adorned.relation) ?? []) {
|
|
276
|
-
const guard = anyBound(adorned.adornment) ? [
|
|
277
|
-
positiveLiteral(
|
|
278
|
-
demandOn(adorned.relation, adorned.adornment),
|
|
279
|
-
boundTerms(r.head.terms, adorned.adornment)
|
|
280
|
-
)
|
|
281
|
-
] : [];
|
|
282
|
-
const adornments = bodyAdornments(r, adorned.adornment, derives);
|
|
283
|
-
const body = [];
|
|
284
|
-
r.body.forEach((literal, at) => {
|
|
285
|
-
const adornment = adornments[at];
|
|
286
|
-
if (adornment !== null && anyBound(adornment)) {
|
|
287
|
-
emit({
|
|
288
|
-
head: {
|
|
289
|
-
relation: demandOn(literal.relation, adornment),
|
|
290
|
-
terms: boundTerms(literal.terms, adornment)
|
|
291
|
-
},
|
|
292
|
-
body: [...guard, ...body]
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
body.push(
|
|
296
|
-
adornment === null ? literal : positiveLiteral(
|
|
297
|
-
nameOf(literal.relation, adornment),
|
|
298
|
-
literal.terms
|
|
299
|
-
)
|
|
300
|
-
);
|
|
301
|
-
});
|
|
302
|
-
emit({
|
|
303
|
-
head: {
|
|
304
|
-
relation: nameOf(adorned.relation, adorned.adornment),
|
|
305
|
-
terms: r.head.terms
|
|
306
|
-
},
|
|
307
|
-
body: [...guard, ...body]
|
|
308
|
-
});
|
|
309
|
-
}
|
|
409
|
+
if (depth <= 0) {
|
|
410
|
+
return { kind: "truncated", relation, tuple, reason: "depth" };
|
|
310
411
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
return
|
|
321
|
-
const adornment = derives(literal.relation) ? literal.terms.map(
|
|
322
|
-
(term) => term.type === "constant" || bound.has(term.name)
|
|
323
|
-
) : null;
|
|
324
|
-
for (const term of literal.terms) {
|
|
325
|
-
if (term.type === "variable") {
|
|
326
|
-
bound.add(term.name);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
return adornment;
|
|
330
|
-
});
|
|
412
|
+
const key = tupleKey([relation, ...tuple]);
|
|
413
|
+
if (path.has(key)) {
|
|
414
|
+
return { kind: "truncated", relation, tuple, reason: "cycle" };
|
|
415
|
+
}
|
|
416
|
+
path.add(key);
|
|
417
|
+
const premises = tag.body.map(
|
|
418
|
+
(match) => premiseOf[match.kind](db, match, depth - 1, path)
|
|
419
|
+
);
|
|
420
|
+
path.delete(key);
|
|
421
|
+
return { kind: "derived", relation, tuple, rule: tag.rule, premises };
|
|
331
422
|
}
|
|
332
423
|
|
|
333
424
|
// src/index.ts
|
|
@@ -343,8 +434,16 @@ var notLit = (relation, ...terms) => ({
|
|
|
343
434
|
terms,
|
|
344
435
|
negated: true
|
|
345
436
|
});
|
|
346
|
-
var rule = (relation, terms, body) =>
|
|
437
|
+
var rule = (relation, terms, body, name) => name === void 0 ? { head: { relation, terms }, body } : { head: { relation, terms }, body, name };
|
|
438
|
+
var literalName = (literal) => literal.negated ? `!${literal.relation}` : literal.relation;
|
|
439
|
+
var ruleLabel = (r) => r.name ?? `${r.head.relation} :- ${r.body.map(literalName).join(", ")}`;
|
|
347
440
|
var keyOf2 = (tuple) => tupleKey(tuple);
|
|
441
|
+
function tagSlots(relation) {
|
|
442
|
+
if (relation.tags === void 0) {
|
|
443
|
+
relation.tags = /* @__PURE__ */ new Map();
|
|
444
|
+
}
|
|
445
|
+
return relation.tags;
|
|
446
|
+
}
|
|
348
447
|
function addToBucket(index, key, tuple) {
|
|
349
448
|
const bucket = index.get(key);
|
|
350
449
|
if (bucket === void 0) {
|
|
@@ -363,30 +462,53 @@ var Database = class {
|
|
|
363
462
|
const created = {
|
|
364
463
|
keys: /* @__PURE__ */ new Set(),
|
|
365
464
|
tuples: [],
|
|
465
|
+
tags: void 0,
|
|
366
466
|
indexes: /* @__PURE__ */ new Map()
|
|
367
467
|
};
|
|
368
468
|
this.store.set(name, created);
|
|
369
469
|
return created;
|
|
370
470
|
}
|
|
371
|
-
/**
|
|
372
|
-
|
|
471
|
+
/**
|
|
472
|
+
* Add a fact, or merge a tag into one already present. A new fact
|
|
473
|
+
* stores `tag` when one is given. For a fact already present,
|
|
474
|
+
* `merge` gets the stored tag (undefined when there is none) and the
|
|
475
|
+
* incoming one; returning anything but the stored value stores the
|
|
476
|
+
* result and reports "improved".
|
|
477
|
+
*/
|
|
478
|
+
add(relationName, tuple, tag, merge) {
|
|
373
479
|
const relation = this.relation(relationName);
|
|
374
480
|
const key = keyOf2(tuple);
|
|
375
481
|
if (relation.keys.has(key)) {
|
|
482
|
+
let outcome = "unchanged";
|
|
483
|
+
if (merge !== void 0) {
|
|
484
|
+
const stored = relation.tags?.get(key);
|
|
485
|
+
const merged = merge(stored, tag);
|
|
486
|
+
if (merged !== stored) {
|
|
487
|
+
tagSlots(relation).set(key, merged);
|
|
488
|
+
outcome = "improved";
|
|
489
|
+
}
|
|
490
|
+
}
|
|
376
491
|
if (!isDeriving(this)) {
|
|
377
492
|
claimFact(this, relationName, key);
|
|
378
493
|
}
|
|
379
|
-
return
|
|
494
|
+
return outcome;
|
|
380
495
|
}
|
|
381
496
|
relation.keys.add(key);
|
|
382
497
|
relation.tuples.push(tuple);
|
|
498
|
+
if (tag !== void 0) {
|
|
499
|
+
tagSlots(relation).set(key, tag);
|
|
500
|
+
}
|
|
383
501
|
for (const [column, index] of relation.indexes) {
|
|
384
502
|
const value = tuple[column];
|
|
385
503
|
if (value !== void 0) {
|
|
386
504
|
addToBucket(index, value, tuple);
|
|
387
505
|
}
|
|
388
506
|
}
|
|
389
|
-
return
|
|
507
|
+
return "added";
|
|
508
|
+
}
|
|
509
|
+
/** The tag stored for this fact, or undefined when there is none. */
|
|
510
|
+
tagOf(relationName, tuple) {
|
|
511
|
+
return this.store.get(relationName)?.tags?.get(keyOf2(tuple));
|
|
390
512
|
}
|
|
391
513
|
has(relationName, tuple) {
|
|
392
514
|
return this.store.get(relationName)?.keys.has(keyOf2(tuple)) ?? false;
|
|
@@ -445,6 +567,7 @@ var Database = class {
|
|
|
445
567
|
}
|
|
446
568
|
for (const key of going) {
|
|
447
569
|
relation.keys.delete(key);
|
|
570
|
+
relation.tags?.delete(key);
|
|
448
571
|
}
|
|
449
572
|
relation.tuples = relation.tuples.filter(
|
|
450
573
|
(tuple) => !going.has(keyOf2(tuple))
|
|
@@ -585,6 +708,69 @@ function boundSource(db, literal, bindings) {
|
|
|
585
708
|
}
|
|
586
709
|
return db.facts(literal.relation);
|
|
587
710
|
}
|
|
711
|
+
var EMPTY_TAGS = [];
|
|
712
|
+
function evaluateRuleTagged(db, deltas, r, deltaIndex, algebra) {
|
|
713
|
+
const results = [];
|
|
714
|
+
const bodyTags = [];
|
|
715
|
+
const bodyMatches = [];
|
|
716
|
+
const readsTags = algebra.ignoresBodyTags !== true;
|
|
717
|
+
const step = (literalIndex, positiveIndex, bindings) => {
|
|
718
|
+
if (literalIndex === r.body.length) {
|
|
719
|
+
results.push({
|
|
720
|
+
tuple: headTuple(r.head, bindings),
|
|
721
|
+
tag: algebra.combine(readsTags ? bodyTags.slice() : EMPTY_TAGS, {
|
|
722
|
+
rule: r,
|
|
723
|
+
body: bodyMatches.slice()
|
|
724
|
+
})
|
|
725
|
+
});
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
const literal = r.body[literalIndex];
|
|
729
|
+
if (literal.negated) {
|
|
730
|
+
const grounded = groundNegated(literal, bindings);
|
|
731
|
+
if (!db.has(literal.relation, grounded)) {
|
|
732
|
+
if (readsTags) {
|
|
733
|
+
bodyTags.push(algebra.absent);
|
|
734
|
+
}
|
|
735
|
+
bodyMatches.push({
|
|
736
|
+
kind: "absence",
|
|
737
|
+
relation: literal.relation,
|
|
738
|
+
tuple: grounded
|
|
739
|
+
});
|
|
740
|
+
step(literalIndex + 1, positiveIndex, bindings);
|
|
741
|
+
if (readsTags) {
|
|
742
|
+
bodyTags.pop();
|
|
743
|
+
}
|
|
744
|
+
bodyMatches.pop();
|
|
745
|
+
}
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
const source = positiveIndex === deltaIndex ? deltas.get(literal.relation) ?? [] : boundSource(db, literal, bindings);
|
|
749
|
+
for (const tuple of source) {
|
|
750
|
+
const next = unify(literal, tuple, bindings);
|
|
751
|
+
if (next !== null) {
|
|
752
|
+
if (readsTags) {
|
|
753
|
+
const stored = db.tagOf(literal.relation, tuple);
|
|
754
|
+
bodyTags.push(
|
|
755
|
+
stored === void 0 ? algebra.asserted : stored
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
bodyMatches.push({
|
|
759
|
+
kind: "fact",
|
|
760
|
+
relation: literal.relation,
|
|
761
|
+
tuple
|
|
762
|
+
});
|
|
763
|
+
step(literalIndex + 1, positiveIndex + 1, next);
|
|
764
|
+
if (readsTags) {
|
|
765
|
+
bodyTags.pop();
|
|
766
|
+
}
|
|
767
|
+
bodyMatches.pop();
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
step(0, 0, /* @__PURE__ */ new Map());
|
|
772
|
+
return results;
|
|
773
|
+
}
|
|
588
774
|
var evaluated = /* @__PURE__ */ new WeakMap();
|
|
589
775
|
var deriving = /* @__PURE__ */ new Map();
|
|
590
776
|
function isDeriving(db) {
|
|
@@ -644,10 +830,15 @@ function currentMarks(db) {
|
|
|
644
830
|
}
|
|
645
831
|
return marks;
|
|
646
832
|
}
|
|
647
|
-
function evaluate(db, rules) {
|
|
833
|
+
function evaluate(db, rules, algebra) {
|
|
834
|
+
if (algebra !== void 0 && isDemandRewritten(rules)) {
|
|
835
|
+
throw new Error(
|
|
836
|
+
"cannot evaluate demand-rewritten rules with a tag algebra: tags would follow the demand-transformed rules rather than the ones the caller wrote"
|
|
837
|
+
);
|
|
838
|
+
}
|
|
648
839
|
deriving.set(db, (deriving.get(db) ?? 0) + 1);
|
|
649
840
|
try {
|
|
650
|
-
return runRules(db, rules);
|
|
841
|
+
return runRules(db, rules, algebra);
|
|
651
842
|
} finally {
|
|
652
843
|
const depth = (deriving.get(db) ?? 1) - 1;
|
|
653
844
|
if (depth === 0) {
|
|
@@ -688,7 +879,16 @@ function couldProduce(db, rule2) {
|
|
|
688
879
|
(literal) => literal.negated || db.size(literal.relation) > 0
|
|
689
880
|
);
|
|
690
881
|
}
|
|
691
|
-
function
|
|
882
|
+
function storedMergeFor(algebra) {
|
|
883
|
+
return (stored, incoming) => {
|
|
884
|
+
const merged = algebra.merge(
|
|
885
|
+
stored === void 0 ? algebra.asserted : stored,
|
|
886
|
+
incoming
|
|
887
|
+
);
|
|
888
|
+
return stored === void 0 && merged === algebra.asserted ? void 0 : merged;
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
function runRules(db, rules, algebra) {
|
|
692
892
|
const {
|
|
693
893
|
signature,
|
|
694
894
|
name: ruleSetName,
|
|
@@ -709,37 +909,64 @@ function runRules(db, rules) {
|
|
|
709
909
|
state.derived.clear();
|
|
710
910
|
}
|
|
711
911
|
const derived = state.derived;
|
|
912
|
+
const noteDerived = (relation, tuple) => {
|
|
913
|
+
let ledger = derived.get(relation);
|
|
914
|
+
if (ledger === void 0) {
|
|
915
|
+
ledger = /* @__PURE__ */ new Map();
|
|
916
|
+
derived.set(relation, ledger);
|
|
917
|
+
}
|
|
918
|
+
ledger.set(keyOf2(tuple), tuple);
|
|
919
|
+
};
|
|
920
|
+
const mergeStored = algebra === void 0 ? void 0 : storedMergeFor(algebra);
|
|
712
921
|
const marks = canResume(rules, state) ? state.marks : void 0;
|
|
713
922
|
for (const stratum of strata) {
|
|
714
923
|
let delta = /* @__PURE__ */ new Map();
|
|
715
924
|
const derivedHere = new Set(stratum.map((r) => r.head.relation));
|
|
716
925
|
const record = (relation, tuple) => {
|
|
717
|
-
if (db.add(relation, tuple)) {
|
|
926
|
+
if (db.add(relation, tuple) === "added") {
|
|
718
927
|
addTo(delta, relation, tuple);
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
928
|
+
noteDerived(relation, tuple);
|
|
929
|
+
}
|
|
930
|
+
};
|
|
931
|
+
const recordTagged = mergeStored === void 0 ? void 0 : (relation, tuple, tag) => {
|
|
932
|
+
const outcome = db.add(relation, tuple, tag, mergeStored);
|
|
933
|
+
if (outcome === "unchanged") {
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
addTo(delta, relation, tuple);
|
|
937
|
+
if (outcome === "added") {
|
|
938
|
+
noteDerived(relation, tuple);
|
|
939
|
+
}
|
|
940
|
+
};
|
|
941
|
+
const deriveInto = (r, seed, deltaIndex) => {
|
|
942
|
+
if (algebra !== void 0 && recordTagged !== void 0) {
|
|
943
|
+
for (const found of evaluateRuleTagged(
|
|
944
|
+
db,
|
|
945
|
+
seed,
|
|
946
|
+
r,
|
|
947
|
+
deltaIndex,
|
|
948
|
+
algebra
|
|
949
|
+
)) {
|
|
950
|
+
recordTagged(r.head.relation, found.tuple, found.tag);
|
|
723
951
|
}
|
|
724
|
-
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
for (const tuple of evaluateRule(db, seed, r, deltaIndex)) {
|
|
955
|
+
record(r.head.relation, tuple);
|
|
725
956
|
}
|
|
726
957
|
};
|
|
727
958
|
const runOneRule = (r, seed, deltaIndex) => {
|
|
728
959
|
if (!isProfiling()) {
|
|
729
|
-
|
|
730
|
-
record(r.head.relation, tuple);
|
|
731
|
-
}
|
|
960
|
+
deriveInto(r, seed, deltaIndex);
|
|
732
961
|
return;
|
|
733
962
|
}
|
|
734
963
|
const startedAt = performance.now();
|
|
735
964
|
const before = db.size(r.head.relation);
|
|
736
|
-
|
|
737
|
-
record(r.head.relation, tuple);
|
|
738
|
-
}
|
|
965
|
+
deriveInto(r, seed, deltaIndex);
|
|
739
966
|
chargeRule(
|
|
740
967
|
ruleSetName,
|
|
741
968
|
r.head.relation,
|
|
742
|
-
r.body.map(
|
|
969
|
+
r.body.map(literalName),
|
|
743
970
|
performance.now() - startedAt,
|
|
744
971
|
db.size(r.head.relation) - before
|
|
745
972
|
);
|
|
@@ -806,7 +1033,10 @@ function addTo(buckets, relation, tuple) {
|
|
|
806
1033
|
}
|
|
807
1034
|
export {
|
|
808
1035
|
Database,
|
|
1036
|
+
Witness,
|
|
809
1037
|
clearRelations,
|
|
1038
|
+
confidence,
|
|
1039
|
+
confidenceWith,
|
|
810
1040
|
constant,
|
|
811
1041
|
deriveOnDemand,
|
|
812
1042
|
evaluate,
|
|
@@ -815,9 +1045,12 @@ export {
|
|
|
815
1045
|
notLit,
|
|
816
1046
|
profileEvaluation,
|
|
817
1047
|
profileEvaluationAsync,
|
|
1048
|
+
proofOf,
|
|
818
1049
|
rule,
|
|
1050
|
+
ruleLabel,
|
|
819
1051
|
stratify,
|
|
820
1052
|
tupleKey,
|
|
821
1053
|
tupleKeyParts,
|
|
822
|
-
variable
|
|
1054
|
+
variable,
|
|
1055
|
+
witnesses
|
|
823
1056
|
};
|
package/package.json
CHANGED