factbag 0.0.0-alpha.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/CHANGELOG.md +5 -0
- package/LICENSE +202 -0
- package/README.md +44 -0
- package/dist/core/chunk-4IP7CXNV.js +86 -0
- package/dist/core/chunk-UVOCOH2I.js +41 -0
- package/dist/core/chunk-X3LSDJL4.js +59 -0
- package/dist/core/chunk-YF4PP42K.js +8 -0
- package/dist/core/dsl/enumeration.d.ts +3 -0
- package/dist/core/dsl/formula.d.ts +62 -0
- package/dist/core/dsl/index.d.ts +21 -0
- package/dist/core/dsl/index.js +132 -0
- package/dist/core/dsl/operators/aggregate.d.ts +30 -0
- package/dist/core/dsl/operators/arithmetic.d.ts +27 -0
- package/dist/core/dsl/operators/comparison.d.ts +13 -0
- package/dist/core/dsl/predicate.d.ts +33 -0
- package/dist/core/dsl/types.d.ts +59 -0
- package/dist/core/dsl/variable.d.ts +3 -0
- package/dist/core/exceptions/programmer-errors.d.ts +36 -0
- package/dist/core/explanation/explain.d.ts +20 -0
- package/dist/core/explanation/index.d.ts +8 -0
- package/dist/core/explanation/index.js +187 -0
- package/dist/core/explanation/types.d.ts +172 -0
- package/dist/core/knowledge-base/assertions/exclusive-definitions.d.ts +3 -0
- package/dist/core/knowledge-base/index.d.ts +13 -0
- package/dist/core/knowledge-base/index.js +196 -0
- package/dist/core/knowledge-base/knowledge-base.d.ts +81 -0
- package/dist/core/query/index.d.ts +10 -0
- package/dist/core/query/index.js +1947 -0
- package/dist/core/query/query.d.ts +47 -0
- package/dist/core/solver/candidate-search.d.ts +12 -0
- package/dist/core/solver/goal-value.d.ts +6 -0
- package/dist/core/solver/matching.d.ts +10 -0
- package/dist/core/solver/query-validation/constructive-recursion.d.ts +3 -0
- package/dist/core/solver/query-validation/constructive-variable-use.d.ts +3 -0
- package/dist/core/solver/query-validation/context.d.ts +10 -0
- package/dist/core/solver/query-validation/index.d.ts +1 -0
- package/dist/core/solver/query-validation/utils.d.ts +27 -0
- package/dist/core/solver/query-validation/validate-query.d.ts +6 -0
- package/dist/core/solver/solve-all.d.ts +6 -0
- package/dist/core/solver/solve.d.ts +6 -0
- package/dist/core/types/annotations.d.ts +6 -0
- package/dist/core/types/atom.d.ts +55 -0
- package/dist/core/types/common.d.ts +7 -0
- package/dist/core/types/evidence-metadata.d.ts +18 -0
- package/dist/core/types/goal.d.ts +103 -0
- package/dist/core/types/judgment.d.ts +137 -0
- package/dist/core/types/literal.d.ts +25 -0
- package/dist/core/types/predicate.d.ts +35 -0
- package/dist/core/types/rule.d.ts +16 -0
- package/dist/core/types/schema.d.ts +33 -0
- package/dist/core/types/variable.d.ts +13 -0
- package/dist/core/utils/assert.d.ts +4 -0
- package/dist/core/utils/normalize-to-list.d.ts +2 -0
- package/dist/corpus/chunkers.d.ts +7 -0
- package/dist/corpus/corpus.d.ts +41 -0
- package/dist/corpus/index.d.ts +8 -0
- package/dist/corpus/index.js +312 -0
- package/dist/corpus/records.d.ts +8 -0
- package/dist/corpus/text.d.ts +6 -0
- package/dist/corpus/types.d.ts +62 -0
- package/dist/guards/guarded-kb.d.ts +59 -0
- package/dist/guards/index.d.ts +7 -0
- package/dist/guards/index.js +121 -0
- package/dist/guards/types.d.ts +15 -0
- package/dist/jev/chunk-2UXHML67.js +206 -0
- package/dist/jev/chunk-M7RHUI2O.js +15 -0
- package/dist/jev/classifier.d.ts +28 -0
- package/dist/jev/classifier.js +171 -0
- package/dist/jev/client.d.ts +62 -0
- package/dist/jev/errors.d.ts +22 -0
- package/dist/jev/errors.js +6 -0
- package/dist/jev/jobs.d.ts +61 -0
- package/dist/jev/resolve.d.ts +82 -0
- package/dist/jev/resolve.js +99 -0
- package/dist/jev/tools.d.ts +9 -0
- package/dist/jev/types.d.ts +63 -0
- package/package.json +92 -0
|
@@ -0,0 +1,1947 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isFormula
|
|
3
|
+
} from "../chunk-YF4PP42K.js";
|
|
4
|
+
import {
|
|
5
|
+
PrimitiveArithmeticProgrammerError,
|
|
6
|
+
UnconstructiveRecursionProgrammerError,
|
|
7
|
+
UnsafeUnboundVariableProgrammerError
|
|
8
|
+
} from "../chunk-X3LSDJL4.js";
|
|
9
|
+
import {
|
|
10
|
+
assertGroundAtom,
|
|
11
|
+
atomEquals,
|
|
12
|
+
fieldEquals,
|
|
13
|
+
fieldIdentityToken,
|
|
14
|
+
isAtomForPredicate,
|
|
15
|
+
isAtomLike,
|
|
16
|
+
isGroundAtom
|
|
17
|
+
} from "../chunk-4IP7CXNV.js";
|
|
18
|
+
import {
|
|
19
|
+
AssertionError,
|
|
20
|
+
Var,
|
|
21
|
+
assert,
|
|
22
|
+
isVar
|
|
23
|
+
} from "../chunk-UVOCOH2I.js";
|
|
24
|
+
|
|
25
|
+
// src/solver/query-validation/utils.ts
|
|
26
|
+
function ruleEdges(rule) {
|
|
27
|
+
const headPredicate = rule.head.atom.predicate;
|
|
28
|
+
const headBindings = variablesIn(rule.head.atom);
|
|
29
|
+
const edges = [];
|
|
30
|
+
collectBodyEdges(rule.body, headPredicate, new Set(headBindings), edges);
|
|
31
|
+
return edges;
|
|
32
|
+
}
|
|
33
|
+
function reachablePredicates(start, edges) {
|
|
34
|
+
const reachable = /* @__PURE__ */ new Set([start]);
|
|
35
|
+
const stack = [start];
|
|
36
|
+
while (stack.length > 0) {
|
|
37
|
+
const current = stack.pop();
|
|
38
|
+
if (current === void 0) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
for (const edge of edges) {
|
|
42
|
+
if (edge.from !== current || reachable.has(edge.to)) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
reachable.add(edge.to);
|
|
46
|
+
stack.push(edge.to);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return reachable;
|
|
50
|
+
}
|
|
51
|
+
function isNegativeLiteral(value) {
|
|
52
|
+
return typeof value === "object" && value !== null && "sign" in value && value.sign === "-" && "atom" in value && isAtomLike(value.atom);
|
|
53
|
+
}
|
|
54
|
+
function isAggregateFormula(value) {
|
|
55
|
+
return isFormula(value) && (value.kind === "sum" || value.kind === "count");
|
|
56
|
+
}
|
|
57
|
+
function isCompleteWhenOption(value) {
|
|
58
|
+
return typeof value === "object" && value !== null && "completeWhen" in value;
|
|
59
|
+
}
|
|
60
|
+
function isPrimitiveFormula(value) {
|
|
61
|
+
return isFormula(value) && ["add", "sub", "mul", "div", "eq", "neq", "lt", "lte", "gt", "gte"].includes(value.kind);
|
|
62
|
+
}
|
|
63
|
+
function bindVariables(value, bound) {
|
|
64
|
+
for (const variable of variablesIn(value)) {
|
|
65
|
+
bound.add(variable);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function hasUnboundVariable(value, bound) {
|
|
69
|
+
return variablesIn(value).some((variable) => !bound.has(variable));
|
|
70
|
+
}
|
|
71
|
+
function uniqueVariables(variables) {
|
|
72
|
+
const seen = /* @__PURE__ */ new Set();
|
|
73
|
+
const unique = [];
|
|
74
|
+
for (const variable of variables) {
|
|
75
|
+
if (!seen.has(variable)) {
|
|
76
|
+
seen.add(variable);
|
|
77
|
+
unique.push(variable);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return unique;
|
|
81
|
+
}
|
|
82
|
+
function variablesIn(value) {
|
|
83
|
+
if (isVar(value)) {
|
|
84
|
+
return [value];
|
|
85
|
+
}
|
|
86
|
+
if (typeof value !== "object" || value === null) {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
if (value instanceof Date) {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
return Object.values(value).flatMap(variablesIn);
|
|
93
|
+
}
|
|
94
|
+
function collectBodyEdges(body, headPredicate, bound, edges) {
|
|
95
|
+
if (isAtomLike(body)) {
|
|
96
|
+
edges.push({ from: headPredicate, to: body.predicate, kind: "positive" });
|
|
97
|
+
bindVariables(body, bound);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (isNegativeLiteral(body)) {
|
|
101
|
+
edges.push({
|
|
102
|
+
from: headPredicate,
|
|
103
|
+
to: body.atom.predicate,
|
|
104
|
+
kind: body.atom.predicate.world === "closed" ? "closed-world-not" : "explicit-not"
|
|
105
|
+
});
|
|
106
|
+
bindVariables(body.atom, bound);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (isFormula(body, "and")) {
|
|
110
|
+
for (const arg of body.args) {
|
|
111
|
+
collectBodyEdges(arg, headPredicate, bound, edges);
|
|
112
|
+
}
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (isFormula(body, "or") || isFormula(body, "exists")) {
|
|
116
|
+
for (const arg of body.args) {
|
|
117
|
+
collectBodyEdges(arg, headPredicate, new Set(bound), edges);
|
|
118
|
+
}
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (isFormula(body, "letIn")) {
|
|
122
|
+
const [variable, value, nextBody] = body.args;
|
|
123
|
+
if (isAggregateFormula(value)) {
|
|
124
|
+
collectAggregateEdges(value, headPredicate, bound, edges);
|
|
125
|
+
} else if (hasUnboundVariable(value, bound)) {
|
|
126
|
+
edges.push({ from: headPredicate, to: headPredicate, kind: "primitive-ungrounded" });
|
|
127
|
+
}
|
|
128
|
+
if (isVar(variable)) {
|
|
129
|
+
bound.add(variable);
|
|
130
|
+
}
|
|
131
|
+
collectBodyEdges(nextBody, headPredicate, bound, edges);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (isPrimitiveFormula(body) && hasUnboundVariable(body, bound)) {
|
|
135
|
+
edges.push({ from: headPredicate, to: headPredicate, kind: "primitive-ungrounded" });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function collectAggregateEdges(aggregate, headPredicate, bound, edges) {
|
|
139
|
+
for (const arg of aggregate.args.slice(1)) {
|
|
140
|
+
collectBodyEdgesWithKind(arg, headPredicate, bound, edges, "aggregate");
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function collectBodyEdgesWithKind(body, headPredicate, bound, edges, kind) {
|
|
144
|
+
if (isAtomLike(body)) {
|
|
145
|
+
edges.push({ from: headPredicate, to: body.predicate, kind });
|
|
146
|
+
bindVariables(body, bound);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (isNegativeLiteral(body)) {
|
|
150
|
+
edges.push({ from: headPredicate, to: body.atom.predicate, kind });
|
|
151
|
+
bindVariables(body.atom, bound);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (isFormula(body)) {
|
|
155
|
+
for (const arg of body.args) {
|
|
156
|
+
collectBodyEdgesWithKind(arg, headPredicate, bound, edges, kind);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/solver/query-validation/constructive-recursion.ts
|
|
162
|
+
function assertConstructiveRecursion(context) {
|
|
163
|
+
const edges = context.rules.flatMap(ruleEdges);
|
|
164
|
+
const reachable = reachablePredicates(context.atom.predicate, edges);
|
|
165
|
+
const components = stronglyConnectedComponents(reachable, edges);
|
|
166
|
+
const needsCandidateEnumeration = variablesIn(context.atom).length > 0;
|
|
167
|
+
for (const component of components) {
|
|
168
|
+
const internalEdges = edges.filter(
|
|
169
|
+
(edge) => component.has(edge.from) && component.has(edge.to)
|
|
170
|
+
);
|
|
171
|
+
const recursive = component.size > 1 || internalEdges.some((edge) => edge.from === edge.to);
|
|
172
|
+
if (!recursive) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (internalEdges.some((edge) => edge.kind !== "positive")) {
|
|
176
|
+
throw new UnconstructiveRecursionProgrammerError();
|
|
177
|
+
}
|
|
178
|
+
const hasGroundEvidence = context.literals.some(
|
|
179
|
+
(literal) => component.has(literal.atom.predicate) && isGroundAtom(literal.atom)
|
|
180
|
+
);
|
|
181
|
+
if (needsCandidateEnumeration && !hasGroundEvidence && !hasNonRecursiveBaseRule(component, context.rules)) {
|
|
182
|
+
throw new UnconstructiveRecursionProgrammerError();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function hasNonRecursiveBaseRule(component, rules) {
|
|
187
|
+
return rules.some(
|
|
188
|
+
(rule) => component.has(rule.head.atom.predicate) && !bodyReferencesPredicates(rule.body, component)
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
function stronglyConnectedComponents(predicates, edges) {
|
|
192
|
+
const components = [];
|
|
193
|
+
const remaining = new Set(predicates);
|
|
194
|
+
for (const predicate of predicates) {
|
|
195
|
+
if (!remaining.has(predicate)) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const forward = reachablePredicates(predicate, edges);
|
|
199
|
+
const backward = reachablePredicates(predicate, reverseEdges(edges));
|
|
200
|
+
const component = /* @__PURE__ */ new Set();
|
|
201
|
+
for (const candidate of predicates) {
|
|
202
|
+
if (forward.has(candidate) && backward.has(candidate)) {
|
|
203
|
+
component.add(candidate);
|
|
204
|
+
remaining.delete(candidate);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
components.push(component);
|
|
208
|
+
}
|
|
209
|
+
return components;
|
|
210
|
+
}
|
|
211
|
+
function reverseEdges(edges) {
|
|
212
|
+
return edges.map((edge) => ({ ...edge, from: edge.to, to: edge.from }));
|
|
213
|
+
}
|
|
214
|
+
function bodyReferencesPredicates(body, predicates) {
|
|
215
|
+
if (isAtomLike(body)) {
|
|
216
|
+
return predicates.has(body.predicate);
|
|
217
|
+
}
|
|
218
|
+
if (isNegativeLiteral(body)) {
|
|
219
|
+
return predicates.has(body.atom.predicate);
|
|
220
|
+
}
|
|
221
|
+
if (isFormula(body)) {
|
|
222
|
+
return body.args.some((arg) => bodyReferencesPredicates(arg, predicates));
|
|
223
|
+
}
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/solver/query-validation/constructive-variable-use.ts
|
|
228
|
+
function assertConstructiveVariableUse(context) {
|
|
229
|
+
const edges = context.rules.flatMap(ruleEdges);
|
|
230
|
+
const reachable = reachablePredicates(context.atom.predicate, edges);
|
|
231
|
+
for (const rule of context.rules) {
|
|
232
|
+
if (!reachable.has(rule.head.atom.predicate)) {
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
assertSafeBody(rule.body, new Set(variablesIn(rule.head.atom)));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function assertSafeBody(body, bound) {
|
|
239
|
+
if (isAtomLike(body)) {
|
|
240
|
+
bindVariables(body, bound);
|
|
241
|
+
return bound;
|
|
242
|
+
}
|
|
243
|
+
if (isNegativeLiteral(body)) {
|
|
244
|
+
assertVariablesBound(variablesIn(body.atom), bound);
|
|
245
|
+
return bound;
|
|
246
|
+
}
|
|
247
|
+
if (isFormula(body, "and")) {
|
|
248
|
+
let nextBound = bound;
|
|
249
|
+
for (const arg of body.args) {
|
|
250
|
+
nextBound = assertSafeBody(arg, nextBound);
|
|
251
|
+
}
|
|
252
|
+
return nextBound;
|
|
253
|
+
}
|
|
254
|
+
if (isFormula(body, "or")) {
|
|
255
|
+
return bindingsAvailableAfterOr(body.args, bound);
|
|
256
|
+
}
|
|
257
|
+
if (isFormula(body, "exists")) {
|
|
258
|
+
const nextBody = body.args.at(-1);
|
|
259
|
+
if (nextBody !== void 0) {
|
|
260
|
+
assertSafeBody(nextBody, new Set(bound));
|
|
261
|
+
}
|
|
262
|
+
return bound;
|
|
263
|
+
}
|
|
264
|
+
if (isFormula(body, "letIn")) {
|
|
265
|
+
const [variable, value, nextBody] = body.args;
|
|
266
|
+
if (isAggregateFormula(value)) {
|
|
267
|
+
assertSafeAggregate(value, bound);
|
|
268
|
+
} else {
|
|
269
|
+
assertVariablesBound(variablesIn(value), bound);
|
|
270
|
+
}
|
|
271
|
+
if (isVar(variable)) {
|
|
272
|
+
bound.add(variable);
|
|
273
|
+
}
|
|
274
|
+
return assertSafeBody(nextBody, bound);
|
|
275
|
+
}
|
|
276
|
+
if (isPrimitiveFormula(body)) {
|
|
277
|
+
assertVariablesBound(variablesIn(body), bound);
|
|
278
|
+
}
|
|
279
|
+
return bound;
|
|
280
|
+
}
|
|
281
|
+
function assertSafeAggregate(aggregate, bound) {
|
|
282
|
+
const [value, whereBody, options] = aggregate.args;
|
|
283
|
+
const rowBindings = assertSafeBody(whereBody, new Set(bound));
|
|
284
|
+
if (aggregate.kind === "sum") {
|
|
285
|
+
assertVariablesBound(variablesIn(value), rowBindings);
|
|
286
|
+
}
|
|
287
|
+
if (isCompleteWhenOption(options)) {
|
|
288
|
+
assertSafeBody(options.completeWhen, new Set(bound));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
function bindingsAvailableAfterOr(args, bound) {
|
|
292
|
+
const branchBindings = args.map((arg) => assertSafeBody(arg, new Set(bound)));
|
|
293
|
+
const next = new Set(bound);
|
|
294
|
+
for (const candidate of uniqueVariables(branchBindings.flatMap((branch) => [...branch]))) {
|
|
295
|
+
if (branchBindings.length > 0 && branchBindings.every((branch) => branch.has(candidate))) {
|
|
296
|
+
next.add(candidate);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return next;
|
|
300
|
+
}
|
|
301
|
+
function assertVariablesBound(variables, bound) {
|
|
302
|
+
const unbound = uniqueVariables(variables).filter((variable) => !bound.has(variable));
|
|
303
|
+
if (unbound.length > 0) {
|
|
304
|
+
throw new UnsafeUnboundVariableProgrammerError(unbound);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// src/solver/query-validation/validate-query.ts
|
|
309
|
+
var queryValidations = [
|
|
310
|
+
assertConstructiveVariableUse,
|
|
311
|
+
assertConstructiveRecursion
|
|
312
|
+
];
|
|
313
|
+
function validateQuery(literals, rules, atom) {
|
|
314
|
+
const context = { literals, rules, atom };
|
|
315
|
+
for (const validate of queryValidations) {
|
|
316
|
+
validate(context);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/solver/goal-value.ts
|
|
321
|
+
var GOAL_VALUE_RANK = {
|
|
322
|
+
false: 0,
|
|
323
|
+
unknown: 1,
|
|
324
|
+
conflicted: 2,
|
|
325
|
+
true: 3
|
|
326
|
+
};
|
|
327
|
+
var GOAL_VALUES_BY_RANK = ["false", "unknown", "conflicted", "true"];
|
|
328
|
+
function rankGoalValue(value) {
|
|
329
|
+
return GOAL_VALUE_RANK[value];
|
|
330
|
+
}
|
|
331
|
+
function minGoalValue(...values) {
|
|
332
|
+
if (values.length === 0) {
|
|
333
|
+
return "true";
|
|
334
|
+
}
|
|
335
|
+
let rank = 3;
|
|
336
|
+
for (const value of values) {
|
|
337
|
+
rank = Math.min(rank, rankGoalValue(value));
|
|
338
|
+
}
|
|
339
|
+
return goalValueByRank(rank);
|
|
340
|
+
}
|
|
341
|
+
function maxGoalValue(...values) {
|
|
342
|
+
if (values.length === 0) {
|
|
343
|
+
return "false";
|
|
344
|
+
}
|
|
345
|
+
let rank = 0;
|
|
346
|
+
for (const value of values) {
|
|
347
|
+
rank = Math.max(rank, rankGoalValue(value));
|
|
348
|
+
}
|
|
349
|
+
return goalValueByRank(rank);
|
|
350
|
+
}
|
|
351
|
+
function negateGoalValue(value) {
|
|
352
|
+
switch (value) {
|
|
353
|
+
case "true":
|
|
354
|
+
return "false";
|
|
355
|
+
case "false":
|
|
356
|
+
return "true";
|
|
357
|
+
case "unknown":
|
|
358
|
+
return "unknown";
|
|
359
|
+
case "conflicted":
|
|
360
|
+
return "conflicted";
|
|
361
|
+
default:
|
|
362
|
+
throw new AssertionError("Unhandled goal value");
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
function negateAsFailureGoalValue(value) {
|
|
366
|
+
switch (value) {
|
|
367
|
+
case "true":
|
|
368
|
+
return "false";
|
|
369
|
+
case "false":
|
|
370
|
+
return "true";
|
|
371
|
+
case "unknown":
|
|
372
|
+
return "true";
|
|
373
|
+
case "conflicted":
|
|
374
|
+
return "conflicted";
|
|
375
|
+
default:
|
|
376
|
+
throw new AssertionError("Unhandled goal value");
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
function goalValueByRank(rank) {
|
|
380
|
+
const value = GOAL_VALUES_BY_RANK[rank];
|
|
381
|
+
if (value === void 0) {
|
|
382
|
+
throw new Error(`Invalid goal value rank: ${rank}`);
|
|
383
|
+
}
|
|
384
|
+
return value;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// src/solver/matching.ts
|
|
388
|
+
function matchRuleForAtom(rule, atom) {
|
|
389
|
+
if (rule.head.atom.predicate !== atom.predicate) {
|
|
390
|
+
return void 0;
|
|
391
|
+
}
|
|
392
|
+
assertRuleMatchesAtom(rule, atom);
|
|
393
|
+
const bindings = matchPattern(rule.head.atom, atom);
|
|
394
|
+
if (bindings === void 0) {
|
|
395
|
+
return void 0;
|
|
396
|
+
}
|
|
397
|
+
return { rule, bindings };
|
|
398
|
+
}
|
|
399
|
+
function assertRuleMatchesAtom(rule, atom) {
|
|
400
|
+
assert(
|
|
401
|
+
rule.head.atom.predicate === atom.predicate,
|
|
402
|
+
"Rule head predicate does not match queried atom"
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
function matchPattern(pattern, atom) {
|
|
406
|
+
if (pattern.predicate !== atom.predicate) {
|
|
407
|
+
return void 0;
|
|
408
|
+
}
|
|
409
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
410
|
+
for (const [name, patternValue] of Object.entries(pattern.fields)) {
|
|
411
|
+
const atomValue = atom.fields[name];
|
|
412
|
+
if (isVar(patternValue)) {
|
|
413
|
+
const boundValue = bindings.get(patternValue);
|
|
414
|
+
if (bindings.has(patternValue) && !fieldEquals(boundValue, atomValue)) {
|
|
415
|
+
return void 0;
|
|
416
|
+
}
|
|
417
|
+
bindings.set(patternValue, atomValue);
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
if (!fieldEquals(patternValue, atomValue)) {
|
|
421
|
+
return void 0;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return bindings;
|
|
425
|
+
}
|
|
426
|
+
function groundAtom(atom, bindings) {
|
|
427
|
+
const fields = {};
|
|
428
|
+
for (const [name, value] of Object.entries(atom.fields)) {
|
|
429
|
+
if (isVar(value)) {
|
|
430
|
+
assert(bindings.has(value), `No binding for variable ${value.name}`);
|
|
431
|
+
fields[name] = bindings.get(value);
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
fields[name] = value;
|
|
435
|
+
}
|
|
436
|
+
const grounded = { predicate: atom.predicate, fields };
|
|
437
|
+
assert(isAtomForPredicate(grounded, atom.predicate), "Grounded atom must retain its schema");
|
|
438
|
+
assertGroundAtom(grounded);
|
|
439
|
+
return grounded;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// src/solver/candidate-search.ts
|
|
443
|
+
var CandidateSearch = class {
|
|
444
|
+
groups = /* @__PURE__ */ new Map();
|
|
445
|
+
tables = /* @__PURE__ */ new Map();
|
|
446
|
+
constructor(rules) {
|
|
447
|
+
const edges = rules.flatMap(ruleEdges);
|
|
448
|
+
for (const rule of rules) {
|
|
449
|
+
const predicate = rule.head.atom.predicate;
|
|
450
|
+
if (this.groups.has(predicate)) {
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
const group = { tables: /* @__PURE__ */ new Set(), evaluating: false, changed: false };
|
|
454
|
+
for (const dependency of reachablePredicates(predicate, edges)) {
|
|
455
|
+
if (reachablePredicates(dependency, edges).has(predicate)) {
|
|
456
|
+
this.groups.set(dependency, group);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
candidates(atom, seeds, derive) {
|
|
462
|
+
let tables = this.tables.get(atom.predicate);
|
|
463
|
+
if (tables === void 0) {
|
|
464
|
+
tables = /* @__PURE__ */ new Map();
|
|
465
|
+
this.tables.set(atom.predicate, tables);
|
|
466
|
+
}
|
|
467
|
+
const key = patternKey(atom);
|
|
468
|
+
const existing = tables.get(key);
|
|
469
|
+
if (existing !== void 0) {
|
|
470
|
+
return [...existing.atoms.values()];
|
|
471
|
+
}
|
|
472
|
+
let group = this.groups.get(atom.predicate);
|
|
473
|
+
if (group === void 0) {
|
|
474
|
+
group = { tables: /* @__PURE__ */ new Set(), evaluating: false, changed: false };
|
|
475
|
+
this.groups.set(atom.predicate, group);
|
|
476
|
+
}
|
|
477
|
+
const table = {
|
|
478
|
+
atoms: new Map(seeds.map((seed) => [patternKey(seed), seed])),
|
|
479
|
+
derive
|
|
480
|
+
};
|
|
481
|
+
tables.set(key, table);
|
|
482
|
+
group.tables.add(table);
|
|
483
|
+
group.changed = true;
|
|
484
|
+
if (!group.evaluating) {
|
|
485
|
+
group.evaluating = true;
|
|
486
|
+
try {
|
|
487
|
+
do {
|
|
488
|
+
group.changed = false;
|
|
489
|
+
for (const member of [...group.tables]) {
|
|
490
|
+
group.changed = expandTable(member) || group.changed;
|
|
491
|
+
}
|
|
492
|
+
} while (group.changed);
|
|
493
|
+
} finally {
|
|
494
|
+
group.evaluating = false;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return [...table.atoms.values()];
|
|
498
|
+
}
|
|
499
|
+
sameComponent(left, right) {
|
|
500
|
+
return left === right || this.groups.has(left) && this.groups.get(left) === this.groups.get(right);
|
|
501
|
+
}
|
|
502
|
+
get isEvaluating() {
|
|
503
|
+
return [...this.groups.values()].some((group) => group.evaluating);
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
function expandTable(table) {
|
|
507
|
+
let changed = false;
|
|
508
|
+
for (const candidate of table.derive()) {
|
|
509
|
+
const key = patternKey(candidate);
|
|
510
|
+
if (!table.atoms.has(key)) {
|
|
511
|
+
table.atoms.set(key, candidate);
|
|
512
|
+
changed = true;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return changed;
|
|
516
|
+
}
|
|
517
|
+
function patternKey(atom) {
|
|
518
|
+
const variables = /* @__PURE__ */ new Map();
|
|
519
|
+
return JSON.stringify(
|
|
520
|
+
atom.predicate.paramNames.map((name) => {
|
|
521
|
+
const value = atom.fields[name];
|
|
522
|
+
if (!isVar(value)) {
|
|
523
|
+
return ["value", fieldIdentityToken(value)];
|
|
524
|
+
}
|
|
525
|
+
let index = variables.get(value);
|
|
526
|
+
if (index === void 0) {
|
|
527
|
+
index = variables.size;
|
|
528
|
+
variables.set(value, index);
|
|
529
|
+
}
|
|
530
|
+
return ["variable", index];
|
|
531
|
+
})
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// src/solver/solve-all.ts
|
|
536
|
+
var UNBOUND = /* @__PURE__ */ Symbol("factbag.unbound");
|
|
537
|
+
function solveQueryAll(literals, rules, atom) {
|
|
538
|
+
validateQuery(literals, rules, atom);
|
|
539
|
+
const result = solveQueryAllInternal(literals, rules, atom, /* @__PURE__ */ new Set(), {
|
|
540
|
+
candidates: new CandidateSearch(rules),
|
|
541
|
+
judgments: /* @__PURE__ */ new Map()
|
|
542
|
+
});
|
|
543
|
+
const stack = [result];
|
|
544
|
+
const visited = /* @__PURE__ */ new Set();
|
|
545
|
+
while (stack.length > 0) {
|
|
546
|
+
const value = stack.pop();
|
|
547
|
+
assert(value !== UNBOUND, "Public query result must not contain UNBOUND");
|
|
548
|
+
if (typeof value === "object" && value !== null) {
|
|
549
|
+
if (visited.has(value)) {
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
visited.add(value);
|
|
553
|
+
}
|
|
554
|
+
if (Array.isArray(value)) {
|
|
555
|
+
for (const item of value) {
|
|
556
|
+
stack.push(item);
|
|
557
|
+
}
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
if (typeof value === "object" && value !== null) {
|
|
561
|
+
for (const item of Object.values(value)) {
|
|
562
|
+
stack.push(item);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return result;
|
|
567
|
+
}
|
|
568
|
+
function solveQueryAllInternal(literals, rules, atom, active, search) {
|
|
569
|
+
if (isGroundAtom(atom)) {
|
|
570
|
+
const result = solveGroundAtom(literals, rules, atom, active, search);
|
|
571
|
+
assert(atomEquals(result.goal.atom, atom), "Ground query result must match queried atom");
|
|
572
|
+
return [result];
|
|
573
|
+
}
|
|
574
|
+
return candidateGroundAtoms(literals, rules, atom, active, search).map(
|
|
575
|
+
(candidate) => solveGroundAtom(literals, rules, candidate, active, search)
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
function solveGroundAtom(literals, rules, atom, active, search) {
|
|
579
|
+
if (search.candidates.isEvaluating) {
|
|
580
|
+
return evaluateGroundAtom(literals, rules, atom, active, search);
|
|
581
|
+
}
|
|
582
|
+
let judgments = search.judgments.get(atom.predicate);
|
|
583
|
+
if (judgments === void 0) {
|
|
584
|
+
judgments = /* @__PURE__ */ new Map();
|
|
585
|
+
search.judgments.set(atom.predicate, judgments);
|
|
586
|
+
}
|
|
587
|
+
const key = JSON.stringify([groundAtomKey(atom), [...active].sort()]);
|
|
588
|
+
const cached = judgments.get(key);
|
|
589
|
+
if (cached !== void 0 && judgmentMatchesAtom(cached, atom)) {
|
|
590
|
+
return cached;
|
|
591
|
+
}
|
|
592
|
+
const result = evaluateGroundAtom(literals, rules, atom, active, search);
|
|
593
|
+
judgments.set(key, result);
|
|
594
|
+
return result;
|
|
595
|
+
}
|
|
596
|
+
function judgmentMatchesAtom(judgment, atom) {
|
|
597
|
+
return atomEquals(judgment.goal.atom, atom);
|
|
598
|
+
}
|
|
599
|
+
function evaluateGroundAtom(literals, rules, atom, active, search) {
|
|
600
|
+
const goal = { kind: "atom", atom };
|
|
601
|
+
const bindings = [];
|
|
602
|
+
const atomKey = groundAtomKey(atom);
|
|
603
|
+
const positiveLiterals = matchingLiterals(literals, atom, "+");
|
|
604
|
+
const negativeLiterals = matchingLiterals(literals, atom, "-");
|
|
605
|
+
const values = [];
|
|
606
|
+
const positive = [];
|
|
607
|
+
const negative = [];
|
|
608
|
+
const inconclusive = [];
|
|
609
|
+
if (positiveLiterals.length > 0 && negativeLiterals.length > 0) {
|
|
610
|
+
values.push("conflicted");
|
|
611
|
+
} else if (positiveLiterals.length > 0) {
|
|
612
|
+
values.push("true");
|
|
613
|
+
} else if (negativeLiterals.length > 0) {
|
|
614
|
+
values.push("false");
|
|
615
|
+
}
|
|
616
|
+
if (!active.has(atomKey)) {
|
|
617
|
+
const nextActive = new Set(active);
|
|
618
|
+
nextActive.add(atomKey);
|
|
619
|
+
for (const rule of rules) {
|
|
620
|
+
const match = matchRuleForAtom(rule, atom);
|
|
621
|
+
if (match === void 0) {
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
const bodyResult = evaluateRuleBody(
|
|
625
|
+
literals,
|
|
626
|
+
rules,
|
|
627
|
+
match.rule.body,
|
|
628
|
+
match.bindings,
|
|
629
|
+
nextActive,
|
|
630
|
+
search
|
|
631
|
+
);
|
|
632
|
+
const body = bodyResult.judgment;
|
|
633
|
+
const contribution = ruleContribution(match.rule.kind, body.value);
|
|
634
|
+
const support = {
|
|
635
|
+
source: "rule",
|
|
636
|
+
rule: match.rule,
|
|
637
|
+
body
|
|
638
|
+
};
|
|
639
|
+
if (contribution === "unknown") {
|
|
640
|
+
inconclusive.push(support);
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
values.push(contribution);
|
|
644
|
+
if (contribution === "true" || contribution === "conflicted") {
|
|
645
|
+
positive.push(support);
|
|
646
|
+
}
|
|
647
|
+
if (contribution === "false" || contribution === "conflicted") {
|
|
648
|
+
negative.push(support);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
if (values.length === 0) {
|
|
653
|
+
if (inconclusive.length > 0) {
|
|
654
|
+
return { value: "unknown", goal, bindings, inconclusive };
|
|
655
|
+
}
|
|
656
|
+
if (atom.predicate.world === "closed") {
|
|
657
|
+
assert(positiveLiterals.length === 0, "Closed-world absence requires no positive evidence");
|
|
658
|
+
assert(negativeLiterals.length === 0, "Closed-world absence requires no negative evidence");
|
|
659
|
+
assert(positive.length === 0, "Closed-world absence requires no positive rules");
|
|
660
|
+
assert(negative.length === 0, "Closed-world absence requires no negative rules");
|
|
661
|
+
assert(inconclusive.length === 0, "Closed-world absence requires no inconclusive rules");
|
|
662
|
+
return {
|
|
663
|
+
value: "false",
|
|
664
|
+
goal,
|
|
665
|
+
bindings,
|
|
666
|
+
negative: [{ source: "closed-world-absence" }]
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
return { value: "unknown", goal, bindings };
|
|
670
|
+
}
|
|
671
|
+
const hasEvidenceRuleConflict = positiveLiterals.length > 0 && negative.length > 0 || negativeLiterals.length > 0 && positive.length > 0;
|
|
672
|
+
const value = hasEvidenceRuleConflict ? "conflicted" : maxGoalValue(...values);
|
|
673
|
+
switch (value) {
|
|
674
|
+
case "conflicted":
|
|
675
|
+
return {
|
|
676
|
+
value,
|
|
677
|
+
goal,
|
|
678
|
+
bindings,
|
|
679
|
+
positive: [
|
|
680
|
+
...positiveLiterals.length > 0 ? [{ source: "evidence", literals: positiveLiterals }] : [],
|
|
681
|
+
...positive
|
|
682
|
+
],
|
|
683
|
+
negative: [
|
|
684
|
+
...negativeLiterals.length > 0 ? [{ source: "evidence", literals: negativeLiterals }] : [],
|
|
685
|
+
...negative
|
|
686
|
+
]
|
|
687
|
+
};
|
|
688
|
+
case "true":
|
|
689
|
+
return {
|
|
690
|
+
value,
|
|
691
|
+
goal,
|
|
692
|
+
bindings,
|
|
693
|
+
positive: [
|
|
694
|
+
...positiveLiterals.length > 0 ? [{ source: "evidence", literals: positiveLiterals }] : [],
|
|
695
|
+
...positive
|
|
696
|
+
]
|
|
697
|
+
};
|
|
698
|
+
case "false":
|
|
699
|
+
return {
|
|
700
|
+
value,
|
|
701
|
+
goal,
|
|
702
|
+
bindings,
|
|
703
|
+
negative: [
|
|
704
|
+
...negativeLiterals.length > 0 ? [{ source: "evidence", literals: negativeLiterals }] : [],
|
|
705
|
+
...negative
|
|
706
|
+
]
|
|
707
|
+
};
|
|
708
|
+
case "unknown":
|
|
709
|
+
return { value, goal, bindings };
|
|
710
|
+
default:
|
|
711
|
+
return assertNever(value, "Unhandled ground atom goal value");
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
function evaluateRuleBody(literals, rules, body, bindings, active, search) {
|
|
715
|
+
const context = {
|
|
716
|
+
nextTemp: 0,
|
|
717
|
+
search
|
|
718
|
+
};
|
|
719
|
+
const results = evaluateRuleBodyMany(literals, rules, body, bindings, active, context);
|
|
720
|
+
assert(results.length > 0, "Rule body evaluation produced no results");
|
|
721
|
+
const result = bestResult(results);
|
|
722
|
+
assert(result !== void 0, "Rule body evaluation produced no best result");
|
|
723
|
+
return result;
|
|
724
|
+
}
|
|
725
|
+
function assertNever(value, message) {
|
|
726
|
+
throw new AssertionError(`${message}: ${String(value)}`);
|
|
727
|
+
}
|
|
728
|
+
function evaluateRuleBodyMany(literals, rules, body, bindings, active, context, pathCanHaveUnavailableBindings = false) {
|
|
729
|
+
if (isAtomLike(body)) {
|
|
730
|
+
return evaluateAtomBody(literals, rules, erasedAtom(body), bindings, active, context);
|
|
731
|
+
}
|
|
732
|
+
if (isLiteralLike(body) && body.sign === "-") {
|
|
733
|
+
const bodyAtom = erasedAtom(body.atom);
|
|
734
|
+
if (!canGroundAtom(bodyAtom, bindings)) {
|
|
735
|
+
return [
|
|
736
|
+
{
|
|
737
|
+
judgment: {
|
|
738
|
+
value: "unknown",
|
|
739
|
+
goal: { kind: "not", goal: { kind: "atom", atom: body.atom } },
|
|
740
|
+
bindings: bindingsArray(bindings)
|
|
741
|
+
},
|
|
742
|
+
bindings
|
|
743
|
+
}
|
|
744
|
+
];
|
|
745
|
+
}
|
|
746
|
+
const atom = groundAtom(bodyAtom, bindings);
|
|
747
|
+
assert(!active.has(groundAtomKey(atom)), "Validated query must not recurse through negation");
|
|
748
|
+
const child = solveGroundAtom(literals, rules, atom, active, context.search);
|
|
749
|
+
const goal = { kind: "not", goal: { kind: "atom", atom } };
|
|
750
|
+
const value = atom.predicate.world === "closed" ? negateAsFailureGoalValue(child.value) : negateGoalValue(child.value);
|
|
751
|
+
const judgmentBindings = bindingsArray(bindings);
|
|
752
|
+
switch (value) {
|
|
753
|
+
case "true":
|
|
754
|
+
return [
|
|
755
|
+
{
|
|
756
|
+
judgment: {
|
|
757
|
+
value,
|
|
758
|
+
goal,
|
|
759
|
+
bindings: judgmentBindings,
|
|
760
|
+
positive: [
|
|
761
|
+
{ source: "subgoals", judgments: [withBindings(child, judgmentBindings)] }
|
|
762
|
+
]
|
|
763
|
+
},
|
|
764
|
+
bindings
|
|
765
|
+
}
|
|
766
|
+
];
|
|
767
|
+
case "false":
|
|
768
|
+
return [
|
|
769
|
+
{
|
|
770
|
+
judgment: {
|
|
771
|
+
value,
|
|
772
|
+
goal,
|
|
773
|
+
bindings: judgmentBindings,
|
|
774
|
+
negative: [
|
|
775
|
+
{ source: "subgoals", judgments: [withBindings(child, judgmentBindings)] }
|
|
776
|
+
]
|
|
777
|
+
},
|
|
778
|
+
bindings
|
|
779
|
+
}
|
|
780
|
+
];
|
|
781
|
+
case "conflicted":
|
|
782
|
+
return [
|
|
783
|
+
{
|
|
784
|
+
judgment: {
|
|
785
|
+
value,
|
|
786
|
+
goal,
|
|
787
|
+
bindings: judgmentBindings,
|
|
788
|
+
positive: [
|
|
789
|
+
{ source: "subgoals", judgments: [withBindings(child, judgmentBindings)] }
|
|
790
|
+
],
|
|
791
|
+
negative: [
|
|
792
|
+
{ source: "subgoals", judgments: [withBindings(child, judgmentBindings)] }
|
|
793
|
+
]
|
|
794
|
+
},
|
|
795
|
+
bindings
|
|
796
|
+
}
|
|
797
|
+
];
|
|
798
|
+
case "unknown":
|
|
799
|
+
return [
|
|
800
|
+
{
|
|
801
|
+
judgment: {
|
|
802
|
+
value,
|
|
803
|
+
goal,
|
|
804
|
+
bindings: judgmentBindings,
|
|
805
|
+
inconclusive: [
|
|
806
|
+
{ source: "subgoals", judgments: [withBindings(child, judgmentBindings)] }
|
|
807
|
+
]
|
|
808
|
+
},
|
|
809
|
+
bindings
|
|
810
|
+
}
|
|
811
|
+
];
|
|
812
|
+
default:
|
|
813
|
+
return assertNever(value, "Unhandled negated goal value");
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
if (isFormula(body, "and")) {
|
|
817
|
+
return evaluateAndBody(
|
|
818
|
+
literals,
|
|
819
|
+
rules,
|
|
820
|
+
body.args,
|
|
821
|
+
bindings,
|
|
822
|
+
active,
|
|
823
|
+
context,
|
|
824
|
+
pathCanHaveUnavailableBindings
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
if (isFormula(body, "or")) {
|
|
828
|
+
return evaluateOrBody(
|
|
829
|
+
literals,
|
|
830
|
+
rules,
|
|
831
|
+
body.args,
|
|
832
|
+
bindings,
|
|
833
|
+
active,
|
|
834
|
+
context,
|
|
835
|
+
pathCanHaveUnavailableBindings
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
if (isFormula(body, "exists")) {
|
|
839
|
+
return evaluateExistsBody(
|
|
840
|
+
literals,
|
|
841
|
+
rules,
|
|
842
|
+
body.args,
|
|
843
|
+
bindings,
|
|
844
|
+
active,
|
|
845
|
+
context,
|
|
846
|
+
pathCanHaveUnavailableBindings
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
if (isLetInFormula(body)) {
|
|
850
|
+
return evaluateLetInBody(
|
|
851
|
+
literals,
|
|
852
|
+
rules,
|
|
853
|
+
body.args,
|
|
854
|
+
bindings,
|
|
855
|
+
active,
|
|
856
|
+
context,
|
|
857
|
+
pathCanHaveUnavailableBindings
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
if (isComparisonFormula(body)) {
|
|
861
|
+
return evaluateComparisonBody(
|
|
862
|
+
body.kind,
|
|
863
|
+
body.args,
|
|
864
|
+
bindings,
|
|
865
|
+
context,
|
|
866
|
+
pathCanHaveUnavailableBindings
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
assert(!isFormula(body), "Unsupported rule body formula");
|
|
870
|
+
throw new AssertionError("Unsupported rule body shape");
|
|
871
|
+
}
|
|
872
|
+
function evaluateExistsBody(literals, rules, args, bindings, active, context, pathCanHaveUnavailableBindings) {
|
|
873
|
+
const body = args.at(-1);
|
|
874
|
+
const variables = args.slice(0, -1);
|
|
875
|
+
assert(body !== void 0, "exists formula must include a body");
|
|
876
|
+
assert(variables.every(isVar), "exists formula quantifiers must be variables");
|
|
877
|
+
return evaluateRuleBodyMany(
|
|
878
|
+
literals,
|
|
879
|
+
rules,
|
|
880
|
+
body,
|
|
881
|
+
bindings,
|
|
882
|
+
active,
|
|
883
|
+
context,
|
|
884
|
+
pathCanHaveUnavailableBindings
|
|
885
|
+
).map((result) => {
|
|
886
|
+
const judgmentBindings = bindingsArray(result.bindings);
|
|
887
|
+
const child = withBindings(result.judgment, judgmentBindings);
|
|
888
|
+
const goal = { kind: "exists", variables, goal: child.goal };
|
|
889
|
+
return {
|
|
890
|
+
judgment: compoundExistsJudgment(goal, child.value, judgmentBindings, [child]),
|
|
891
|
+
bindings: result.bindings
|
|
892
|
+
};
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
function evaluateOrBody(literals, rules, args, bindings, active, context, pathCanHaveUnavailableBindings) {
|
|
896
|
+
const candidateBindings = uniqueBindings(
|
|
897
|
+
args.flatMap(
|
|
898
|
+
(arg) => evaluateRuleBodyMany(
|
|
899
|
+
literals,
|
|
900
|
+
rules,
|
|
901
|
+
arg,
|
|
902
|
+
bindings,
|
|
903
|
+
active,
|
|
904
|
+
context,
|
|
905
|
+
pathCanHaveUnavailableBindings
|
|
906
|
+
).map((result) => result.bindings)
|
|
907
|
+
)
|
|
908
|
+
);
|
|
909
|
+
const environments = candidateBindings.length === 0 ? [bindings] : candidateBindings;
|
|
910
|
+
return environments.map((environment) => {
|
|
911
|
+
const judgments = args.map((arg) => {
|
|
912
|
+
const results = evaluateRuleBodyMany(
|
|
913
|
+
literals,
|
|
914
|
+
rules,
|
|
915
|
+
arg,
|
|
916
|
+
environment,
|
|
917
|
+
active,
|
|
918
|
+
context,
|
|
919
|
+
pathCanHaveUnavailableBindings
|
|
920
|
+
);
|
|
921
|
+
assert(results.length > 0, "Or branch evaluation produced no results");
|
|
922
|
+
const result = bestResult(results);
|
|
923
|
+
assert(result !== void 0, "Or branch evaluation produced no best result");
|
|
924
|
+
return result.judgment;
|
|
925
|
+
});
|
|
926
|
+
const judgmentBindings = bindingsArray(environment);
|
|
927
|
+
const reboundJudgments = judgments.map((judgment) => withBindings(judgment, judgmentBindings));
|
|
928
|
+
const goal = { kind: "or", goals: reboundJudgments.map((judgment) => judgment.goal) };
|
|
929
|
+
return {
|
|
930
|
+
judgment: compoundJudgment(
|
|
931
|
+
goal,
|
|
932
|
+
maxGoalValue(...reboundJudgments.map((judgment) => judgment.value)),
|
|
933
|
+
judgmentBindings,
|
|
934
|
+
reboundJudgments
|
|
935
|
+
),
|
|
936
|
+
bindings: environment
|
|
937
|
+
};
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
function evaluateAndBody(literals, rules, args, bindings, active, context, pathCanHaveUnavailableBindings) {
|
|
941
|
+
let partials = [{ bindings, judgments: [] }];
|
|
942
|
+
for (const arg of args) {
|
|
943
|
+
const next = [];
|
|
944
|
+
for (const partial of partials) {
|
|
945
|
+
const partialPathCanHaveUnavailableBindings = pathCanHaveUnavailableBindings || partial.judgments.some((judgment) => judgment.value !== "true");
|
|
946
|
+
const results = evaluateRuleBodyMany(
|
|
947
|
+
literals,
|
|
948
|
+
rules,
|
|
949
|
+
arg,
|
|
950
|
+
partial.bindings,
|
|
951
|
+
active,
|
|
952
|
+
context,
|
|
953
|
+
partialPathCanHaveUnavailableBindings
|
|
954
|
+
);
|
|
955
|
+
for (const result of results) {
|
|
956
|
+
next.push({
|
|
957
|
+
bindings: result.bindings,
|
|
958
|
+
judgments: [...partial.judgments, result.judgment]
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
partials = next;
|
|
963
|
+
}
|
|
964
|
+
return partials.map((partial) => {
|
|
965
|
+
const judgmentBindings = bindingsArray(partial.bindings);
|
|
966
|
+
const judgments = partial.judgments.map((judgment) => withBindings(judgment, judgmentBindings));
|
|
967
|
+
const goal = { kind: "and", goals: judgments.map((judgment) => judgment.goal) };
|
|
968
|
+
return {
|
|
969
|
+
judgment: compoundJudgment(
|
|
970
|
+
goal,
|
|
971
|
+
minGoalValue(...judgments.map((judgment) => judgment.value)),
|
|
972
|
+
judgmentBindings,
|
|
973
|
+
judgments
|
|
974
|
+
),
|
|
975
|
+
bindings: partial.bindings
|
|
976
|
+
};
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
function evaluateAtomBody(literals, rules, atom, bindings, active, context) {
|
|
980
|
+
const evaluateGrounded = (environment) => {
|
|
981
|
+
const grounded = groundAtom(atom, environment);
|
|
982
|
+
const judgmentBindings = bindingsArray(environment);
|
|
983
|
+
const judgment = context.candidatePredicate !== void 0 && context.search.candidates.sameComponent(context.candidatePredicate, grounded.predicate) ? { value: "unknown", goal: { kind: "atom", atom: grounded }, bindings: judgmentBindings } : withBindings(
|
|
984
|
+
solveGroundAtom(literals, rules, grounded, active, context.search),
|
|
985
|
+
judgmentBindings
|
|
986
|
+
);
|
|
987
|
+
return { judgment, bindings: environment };
|
|
988
|
+
};
|
|
989
|
+
if (canGroundAtom(atom, bindings)) {
|
|
990
|
+
return [evaluateGrounded(bindings)];
|
|
991
|
+
}
|
|
992
|
+
const pattern = {
|
|
993
|
+
predicate: atom.predicate,
|
|
994
|
+
fields: Object.fromEntries(
|
|
995
|
+
Object.entries(atom.fields).map(([name, value]) => [
|
|
996
|
+
name,
|
|
997
|
+
isVar(value) && bindings.has(value) ? bindings.get(value) : value
|
|
998
|
+
])
|
|
999
|
+
)
|
|
1000
|
+
};
|
|
1001
|
+
const candidateBindings = candidateGroundAtoms(literals, rules, pattern, active, context.search).map((candidate) => extendBindings(atom, candidate, bindings)).filter((candidate) => candidate !== void 0);
|
|
1002
|
+
if (candidateBindings.length === 0) {
|
|
1003
|
+
if (atom.predicate.world === "closed") {
|
|
1004
|
+
assert(!canGroundAtom(atom, bindings), "Ground closed-world absence must use ground solving");
|
|
1005
|
+
return [
|
|
1006
|
+
{
|
|
1007
|
+
judgment: {
|
|
1008
|
+
value: "false",
|
|
1009
|
+
goal: { kind: "atom", atom },
|
|
1010
|
+
bindings: bindingsArray(bindings),
|
|
1011
|
+
negative: [{ source: "closed-world-absence" }]
|
|
1012
|
+
},
|
|
1013
|
+
bindings
|
|
1014
|
+
}
|
|
1015
|
+
];
|
|
1016
|
+
}
|
|
1017
|
+
return [
|
|
1018
|
+
{
|
|
1019
|
+
judgment: {
|
|
1020
|
+
value: "unknown",
|
|
1021
|
+
goal: { kind: "atom", atom },
|
|
1022
|
+
bindings: bindingsArray(bindings)
|
|
1023
|
+
},
|
|
1024
|
+
bindings
|
|
1025
|
+
}
|
|
1026
|
+
];
|
|
1027
|
+
}
|
|
1028
|
+
return candidateBindings.map(evaluateGrounded);
|
|
1029
|
+
}
|
|
1030
|
+
function evaluateComparisonBody(kind, args, bindings, context, pathCanHaveUnavailableBindings) {
|
|
1031
|
+
const left = evaluateExpression(args[0], bindings, context);
|
|
1032
|
+
const right = evaluateExpression(args[1], left.bindings, context);
|
|
1033
|
+
assert(isScalarGoalExpression(left.expression), "Left comparison operand must be scalar");
|
|
1034
|
+
assert(isScalarGoalExpression(right.expression), "Right comparison operand must be scalar");
|
|
1035
|
+
const nextBindings = right.bindings;
|
|
1036
|
+
const judgmentBindings = bindingsArray(nextBindings);
|
|
1037
|
+
const goal = {
|
|
1038
|
+
kind,
|
|
1039
|
+
left: left.expression,
|
|
1040
|
+
right: right.expression
|
|
1041
|
+
};
|
|
1042
|
+
const hasUnavailableOperand = left.value === UNBOUND || right.value === UNBOUND;
|
|
1043
|
+
assert(
|
|
1044
|
+
!hasUnavailableOperand || pathCanHaveUnavailableBindings,
|
|
1045
|
+
"Primitive comparison operands must be bound"
|
|
1046
|
+
);
|
|
1047
|
+
const value = hasUnavailableOperand ? "unknown" : compareValues(kind, left.value, right.value);
|
|
1048
|
+
const comparison = primitiveComparisonJudgment(
|
|
1049
|
+
goal,
|
|
1050
|
+
value,
|
|
1051
|
+
judgmentBindings,
|
|
1052
|
+
left.value,
|
|
1053
|
+
left.expression,
|
|
1054
|
+
right.value,
|
|
1055
|
+
right.expression
|
|
1056
|
+
);
|
|
1057
|
+
const judgments = [...left.judgments, ...right.judgments, comparison];
|
|
1058
|
+
if (judgments.length === 1) {
|
|
1059
|
+
return [{ judgment: comparison, bindings: nextBindings }];
|
|
1060
|
+
}
|
|
1061
|
+
const andGoal = { kind: "and", goals: judgments.map((judgment) => judgment.goal) };
|
|
1062
|
+
return [
|
|
1063
|
+
{
|
|
1064
|
+
judgment: compoundJudgment(
|
|
1065
|
+
andGoal,
|
|
1066
|
+
minGoalValue(...judgments.map((judgment) => judgment.value)),
|
|
1067
|
+
judgmentBindings,
|
|
1068
|
+
judgments
|
|
1069
|
+
),
|
|
1070
|
+
bindings: nextBindings
|
|
1071
|
+
}
|
|
1072
|
+
];
|
|
1073
|
+
}
|
|
1074
|
+
function evaluateLetInBody(literals, rules, args, bindings, active, context, pathCanHaveUnavailableBindings) {
|
|
1075
|
+
const [variable, valueExpression, body] = args;
|
|
1076
|
+
assert(isVar(variable), "letIn binder must be a variable");
|
|
1077
|
+
if (isAggregateFormula2(valueExpression)) {
|
|
1078
|
+
const aggregate = evaluateAggregateInto(
|
|
1079
|
+
literals,
|
|
1080
|
+
rules,
|
|
1081
|
+
variable,
|
|
1082
|
+
valueExpression,
|
|
1083
|
+
bindings,
|
|
1084
|
+
active,
|
|
1085
|
+
context
|
|
1086
|
+
);
|
|
1087
|
+
switch (aggregate.kind) {
|
|
1088
|
+
case "incomplete":
|
|
1089
|
+
return [aggregate.result];
|
|
1090
|
+
case "complete":
|
|
1091
|
+
return evaluateLetBodyAfterValue(
|
|
1092
|
+
literals,
|
|
1093
|
+
rules,
|
|
1094
|
+
body,
|
|
1095
|
+
aggregate.expression,
|
|
1096
|
+
active,
|
|
1097
|
+
context,
|
|
1098
|
+
pathCanHaveUnavailableBindings
|
|
1099
|
+
);
|
|
1100
|
+
default:
|
|
1101
|
+
return assertNever(aggregate, "Unhandled aggregate evaluation kind");
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
if (isFormula(valueExpression) && !isArithmeticFormula(valueExpression)) {
|
|
1105
|
+
assert(
|
|
1106
|
+
isArithmeticFormula(valueExpression),
|
|
1107
|
+
`Unsupported letIn value formula: ${valueExpression.kind}`
|
|
1108
|
+
);
|
|
1109
|
+
}
|
|
1110
|
+
const value = evaluateExpressionInto(variable, valueExpression, bindings, context);
|
|
1111
|
+
return evaluateLetBodyAfterValue(
|
|
1112
|
+
literals,
|
|
1113
|
+
rules,
|
|
1114
|
+
body,
|
|
1115
|
+
value,
|
|
1116
|
+
active,
|
|
1117
|
+
context,
|
|
1118
|
+
pathCanHaveUnavailableBindings
|
|
1119
|
+
);
|
|
1120
|
+
}
|
|
1121
|
+
function evaluateLetBodyAfterValue(literals, rules, body, value, active, context, pathCanHaveUnavailableBindings) {
|
|
1122
|
+
const results = evaluateRuleBodyMany(
|
|
1123
|
+
literals,
|
|
1124
|
+
rules,
|
|
1125
|
+
body,
|
|
1126
|
+
value.bindings,
|
|
1127
|
+
active,
|
|
1128
|
+
context,
|
|
1129
|
+
pathCanHaveUnavailableBindings || value.judgments.some((judgment) => judgment.value !== "true")
|
|
1130
|
+
);
|
|
1131
|
+
return results.map((result) => {
|
|
1132
|
+
const judgmentBindings = bindingsArray(result.bindings);
|
|
1133
|
+
const judgments = [...value.judgments, result.judgment].map(
|
|
1134
|
+
(judgment) => withBindings(judgment, judgmentBindings)
|
|
1135
|
+
);
|
|
1136
|
+
const goal = { kind: "and", goals: judgments.map((judgment) => judgment.goal) };
|
|
1137
|
+
return {
|
|
1138
|
+
judgment: compoundJudgment(
|
|
1139
|
+
goal,
|
|
1140
|
+
minGoalValue(...judgments.map((judgment) => judgment.value)),
|
|
1141
|
+
judgmentBindings,
|
|
1142
|
+
judgments
|
|
1143
|
+
),
|
|
1144
|
+
bindings: result.bindings
|
|
1145
|
+
};
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
function valueForVariable(bindings, variable) {
|
|
1149
|
+
if (!bindings.has(variable)) {
|
|
1150
|
+
return UNBOUND;
|
|
1151
|
+
}
|
|
1152
|
+
return definedValue(bindings.get(variable), "Bound variable values must be defined");
|
|
1153
|
+
}
|
|
1154
|
+
function definedValue(value, message) {
|
|
1155
|
+
assert(isDefinedValue(value), message);
|
|
1156
|
+
return value;
|
|
1157
|
+
}
|
|
1158
|
+
function isDefinedValue(value) {
|
|
1159
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean" || typeof value === "object" && value !== null;
|
|
1160
|
+
}
|
|
1161
|
+
function isGoalExpression(value) {
|
|
1162
|
+
return isVar(value) || typeof value === "string" || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean" || value instanceof Date || typeof value === "object" && value !== null && "id" in value && typeof value.id === "string";
|
|
1163
|
+
}
|
|
1164
|
+
function isScalarGoalExpression(value) {
|
|
1165
|
+
return isVar(value) || typeof value === "string" || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean" || value instanceof Date;
|
|
1166
|
+
}
|
|
1167
|
+
function isNumericGoalExpression(value) {
|
|
1168
|
+
return isVar(value) || typeof value === "number" || typeof value === "bigint";
|
|
1169
|
+
}
|
|
1170
|
+
function evaluateExpression(expression, bindings, context) {
|
|
1171
|
+
if (isVar(expression)) {
|
|
1172
|
+
return {
|
|
1173
|
+
value: valueForVariable(bindings, expression),
|
|
1174
|
+
expression,
|
|
1175
|
+
bindings,
|
|
1176
|
+
judgments: []
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
if (isArithmeticFormula(expression)) {
|
|
1180
|
+
const left = evaluateExpression(expression.args[0], bindings, context);
|
|
1181
|
+
const right = evaluateExpression(expression.args[1], left.bindings, context);
|
|
1182
|
+
assert(isScalarGoalExpression(left.expression), "Left arithmetic operand must be scalar");
|
|
1183
|
+
assert(isScalarGoalExpression(right.expression), "Right arithmetic operand must be scalar");
|
|
1184
|
+
const variable = new Var(`$tmp${context.nextTemp++}`);
|
|
1185
|
+
const goal = {
|
|
1186
|
+
kind: expression.kind,
|
|
1187
|
+
bind: variable,
|
|
1188
|
+
left: left.expression,
|
|
1189
|
+
right: right.expression
|
|
1190
|
+
};
|
|
1191
|
+
const value = calculateValue(expression.kind, left.value, right.value, goal);
|
|
1192
|
+
const nextBindings = new Map(right.bindings);
|
|
1193
|
+
if (value !== UNBOUND) {
|
|
1194
|
+
nextBindings.set(variable, value);
|
|
1195
|
+
}
|
|
1196
|
+
const judgmentBindings = bindingsArray(nextBindings);
|
|
1197
|
+
const judgment = primitiveArithmeticJudgment(
|
|
1198
|
+
goal,
|
|
1199
|
+
judgmentBindings,
|
|
1200
|
+
left.value,
|
|
1201
|
+
left.expression,
|
|
1202
|
+
right.value,
|
|
1203
|
+
right.expression,
|
|
1204
|
+
variable,
|
|
1205
|
+
value
|
|
1206
|
+
);
|
|
1207
|
+
return {
|
|
1208
|
+
value,
|
|
1209
|
+
expression: variable,
|
|
1210
|
+
bindings: nextBindings,
|
|
1211
|
+
judgments: [...left.judgments, ...right.judgments, judgment]
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
assert(isGoalExpression(expression), "Expression must be a scalar, entity, or variable");
|
|
1215
|
+
return {
|
|
1216
|
+
value: definedValue(expression, "Expression values must be defined"),
|
|
1217
|
+
expression,
|
|
1218
|
+
bindings,
|
|
1219
|
+
judgments: []
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
function evaluateExpressionInto(variable, expression, bindings, context) {
|
|
1223
|
+
if (isArithmeticFormula(expression)) {
|
|
1224
|
+
const left = evaluateExpression(expression.args[0], bindings, context);
|
|
1225
|
+
const right = evaluateExpression(expression.args[1], left.bindings, context);
|
|
1226
|
+
assert(isScalarGoalExpression(left.expression), "Left arithmetic operand must be scalar");
|
|
1227
|
+
assert(isScalarGoalExpression(right.expression), "Right arithmetic operand must be scalar");
|
|
1228
|
+
const goal = {
|
|
1229
|
+
kind: expression.kind,
|
|
1230
|
+
bind: variable,
|
|
1231
|
+
left: left.expression,
|
|
1232
|
+
right: right.expression
|
|
1233
|
+
};
|
|
1234
|
+
const value2 = calculateValue(expression.kind, left.value, right.value, goal);
|
|
1235
|
+
const nextBindings2 = new Map(right.bindings);
|
|
1236
|
+
if (value2 !== UNBOUND) {
|
|
1237
|
+
nextBindings2.set(variable, value2);
|
|
1238
|
+
}
|
|
1239
|
+
const judgmentBindings = bindingsArray(nextBindings2);
|
|
1240
|
+
const judgment = primitiveArithmeticJudgment(
|
|
1241
|
+
goal,
|
|
1242
|
+
judgmentBindings,
|
|
1243
|
+
left.value,
|
|
1244
|
+
left.expression,
|
|
1245
|
+
right.value,
|
|
1246
|
+
right.expression,
|
|
1247
|
+
variable,
|
|
1248
|
+
value2
|
|
1249
|
+
);
|
|
1250
|
+
return {
|
|
1251
|
+
value: value2,
|
|
1252
|
+
expression: variable,
|
|
1253
|
+
bindings: nextBindings2,
|
|
1254
|
+
judgments: [...left.judgments, ...right.judgments, judgment]
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
const value = isVar(expression) ? valueForVariable(bindings, expression) : definedValue(expression, "Expression values must be defined");
|
|
1258
|
+
const nextBindings = new Map(bindings);
|
|
1259
|
+
nextBindings.set(variable, value);
|
|
1260
|
+
return { value, expression: variable, bindings: nextBindings, judgments: [] };
|
|
1261
|
+
}
|
|
1262
|
+
function evaluateAggregateInto(literals, rules, variable, aggregate, bindings, active, context) {
|
|
1263
|
+
const [valueExpression, whereBody, options] = aggregate.args;
|
|
1264
|
+
const completeWhen = options?.completeWhen;
|
|
1265
|
+
const rowResults = evaluateRuleBodyMany(literals, rules, whereBody, bindings, active, context);
|
|
1266
|
+
const rows = rowResults.filter((result) => result.judgment.value === "true");
|
|
1267
|
+
const possibleRows = rowResults.filter(
|
|
1268
|
+
(result) => result.judgment.value !== "false" && (aggregate.kind !== "count" || aggregateValueIsAvailable(valueExpression, result.bindings))
|
|
1269
|
+
);
|
|
1270
|
+
const whereGoal = aggregateWhereGoal(possibleRows.map((row) => row.judgment));
|
|
1271
|
+
if (completeWhen !== void 0) {
|
|
1272
|
+
const completenessResults = evaluateRuleBodyMany(
|
|
1273
|
+
literals,
|
|
1274
|
+
rules,
|
|
1275
|
+
completeWhen,
|
|
1276
|
+
bindings,
|
|
1277
|
+
active,
|
|
1278
|
+
context
|
|
1279
|
+
);
|
|
1280
|
+
assert(completenessResults.length > 0, "Aggregate completeness evaluation produced no results");
|
|
1281
|
+
const completeness = bestResult(completenessResults);
|
|
1282
|
+
assert(completeness !== void 0, "Aggregate completeness evaluation produced no best result");
|
|
1283
|
+
if (completeness.judgment.value !== "true") {
|
|
1284
|
+
const judgmentBindings = bindingsArray(bindings);
|
|
1285
|
+
let goal;
|
|
1286
|
+
switch (aggregate.kind) {
|
|
1287
|
+
case "sum":
|
|
1288
|
+
assert(isNumericGoalExpression(valueExpression), "Sum value must be numeric");
|
|
1289
|
+
goal = {
|
|
1290
|
+
kind: "sum",
|
|
1291
|
+
bind: variable,
|
|
1292
|
+
value: valueExpression,
|
|
1293
|
+
where: whereGoal,
|
|
1294
|
+
completeWhen: completeness.judgment.goal
|
|
1295
|
+
};
|
|
1296
|
+
break;
|
|
1297
|
+
case "count":
|
|
1298
|
+
goal = {
|
|
1299
|
+
kind: "count",
|
|
1300
|
+
bind: variable,
|
|
1301
|
+
where: whereGoal,
|
|
1302
|
+
completeWhen: completeness.judgment.goal
|
|
1303
|
+
};
|
|
1304
|
+
break;
|
|
1305
|
+
default:
|
|
1306
|
+
return assertNever(aggregate.kind, "Unhandled aggregate kind");
|
|
1307
|
+
}
|
|
1308
|
+
return {
|
|
1309
|
+
kind: "incomplete",
|
|
1310
|
+
result: {
|
|
1311
|
+
judgment: { value: "unknown", goal, bindings: judgmentBindings },
|
|
1312
|
+
bindings
|
|
1313
|
+
}
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
const nextBindings = new Map(bindings);
|
|
1318
|
+
const values = rows.map((row) => resolveAggregateValue(valueExpression, row.bindings));
|
|
1319
|
+
let value;
|
|
1320
|
+
let judgment;
|
|
1321
|
+
switch (aggregate.kind) {
|
|
1322
|
+
case "sum":
|
|
1323
|
+
assert(isNumericGoalExpression(valueExpression), "Sum value must be numeric");
|
|
1324
|
+
value = sumValues(values);
|
|
1325
|
+
nextBindings.set(variable, value);
|
|
1326
|
+
judgment = primitiveSumJudgment(
|
|
1327
|
+
{
|
|
1328
|
+
kind: "sum",
|
|
1329
|
+
bind: variable,
|
|
1330
|
+
value: valueExpression,
|
|
1331
|
+
where: whereGoal
|
|
1332
|
+
},
|
|
1333
|
+
bindingsArray(nextBindings),
|
|
1334
|
+
values,
|
|
1335
|
+
variable,
|
|
1336
|
+
value
|
|
1337
|
+
);
|
|
1338
|
+
break;
|
|
1339
|
+
case "count": {
|
|
1340
|
+
const range = countRange(possibleRows, isOpenAggregate(whereBody));
|
|
1341
|
+
value = exactCountValue(range);
|
|
1342
|
+
nextBindings.set(variable, value);
|
|
1343
|
+
judgment = primitiveCountJudgment(
|
|
1344
|
+
{ kind: "count", bind: variable, where: whereGoal },
|
|
1345
|
+
bindingsArray(nextBindings),
|
|
1346
|
+
range,
|
|
1347
|
+
variable,
|
|
1348
|
+
value
|
|
1349
|
+
);
|
|
1350
|
+
break;
|
|
1351
|
+
}
|
|
1352
|
+
default:
|
|
1353
|
+
return assertNever(aggregate.kind, "Unhandled aggregate kind");
|
|
1354
|
+
}
|
|
1355
|
+
return {
|
|
1356
|
+
kind: "complete",
|
|
1357
|
+
expression: { value, expression: variable, bindings: nextBindings, judgments: [judgment] }
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
function resolveAggregateValue(expression, bindings) {
|
|
1361
|
+
const value = isVar(expression) ? valueForVariable(bindings, expression) : definedValue(expression, "Aggregate values must be defined");
|
|
1362
|
+
assert(value !== UNBOUND, "Aggregate variables must be bound");
|
|
1363
|
+
return value;
|
|
1364
|
+
}
|
|
1365
|
+
function aggregateValueIsAvailable(expression, bindings) {
|
|
1366
|
+
if (!isVar(expression)) {
|
|
1367
|
+
return expression !== void 0;
|
|
1368
|
+
}
|
|
1369
|
+
return valueForVariable(bindings, expression) !== UNBOUND;
|
|
1370
|
+
}
|
|
1371
|
+
function sumValues(values) {
|
|
1372
|
+
if (values.every((value) => typeof value === "bigint")) {
|
|
1373
|
+
return values.reduce((total, value) => total + value, 0n);
|
|
1374
|
+
}
|
|
1375
|
+
return values.reduce((total, value) => total + Number(value), 0);
|
|
1376
|
+
}
|
|
1377
|
+
function aggregateWhereGoal(judgments) {
|
|
1378
|
+
if (judgments.length === 1) {
|
|
1379
|
+
const judgment = judgments[0];
|
|
1380
|
+
assert(judgment !== void 0, "Expected one aggregate row judgment");
|
|
1381
|
+
return judgment.goal;
|
|
1382
|
+
}
|
|
1383
|
+
return { kind: "or", goals: judgments.map((judgment) => judgment.goal) };
|
|
1384
|
+
}
|
|
1385
|
+
function countRange(rows, open) {
|
|
1386
|
+
const definite = rows.filter((row) => row.judgment.value === "true").length;
|
|
1387
|
+
const conflicted = rows.filter((row) => row.judgment.value === "conflicted").length;
|
|
1388
|
+
const unknown = rows.filter((row) => row.judgment.value === "unknown").length;
|
|
1389
|
+
const uncertainty = [];
|
|
1390
|
+
if (conflicted > 0) {
|
|
1391
|
+
uncertainty.push({ kind: "conflict", count: conflicted });
|
|
1392
|
+
}
|
|
1393
|
+
if (unknown > 0) {
|
|
1394
|
+
uncertainty.push({ kind: "unknown", count: unknown });
|
|
1395
|
+
}
|
|
1396
|
+
if (open) {
|
|
1397
|
+
uncertainty.push({ kind: "open-world" });
|
|
1398
|
+
}
|
|
1399
|
+
return {
|
|
1400
|
+
kind: "numeric-range",
|
|
1401
|
+
lower: definite,
|
|
1402
|
+
upper: open ? "infinity" : definite + conflicted + unknown,
|
|
1403
|
+
uncertainty
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
1406
|
+
function exactCountValue(range) {
|
|
1407
|
+
return range.uncertainty.length === 0 && range.upper !== "infinity" && range.lower === range.upper ? range.lower : range;
|
|
1408
|
+
}
|
|
1409
|
+
function isOpenAggregate(whereBody) {
|
|
1410
|
+
if (isAtomLike(whereBody)) {
|
|
1411
|
+
return whereBody.predicate.world === "open";
|
|
1412
|
+
}
|
|
1413
|
+
if (isFormula(whereBody, "and") || isFormula(whereBody, "or")) {
|
|
1414
|
+
return whereBody.args.some(isOpenAggregate);
|
|
1415
|
+
}
|
|
1416
|
+
if (isFormula(whereBody, "exists")) {
|
|
1417
|
+
const body = whereBody.args.at(-1);
|
|
1418
|
+
return body !== void 0 && isOpenAggregate(body);
|
|
1419
|
+
}
|
|
1420
|
+
return true;
|
|
1421
|
+
}
|
|
1422
|
+
function primitiveSumJudgment(goal, bindings, values, variable, value) {
|
|
1423
|
+
return {
|
|
1424
|
+
value: "true",
|
|
1425
|
+
goal,
|
|
1426
|
+
bindings,
|
|
1427
|
+
positive: [
|
|
1428
|
+
primitiveSupport({
|
|
1429
|
+
operation: "sum",
|
|
1430
|
+
operands: values,
|
|
1431
|
+
value: "true",
|
|
1432
|
+
result: value,
|
|
1433
|
+
results: [[variable, value]],
|
|
1434
|
+
...primitiveRangeForExact(value)
|
|
1435
|
+
})
|
|
1436
|
+
]
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
function primitiveCountJudgment(goal, bindings, range, variable, value) {
|
|
1440
|
+
return {
|
|
1441
|
+
value: "true",
|
|
1442
|
+
goal,
|
|
1443
|
+
bindings,
|
|
1444
|
+
positive: [
|
|
1445
|
+
primitiveSupport({
|
|
1446
|
+
operation: "count",
|
|
1447
|
+
operands: [],
|
|
1448
|
+
value: "true",
|
|
1449
|
+
result: value,
|
|
1450
|
+
results: [[variable, value]],
|
|
1451
|
+
range: { lower: range.lower, upper: range.upper },
|
|
1452
|
+
...range.uncertainty.length === 0 ? {} : { uncertainty: range.uncertainty }
|
|
1453
|
+
})
|
|
1454
|
+
]
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
function primitiveArithmeticJudgment(goal, bindings, left, leftExpression, right, rightExpression, variable, value) {
|
|
1458
|
+
if (value === UNBOUND) {
|
|
1459
|
+
assert(
|
|
1460
|
+
left === UNBOUND || right === UNBOUND,
|
|
1461
|
+
"Unknown primitive arithmetic requires an unavailable operand"
|
|
1462
|
+
);
|
|
1463
|
+
const evaluation = {
|
|
1464
|
+
operation: goal.kind,
|
|
1465
|
+
operands: [publicOperand(left, leftExpression), publicOperand(right, rightExpression)],
|
|
1466
|
+
value: "unknown",
|
|
1467
|
+
uncertainty: missingBindingUncertainty(left, leftExpression, right, rightExpression)
|
|
1468
|
+
};
|
|
1469
|
+
return { value: "unknown", goal, bindings, inconclusive: [primitiveSupport(evaluation)] };
|
|
1470
|
+
}
|
|
1471
|
+
return {
|
|
1472
|
+
value: "true",
|
|
1473
|
+
goal,
|
|
1474
|
+
bindings,
|
|
1475
|
+
positive: [
|
|
1476
|
+
primitiveSupport({
|
|
1477
|
+
operation: goal.kind,
|
|
1478
|
+
operands: [left, right],
|
|
1479
|
+
value: "true",
|
|
1480
|
+
result: value,
|
|
1481
|
+
results: [[variable, value]],
|
|
1482
|
+
...primitiveRangeForExact(value)
|
|
1483
|
+
})
|
|
1484
|
+
]
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
function primitiveComparisonJudgment(goal, value, bindings, left, leftExpression, right, rightExpression) {
|
|
1488
|
+
const support = primitiveSupport({
|
|
1489
|
+
operation: goal.kind,
|
|
1490
|
+
operands: [publicOperand(left, leftExpression), publicOperand(right, rightExpression)],
|
|
1491
|
+
value,
|
|
1492
|
+
...isNumericRangeValue(left) ? { range: { lower: left.lower, upper: left.upper } } : {},
|
|
1493
|
+
...isNumericRangeValue(right) ? { range: { lower: right.lower, upper: right.upper } } : {},
|
|
1494
|
+
...primitiveUncertaintyForComparison(value, left, leftExpression, right, rightExpression)
|
|
1495
|
+
});
|
|
1496
|
+
assert(
|
|
1497
|
+
value === "unknown" || left !== UNBOUND && right !== UNBOUND,
|
|
1498
|
+
"Resolved primitive comparison operands must be bound"
|
|
1499
|
+
);
|
|
1500
|
+
assert(
|
|
1501
|
+
value !== "unknown" || left === UNBOUND || right === UNBOUND || isNumericRangeValue(left) || isNumericRangeValue(right),
|
|
1502
|
+
"Unknown primitive comparison requires an unavailable operand"
|
|
1503
|
+
);
|
|
1504
|
+
switch (value) {
|
|
1505
|
+
case "true":
|
|
1506
|
+
return { value, goal, bindings, positive: [support] };
|
|
1507
|
+
case "false":
|
|
1508
|
+
return { value, goal, bindings, negative: [support] };
|
|
1509
|
+
case "unknown":
|
|
1510
|
+
return { value, goal, bindings, inconclusive: [support] };
|
|
1511
|
+
case "conflicted":
|
|
1512
|
+
return { value, goal, bindings, positive: [support], negative: [support] };
|
|
1513
|
+
default:
|
|
1514
|
+
return assertNever(value, "Unhandled primitive comparison value");
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
function primitiveSupport(evaluation) {
|
|
1518
|
+
return { source: "primitive", evaluation };
|
|
1519
|
+
}
|
|
1520
|
+
function publicOperand(value, expression) {
|
|
1521
|
+
return value === UNBOUND ? missingBinding(expression) : value;
|
|
1522
|
+
}
|
|
1523
|
+
function missingBinding(expression) {
|
|
1524
|
+
assert(isVar(expression), "Only variables can have missing bindings");
|
|
1525
|
+
return { kind: "missing-binding", variable: expression };
|
|
1526
|
+
}
|
|
1527
|
+
function missingBindingUncertainty(left, leftExpression, right, rightExpression) {
|
|
1528
|
+
return [
|
|
1529
|
+
...left === UNBOUND ? [missingBinding(leftExpression)] : [],
|
|
1530
|
+
...right === UNBOUND ? [missingBinding(rightExpression)] : []
|
|
1531
|
+
];
|
|
1532
|
+
}
|
|
1533
|
+
function primitiveUncertaintyForComparison(value, left, leftExpression, right, rightExpression) {
|
|
1534
|
+
if (left === UNBOUND || right === UNBOUND) {
|
|
1535
|
+
return { uncertainty: missingBindingUncertainty(left, leftExpression, right, rightExpression) };
|
|
1536
|
+
}
|
|
1537
|
+
if (value === "true" || value === "false") {
|
|
1538
|
+
return {};
|
|
1539
|
+
}
|
|
1540
|
+
if (isNumericRangeValue(left)) {
|
|
1541
|
+
return { uncertainty: comparisonUncertainty(value, left) };
|
|
1542
|
+
}
|
|
1543
|
+
if (isNumericRangeValue(right)) {
|
|
1544
|
+
return { uncertainty: comparisonUncertainty(value, right) };
|
|
1545
|
+
}
|
|
1546
|
+
return {};
|
|
1547
|
+
}
|
|
1548
|
+
function primitiveRangeForExact(value) {
|
|
1549
|
+
if (typeof value !== "number" && typeof value !== "bigint") {
|
|
1550
|
+
return {};
|
|
1551
|
+
}
|
|
1552
|
+
return { range: { lower: value, upper: value } };
|
|
1553
|
+
}
|
|
1554
|
+
function isArithmeticFormula(value) {
|
|
1555
|
+
return isFormula(value) && ["add", "sub", "mul", "div"].includes(value.kind) && value.args.length === 2;
|
|
1556
|
+
}
|
|
1557
|
+
function isComparisonFormula(value) {
|
|
1558
|
+
return isFormula(value) && ["eq", "neq", "lt", "lte", "gt", "gte"].includes(value.kind) && value.args.length === 2;
|
|
1559
|
+
}
|
|
1560
|
+
function isLetInFormula(value) {
|
|
1561
|
+
return isFormula(value, "letIn") && value.args.length === 3;
|
|
1562
|
+
}
|
|
1563
|
+
function isAggregateFormula2(value) {
|
|
1564
|
+
return isFormula(value) && (value.kind === "sum" || value.kind === "count") && (value.args.length === 2 || value.args.length === 3);
|
|
1565
|
+
}
|
|
1566
|
+
function isLiteralLike(value) {
|
|
1567
|
+
return typeof value === "object" && value !== null && "sign" in value && (value.sign === "+" || value.sign === "-") && "atom" in value && isAtomLike(value.atom);
|
|
1568
|
+
}
|
|
1569
|
+
function calculateValue(kind, left, right, goal) {
|
|
1570
|
+
if (left === UNBOUND || right === UNBOUND) {
|
|
1571
|
+
return UNBOUND;
|
|
1572
|
+
}
|
|
1573
|
+
if (left instanceof Date && typeof right === "number") {
|
|
1574
|
+
switch (kind) {
|
|
1575
|
+
case "sub":
|
|
1576
|
+
return new Date(left.getTime() - right);
|
|
1577
|
+
case "add":
|
|
1578
|
+
case "mul":
|
|
1579
|
+
case "div":
|
|
1580
|
+
break;
|
|
1581
|
+
default:
|
|
1582
|
+
return assertNever(kind, "Unhandled arithmetic kind");
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
if (typeof left === "number" && typeof right === "number") {
|
|
1586
|
+
switch (kind) {
|
|
1587
|
+
case "add":
|
|
1588
|
+
return left + right;
|
|
1589
|
+
case "sub":
|
|
1590
|
+
return left - right;
|
|
1591
|
+
case "mul":
|
|
1592
|
+
return left * right;
|
|
1593
|
+
case "div":
|
|
1594
|
+
return left / right;
|
|
1595
|
+
default:
|
|
1596
|
+
return assertNever(kind, "Unhandled arithmetic kind");
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
if (typeof left === "bigint" && typeof right === "bigint") {
|
|
1600
|
+
switch (kind) {
|
|
1601
|
+
case "add":
|
|
1602
|
+
return left + right;
|
|
1603
|
+
case "sub":
|
|
1604
|
+
return left - right;
|
|
1605
|
+
case "mul":
|
|
1606
|
+
return left * right;
|
|
1607
|
+
case "div":
|
|
1608
|
+
return left / right;
|
|
1609
|
+
default:
|
|
1610
|
+
return assertNever(kind, "Unhandled arithmetic kind");
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
throw new PrimitiveArithmeticProgrammerError(kind, [left, right], goal);
|
|
1614
|
+
}
|
|
1615
|
+
function compareValues(kind, left, right) {
|
|
1616
|
+
assert(left !== UNBOUND && right !== UNBOUND, "Comparison operands must be bound");
|
|
1617
|
+
if (isNumericRangeValue(left) && typeof right === "number") {
|
|
1618
|
+
return compareRangeToNumber(kind, left, right);
|
|
1619
|
+
}
|
|
1620
|
+
if (typeof left === "number" && isNumericRangeValue(right)) {
|
|
1621
|
+
return compareNumberToRange(kind, left, right);
|
|
1622
|
+
}
|
|
1623
|
+
const leftComparable = left instanceof Date ? left.getTime() : left;
|
|
1624
|
+
const rightComparable = right instanceof Date ? right.getTime() : right;
|
|
1625
|
+
switch (kind) {
|
|
1626
|
+
case "eq":
|
|
1627
|
+
return leftComparable === rightComparable ? "true" : "false";
|
|
1628
|
+
case "neq":
|
|
1629
|
+
return leftComparable === rightComparable ? "false" : "true";
|
|
1630
|
+
case "lt":
|
|
1631
|
+
case "lte":
|
|
1632
|
+
case "gt":
|
|
1633
|
+
case "gte":
|
|
1634
|
+
return compareOrderedValues(kind, leftComparable, rightComparable);
|
|
1635
|
+
default:
|
|
1636
|
+
return assertNever(kind, "Unhandled comparison kind");
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
function compareOrderedValues(kind, left, right) {
|
|
1640
|
+
assert(
|
|
1641
|
+
(typeof left === "number" || typeof left === "bigint") && (typeof right === "number" || typeof right === "bigint"),
|
|
1642
|
+
"Values must be comparable"
|
|
1643
|
+
);
|
|
1644
|
+
switch (kind) {
|
|
1645
|
+
case "lt":
|
|
1646
|
+
return left < right ? "true" : "false";
|
|
1647
|
+
case "lte":
|
|
1648
|
+
return left <= right ? "true" : "false";
|
|
1649
|
+
case "gt":
|
|
1650
|
+
return left > right ? "true" : "false";
|
|
1651
|
+
case "gte":
|
|
1652
|
+
return left >= right ? "true" : "false";
|
|
1653
|
+
default:
|
|
1654
|
+
return assertNever(kind, "Unhandled ordered comparison kind");
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
function compareRangeToNumber(kind, range, threshold) {
|
|
1658
|
+
switch (kind) {
|
|
1659
|
+
case "gte":
|
|
1660
|
+
if (range.lower >= threshold) {
|
|
1661
|
+
return "true";
|
|
1662
|
+
}
|
|
1663
|
+
if (range.upper !== "infinity" && range.upper < threshold) {
|
|
1664
|
+
return "false";
|
|
1665
|
+
}
|
|
1666
|
+
return intervalUncertaintyValue(kind, range, threshold);
|
|
1667
|
+
case "gt":
|
|
1668
|
+
if (range.lower > threshold) {
|
|
1669
|
+
return "true";
|
|
1670
|
+
}
|
|
1671
|
+
if (range.upper !== "infinity" && range.upper <= threshold) {
|
|
1672
|
+
return "false";
|
|
1673
|
+
}
|
|
1674
|
+
return intervalUncertaintyValue(kind, range, threshold);
|
|
1675
|
+
case "lte":
|
|
1676
|
+
if (range.upper !== "infinity" && range.upper <= threshold) {
|
|
1677
|
+
return "true";
|
|
1678
|
+
}
|
|
1679
|
+
if (range.lower > threshold) {
|
|
1680
|
+
return "false";
|
|
1681
|
+
}
|
|
1682
|
+
return intervalUncertaintyValue(kind, range, threshold);
|
|
1683
|
+
case "lt":
|
|
1684
|
+
if (range.upper !== "infinity" && range.upper < threshold) {
|
|
1685
|
+
return "true";
|
|
1686
|
+
}
|
|
1687
|
+
if (range.lower >= threshold) {
|
|
1688
|
+
return "false";
|
|
1689
|
+
}
|
|
1690
|
+
return intervalUncertaintyValue(kind, range, threshold);
|
|
1691
|
+
case "eq":
|
|
1692
|
+
case "neq":
|
|
1693
|
+
return range.upper !== "infinity" && range.lower === range.upper ? compareValues(kind, range.lower, threshold) : intervalUncertaintyValue(kind, range, threshold);
|
|
1694
|
+
default:
|
|
1695
|
+
return assertNever(kind, "Unhandled range comparison kind");
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
function compareNumberToRange(kind, value, range) {
|
|
1699
|
+
switch (kind) {
|
|
1700
|
+
case "gte":
|
|
1701
|
+
return compareRangeToNumber("lte", range, value);
|
|
1702
|
+
case "gt":
|
|
1703
|
+
return compareRangeToNumber("lt", range, value);
|
|
1704
|
+
case "lte":
|
|
1705
|
+
return compareRangeToNumber("gte", range, value);
|
|
1706
|
+
case "lt":
|
|
1707
|
+
return compareRangeToNumber("gt", range, value);
|
|
1708
|
+
case "eq":
|
|
1709
|
+
case "neq":
|
|
1710
|
+
return compareRangeToNumber(kind, range, value);
|
|
1711
|
+
default:
|
|
1712
|
+
return assertNever(kind, "Unhandled number-to-range comparison kind");
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
function intervalUncertaintyValue(kind, range, threshold) {
|
|
1716
|
+
return conflictCanDecide(kind, range, threshold) ? "conflicted" : "unknown";
|
|
1717
|
+
}
|
|
1718
|
+
function conflictCanDecide(kind, range, threshold) {
|
|
1719
|
+
const conflicted = range.uncertainty.find((item) => item.kind === "conflict")?.count ?? 0;
|
|
1720
|
+
if (conflicted === 0) {
|
|
1721
|
+
return false;
|
|
1722
|
+
}
|
|
1723
|
+
const conflictUpper = range.lower + conflicted;
|
|
1724
|
+
switch (kind) {
|
|
1725
|
+
case "gte":
|
|
1726
|
+
return range.lower < threshold && threshold <= conflictUpper;
|
|
1727
|
+
case "gt":
|
|
1728
|
+
return range.lower <= threshold && threshold < conflictUpper;
|
|
1729
|
+
case "lte":
|
|
1730
|
+
return range.lower <= threshold && threshold < conflictUpper;
|
|
1731
|
+
case "lt":
|
|
1732
|
+
return range.lower < threshold && threshold <= conflictUpper;
|
|
1733
|
+
case "eq":
|
|
1734
|
+
case "neq":
|
|
1735
|
+
return range.lower <= threshold && threshold <= conflictUpper;
|
|
1736
|
+
default:
|
|
1737
|
+
return assertNever(kind, "Unhandled conflict range comparison kind");
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
function comparisonUncertainty(value, range) {
|
|
1741
|
+
if (value === "conflicted") {
|
|
1742
|
+
return range.uncertainty.filter((item) => item.kind === "conflict");
|
|
1743
|
+
}
|
|
1744
|
+
if (value === "unknown") {
|
|
1745
|
+
return range.uncertainty.filter((item) => item.kind !== "conflict");
|
|
1746
|
+
}
|
|
1747
|
+
return [];
|
|
1748
|
+
}
|
|
1749
|
+
function isNumericRangeValue(value) {
|
|
1750
|
+
return typeof value === "object" && value !== null && "kind" in value && value.kind === "numeric-range";
|
|
1751
|
+
}
|
|
1752
|
+
function bestResult(results) {
|
|
1753
|
+
return results.reduce((best, result) => {
|
|
1754
|
+
if (best === void 0) {
|
|
1755
|
+
return result;
|
|
1756
|
+
}
|
|
1757
|
+
return maxGoalValue(best.judgment.value, result.judgment.value) === result.judgment.value ? result : best;
|
|
1758
|
+
}, void 0);
|
|
1759
|
+
}
|
|
1760
|
+
function extendBindings(pattern, atom, bindings) {
|
|
1761
|
+
const matched = matchPattern(pattern, atom);
|
|
1762
|
+
if (matched === void 0) {
|
|
1763
|
+
return void 0;
|
|
1764
|
+
}
|
|
1765
|
+
const next = new Map(bindings);
|
|
1766
|
+
for (const [variable, value] of matched) {
|
|
1767
|
+
if (next.has(variable) && !fieldEquals(next.get(variable), value)) {
|
|
1768
|
+
return void 0;
|
|
1769
|
+
}
|
|
1770
|
+
next.set(variable, value);
|
|
1771
|
+
}
|
|
1772
|
+
return next;
|
|
1773
|
+
}
|
|
1774
|
+
function compoundJudgment(goal, value, bindings, judgments) {
|
|
1775
|
+
const support = { source: "subgoals", judgments };
|
|
1776
|
+
switch (value) {
|
|
1777
|
+
case "true":
|
|
1778
|
+
return { value, goal, bindings, positive: [support] };
|
|
1779
|
+
case "false":
|
|
1780
|
+
return { value, goal, bindings, negative: [support] };
|
|
1781
|
+
case "conflicted":
|
|
1782
|
+
return { value, goal, bindings, positive: [support], negative: [support] };
|
|
1783
|
+
case "unknown":
|
|
1784
|
+
return { value, goal, bindings, inconclusive: [support] };
|
|
1785
|
+
default:
|
|
1786
|
+
return assertNever(value, "Unhandled compound goal value");
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
function compoundExistsJudgment(goal, value, bindings, judgments) {
|
|
1790
|
+
const support = { source: "subgoals", judgments };
|
|
1791
|
+
switch (value) {
|
|
1792
|
+
case "true":
|
|
1793
|
+
return { value, goal, bindings, positive: [support] };
|
|
1794
|
+
case "false":
|
|
1795
|
+
return { value, goal, bindings, negative: [support] };
|
|
1796
|
+
case "conflicted":
|
|
1797
|
+
return { value, goal, bindings, positive: [support], negative: [support] };
|
|
1798
|
+
case "unknown":
|
|
1799
|
+
return { value, goal, bindings, inconclusive: [support] };
|
|
1800
|
+
default:
|
|
1801
|
+
return assertNever(value, "Unhandled exists goal value");
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
function withBindings(judgment, bindings) {
|
|
1805
|
+
return { ...judgment, bindings };
|
|
1806
|
+
}
|
|
1807
|
+
function ruleContribution(kind, value) {
|
|
1808
|
+
return kind === "if" && value === "false" ? "unknown" : value;
|
|
1809
|
+
}
|
|
1810
|
+
function bindingsArray(bindings) {
|
|
1811
|
+
return [...bindings.entries()];
|
|
1812
|
+
}
|
|
1813
|
+
function uniqueBindings(bindings) {
|
|
1814
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1815
|
+
const unique = [];
|
|
1816
|
+
for (const binding of bindings) {
|
|
1817
|
+
const key = JSON.stringify(
|
|
1818
|
+
bindingsArray(binding).map(([variable, value]) => [variable.name, fieldIdentityToken(value)])
|
|
1819
|
+
);
|
|
1820
|
+
if (!seen.has(key)) {
|
|
1821
|
+
seen.add(key);
|
|
1822
|
+
unique.push(binding);
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
return unique;
|
|
1826
|
+
}
|
|
1827
|
+
function canGroundAtom(atom, bindings) {
|
|
1828
|
+
return Object.values(atom.fields).every((value) => !isVar(value) || bindings.has(value));
|
|
1829
|
+
}
|
|
1830
|
+
function groundAtomKey(atom) {
|
|
1831
|
+
const fields = atom.predicate.paramNames.map((name) => fieldIdentityToken(atom.fields[name]));
|
|
1832
|
+
return `${atom.predicate.name}:${JSON.stringify(fields)}`;
|
|
1833
|
+
}
|
|
1834
|
+
function candidateGroundAtoms(literals, rules, atom, active, search) {
|
|
1835
|
+
return search.candidates.candidates(
|
|
1836
|
+
atom,
|
|
1837
|
+
matchingGroundAtoms(literals, atom),
|
|
1838
|
+
() => deriveCandidateAtoms(literals, rules, atom, active, search)
|
|
1839
|
+
).filter(
|
|
1840
|
+
(candidate) => isAtomForPredicate(candidate, atom.predicate)
|
|
1841
|
+
);
|
|
1842
|
+
}
|
|
1843
|
+
function deriveCandidateAtoms(literals, rules, atom, active, search) {
|
|
1844
|
+
const atoms = [];
|
|
1845
|
+
for (const rule of rules) {
|
|
1846
|
+
const ruleHead = atomForPredicate(rule.head.atom, atom.predicate);
|
|
1847
|
+
if (ruleHead === void 0) {
|
|
1848
|
+
continue;
|
|
1849
|
+
}
|
|
1850
|
+
const bindings = matchRuleHeadToQuery(ruleHead, atom);
|
|
1851
|
+
if (bindings === void 0) {
|
|
1852
|
+
continue;
|
|
1853
|
+
}
|
|
1854
|
+
const context = { nextTemp: 0, search, candidatePredicate: atom.predicate };
|
|
1855
|
+
const bodies = evaluateRuleBodyMany(literals, rules, rule.body, bindings, active, context);
|
|
1856
|
+
for (const body of bodies) {
|
|
1857
|
+
if (!canGroundAtom(ruleHead, body.bindings)) {
|
|
1858
|
+
continue;
|
|
1859
|
+
}
|
|
1860
|
+
const candidate = groundAtom(ruleHead, body.bindings);
|
|
1861
|
+
if (!matchesAtom(candidate, atom) || atoms.some((existing) => atomEquals(existing, candidate))) {
|
|
1862
|
+
continue;
|
|
1863
|
+
}
|
|
1864
|
+
atoms.push(candidate);
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
return atoms;
|
|
1868
|
+
}
|
|
1869
|
+
function matchRuleHeadToQuery(ruleHead, query2) {
|
|
1870
|
+
if (ruleHead.predicate !== query2.predicate) {
|
|
1871
|
+
return void 0;
|
|
1872
|
+
}
|
|
1873
|
+
const bindings = /* @__PURE__ */ new Map();
|
|
1874
|
+
for (const [name, ruleValue] of Object.entries(ruleHead.fields)) {
|
|
1875
|
+
const queryValue = query2.fields[name];
|
|
1876
|
+
if (isVar(queryValue)) {
|
|
1877
|
+
continue;
|
|
1878
|
+
}
|
|
1879
|
+
if (isVar(ruleValue)) {
|
|
1880
|
+
const boundValue = bindings.get(ruleValue);
|
|
1881
|
+
if (bindings.has(ruleValue) && !fieldEquals(boundValue, queryValue)) {
|
|
1882
|
+
return void 0;
|
|
1883
|
+
}
|
|
1884
|
+
bindings.set(ruleValue, queryValue);
|
|
1885
|
+
continue;
|
|
1886
|
+
}
|
|
1887
|
+
if (!fieldEquals(ruleValue, queryValue)) {
|
|
1888
|
+
return void 0;
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
return bindings;
|
|
1892
|
+
}
|
|
1893
|
+
function matchingGroundAtoms(literals, atom) {
|
|
1894
|
+
const atoms = [];
|
|
1895
|
+
for (const literal of literals) {
|
|
1896
|
+
const literalAtom = atomForPredicate(literal.atom, atom.predicate);
|
|
1897
|
+
if (literalAtom === void 0 || !isGroundAtom(literalAtom)) {
|
|
1898
|
+
continue;
|
|
1899
|
+
}
|
|
1900
|
+
if (!matchesAtom(literalAtom, atom)) {
|
|
1901
|
+
continue;
|
|
1902
|
+
}
|
|
1903
|
+
if (atoms.some((candidate) => atomEquals(candidate, literalAtom))) {
|
|
1904
|
+
continue;
|
|
1905
|
+
}
|
|
1906
|
+
atoms.push(literalAtom);
|
|
1907
|
+
}
|
|
1908
|
+
return atoms;
|
|
1909
|
+
}
|
|
1910
|
+
function matchesAtom(candidate, atom) {
|
|
1911
|
+
return matchPattern(atom, candidate) !== void 0;
|
|
1912
|
+
}
|
|
1913
|
+
function matchingLiterals(literals, atom, sign) {
|
|
1914
|
+
return literals.filter((literal) => {
|
|
1915
|
+
const literalAtom = atomForPredicate(literal.atom, atom.predicate);
|
|
1916
|
+
return literal.sign === sign && literalAtom !== void 0 && atomEquals(literalAtom, atom);
|
|
1917
|
+
});
|
|
1918
|
+
}
|
|
1919
|
+
function erasedAtom(atom) {
|
|
1920
|
+
return atom;
|
|
1921
|
+
}
|
|
1922
|
+
function atomForPredicate(atom, predicate) {
|
|
1923
|
+
if (!isAtomForPredicate(atom, predicate)) {
|
|
1924
|
+
return void 0;
|
|
1925
|
+
}
|
|
1926
|
+
return atom;
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
// src/solver/solve.ts
|
|
1930
|
+
function solveQuery(literals, rules, atom) {
|
|
1931
|
+
const results = solveQueryAll(literals, rules, atom);
|
|
1932
|
+
const [result] = results;
|
|
1933
|
+
assert(result !== void 0, "Ground query must produce one result");
|
|
1934
|
+
return result;
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
// src/query/query.ts
|
|
1938
|
+
function query(kb, atom) {
|
|
1939
|
+
return solveQuery(kb.literals(), kb.rules(), atom);
|
|
1940
|
+
}
|
|
1941
|
+
function queryAll(kb, pattern) {
|
|
1942
|
+
return solveQueryAll(kb.literals(), kb.rules(), pattern);
|
|
1943
|
+
}
|
|
1944
|
+
export {
|
|
1945
|
+
query,
|
|
1946
|
+
queryAll
|
|
1947
|
+
};
|