@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.cjs
CHANGED
|
@@ -21,7 +21,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
Database: () => Database,
|
|
24
|
+
Witness: () => Witness,
|
|
24
25
|
clearRelations: () => clearRelations,
|
|
26
|
+
confidence: () => confidence,
|
|
27
|
+
confidenceWith: () => confidenceWith,
|
|
25
28
|
constant: () => constant,
|
|
26
29
|
deriveOnDemand: () => deriveOnDemand,
|
|
27
30
|
evaluate: () => evaluate,
|
|
@@ -30,14 +33,161 @@ __export(index_exports, {
|
|
|
30
33
|
notLit: () => notLit,
|
|
31
34
|
profileEvaluation: () => profileEvaluation,
|
|
32
35
|
profileEvaluationAsync: () => profileEvaluationAsync,
|
|
36
|
+
proofOf: () => proofOf,
|
|
33
37
|
rule: () => rule,
|
|
38
|
+
ruleLabel: () => ruleLabel,
|
|
34
39
|
stratify: () => stratify,
|
|
35
40
|
tupleKey: () => tupleKey,
|
|
36
41
|
tupleKeyParts: () => tupleKeyParts,
|
|
37
|
-
variable: () => variable
|
|
42
|
+
variable: () => variable,
|
|
43
|
+
witnesses: () => witnesses
|
|
38
44
|
});
|
|
39
45
|
module.exports = __toCommonJS(index_exports);
|
|
40
46
|
|
|
47
|
+
// src/onDemand.ts
|
|
48
|
+
var adornmentKey = (adornment) => adornment.map((bound) => bound ? "b" : "f").join("");
|
|
49
|
+
var anyBound = (adornment) => adornment.some(Boolean);
|
|
50
|
+
var keyOf = ({ relation, adornment }) => `${relation}/${adornmentKey(adornment)}`;
|
|
51
|
+
var boundTerms = (terms, adornment) => terms.filter((_, column) => adornment[column]);
|
|
52
|
+
var positiveLiteral = (relation, terms) => ({
|
|
53
|
+
relation,
|
|
54
|
+
terms,
|
|
55
|
+
negated: false
|
|
56
|
+
});
|
|
57
|
+
var demandRewrites = /* @__PURE__ */ new WeakSet();
|
|
58
|
+
var isDemandRewritten = (rules) => demandRewrites.has(rules);
|
|
59
|
+
function deriveOnDemand(rules, complete) {
|
|
60
|
+
const negated = rules.find((r) => r.body.some((l) => l.negated));
|
|
61
|
+
if (negated !== void 0) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`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`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
const rulesByHead = /* @__PURE__ */ new Map();
|
|
67
|
+
for (const r of rules) {
|
|
68
|
+
const existing = rulesByHead.get(r.head.relation);
|
|
69
|
+
if (existing === void 0) {
|
|
70
|
+
rulesByHead.set(r.head.relation, [r]);
|
|
71
|
+
} else {
|
|
72
|
+
existing.push(r);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const derives = (relation) => rulesByHead.has(relation);
|
|
76
|
+
const asked = /* @__PURE__ */ new Map();
|
|
77
|
+
const pending = [];
|
|
78
|
+
const want = (adorned) => {
|
|
79
|
+
const key = keyOf(adorned);
|
|
80
|
+
if (!asked.has(key)) {
|
|
81
|
+
asked.set(key, adorned);
|
|
82
|
+
pending.push(adorned);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
for (const relation of complete) {
|
|
86
|
+
const derivedBy = rulesByHead.get(relation);
|
|
87
|
+
if (derivedBy === void 0) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`no rule derives "${relation}", so it cannot be asked for in full`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
want({ relation, adornment: derivedBy[0].head.terms.map(() => false) });
|
|
93
|
+
}
|
|
94
|
+
while (pending.length > 0) {
|
|
95
|
+
const adorned = pending.pop();
|
|
96
|
+
for (const r of rulesByHead.get(adorned.relation) ?? []) {
|
|
97
|
+
const adornments = bodyAdornments(r, adorned.adornment, derives);
|
|
98
|
+
r.body.forEach((literal, at) => {
|
|
99
|
+
const adornment = adornments[at];
|
|
100
|
+
if (adornment !== null) {
|
|
101
|
+
want({ relation: literal.relation, adornment });
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const variantsOf = /* @__PURE__ */ new Map();
|
|
107
|
+
for (const { relation } of asked.values()) {
|
|
108
|
+
variantsOf.set(relation, (variantsOf.get(relation) ?? 0) + 1);
|
|
109
|
+
}
|
|
110
|
+
const nameOf = (relation, adornment) => {
|
|
111
|
+
if (!anyBound(adornment) || variantsOf.get(relation) === 1) {
|
|
112
|
+
return relation;
|
|
113
|
+
}
|
|
114
|
+
return `${relation}:${adornmentKey(adornment)}`;
|
|
115
|
+
};
|
|
116
|
+
const demandOn = (relation, adornment) => `wanted:${nameOf(relation, adornment)}`;
|
|
117
|
+
const rewritten = [];
|
|
118
|
+
const demandDriven = /* @__PURE__ */ new Set();
|
|
119
|
+
const already = /* @__PURE__ */ new Set();
|
|
120
|
+
const emit = (r) => {
|
|
121
|
+
const key = JSON.stringify(r);
|
|
122
|
+
if (!already.has(key)) {
|
|
123
|
+
already.add(key);
|
|
124
|
+
rewritten.push(r);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
for (const adorned of asked.values()) {
|
|
128
|
+
if (anyBound(adorned.adornment)) {
|
|
129
|
+
demandDriven.add(nameOf(adorned.relation, adorned.adornment));
|
|
130
|
+
demandDriven.add(demandOn(adorned.relation, adorned.adornment));
|
|
131
|
+
}
|
|
132
|
+
for (const r of rulesByHead.get(adorned.relation) ?? []) {
|
|
133
|
+
const guard = anyBound(adorned.adornment) ? [
|
|
134
|
+
positiveLiteral(
|
|
135
|
+
demandOn(adorned.relation, adorned.adornment),
|
|
136
|
+
boundTerms(r.head.terms, adorned.adornment)
|
|
137
|
+
)
|
|
138
|
+
] : [];
|
|
139
|
+
const adornments = bodyAdornments(r, adorned.adornment, derives);
|
|
140
|
+
const body = [];
|
|
141
|
+
r.body.forEach((literal, at) => {
|
|
142
|
+
const adornment = adornments[at];
|
|
143
|
+
if (adornment !== null && anyBound(adornment)) {
|
|
144
|
+
emit({
|
|
145
|
+
head: {
|
|
146
|
+
relation: demandOn(literal.relation, adornment),
|
|
147
|
+
terms: boundTerms(literal.terms, adornment)
|
|
148
|
+
},
|
|
149
|
+
body: [...guard, ...body]
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
body.push(
|
|
153
|
+
adornment === null ? literal : positiveLiteral(
|
|
154
|
+
nameOf(literal.relation, adornment),
|
|
155
|
+
literal.terms
|
|
156
|
+
)
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
emit({
|
|
160
|
+
head: {
|
|
161
|
+
relation: nameOf(adorned.relation, adorned.adornment),
|
|
162
|
+
terms: r.head.terms
|
|
163
|
+
},
|
|
164
|
+
body: [...guard, ...body]
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
demandRewrites.add(rewritten);
|
|
169
|
+
return { rules: rewritten, demandDriven: [...demandDriven] };
|
|
170
|
+
}
|
|
171
|
+
function bodyAdornments(r, headAdornment, derives) {
|
|
172
|
+
const bound = /* @__PURE__ */ new Set();
|
|
173
|
+
r.head.terms.forEach((term, column) => {
|
|
174
|
+
if (headAdornment[column] && term.type === "variable") {
|
|
175
|
+
bound.add(term.name);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
return r.body.map((literal) => {
|
|
179
|
+
const adornment = derives(literal.relation) ? literal.terms.map(
|
|
180
|
+
(term) => term.type === "constant" || bound.has(term.name)
|
|
181
|
+
) : null;
|
|
182
|
+
for (const term of literal.terms) {
|
|
183
|
+
if (term.type === "variable") {
|
|
184
|
+
bound.add(term.name);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return adornment;
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
41
191
|
// src/profile.ts
|
|
42
192
|
var open = [];
|
|
43
193
|
var isProfiling = () => open.length > 0;
|
|
@@ -229,145 +379,92 @@ function tupleKeyParts(key) {
|
|
|
229
379
|
return parts;
|
|
230
380
|
}
|
|
231
381
|
|
|
232
|
-
// src/
|
|
233
|
-
var
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
rulesByHead.set(r.head.relation, [r]);
|
|
254
|
-
} else {
|
|
255
|
-
existing.push(r);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
const derives = (relation) => rulesByHead.has(relation);
|
|
259
|
-
const asked = /* @__PURE__ */ new Map();
|
|
260
|
-
const pending = [];
|
|
261
|
-
const want = (adorned) => {
|
|
262
|
-
const key = keyOf(adorned);
|
|
263
|
-
if (!asked.has(key)) {
|
|
264
|
-
asked.set(key, adorned);
|
|
265
|
-
pending.push(adorned);
|
|
382
|
+
// src/confidence.ts
|
|
383
|
+
var RANK = {
|
|
384
|
+
low: 0,
|
|
385
|
+
medium: 1,
|
|
386
|
+
high: 2
|
|
387
|
+
};
|
|
388
|
+
function confidenceWith(levelOf) {
|
|
389
|
+
return {
|
|
390
|
+
asserted: "high",
|
|
391
|
+
absent: "high",
|
|
392
|
+
combine(bodyTags, derivation) {
|
|
393
|
+
let lowest = levelOf(derivation.rule) ?? "high";
|
|
394
|
+
for (const tag of bodyTags) {
|
|
395
|
+
if (RANK[tag] < RANK[lowest]) {
|
|
396
|
+
lowest = tag;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return lowest;
|
|
400
|
+
},
|
|
401
|
+
merge(stored, incoming) {
|
|
402
|
+
return RANK[incoming] > RANK[stored] ? incoming : stored;
|
|
266
403
|
}
|
|
267
404
|
};
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
405
|
+
}
|
|
406
|
+
var confidence = confidenceWith(
|
|
407
|
+
() => void 0
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
// src/witness.ts
|
|
411
|
+
var Witness = class {
|
|
412
|
+
constructor(rule2, body) {
|
|
413
|
+
this.rule = rule2;
|
|
414
|
+
this.body = body;
|
|
276
415
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
416
|
+
rule;
|
|
417
|
+
body;
|
|
418
|
+
};
|
|
419
|
+
var witnesses = {
|
|
420
|
+
asserted: "asserted",
|
|
421
|
+
absent: "absent",
|
|
422
|
+
// A witness is built from the derivation alone, and saying so lets
|
|
423
|
+
// the evaluator skip the body-tag lookups, which were most of the
|
|
424
|
+
// tagged path's cost on a large sweep.
|
|
425
|
+
ignoresBodyTags: true,
|
|
426
|
+
combine: (_bodyTags, derivation) => new Witness(derivation.rule, derivation.body),
|
|
427
|
+
merge: (stored) => stored
|
|
428
|
+
};
|
|
429
|
+
var DEFAULT_MAX_DEPTH = 128;
|
|
430
|
+
function proofOf(db, relation, tuple, options = {}) {
|
|
431
|
+
return walk(
|
|
432
|
+
db,
|
|
433
|
+
relation,
|
|
434
|
+
tuple,
|
|
435
|
+
options.maxDepth ?? DEFAULT_MAX_DEPTH,
|
|
436
|
+
/* @__PURE__ */ new Set()
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
var premiseOf = {
|
|
440
|
+
fact: (db, match, depth, path) => walk(db, match.relation, match.tuple, depth, path),
|
|
441
|
+
absence: (_db, match) => ({
|
|
442
|
+
kind: "absence",
|
|
443
|
+
relation: match.relation,
|
|
444
|
+
tuple: match.tuple
|
|
445
|
+
})
|
|
446
|
+
};
|
|
447
|
+
function walk(db, relation, tuple, depth, path) {
|
|
448
|
+
if (!db.has(relation, tuple)) {
|
|
449
|
+
return { kind: "absence", relation, tuple };
|
|
288
450
|
}
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
451
|
+
const tag = db.tagOf(relation, tuple);
|
|
452
|
+
if (!(tag instanceof Witness)) {
|
|
453
|
+
return { kind: "fact", relation, tuple };
|
|
292
454
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
return relation;
|
|
296
|
-
}
|
|
297
|
-
return `${relation}:${adornmentKey(adornment)}`;
|
|
298
|
-
};
|
|
299
|
-
const demandOn = (relation, adornment) => `wanted:${nameOf(relation, adornment)}`;
|
|
300
|
-
const rewritten = [];
|
|
301
|
-
const demandDriven = /* @__PURE__ */ new Set();
|
|
302
|
-
const already = /* @__PURE__ */ new Set();
|
|
303
|
-
const emit = (r) => {
|
|
304
|
-
const key = JSON.stringify(r);
|
|
305
|
-
if (!already.has(key)) {
|
|
306
|
-
already.add(key);
|
|
307
|
-
rewritten.push(r);
|
|
308
|
-
}
|
|
309
|
-
};
|
|
310
|
-
for (const adorned of asked.values()) {
|
|
311
|
-
if (anyBound(adorned.adornment)) {
|
|
312
|
-
demandDriven.add(nameOf(adorned.relation, adorned.adornment));
|
|
313
|
-
demandDriven.add(demandOn(adorned.relation, adorned.adornment));
|
|
314
|
-
}
|
|
315
|
-
for (const r of rulesByHead.get(adorned.relation) ?? []) {
|
|
316
|
-
const guard = anyBound(adorned.adornment) ? [
|
|
317
|
-
positiveLiteral(
|
|
318
|
-
demandOn(adorned.relation, adorned.adornment),
|
|
319
|
-
boundTerms(r.head.terms, adorned.adornment)
|
|
320
|
-
)
|
|
321
|
-
] : [];
|
|
322
|
-
const adornments = bodyAdornments(r, adorned.adornment, derives);
|
|
323
|
-
const body = [];
|
|
324
|
-
r.body.forEach((literal, at) => {
|
|
325
|
-
const adornment = adornments[at];
|
|
326
|
-
if (adornment !== null && anyBound(adornment)) {
|
|
327
|
-
emit({
|
|
328
|
-
head: {
|
|
329
|
-
relation: demandOn(literal.relation, adornment),
|
|
330
|
-
terms: boundTerms(literal.terms, adornment)
|
|
331
|
-
},
|
|
332
|
-
body: [...guard, ...body]
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
body.push(
|
|
336
|
-
adornment === null ? literal : positiveLiteral(
|
|
337
|
-
nameOf(literal.relation, adornment),
|
|
338
|
-
literal.terms
|
|
339
|
-
)
|
|
340
|
-
);
|
|
341
|
-
});
|
|
342
|
-
emit({
|
|
343
|
-
head: {
|
|
344
|
-
relation: nameOf(adorned.relation, adorned.adornment),
|
|
345
|
-
terms: r.head.terms
|
|
346
|
-
},
|
|
347
|
-
body: [...guard, ...body]
|
|
348
|
-
});
|
|
349
|
-
}
|
|
455
|
+
if (depth <= 0) {
|
|
456
|
+
return { kind: "truncated", relation, tuple, reason: "depth" };
|
|
350
457
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
return
|
|
361
|
-
const adornment = derives(literal.relation) ? literal.terms.map(
|
|
362
|
-
(term) => term.type === "constant" || bound.has(term.name)
|
|
363
|
-
) : null;
|
|
364
|
-
for (const term of literal.terms) {
|
|
365
|
-
if (term.type === "variable") {
|
|
366
|
-
bound.add(term.name);
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
return adornment;
|
|
370
|
-
});
|
|
458
|
+
const key = tupleKey([relation, ...tuple]);
|
|
459
|
+
if (path.has(key)) {
|
|
460
|
+
return { kind: "truncated", relation, tuple, reason: "cycle" };
|
|
461
|
+
}
|
|
462
|
+
path.add(key);
|
|
463
|
+
const premises = tag.body.map(
|
|
464
|
+
(match) => premiseOf[match.kind](db, match, depth - 1, path)
|
|
465
|
+
);
|
|
466
|
+
path.delete(key);
|
|
467
|
+
return { kind: "derived", relation, tuple, rule: tag.rule, premises };
|
|
371
468
|
}
|
|
372
469
|
|
|
373
470
|
// src/index.ts
|
|
@@ -383,8 +480,16 @@ var notLit = (relation, ...terms) => ({
|
|
|
383
480
|
terms,
|
|
384
481
|
negated: true
|
|
385
482
|
});
|
|
386
|
-
var rule = (relation, terms, body) =>
|
|
483
|
+
var rule = (relation, terms, body, name) => name === void 0 ? { head: { relation, terms }, body } : { head: { relation, terms }, body, name };
|
|
484
|
+
var literalName = (literal) => literal.negated ? `!${literal.relation}` : literal.relation;
|
|
485
|
+
var ruleLabel = (r) => r.name ?? `${r.head.relation} :- ${r.body.map(literalName).join(", ")}`;
|
|
387
486
|
var keyOf2 = (tuple) => tupleKey(tuple);
|
|
487
|
+
function tagSlots(relation) {
|
|
488
|
+
if (relation.tags === void 0) {
|
|
489
|
+
relation.tags = /* @__PURE__ */ new Map();
|
|
490
|
+
}
|
|
491
|
+
return relation.tags;
|
|
492
|
+
}
|
|
388
493
|
function addToBucket(index, key, tuple) {
|
|
389
494
|
const bucket = index.get(key);
|
|
390
495
|
if (bucket === void 0) {
|
|
@@ -403,30 +508,53 @@ var Database = class {
|
|
|
403
508
|
const created = {
|
|
404
509
|
keys: /* @__PURE__ */ new Set(),
|
|
405
510
|
tuples: [],
|
|
511
|
+
tags: void 0,
|
|
406
512
|
indexes: /* @__PURE__ */ new Map()
|
|
407
513
|
};
|
|
408
514
|
this.store.set(name, created);
|
|
409
515
|
return created;
|
|
410
516
|
}
|
|
411
|
-
/**
|
|
412
|
-
|
|
517
|
+
/**
|
|
518
|
+
* Add a fact, or merge a tag into one already present. A new fact
|
|
519
|
+
* stores `tag` when one is given. For a fact already present,
|
|
520
|
+
* `merge` gets the stored tag (undefined when there is none) and the
|
|
521
|
+
* incoming one; returning anything but the stored value stores the
|
|
522
|
+
* result and reports "improved".
|
|
523
|
+
*/
|
|
524
|
+
add(relationName, tuple, tag, merge) {
|
|
413
525
|
const relation = this.relation(relationName);
|
|
414
526
|
const key = keyOf2(tuple);
|
|
415
527
|
if (relation.keys.has(key)) {
|
|
528
|
+
let outcome = "unchanged";
|
|
529
|
+
if (merge !== void 0) {
|
|
530
|
+
const stored = relation.tags?.get(key);
|
|
531
|
+
const merged = merge(stored, tag);
|
|
532
|
+
if (merged !== stored) {
|
|
533
|
+
tagSlots(relation).set(key, merged);
|
|
534
|
+
outcome = "improved";
|
|
535
|
+
}
|
|
536
|
+
}
|
|
416
537
|
if (!isDeriving(this)) {
|
|
417
538
|
claimFact(this, relationName, key);
|
|
418
539
|
}
|
|
419
|
-
return
|
|
540
|
+
return outcome;
|
|
420
541
|
}
|
|
421
542
|
relation.keys.add(key);
|
|
422
543
|
relation.tuples.push(tuple);
|
|
544
|
+
if (tag !== void 0) {
|
|
545
|
+
tagSlots(relation).set(key, tag);
|
|
546
|
+
}
|
|
423
547
|
for (const [column, index] of relation.indexes) {
|
|
424
548
|
const value = tuple[column];
|
|
425
549
|
if (value !== void 0) {
|
|
426
550
|
addToBucket(index, value, tuple);
|
|
427
551
|
}
|
|
428
552
|
}
|
|
429
|
-
return
|
|
553
|
+
return "added";
|
|
554
|
+
}
|
|
555
|
+
/** The tag stored for this fact, or undefined when there is none. */
|
|
556
|
+
tagOf(relationName, tuple) {
|
|
557
|
+
return this.store.get(relationName)?.tags?.get(keyOf2(tuple));
|
|
430
558
|
}
|
|
431
559
|
has(relationName, tuple) {
|
|
432
560
|
return this.store.get(relationName)?.keys.has(keyOf2(tuple)) ?? false;
|
|
@@ -485,6 +613,7 @@ var Database = class {
|
|
|
485
613
|
}
|
|
486
614
|
for (const key of going) {
|
|
487
615
|
relation.keys.delete(key);
|
|
616
|
+
relation.tags?.delete(key);
|
|
488
617
|
}
|
|
489
618
|
relation.tuples = relation.tuples.filter(
|
|
490
619
|
(tuple) => !going.has(keyOf2(tuple))
|
|
@@ -625,6 +754,69 @@ function boundSource(db, literal, bindings) {
|
|
|
625
754
|
}
|
|
626
755
|
return db.facts(literal.relation);
|
|
627
756
|
}
|
|
757
|
+
var EMPTY_TAGS = [];
|
|
758
|
+
function evaluateRuleTagged(db, deltas, r, deltaIndex, algebra) {
|
|
759
|
+
const results = [];
|
|
760
|
+
const bodyTags = [];
|
|
761
|
+
const bodyMatches = [];
|
|
762
|
+
const readsTags = algebra.ignoresBodyTags !== true;
|
|
763
|
+
const step = (literalIndex, positiveIndex, bindings) => {
|
|
764
|
+
if (literalIndex === r.body.length) {
|
|
765
|
+
results.push({
|
|
766
|
+
tuple: headTuple(r.head, bindings),
|
|
767
|
+
tag: algebra.combine(readsTags ? bodyTags.slice() : EMPTY_TAGS, {
|
|
768
|
+
rule: r,
|
|
769
|
+
body: bodyMatches.slice()
|
|
770
|
+
})
|
|
771
|
+
});
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
const literal = r.body[literalIndex];
|
|
775
|
+
if (literal.negated) {
|
|
776
|
+
const grounded = groundNegated(literal, bindings);
|
|
777
|
+
if (!db.has(literal.relation, grounded)) {
|
|
778
|
+
if (readsTags) {
|
|
779
|
+
bodyTags.push(algebra.absent);
|
|
780
|
+
}
|
|
781
|
+
bodyMatches.push({
|
|
782
|
+
kind: "absence",
|
|
783
|
+
relation: literal.relation,
|
|
784
|
+
tuple: grounded
|
|
785
|
+
});
|
|
786
|
+
step(literalIndex + 1, positiveIndex, bindings);
|
|
787
|
+
if (readsTags) {
|
|
788
|
+
bodyTags.pop();
|
|
789
|
+
}
|
|
790
|
+
bodyMatches.pop();
|
|
791
|
+
}
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
const source = positiveIndex === deltaIndex ? deltas.get(literal.relation) ?? [] : boundSource(db, literal, bindings);
|
|
795
|
+
for (const tuple of source) {
|
|
796
|
+
const next = unify(literal, tuple, bindings);
|
|
797
|
+
if (next !== null) {
|
|
798
|
+
if (readsTags) {
|
|
799
|
+
const stored = db.tagOf(literal.relation, tuple);
|
|
800
|
+
bodyTags.push(
|
|
801
|
+
stored === void 0 ? algebra.asserted : stored
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
bodyMatches.push({
|
|
805
|
+
kind: "fact",
|
|
806
|
+
relation: literal.relation,
|
|
807
|
+
tuple
|
|
808
|
+
});
|
|
809
|
+
step(literalIndex + 1, positiveIndex + 1, next);
|
|
810
|
+
if (readsTags) {
|
|
811
|
+
bodyTags.pop();
|
|
812
|
+
}
|
|
813
|
+
bodyMatches.pop();
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
};
|
|
817
|
+
step(0, 0, /* @__PURE__ */ new Map());
|
|
818
|
+
return results;
|
|
819
|
+
}
|
|
628
820
|
var evaluated = /* @__PURE__ */ new WeakMap();
|
|
629
821
|
var deriving = /* @__PURE__ */ new Map();
|
|
630
822
|
function isDeriving(db) {
|
|
@@ -684,10 +876,15 @@ function currentMarks(db) {
|
|
|
684
876
|
}
|
|
685
877
|
return marks;
|
|
686
878
|
}
|
|
687
|
-
function evaluate(db, rules) {
|
|
879
|
+
function evaluate(db, rules, algebra) {
|
|
880
|
+
if (algebra !== void 0 && isDemandRewritten(rules)) {
|
|
881
|
+
throw new Error(
|
|
882
|
+
"cannot evaluate demand-rewritten rules with a tag algebra: tags would follow the demand-transformed rules rather than the ones the caller wrote"
|
|
883
|
+
);
|
|
884
|
+
}
|
|
688
885
|
deriving.set(db, (deriving.get(db) ?? 0) + 1);
|
|
689
886
|
try {
|
|
690
|
-
return runRules(db, rules);
|
|
887
|
+
return runRules(db, rules, algebra);
|
|
691
888
|
} finally {
|
|
692
889
|
const depth = (deriving.get(db) ?? 1) - 1;
|
|
693
890
|
if (depth === 0) {
|
|
@@ -728,7 +925,16 @@ function couldProduce(db, rule2) {
|
|
|
728
925
|
(literal) => literal.negated || db.size(literal.relation) > 0
|
|
729
926
|
);
|
|
730
927
|
}
|
|
731
|
-
function
|
|
928
|
+
function storedMergeFor(algebra) {
|
|
929
|
+
return (stored, incoming) => {
|
|
930
|
+
const merged = algebra.merge(
|
|
931
|
+
stored === void 0 ? algebra.asserted : stored,
|
|
932
|
+
incoming
|
|
933
|
+
);
|
|
934
|
+
return stored === void 0 && merged === algebra.asserted ? void 0 : merged;
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
function runRules(db, rules, algebra) {
|
|
732
938
|
const {
|
|
733
939
|
signature,
|
|
734
940
|
name: ruleSetName,
|
|
@@ -749,37 +955,64 @@ function runRules(db, rules) {
|
|
|
749
955
|
state.derived.clear();
|
|
750
956
|
}
|
|
751
957
|
const derived = state.derived;
|
|
958
|
+
const noteDerived = (relation, tuple) => {
|
|
959
|
+
let ledger = derived.get(relation);
|
|
960
|
+
if (ledger === void 0) {
|
|
961
|
+
ledger = /* @__PURE__ */ new Map();
|
|
962
|
+
derived.set(relation, ledger);
|
|
963
|
+
}
|
|
964
|
+
ledger.set(keyOf2(tuple), tuple);
|
|
965
|
+
};
|
|
966
|
+
const mergeStored = algebra === void 0 ? void 0 : storedMergeFor(algebra);
|
|
752
967
|
const marks = canResume(rules, state) ? state.marks : void 0;
|
|
753
968
|
for (const stratum of strata) {
|
|
754
969
|
let delta = /* @__PURE__ */ new Map();
|
|
755
970
|
const derivedHere = new Set(stratum.map((r) => r.head.relation));
|
|
756
971
|
const record = (relation, tuple) => {
|
|
757
|
-
if (db.add(relation, tuple)) {
|
|
972
|
+
if (db.add(relation, tuple) === "added") {
|
|
758
973
|
addTo(delta, relation, tuple);
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
974
|
+
noteDerived(relation, tuple);
|
|
975
|
+
}
|
|
976
|
+
};
|
|
977
|
+
const recordTagged = mergeStored === void 0 ? void 0 : (relation, tuple, tag) => {
|
|
978
|
+
const outcome = db.add(relation, tuple, tag, mergeStored);
|
|
979
|
+
if (outcome === "unchanged") {
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
addTo(delta, relation, tuple);
|
|
983
|
+
if (outcome === "added") {
|
|
984
|
+
noteDerived(relation, tuple);
|
|
985
|
+
}
|
|
986
|
+
};
|
|
987
|
+
const deriveInto = (r, seed, deltaIndex) => {
|
|
988
|
+
if (algebra !== void 0 && recordTagged !== void 0) {
|
|
989
|
+
for (const found of evaluateRuleTagged(
|
|
990
|
+
db,
|
|
991
|
+
seed,
|
|
992
|
+
r,
|
|
993
|
+
deltaIndex,
|
|
994
|
+
algebra
|
|
995
|
+
)) {
|
|
996
|
+
recordTagged(r.head.relation, found.tuple, found.tag);
|
|
763
997
|
}
|
|
764
|
-
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
for (const tuple of evaluateRule(db, seed, r, deltaIndex)) {
|
|
1001
|
+
record(r.head.relation, tuple);
|
|
765
1002
|
}
|
|
766
1003
|
};
|
|
767
1004
|
const runOneRule = (r, seed, deltaIndex) => {
|
|
768
1005
|
if (!isProfiling()) {
|
|
769
|
-
|
|
770
|
-
record(r.head.relation, tuple);
|
|
771
|
-
}
|
|
1006
|
+
deriveInto(r, seed, deltaIndex);
|
|
772
1007
|
return;
|
|
773
1008
|
}
|
|
774
1009
|
const startedAt = performance.now();
|
|
775
1010
|
const before = db.size(r.head.relation);
|
|
776
|
-
|
|
777
|
-
record(r.head.relation, tuple);
|
|
778
|
-
}
|
|
1011
|
+
deriveInto(r, seed, deltaIndex);
|
|
779
1012
|
chargeRule(
|
|
780
1013
|
ruleSetName,
|
|
781
1014
|
r.head.relation,
|
|
782
|
-
r.body.map(
|
|
1015
|
+
r.body.map(literalName),
|
|
783
1016
|
performance.now() - startedAt,
|
|
784
1017
|
db.size(r.head.relation) - before
|
|
785
1018
|
);
|
|
@@ -847,7 +1080,10 @@ function addTo(buckets, relation, tuple) {
|
|
|
847
1080
|
// Annotate the CommonJS export names for ESM import in node:
|
|
848
1081
|
0 && (module.exports = {
|
|
849
1082
|
Database,
|
|
1083
|
+
Witness,
|
|
850
1084
|
clearRelations,
|
|
1085
|
+
confidence,
|
|
1086
|
+
confidenceWith,
|
|
851
1087
|
constant,
|
|
852
1088
|
deriveOnDemand,
|
|
853
1089
|
evaluate,
|
|
@@ -856,9 +1092,12 @@ function addTo(buckets, relation, tuple) {
|
|
|
856
1092
|
notLit,
|
|
857
1093
|
profileEvaluation,
|
|
858
1094
|
profileEvaluationAsync,
|
|
1095
|
+
proofOf,
|
|
859
1096
|
rule,
|
|
1097
|
+
ruleLabel,
|
|
860
1098
|
stratify,
|
|
861
1099
|
tupleKey,
|
|
862
1100
|
tupleKeyParts,
|
|
863
|
-
variable
|
|
1101
|
+
variable,
|
|
1102
|
+
witnesses
|
|
864
1103
|
});
|