factbag 0.0.0-alpha.2 → 0.0.0-alpha.3
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 +21 -0
- package/dist/core/{chunk-X3LSDJL4.js → chunk-QOMFY3XB.js} +4 -1
- package/dist/core/{chunk-5J3WGMN2.js → chunk-RMQ3YX7V.js} +2 -1
- package/dist/core/dsl/index.d.ts +3 -3
- package/dist/core/dsl/index.js +56 -2
- package/dist/core/dsl/operators/arithmetic.d.ts +69 -2
- package/dist/core/dsl/operators/comparison.d.ts +11 -1
- package/dist/core/dsl/types.d.ts +2 -0
- package/dist/core/exceptions/programmer-errors.d.ts +4 -4
- package/dist/core/explanation/explain.d.ts +5 -1
- package/dist/core/explanation/index.js +45 -5
- package/dist/core/knowledge-base/index.js +2 -2
- package/dist/core/query/index.d.ts +1 -1
- package/dist/core/query/index.js +406 -67
- package/dist/core/solver/calendar.d.ts +4 -0
- package/dist/core/solver/candidate-search.d.ts +3 -1
- package/dist/core/solver/program-index.d.ts +17 -0
- package/dist/core/solver/query-validation/utils.d.ts +2 -0
- package/dist/core/types/atom.d.ts +6 -1
- package/dist/core/types/goal.d.ts +23 -5
- package/dist/core/types/judgment.d.ts +2 -2
- package/package.json +1 -1
- /package/dist/core/{chunk-A6ZZJWIO.js → chunk-NDUIQAH4.js} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# factbag
|
|
2
2
|
|
|
3
|
+
## 0.0.0-alpha.3
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- f8f2712: Add `eq` and `neq` rule-body comparisons for numbers, dates, strings, booleans, and entities. Entities compare by type and id rather than instance; dates compare by instant.
|
|
8
|
+
- 900b104: Add min, max, roundTo, floorTo, and ceilTo numeric arithmetic terms for rule bodies. Rounding supports whole-number decimal places (including negative places), with halves rounded away from zero. Decimal exponent shifting avoids multiplication artifacts at decimal rounding boundaries.
|
|
9
|
+
- 4f040e3: Add `daysBetween(later, earlier)` and `yearsBetween(later, earlier)` as number-valued arithmetic terms over dates. They count signed UTC calendar days and whole anniversary years, ignoring time of day; February 29 anniversaries fall on March 1 in common years. Numeric subtraction on variables now composes with other numeric operators.
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 01cc338: Speed up queries over large rulesets with linear-time recursive predicate grouping,
|
|
14
|
+
per-query rule and evidence indexes, and judgment reuse during candidate discovery.
|
|
15
|
+
Preserve candidate pruning and distinguish same-named predicate declarations when
|
|
16
|
+
tracking active atoms, avoiding incorrect recursion cutoffs.
|
|
17
|
+
- 80d1e75: Share explanation nodes for reused proof judgments so explanation construction no longer grows with the number of paths through a shared proof. Identical sub-explanations are still listed once, and rendered content is unchanged.
|
|
18
|
+
- a074dab: Judge evidence-free, closed-world recursive components together as a least fixed
|
|
19
|
+
point instead of enumerating recursive proof paths. Doubly recursive transitive
|
|
20
|
+
closures and symmetric compositions no longer require exponential proof search.
|
|
21
|
+
Other components retain the existing search semantics. Variable queries recognize
|
|
22
|
+
a non-recursive disjunctive base case within an exclusive definition.
|
|
23
|
+
|
|
3
24
|
## 0.0.0-alpha.2
|
|
4
25
|
|
|
5
26
|
### Major Changes
|
|
@@ -13,7 +13,10 @@ var PrimitiveArithmeticProgrammerError = class extends ProgrammerError {
|
|
|
13
13
|
/** The arithmetic goal being evaluated when the failure occurred. */
|
|
14
14
|
goal;
|
|
15
15
|
constructor(operation, operands, goal) {
|
|
16
|
-
|
|
16
|
+
const rounding = operation === "roundTo" || operation === "floorTo" || operation === "ceilTo";
|
|
17
|
+
super(
|
|
18
|
+
`${operation} expected ${operation === "daysBetween" || operation === "yearsBetween" ? "date" : "numeric"} operands${rounding ? " and a whole number of decimal places" : ""}`
|
|
19
|
+
);
|
|
17
20
|
this.name = "PrimitiveArithmeticProgrammerError";
|
|
18
21
|
this.operation = operation;
|
|
19
22
|
this.operands = operands;
|
package/dist/core/dsl/index.d.ts
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export { and, exists, letIn, not, or } from "./formula.js";
|
|
8
8
|
export { count, sum } from "./operators/aggregate.js";
|
|
9
|
-
export type { AggregateBuilder, ComparableTerm, DateTerm, NumericTerm } from "./types.js";
|
|
10
|
-
export { add, div, mul, sub } from "./operators/arithmetic.js";
|
|
11
|
-
export { gt, gte, lte } from "./operators/comparison.js";
|
|
9
|
+
export type { AggregateBuilder, ComparableTerm, DateTerm, EqualityTerm, NumericTerm, } from "./types.js";
|
|
10
|
+
export { add, ceilTo, daysBetween, div, floorTo, max, min, mul, roundTo, sub, yearsBetween, } from "./operators/arithmetic.js";
|
|
11
|
+
export { eq, gt, gte, lte, neq } from "./operators/comparison.js";
|
|
12
12
|
export { closedPredicate, predicate } from "./predicate.js";
|
|
13
13
|
export { enumeration } from "./enumeration.js";
|
|
14
14
|
export type { ClosedWorldPredicate, OpenWorldPredicate, Predicate, PredicateBase, PredicateDeclaration, PredicateMetadata, PredicateWorld, } from "../types/predicate.js";
|
package/dist/core/dsl/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
assertAtomLike,
|
|
3
3
|
assertGroundAtom
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-RMQ3YX7V.js";
|
|
5
5
|
import {
|
|
6
6
|
Var
|
|
7
7
|
} from "../chunk-UVOCOH2I.js";
|
|
@@ -68,8 +68,53 @@ function div(left, right) {
|
|
|
68
68
|
assertSupportedValue(right);
|
|
69
69
|
return { kind: "div", args: [left, right] };
|
|
70
70
|
}
|
|
71
|
+
function daysBetween(later, earlier) {
|
|
72
|
+
assertSupportedValue(later);
|
|
73
|
+
assertSupportedValue(earlier);
|
|
74
|
+
return { kind: "daysBetween", args: [later, earlier] };
|
|
75
|
+
}
|
|
76
|
+
function yearsBetween(later, earlier) {
|
|
77
|
+
assertSupportedValue(later);
|
|
78
|
+
assertSupportedValue(earlier);
|
|
79
|
+
return { kind: "yearsBetween", args: [later, earlier] };
|
|
80
|
+
}
|
|
81
|
+
function min(left, right) {
|
|
82
|
+
assertSupportedValue(left);
|
|
83
|
+
assertSupportedValue(right);
|
|
84
|
+
return { kind: "min", args: [left, right] };
|
|
85
|
+
}
|
|
86
|
+
function max(left, right) {
|
|
87
|
+
assertSupportedValue(left);
|
|
88
|
+
assertSupportedValue(right);
|
|
89
|
+
return { kind: "max", args: [left, right] };
|
|
90
|
+
}
|
|
91
|
+
function roundTo(value, places) {
|
|
92
|
+
assertSupportedValue(value);
|
|
93
|
+
assertSupportedValue(places);
|
|
94
|
+
return { kind: "roundTo", args: [value, places] };
|
|
95
|
+
}
|
|
96
|
+
function floorTo(value, places) {
|
|
97
|
+
assertSupportedValue(value);
|
|
98
|
+
assertSupportedValue(places);
|
|
99
|
+
return { kind: "floorTo", args: [value, places] };
|
|
100
|
+
}
|
|
101
|
+
function ceilTo(value, places) {
|
|
102
|
+
assertSupportedValue(value);
|
|
103
|
+
assertSupportedValue(places);
|
|
104
|
+
return { kind: "ceilTo", args: [value, places] };
|
|
105
|
+
}
|
|
71
106
|
|
|
72
107
|
// src/dsl/operators/comparison.ts
|
|
108
|
+
function eq(left, right) {
|
|
109
|
+
assertSupportedValue(left);
|
|
110
|
+
assertSupportedValue(right);
|
|
111
|
+
return { kind: "eq", args: [left, right] };
|
|
112
|
+
}
|
|
113
|
+
function neq(left, right) {
|
|
114
|
+
assertSupportedValue(left);
|
|
115
|
+
assertSupportedValue(right);
|
|
116
|
+
return { kind: "neq", args: [left, right] };
|
|
117
|
+
}
|
|
73
118
|
function gt(left, right) {
|
|
74
119
|
assertSupportedValue(left);
|
|
75
120
|
assertSupportedValue(right);
|
|
@@ -138,20 +183,29 @@ export {
|
|
|
138
183
|
and,
|
|
139
184
|
assertAtomLike,
|
|
140
185
|
assertGroundAtom,
|
|
186
|
+
ceilTo,
|
|
141
187
|
closedPredicate,
|
|
142
188
|
count,
|
|
189
|
+
daysBetween,
|
|
143
190
|
div,
|
|
144
191
|
enumeration,
|
|
192
|
+
eq,
|
|
145
193
|
exists,
|
|
194
|
+
floorTo,
|
|
146
195
|
gt,
|
|
147
196
|
gte,
|
|
148
197
|
letIn,
|
|
149
198
|
lte,
|
|
199
|
+
max,
|
|
200
|
+
min,
|
|
150
201
|
mul,
|
|
202
|
+
neq,
|
|
151
203
|
not,
|
|
152
204
|
or,
|
|
153
205
|
predicate,
|
|
206
|
+
roundTo,
|
|
154
207
|
sub,
|
|
155
208
|
sum,
|
|
156
|
-
variable
|
|
209
|
+
variable,
|
|
210
|
+
yearsBetween
|
|
157
211
|
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { DateTerm, Formula, NumericTerm } from "../types.js";
|
|
2
2
|
/** Add two numeric terms, producing a numeric rule-body expression. */
|
|
3
3
|
export declare function add(left: NumericTerm, right: NumericTerm): Formula<number, readonly [NumericTerm, NumericTerm]>;
|
|
4
|
+
/** Subtract one numeric term from another. */
|
|
5
|
+
export declare function sub(left: NumericTerm, right: NumericTerm): Formula<number, readonly [NumericTerm, NumericTerm]>;
|
|
4
6
|
/**
|
|
5
7
|
* Subtract a numeric duration in milliseconds from a date term, producing a
|
|
6
8
|
* date expression.
|
|
@@ -19,9 +21,74 @@ export declare function add(left: NumericTerm, right: NumericTerm): Formula<numb
|
|
|
19
21
|
* ```
|
|
20
22
|
*/
|
|
21
23
|
export declare function sub(left: DateTerm, right: NumericTerm): Formula<Date, readonly [DateTerm, NumericTerm]>;
|
|
22
|
-
/** Subtract one numeric term from another. */
|
|
23
|
-
export declare function sub(left: NumericTerm, right: NumericTerm): Formula<number, readonly [NumericTerm, NumericTerm]>;
|
|
24
24
|
/** Multiply two numeric terms, producing a numeric rule-body expression. */
|
|
25
25
|
export declare function mul(left: NumericTerm, right: NumericTerm): Formula<number, readonly [NumericTerm, NumericTerm]>;
|
|
26
26
|
/** Divide one numeric term by another, producing a numeric rule-body expression. */
|
|
27
27
|
export declare function div(left: NumericTerm, right: NumericTerm): Formula<number, readonly [NumericTerm, NumericTerm]>;
|
|
28
|
+
/**
|
|
29
|
+
* Count signed UTC calendar days from `earlier` to `later`, ignoring time of day.
|
|
30
|
+
* Add one to count both the first and last day of a stay.
|
|
31
|
+
*/
|
|
32
|
+
export declare function daysBetween(later: DateTerm, earlier: DateTerm): Formula<number, readonly [DateTerm, DateTerm]>;
|
|
33
|
+
/**
|
|
34
|
+
* Count signed whole years from `earlier` to `later` by UTC calendar anniversaries,
|
|
35
|
+
* ignoring time of day. February 29 anniversaries fall on March 1 in common years.
|
|
36
|
+
* A person's age on a day is `yearsBetween(day, birthday)`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function yearsBetween(later: DateTerm, earlier: DateTerm): Formula<number, readonly [DateTerm, DateTerm]>;
|
|
39
|
+
/**
|
|
40
|
+
* The lesser of two numeric terms.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* // Deduction reduction cannot exceed 80% of the deductions.
|
|
45
|
+
* letIn(R, min(mul(0.03, Excess), mul(0.8, Deductions)), gte(R, 0))
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export declare function min(left: NumericTerm, right: NumericTerm): Formula<number, readonly [NumericTerm, NumericTerm]>;
|
|
49
|
+
/**
|
|
50
|
+
* The greater of two numeric terms.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* // The remaining allowance cannot be negative.
|
|
55
|
+
* letIn(A, max(sub(2000, Reduction), 0), gte(A, 0))
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export declare function max(left: NumericTerm, right: NumericTerm): Formula<number, readonly [NumericTerm, NumericTerm]>;
|
|
59
|
+
/**
|
|
60
|
+
* Round to a whole number of decimal places, with halves away from zero.
|
|
61
|
+
* Negative places round to tens, hundreds, and so on. Decimal exponent shifting
|
|
62
|
+
* avoids multiplication artifacts such as `1.005 * 100`, so `roundTo(1.005, 2)`
|
|
63
|
+
* yields `1.01`. Inputs and results still have JavaScript number precision;
|
|
64
|
+
* this does not recover precision already lost in an earlier expression.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* // Tax is 15% of taxable income, in whole dollars.
|
|
69
|
+
* letIn(Tax, roundTo(mul(Income, 0.15), 0), gte(Tax, 0))
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export declare function roundTo(value: NumericTerm, places: NumericTerm): Formula<number, readonly [NumericTerm, NumericTerm]>;
|
|
73
|
+
/**
|
|
74
|
+
* Round toward negative infinity at a whole number of decimal places.
|
|
75
|
+
* Negative places and decimal exponent shifting work as in {@link roundTo}.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* // Only complete thousands of eligible expenses count.
|
|
80
|
+
* letIn(Eligible, floorTo(Expenses, -3), gte(Eligible, 0))
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export declare function floorTo(value: NumericTerm, places: NumericTerm): Formula<number, readonly [NumericTerm, NumericTerm]>;
|
|
84
|
+
/**
|
|
85
|
+
* Round toward positive infinity at a whole number of decimal places.
|
|
86
|
+
* Negative places and decimal exponent shifting work as in {@link roundTo}.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* // Two percentage points for each $2,500, or fraction thereof, of the excess.
|
|
91
|
+
* letIn(Points, mul(2, ceilTo(div(Excess, 2500), 0)), lte(Points, 100))
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export declare function ceilTo(value: NumericTerm, places: NumericTerm): Formula<number, readonly [NumericTerm, NumericTerm]>;
|
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
import type { ComparableTerm, Formula } from "../types.js";
|
|
1
|
+
import type { ComparableTerm, EqualityTerm, Formula } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Require equal values: numbers, strings, and booleans by value, dates by instant,
|
|
4
|
+
* and entities by type and id rather than instance.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* Comparisons gate whether a rule fires but contribute no proof children.
|
|
8
|
+
*/
|
|
9
|
+
export declare function eq(left: EqualityTerm, right: EqualityTerm): Formula<boolean, readonly [EqualityTerm, EqualityTerm]>;
|
|
10
|
+
/** Require the two terms to differ, in the sense of {@link eq}. */
|
|
11
|
+
export declare function neq(left: EqualityTerm, right: EqualityTerm): Formula<boolean, readonly [EqualityTerm, EqualityTerm]>;
|
|
2
12
|
/**
|
|
3
13
|
* Require the left term to be strictly greater than the right.
|
|
4
14
|
*
|
package/dist/core/dsl/types.d.ts
CHANGED
|
@@ -44,6 +44,8 @@ export type NumericTerm = number | Var | Formula<number, readonly unknown[]>;
|
|
|
44
44
|
export type DateTerm = Date | Var | Formula<Date, readonly unknown[]>;
|
|
45
45
|
/** Term accepted by comparison helpers. */
|
|
46
46
|
export type ComparableTerm = NumericTerm | DateTerm;
|
|
47
|
+
/** Term accepted by equality: a comparable term, a string, a boolean, or an entity. */
|
|
48
|
+
export type EqualityTerm = ComparableTerm | string | boolean | Entity;
|
|
47
49
|
/** Aggregate builder returned by sum(...) and count(...). */
|
|
48
50
|
export type AggregateBuilder<Value extends number = number> = Readonly<{
|
|
49
51
|
where: (<const Body>(body: Body & RuleBodyInput<Body>) => Formula<Value, readonly [Body]>) & (<const Body, const CompleteWhen>(body: Body & RuleBodyInput<Body>, options: Readonly<{
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ArithmeticGoal } from "../types/goal.js";
|
|
1
|
+
import type { ArithmeticGoal, ArithmeticKind } from "../types/goal.js";
|
|
2
2
|
import type { PredicateDeclaration } from "../types/predicate.js";
|
|
3
3
|
import type { AnyRule } from "../types/rule.js";
|
|
4
4
|
import type { Var } from "../types/variable.js";
|
|
@@ -9,12 +9,12 @@ export declare class ProgrammerError extends Error {
|
|
|
9
9
|
/** Arithmetic primitive with operands the solver cannot legally evaluate. */
|
|
10
10
|
export declare class PrimitiveArithmeticProgrammerError extends ProgrammerError {
|
|
11
11
|
/** The arithmetic operation that failed. */
|
|
12
|
-
readonly operation:
|
|
12
|
+
readonly operation: ArithmeticKind;
|
|
13
13
|
/** The evaluated left and right operand values. */
|
|
14
14
|
readonly operands: readonly [unknown, unknown];
|
|
15
15
|
/** The arithmetic goal being evaluated when the failure occurred. */
|
|
16
|
-
readonly goal: ArithmeticGoal<
|
|
17
|
-
constructor(operation:
|
|
16
|
+
readonly goal: ArithmeticGoal<ArithmeticKind>;
|
|
17
|
+
constructor(operation: ArithmeticKind, operands: readonly [unknown, unknown], goal: ArithmeticGoal<ArithmeticKind>);
|
|
18
18
|
}
|
|
19
19
|
/** Rule body uses variables before any constructive source can bind them. */
|
|
20
20
|
export declare class UnsafeUnboundVariableProgrammerError extends ProgrammerError {
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import type { Judgment } from "../types/judgment.js";
|
|
2
2
|
import type { Explanation } from "./types.js";
|
|
3
3
|
/**
|
|
4
|
-
* Convert a judgment's proof-support
|
|
4
|
+
* Convert a judgment's proof-support graph into a compact, LLM-readable
|
|
5
5
|
* {@link Explanation} of what was concluded and why.
|
|
6
6
|
*
|
|
7
|
+
* The result is a directed acyclic graph (DAG): reused judgment objects share
|
|
8
|
+
* explanation nodes within this call. Serializing the result as JSON still
|
|
9
|
+
* expands shared nodes at every path to them.
|
|
10
|
+
*
|
|
7
11
|
* @example
|
|
8
12
|
* ```ts
|
|
9
13
|
* const explanation = explainJudgment(query(kb, doctor({ person: alice })));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isFormula
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-NDUIQAH4.js";
|
|
4
4
|
import {
|
|
5
5
|
AssertionError,
|
|
6
6
|
isVar
|
|
@@ -8,6 +8,21 @@ import {
|
|
|
8
8
|
|
|
9
9
|
// src/explanation/explain.ts
|
|
10
10
|
function explainJudgment(judgment) {
|
|
11
|
+
return explainWithContext(judgment, {
|
|
12
|
+
explanations: /* @__PURE__ */ new WeakMap(),
|
|
13
|
+
contentKeys: /* @__PURE__ */ new WeakMap(),
|
|
14
|
+
keyIds: /* @__PURE__ */ new Map()
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
function explainWithContext(judgment, context) {
|
|
18
|
+
let explanation = context.explanations.get(judgment);
|
|
19
|
+
if (explanation === void 0) {
|
|
20
|
+
explanation = explainOnce(judgment, context);
|
|
21
|
+
context.explanations.set(judgment, explanation);
|
|
22
|
+
}
|
|
23
|
+
return explanation;
|
|
24
|
+
}
|
|
25
|
+
function explainOnce(judgment, context) {
|
|
11
26
|
if (isClosedWorldAbsence(judgment)) {
|
|
12
27
|
const atom = judgment.goal.goal.atom;
|
|
13
28
|
return {
|
|
@@ -21,7 +36,7 @@ function explainJudgment(judgment) {
|
|
|
21
36
|
return {
|
|
22
37
|
claim: claimForJudgment(judgment),
|
|
23
38
|
because: formatRuleApplication(ruleSupport.rule, ruleSupport.body.bindings),
|
|
24
|
-
given: [
|
|
39
|
+
given: [explainWithContext(ruleSupport.body, context)]
|
|
25
40
|
};
|
|
26
41
|
}
|
|
27
42
|
const evidence = supports.flatMap(
|
|
@@ -41,7 +56,10 @@ function explainJudgment(judgment) {
|
|
|
41
56
|
if (subgoals.length > 0) {
|
|
42
57
|
return {
|
|
43
58
|
claim: claimForJudgment(judgment),
|
|
44
|
-
given: uniqueExplanations(
|
|
59
|
+
given: uniqueExplanations(
|
|
60
|
+
subgoals.map((subgoal) => explainWithContext(subgoal, context)),
|
|
61
|
+
context
|
|
62
|
+
)
|
|
45
63
|
};
|
|
46
64
|
}
|
|
47
65
|
const closedWorldAbsence = supports.find((support) => support.source === "closed-world-absence");
|
|
@@ -56,11 +74,11 @@ function explainJudgment(judgment) {
|
|
|
56
74
|
because: "no supporting evidence or rule was found"
|
|
57
75
|
};
|
|
58
76
|
}
|
|
59
|
-
function uniqueExplanations(explanations) {
|
|
77
|
+
function uniqueExplanations(explanations, context) {
|
|
60
78
|
const seen = /* @__PURE__ */ new Set();
|
|
61
79
|
const unique = [];
|
|
62
80
|
for (const explanation of explanations) {
|
|
63
|
-
const key =
|
|
81
|
+
const key = contentKey(explanation, context);
|
|
64
82
|
if (!seen.has(key)) {
|
|
65
83
|
seen.add(key);
|
|
66
84
|
unique.push(explanation);
|
|
@@ -68,6 +86,21 @@ function uniqueExplanations(explanations) {
|
|
|
68
86
|
}
|
|
69
87
|
return unique;
|
|
70
88
|
}
|
|
89
|
+
function contentKey(explanation, context) {
|
|
90
|
+
let id = context.contentKeys.get(explanation);
|
|
91
|
+
if (id === void 0) {
|
|
92
|
+
const given = "given" in explanation ? explanation.given.map((child) => contentKey(child, context)) : void 0;
|
|
93
|
+
const because = "because" in explanation ? explanation.because : void 0;
|
|
94
|
+
const key = JSON.stringify([explanation.claim, because, given]);
|
|
95
|
+
id = context.keyIds.get(key);
|
|
96
|
+
if (id === void 0) {
|
|
97
|
+
id = context.keyIds.size;
|
|
98
|
+
context.keyIds.set(key, id);
|
|
99
|
+
}
|
|
100
|
+
context.contentKeys.set(explanation, id);
|
|
101
|
+
}
|
|
102
|
+
return id;
|
|
103
|
+
}
|
|
71
104
|
function supportsForValue(judgment) {
|
|
72
105
|
if (judgment.value === "true") {
|
|
73
106
|
return judgment.positive;
|
|
@@ -144,6 +177,13 @@ function formatGoal(goal) {
|
|
|
144
177
|
case "sub":
|
|
145
178
|
case "mul":
|
|
146
179
|
case "div":
|
|
180
|
+
case "daysBetween":
|
|
181
|
+
case "yearsBetween":
|
|
182
|
+
case "min":
|
|
183
|
+
case "max":
|
|
184
|
+
case "roundTo":
|
|
185
|
+
case "floorTo":
|
|
186
|
+
case "ceilTo":
|
|
147
187
|
return `${goal.bind.name} = ${goal.kind}(${formatValue(goal.left)}, ${formatValue(goal.right)})`;
|
|
148
188
|
case "eq":
|
|
149
189
|
case "neq":
|
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
PrimitiveArithmeticProgrammerError,
|
|
4
4
|
ProgrammerError,
|
|
5
5
|
UnsafeUnboundVariableProgrammerError
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-QOMFY3XB.js";
|
|
7
7
|
import {
|
|
8
8
|
isAtomLike
|
|
9
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-RMQ3YX7V.js";
|
|
10
10
|
import "../chunk-UVOCOH2I.js";
|
|
11
11
|
|
|
12
12
|
// src/knowledge-base/assertions/exclusive-definitions.ts
|
|
@@ -6,5 +6,5 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export { query, queryAll } from "./query.js";
|
|
8
8
|
export type { Binding, Bindings, GoalValue } from "../types/common.js";
|
|
9
|
-
export type { AddGoal, AndGoal, AnyAtomGoal, ArithmeticGoal, AtomGoal, ComparisonGoal, CompoundGoal, CountGoal, DivGoal, EqGoal, ExistsGoal, Goal, GoalExpression, GroundAtomGoal, GtGoal, GteGoal, LetGoal, LtGoal, LteGoal, MulGoal, NeqGoal, NotGoal, OrGoal, PrimitiveGoal, SubGoal, SumGoal, } from "../types/goal.js";
|
|
9
|
+
export type { AddGoal, AndGoal, AnyAtomGoal, ArithmeticGoal, ArithmeticKind, AtomGoal, CeilToGoal, ComparisonGoal, CompoundGoal, CountGoal, DaysBetweenGoal, DivGoal, EqGoal, ExistsGoal, FloorToGoal, Goal, GoalExpression, GroundAtomGoal, GtGoal, GteGoal, LetGoal, LtGoal, LteGoal, MaxGoal, MinGoal, MulGoal, NeqGoal, NotGoal, OrGoal, PrimitiveGoal, RoundToGoal, SubGoal, SumGoal, YearsBetweenGoal, } from "../types/goal.js";
|
|
10
10
|
export type { ArithmeticPrimitiveSupport, ClosedWorldAbsenceSupport, ComparisonPrimitiveSupport, CountPrimitiveSupport, ErasedEvidenceSupport, ErasedRuleSupport, EvidenceSupport, Judgment, JudgmentBase, JudgmentSupportFor, MissingBinding, NumericRange, PrimitiveEvaluation, PrimitiveSupport, PrimitiveSupportFor, PrimitiveUncertainty, QueryAllJudgment, QueryJudgment, RuleSupport, SubgoalsSupport, SumPrimitiveSupport, } from "../types/judgment.js";
|
package/dist/core/query/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isFormula
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-NDUIQAH4.js";
|
|
4
4
|
import {
|
|
5
5
|
PrimitiveArithmeticProgrammerError,
|
|
6
6
|
UnconstructiveRecursionProgrammerError,
|
|
7
7
|
UnsafeUnboundVariableProgrammerError
|
|
8
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-QOMFY3XB.js";
|
|
9
9
|
import {
|
|
10
10
|
assertGroundAtom,
|
|
11
11
|
atomEquals,
|
|
@@ -13,8 +13,9 @@ import {
|
|
|
13
13
|
fieldIdentityToken,
|
|
14
14
|
isAtomForPredicate,
|
|
15
15
|
isAtomLike,
|
|
16
|
+
isEntityValue,
|
|
16
17
|
isGroundAtom
|
|
17
|
-
} from "../chunk-
|
|
18
|
+
} from "../chunk-RMQ3YX7V.js";
|
|
18
19
|
import {
|
|
19
20
|
AssertionError,
|
|
20
21
|
Var,
|
|
@@ -22,6 +23,39 @@ import {
|
|
|
22
23
|
isVar
|
|
23
24
|
} from "../chunk-UVOCOH2I.js";
|
|
24
25
|
|
|
26
|
+
// src/solver/calendar.ts
|
|
27
|
+
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
28
|
+
function utcDay(date) {
|
|
29
|
+
return Math.floor(date.getTime() / DAY_MS);
|
|
30
|
+
}
|
|
31
|
+
function daysBetween(later, earlier) {
|
|
32
|
+
return utcDay(later) - utcDay(earlier);
|
|
33
|
+
}
|
|
34
|
+
function yearsBetween(later, earlier) {
|
|
35
|
+
if (later.getTime() < earlier.getTime()) {
|
|
36
|
+
return -yearsBetween(earlier, later) || 0;
|
|
37
|
+
}
|
|
38
|
+
const years = later.getUTCFullYear() - earlier.getUTCFullYear();
|
|
39
|
+
const laterDay = later.getUTCMonth() * 100 + later.getUTCDate();
|
|
40
|
+
const earlierDay = earlier.getUTCMonth() * 100 + earlier.getUTCDate();
|
|
41
|
+
return laterDay < earlierDay ? years - 1 : years;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/types/goal.ts
|
|
45
|
+
var ARITHMETIC_KINDS = [
|
|
46
|
+
"add",
|
|
47
|
+
"sub",
|
|
48
|
+
"mul",
|
|
49
|
+
"div",
|
|
50
|
+
"daysBetween",
|
|
51
|
+
"yearsBetween",
|
|
52
|
+
"min",
|
|
53
|
+
"max",
|
|
54
|
+
"roundTo",
|
|
55
|
+
"floorTo",
|
|
56
|
+
"ceilTo"
|
|
57
|
+
];
|
|
58
|
+
|
|
25
59
|
// src/solver/query-validation/utils.ts
|
|
26
60
|
function ruleEdges(rule) {
|
|
27
61
|
const headPredicate = rule.head.atom.predicate;
|
|
@@ -31,6 +65,7 @@ function ruleEdges(rule) {
|
|
|
31
65
|
return edges;
|
|
32
66
|
}
|
|
33
67
|
function reachablePredicates(start, edges) {
|
|
68
|
+
const next = adjacency(edges);
|
|
34
69
|
const reachable = /* @__PURE__ */ new Set([start]);
|
|
35
70
|
const stack = [start];
|
|
36
71
|
while (stack.length > 0) {
|
|
@@ -38,16 +73,71 @@ function reachablePredicates(start, edges) {
|
|
|
38
73
|
if (current === void 0) {
|
|
39
74
|
continue;
|
|
40
75
|
}
|
|
41
|
-
for (const
|
|
42
|
-
if (
|
|
76
|
+
for (const target of next.get(current) ?? []) {
|
|
77
|
+
if (reachable.has(target)) {
|
|
43
78
|
continue;
|
|
44
79
|
}
|
|
45
|
-
reachable.add(
|
|
46
|
-
stack.push(
|
|
80
|
+
reachable.add(target);
|
|
81
|
+
stack.push(target);
|
|
47
82
|
}
|
|
48
83
|
}
|
|
49
84
|
return reachable;
|
|
50
85
|
}
|
|
86
|
+
function adjacency(edges) {
|
|
87
|
+
const next = /* @__PURE__ */ new Map();
|
|
88
|
+
for (const { from, to } of edges) {
|
|
89
|
+
let targets = next.get(from);
|
|
90
|
+
if (targets === void 0) {
|
|
91
|
+
targets = /* @__PURE__ */ new Set();
|
|
92
|
+
next.set(from, targets);
|
|
93
|
+
}
|
|
94
|
+
targets.add(to);
|
|
95
|
+
}
|
|
96
|
+
return next;
|
|
97
|
+
}
|
|
98
|
+
function stronglyConnectedComponents(edges) {
|
|
99
|
+
const next = adjacency(edges);
|
|
100
|
+
const indices = /* @__PURE__ */ new Map();
|
|
101
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
102
|
+
const stack = [];
|
|
103
|
+
const components = /* @__PURE__ */ new Map();
|
|
104
|
+
function visit(predicate) {
|
|
105
|
+
const index = indices.size;
|
|
106
|
+
indices.set(predicate, index);
|
|
107
|
+
let lowLink = index;
|
|
108
|
+
stack.push(predicate);
|
|
109
|
+
onStack.add(predicate);
|
|
110
|
+
for (const target of next.get(predicate) ?? []) {
|
|
111
|
+
const targetIndex = indices.get(target);
|
|
112
|
+
if (targetIndex === void 0) {
|
|
113
|
+
const targetLowLink = visit(target);
|
|
114
|
+
lowLink = Math.min(lowLink, targetLowLink);
|
|
115
|
+
} else if (onStack.has(target)) {
|
|
116
|
+
lowLink = Math.min(lowLink, targetIndex);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (lowLink === index) {
|
|
120
|
+
const component = /* @__PURE__ */ new Set();
|
|
121
|
+
for (let member = stack.pop(); member !== void 0; member = stack.pop()) {
|
|
122
|
+
onStack.delete(member);
|
|
123
|
+
component.add(member);
|
|
124
|
+
if (member === predicate) {
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
for (const member of component) {
|
|
129
|
+
components.set(member, component);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return lowLink;
|
|
133
|
+
}
|
|
134
|
+
for (const predicate of next.keys()) {
|
|
135
|
+
if (!indices.has(predicate)) {
|
|
136
|
+
visit(predicate);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return components;
|
|
140
|
+
}
|
|
51
141
|
function isNegativeLiteral(value) {
|
|
52
142
|
return typeof value === "object" && value !== null && "sign" in value && value.sign === "-" && "atom" in value && isAtomLike(value.atom);
|
|
53
143
|
}
|
|
@@ -58,7 +148,7 @@ function isCompleteWhenOption(value) {
|
|
|
58
148
|
return typeof value === "object" && value !== null && "completeWhen" in value;
|
|
59
149
|
}
|
|
60
150
|
function isPrimitiveFormula(value) {
|
|
61
|
-
return isFormula(value) && [
|
|
151
|
+
return isFormula(value) && [...ARITHMETIC_KINDS, "eq", "neq", "lt", "lte", "gt", "gte"].includes(value.kind);
|
|
62
152
|
}
|
|
63
153
|
function bindVariables(value, bound) {
|
|
64
154
|
for (const variable of variablesIn(value)) {
|
|
@@ -177,7 +267,7 @@ function collectBodyEdgesWithKind(body, headPredicate, bound, edges, kind) {
|
|
|
177
267
|
function assertConstructiveRecursion(context) {
|
|
178
268
|
const edges = context.rules.flatMap(ruleEdges);
|
|
179
269
|
const reachable = reachablePredicates(context.atom.predicate, edges);
|
|
180
|
-
const components =
|
|
270
|
+
const components = componentsAmong(reachable, edges);
|
|
181
271
|
const needsCandidateEnumeration = variablesIn(context.atom).length > 0;
|
|
182
272
|
for (const component of components) {
|
|
183
273
|
const internalEdges = edges.filter(
|
|
@@ -200,31 +290,22 @@ function assertConstructiveRecursion(context) {
|
|
|
200
290
|
}
|
|
201
291
|
function hasNonRecursiveBaseRule(component, rules) {
|
|
202
292
|
return rules.some(
|
|
203
|
-
(rule) => component.has(rule.head.atom.predicate) &&
|
|
293
|
+
(rule) => component.has(rule.head.atom.predicate) && hasNonRecursiveAlternative(rule.body, component)
|
|
204
294
|
);
|
|
205
295
|
}
|
|
206
|
-
function
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
for (const predicate of predicates) {
|
|
210
|
-
if (!remaining.has(predicate)) {
|
|
211
|
-
continue;
|
|
212
|
-
}
|
|
213
|
-
const forward = reachablePredicates(predicate, edges);
|
|
214
|
-
const backward = reachablePredicates(predicate, reverseEdges(edges));
|
|
215
|
-
const component = /* @__PURE__ */ new Set();
|
|
216
|
-
for (const candidate of predicates) {
|
|
217
|
-
if (forward.has(candidate) && backward.has(candidate)) {
|
|
218
|
-
component.add(candidate);
|
|
219
|
-
remaining.delete(candidate);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
components.push(component);
|
|
296
|
+
function hasNonRecursiveAlternative(body, component) {
|
|
297
|
+
if (isFormula(body, "or")) {
|
|
298
|
+
return body.args.some((branch) => hasNonRecursiveAlternative(branch, component));
|
|
223
299
|
}
|
|
224
|
-
return
|
|
300
|
+
return !bodyReferencesPredicates(body, component);
|
|
225
301
|
}
|
|
226
|
-
function
|
|
227
|
-
|
|
302
|
+
function componentsAmong(predicates, edges) {
|
|
303
|
+
const components = /* @__PURE__ */ new Set();
|
|
304
|
+
const byPredicate = stronglyConnectedComponents(edges);
|
|
305
|
+
for (const predicate of predicates) {
|
|
306
|
+
components.add(byPredicate.get(predicate) ?? /* @__PURE__ */ new Set([predicate]));
|
|
307
|
+
}
|
|
308
|
+
return [...components];
|
|
228
309
|
}
|
|
229
310
|
function bodyReferencesPredicates(body, predicates) {
|
|
230
311
|
if (isAtomLike(body)) {
|
|
@@ -451,18 +532,17 @@ function groundAtom(atom, bindings) {
|
|
|
451
532
|
var CandidateSearch = class {
|
|
452
533
|
groups = /* @__PURE__ */ new Map();
|
|
453
534
|
tables = /* @__PURE__ */ new Map();
|
|
535
|
+
evaluatingGroups = 0;
|
|
454
536
|
constructor(rules) {
|
|
455
|
-
const
|
|
537
|
+
const components = stronglyConnectedComponents(rules.flatMap(ruleEdges));
|
|
456
538
|
for (const rule of rules) {
|
|
457
539
|
const predicate = rule.head.atom.predicate;
|
|
458
540
|
if (this.groups.has(predicate)) {
|
|
459
541
|
continue;
|
|
460
542
|
}
|
|
461
543
|
const group = { tables: /* @__PURE__ */ new Set(), evaluating: false, changed: false };
|
|
462
|
-
for (const
|
|
463
|
-
|
|
464
|
-
this.groups.set(dependency, group);
|
|
465
|
-
}
|
|
544
|
+
for (const member of components.get(predicate) ?? [predicate]) {
|
|
545
|
+
this.groups.set(member, group);
|
|
466
546
|
}
|
|
467
547
|
}
|
|
468
548
|
}
|
|
@@ -491,6 +571,7 @@ var CandidateSearch = class {
|
|
|
491
571
|
group.changed = true;
|
|
492
572
|
if (!group.evaluating) {
|
|
493
573
|
group.evaluating = true;
|
|
574
|
+
this.evaluatingGroups += 1;
|
|
494
575
|
try {
|
|
495
576
|
do {
|
|
496
577
|
group.changed = false;
|
|
@@ -500,6 +581,7 @@ var CandidateSearch = class {
|
|
|
500
581
|
} while (group.changed);
|
|
501
582
|
} finally {
|
|
502
583
|
group.evaluating = false;
|
|
584
|
+
this.evaluatingGroups -= 1;
|
|
503
585
|
}
|
|
504
586
|
}
|
|
505
587
|
return [...table.atoms.values()];
|
|
@@ -507,8 +589,17 @@ var CandidateSearch = class {
|
|
|
507
589
|
sameComponent(left, right) {
|
|
508
590
|
return left === right || this.groups.has(left) && this.groups.get(left) === this.groups.get(right);
|
|
509
591
|
}
|
|
510
|
-
|
|
511
|
-
|
|
592
|
+
/** A judgment depending on these predicates might observe a partial candidate table. */
|
|
593
|
+
evaluatingAmong(predicates) {
|
|
594
|
+
if (this.evaluatingGroups === 0) {
|
|
595
|
+
return false;
|
|
596
|
+
}
|
|
597
|
+
for (const predicate of predicates) {
|
|
598
|
+
if (this.groups.get(predicate)?.evaluating === true) {
|
|
599
|
+
return true;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
return false;
|
|
512
603
|
}
|
|
513
604
|
};
|
|
514
605
|
function expandTable(table) {
|
|
@@ -540,13 +631,88 @@ function patternKey(atom) {
|
|
|
540
631
|
);
|
|
541
632
|
}
|
|
542
633
|
|
|
634
|
+
// src/solver/program-index.ts
|
|
635
|
+
var ProgramIndex = class {
|
|
636
|
+
literals = /* @__PURE__ */ new Map();
|
|
637
|
+
rules = /* @__PURE__ */ new Map();
|
|
638
|
+
edges;
|
|
639
|
+
reachable = /* @__PURE__ */ new Map();
|
|
640
|
+
predicateIds = /* @__PURE__ */ new Map();
|
|
641
|
+
constructor(literals, rules) {
|
|
642
|
+
for (const literal of literals) {
|
|
643
|
+
const predicate = literal.atom.predicate;
|
|
644
|
+
const group = this.literals.get(predicate);
|
|
645
|
+
if (group === void 0) {
|
|
646
|
+
this.literals.set(predicate, [literal]);
|
|
647
|
+
} else {
|
|
648
|
+
group.push(literal);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
for (const rule of rules) {
|
|
652
|
+
const predicate = rule.head.atom.predicate;
|
|
653
|
+
const group = this.rules.get(predicate);
|
|
654
|
+
if (group === void 0) {
|
|
655
|
+
this.rules.set(predicate, [rule]);
|
|
656
|
+
} else {
|
|
657
|
+
group.push(rule);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
this.edges = rules.flatMap(ruleEdges);
|
|
661
|
+
}
|
|
662
|
+
literalsFor(predicate) {
|
|
663
|
+
return this.literals.get(predicate) ?? [];
|
|
664
|
+
}
|
|
665
|
+
rulesFor(predicate) {
|
|
666
|
+
return this.rules.get(predicate) ?? [];
|
|
667
|
+
}
|
|
668
|
+
/** Atom keys must distinguish declarations even when they share a display name. */
|
|
669
|
+
predicateId(predicate) {
|
|
670
|
+
let id = this.predicateIds.get(predicate);
|
|
671
|
+
if (id === void 0) {
|
|
672
|
+
id = this.predicateIds.size;
|
|
673
|
+
this.predicateIds.set(predicate, id);
|
|
674
|
+
}
|
|
675
|
+
return id;
|
|
676
|
+
}
|
|
677
|
+
reaches(predicate) {
|
|
678
|
+
let reachable = this.reachable.get(predicate);
|
|
679
|
+
if (reachable === void 0) {
|
|
680
|
+
reachable = reachablePredicates(predicate, this.edges);
|
|
681
|
+
this.reachable.set(predicate, reachable);
|
|
682
|
+
}
|
|
683
|
+
return reachable;
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
|
|
543
687
|
// src/solver/solve-all.ts
|
|
544
688
|
var UNBOUND = /* @__PURE__ */ Symbol("factbag.unbound");
|
|
689
|
+
function tabledComponents(literals, rules) {
|
|
690
|
+
const edges = rules.flatMap(ruleEdges);
|
|
691
|
+
const components = stronglyConnectedComponents(edges);
|
|
692
|
+
const selfRecursive = new Set(
|
|
693
|
+
edges.filter((edge) => edge.from === edge.to).map((edge) => edge.from)
|
|
694
|
+
);
|
|
695
|
+
const stated = new Set(literals.map((literal) => literal.atom.predicate));
|
|
696
|
+
const tabled = /* @__PURE__ */ new Map();
|
|
697
|
+
for (const component of new Set(components.values())) {
|
|
698
|
+
const members = [...component];
|
|
699
|
+
if (members.some((member) => member.world !== "closed" || stated.has(member)) || !(component.size > 1 || members.some((member) => selfRecursive.has(member)))) {
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
for (const member of members) {
|
|
703
|
+
tabled.set(member, component);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
return tabled;
|
|
707
|
+
}
|
|
545
708
|
function solveQueryAll(literals, rules, atom) {
|
|
546
709
|
validateQuery(literals, rules, atom);
|
|
547
|
-
const result = solveQueryAllInternal(literals, rules, atom, /* @__PURE__ */ new
|
|
710
|
+
const result = solveQueryAllInternal(literals, rules, atom, /* @__PURE__ */ new Map(), {
|
|
548
711
|
candidates: new CandidateSearch(rules),
|
|
549
|
-
judgments: /* @__PURE__ */ new Map()
|
|
712
|
+
judgments: /* @__PURE__ */ new Map(),
|
|
713
|
+
tabled: tabledComponents(literals, rules),
|
|
714
|
+
tables: /* @__PURE__ */ new Map(),
|
|
715
|
+
index: new ProgramIndex(literals, rules)
|
|
550
716
|
});
|
|
551
717
|
const stack = [result];
|
|
552
718
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -584,7 +750,11 @@ function solveQueryAllInternal(literals, rules, atom, active, search) {
|
|
|
584
750
|
);
|
|
585
751
|
}
|
|
586
752
|
function solveGroundAtom(literals, rules, atom, active, search) {
|
|
587
|
-
if (search.
|
|
753
|
+
if (search.tabled.has(atom.predicate)) {
|
|
754
|
+
return solveTabledAtom(literals, rules, atom, active, search);
|
|
755
|
+
}
|
|
756
|
+
const dependencies = search.index.reaches(atom.predicate);
|
|
757
|
+
if (search.candidates.evaluatingAmong(dependencies)) {
|
|
588
758
|
return evaluateGroundAtom(literals, rules, atom, active, search);
|
|
589
759
|
}
|
|
590
760
|
let judgments = search.judgments.get(atom.predicate);
|
|
@@ -592,7 +762,8 @@ function solveGroundAtom(literals, rules, atom, active, search) {
|
|
|
592
762
|
judgments = /* @__PURE__ */ new Map();
|
|
593
763
|
search.judgments.set(atom.predicate, judgments);
|
|
594
764
|
}
|
|
595
|
-
const
|
|
765
|
+
const relevantActive = [...active].filter(([, predicate]) => dependencies.has(predicate)).map(([key2]) => key2);
|
|
766
|
+
const key = JSON.stringify([groundAtomKey(atom, search), relevantActive.sort()]);
|
|
596
767
|
const cached = judgments.get(key);
|
|
597
768
|
if (cached !== void 0 && judgmentMatchesAtom(cached, atom)) {
|
|
598
769
|
return cached;
|
|
@@ -604,12 +775,107 @@ function solveGroundAtom(literals, rules, atom, active, search) {
|
|
|
604
775
|
function judgmentMatchesAtom(judgment, atom) {
|
|
605
776
|
return atomEquals(judgment.goal.atom, atom);
|
|
606
777
|
}
|
|
778
|
+
function solveTabledAtom(literals, rules, atom, active, search) {
|
|
779
|
+
const table = tableFor(search, atom.predicate);
|
|
780
|
+
const key = groundAtomKey(atom, search);
|
|
781
|
+
const enter = () => {
|
|
782
|
+
const absent = {
|
|
783
|
+
value: "false",
|
|
784
|
+
goal: { kind: "atom", atom },
|
|
785
|
+
bindings: [],
|
|
786
|
+
negative: [{ source: "closed-world-absence" }]
|
|
787
|
+
};
|
|
788
|
+
table.judgments.set(key, absent);
|
|
789
|
+
table.atoms.set(key, atom);
|
|
790
|
+
table.queue.push(key);
|
|
791
|
+
table.queued.add(key);
|
|
792
|
+
return absent;
|
|
793
|
+
};
|
|
794
|
+
if (table.running) {
|
|
795
|
+
const judgment = table.judgments.get(key) ?? enter();
|
|
796
|
+
if (table.evaluating !== void 0) {
|
|
797
|
+
let dependents = table.dependents.get(key);
|
|
798
|
+
if (dependents === void 0) {
|
|
799
|
+
dependents = /* @__PURE__ */ new Set();
|
|
800
|
+
table.dependents.set(key, dependents);
|
|
801
|
+
}
|
|
802
|
+
dependents.add(table.evaluating);
|
|
803
|
+
}
|
|
804
|
+
assert(judgmentMatchesAtom(judgment, atom), "Tabled judgment must match its atom");
|
|
805
|
+
return judgment;
|
|
806
|
+
}
|
|
807
|
+
const settled = table.judgments.get(key);
|
|
808
|
+
if (settled !== void 0) {
|
|
809
|
+
assert(judgmentMatchesAtom(settled, atom), "Tabled judgment must match its atom");
|
|
810
|
+
return settled;
|
|
811
|
+
}
|
|
812
|
+
enter();
|
|
813
|
+
table.running = true;
|
|
814
|
+
try {
|
|
815
|
+
for (let offset = 0; offset < table.queue.length; offset += 1) {
|
|
816
|
+
const next = table.queue[offset];
|
|
817
|
+
assert(next !== void 0, "Queued atom must have a key");
|
|
818
|
+
table.queued.delete(next);
|
|
819
|
+
const current = table.judgments.get(next);
|
|
820
|
+
const subject = table.atoms.get(next);
|
|
821
|
+
assert(current !== void 0 && subject !== void 0, "Queued atom must be in the table");
|
|
822
|
+
table.evaluating = next;
|
|
823
|
+
const judged = evaluateGroundAtom(literals, rules, subject, active, search);
|
|
824
|
+
table.evaluating = void 0;
|
|
825
|
+
table.judgments.set(next, judged);
|
|
826
|
+
if (judged.value === current.value) {
|
|
827
|
+
continue;
|
|
828
|
+
}
|
|
829
|
+
assert(
|
|
830
|
+
maxGoalValue(current.value, judged.value) === judged.value,
|
|
831
|
+
"Tabled recursion must be monotone"
|
|
832
|
+
);
|
|
833
|
+
const changes = (table.changes.get(next) ?? 0) + 1;
|
|
834
|
+
assert(changes <= 3, "Tabled recursion did not converge");
|
|
835
|
+
table.changes.set(next, changes);
|
|
836
|
+
for (const dependent of table.dependents.get(next) ?? []) {
|
|
837
|
+
if (!table.queued.has(dependent)) {
|
|
838
|
+
table.queue.push(dependent);
|
|
839
|
+
table.queued.add(dependent);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
table.queue.length = 0;
|
|
844
|
+
} finally {
|
|
845
|
+
table.running = false;
|
|
846
|
+
table.evaluating = void 0;
|
|
847
|
+
}
|
|
848
|
+
const result = table.judgments.get(key);
|
|
849
|
+
assert(result !== void 0 && judgmentMatchesAtom(result, atom), "Run must judge its seed");
|
|
850
|
+
return result;
|
|
851
|
+
}
|
|
852
|
+
function tableFor(search, predicate) {
|
|
853
|
+
let table = search.tables.get(predicate);
|
|
854
|
+
if (table === void 0) {
|
|
855
|
+
const component = search.tabled.get(predicate);
|
|
856
|
+
assert(component !== void 0, "Only a tabled predicate has a table");
|
|
857
|
+
table = {
|
|
858
|
+
judgments: /* @__PURE__ */ new Map(),
|
|
859
|
+
atoms: /* @__PURE__ */ new Map(),
|
|
860
|
+
dependents: /* @__PURE__ */ new Map(),
|
|
861
|
+
changes: /* @__PURE__ */ new Map(),
|
|
862
|
+
queue: [],
|
|
863
|
+
queued: /* @__PURE__ */ new Set(),
|
|
864
|
+
running: false,
|
|
865
|
+
evaluating: void 0
|
|
866
|
+
};
|
|
867
|
+
for (const member of component) {
|
|
868
|
+
search.tables.set(member, table);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
return table;
|
|
872
|
+
}
|
|
607
873
|
function evaluateGroundAtom(literals, rules, atom, active, search) {
|
|
608
874
|
const goal = { kind: "atom", atom };
|
|
609
875
|
const bindings = [];
|
|
610
|
-
const atomKey = groundAtomKey(atom);
|
|
611
|
-
const positiveLiterals = matchingLiterals(
|
|
612
|
-
const negativeLiterals = matchingLiterals(
|
|
876
|
+
const atomKey = groundAtomKey(atom, search);
|
|
877
|
+
const positiveLiterals = matchingLiterals(search.index.literalsFor(atom.predicate), atom, "+");
|
|
878
|
+
const negativeLiterals = matchingLiterals(search.index.literalsFor(atom.predicate), atom, "-");
|
|
613
879
|
const values = [];
|
|
614
880
|
const positive = [];
|
|
615
881
|
const negative = [];
|
|
@@ -622,9 +888,9 @@ function evaluateGroundAtom(literals, rules, atom, active, search) {
|
|
|
622
888
|
values.push("false");
|
|
623
889
|
}
|
|
624
890
|
if (!active.has(atomKey)) {
|
|
625
|
-
const nextActive = new
|
|
626
|
-
nextActive.
|
|
627
|
-
for (const rule of
|
|
891
|
+
const nextActive = new Map(active);
|
|
892
|
+
nextActive.set(atomKey, atom.predicate);
|
|
893
|
+
for (const rule of search.index.rulesFor(atom.predicate)) {
|
|
628
894
|
const match = matchRuleForAtom(rule, atom);
|
|
629
895
|
if (match === void 0) {
|
|
630
896
|
continue;
|
|
@@ -760,7 +1026,10 @@ function evaluateRuleBodyMany(literals, rules, body, bindings, active, context,
|
|
|
760
1026
|
];
|
|
761
1027
|
}
|
|
762
1028
|
const atom = groundAtom(bodyAtom, bindings);
|
|
763
|
-
assert(
|
|
1029
|
+
assert(
|
|
1030
|
+
!active.has(groundAtomKey(atom, context.search)),
|
|
1031
|
+
"Validated query must not recurse through negation"
|
|
1032
|
+
);
|
|
764
1033
|
const child = solveGroundAtom(literals, rules, atom, active, context.search);
|
|
765
1034
|
const goal = { kind: "not", goal: { kind: "atom", atom } };
|
|
766
1035
|
const value = atom.predicate.world === "closed" ? negateAsFailureGoalValue(child.value) : negateGoalValue(child.value);
|
|
@@ -1070,8 +1339,15 @@ function evaluateAtomBody(literals, rules, atom, bindings, active, context, path
|
|
|
1070
1339
|
function evaluateComparisonBody(kind, args, bindings, context, pathCanHaveUnavailableBindings) {
|
|
1071
1340
|
const left = evaluateExpression(args[0], bindings, context);
|
|
1072
1341
|
const right = evaluateExpression(args[1], left.bindings, context);
|
|
1073
|
-
|
|
1074
|
-
assert(
|
|
1342
|
+
const allowsEntities = kind === "eq" || kind === "neq";
|
|
1343
|
+
assert(
|
|
1344
|
+
isScalarGoalExpression(left.expression) || allowsEntities && isEntityValue(left.expression),
|
|
1345
|
+
"Left comparison operand must be scalar, or entity for equality"
|
|
1346
|
+
);
|
|
1347
|
+
assert(
|
|
1348
|
+
isScalarGoalExpression(right.expression) || allowsEntities && isEntityValue(right.expression),
|
|
1349
|
+
"Right comparison operand must be scalar, or entity for equality"
|
|
1350
|
+
);
|
|
1075
1351
|
const nextBindings = right.bindings;
|
|
1076
1352
|
const judgmentBindings = bindingsArray(nextBindings);
|
|
1077
1353
|
const goal = {
|
|
@@ -1645,7 +1921,7 @@ function primitiveRangeForExact(value) {
|
|
|
1645
1921
|
return { range: { lower: value, upper: value } };
|
|
1646
1922
|
}
|
|
1647
1923
|
function isArithmeticFormula(value) {
|
|
1648
|
-
return isFormula(value) &&
|
|
1924
|
+
return isFormula(value) && ARITHMETIC_KINDS.some((kind) => kind === value.kind) && value.args.length === 2;
|
|
1649
1925
|
}
|
|
1650
1926
|
function isComparisonFormula(value) {
|
|
1651
1927
|
return isFormula(value) && ["eq", "neq", "lt", "lte", "gt", "gte"].includes(value.kind) && value.args.length === 2;
|
|
@@ -1663,18 +1939,17 @@ function calculateValue(kind, left, right, goal) {
|
|
|
1663
1939
|
if (left === UNBOUND || right === UNBOUND) {
|
|
1664
1940
|
return UNBOUND;
|
|
1665
1941
|
}
|
|
1666
|
-
if (left instanceof Date &&
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
case "div":
|
|
1673
|
-
break;
|
|
1674
|
-
default:
|
|
1675
|
-
return assertNever(kind, "Unhandled arithmetic kind");
|
|
1942
|
+
if (left instanceof Date && right instanceof Date) {
|
|
1943
|
+
if (kind === "daysBetween") {
|
|
1944
|
+
return daysBetween(left, right);
|
|
1945
|
+
}
|
|
1946
|
+
if (kind === "yearsBetween") {
|
|
1947
|
+
return yearsBetween(left, right);
|
|
1676
1948
|
}
|
|
1677
1949
|
}
|
|
1950
|
+
if (left instanceof Date && typeof right === "number" && kind === "sub") {
|
|
1951
|
+
return new Date(left.getTime() - right);
|
|
1952
|
+
}
|
|
1678
1953
|
if (typeof left === "number" && typeof right === "number") {
|
|
1679
1954
|
switch (kind) {
|
|
1680
1955
|
case "add":
|
|
@@ -1685,12 +1960,67 @@ function calculateValue(kind, left, right, goal) {
|
|
|
1685
1960
|
return left * right;
|
|
1686
1961
|
case "div":
|
|
1687
1962
|
return left / right;
|
|
1963
|
+
case "daysBetween":
|
|
1964
|
+
case "yearsBetween":
|
|
1965
|
+
break;
|
|
1966
|
+
case "min":
|
|
1967
|
+
return Math.min(left, right);
|
|
1968
|
+
case "max":
|
|
1969
|
+
return Math.max(left, right);
|
|
1970
|
+
case "roundTo":
|
|
1971
|
+
case "floorTo":
|
|
1972
|
+
case "ceilTo":
|
|
1973
|
+
if (!Number.isInteger(right)) {
|
|
1974
|
+
throw new PrimitiveArithmeticProgrammerError(kind, [left, right], goal);
|
|
1975
|
+
}
|
|
1976
|
+
return roundAtPlaces(left, right, kind);
|
|
1688
1977
|
default:
|
|
1689
1978
|
return assertNever(kind, "Unhandled arithmetic kind");
|
|
1690
1979
|
}
|
|
1691
1980
|
}
|
|
1692
1981
|
throw new PrimitiveArithmeticProgrammerError(kind, [left, right], goal);
|
|
1693
1982
|
}
|
|
1983
|
+
function shiftDecimal(value, places) {
|
|
1984
|
+
if (value === 0 || !Number.isFinite(value)) {
|
|
1985
|
+
return value;
|
|
1986
|
+
}
|
|
1987
|
+
const [coefficient, exponent = "0"] = String(value).split("e");
|
|
1988
|
+
const shiftedExponent = Number(exponent) + places;
|
|
1989
|
+
if (shiftedExponent > 308) {
|
|
1990
|
+
return Math.sign(value) * Infinity;
|
|
1991
|
+
}
|
|
1992
|
+
if (shiftedExponent < -324) {
|
|
1993
|
+
return Math.sign(value) * 0;
|
|
1994
|
+
}
|
|
1995
|
+
return Number(`${coefficient}e${shiftedExponent}`);
|
|
1996
|
+
}
|
|
1997
|
+
function roundAtPlaces(value, places, kind) {
|
|
1998
|
+
if (value === 0 || !Number.isFinite(value)) {
|
|
1999
|
+
return value;
|
|
2000
|
+
}
|
|
2001
|
+
let shifted = shiftDecimal(value, places);
|
|
2002
|
+
if (!Number.isFinite(shifted)) {
|
|
2003
|
+
return value;
|
|
2004
|
+
}
|
|
2005
|
+
if (shifted === 0) {
|
|
2006
|
+
shifted = Math.sign(value) * Number.MIN_VALUE;
|
|
2007
|
+
}
|
|
2008
|
+
let rounded;
|
|
2009
|
+
switch (kind) {
|
|
2010
|
+
case "roundTo":
|
|
2011
|
+
rounded = Math.sign(shifted) * Math.round(Math.abs(shifted));
|
|
2012
|
+
break;
|
|
2013
|
+
case "floorTo":
|
|
2014
|
+
rounded = Math.floor(shifted);
|
|
2015
|
+
break;
|
|
2016
|
+
case "ceilTo":
|
|
2017
|
+
rounded = Math.ceil(shifted);
|
|
2018
|
+
break;
|
|
2019
|
+
default:
|
|
2020
|
+
return assertNever(kind, "Unhandled rounding kind");
|
|
2021
|
+
}
|
|
2022
|
+
return shiftDecimal(rounded, -places);
|
|
2023
|
+
}
|
|
1694
2024
|
function compareValues(kind, left, right) {
|
|
1695
2025
|
assert(left !== UNBOUND && right !== UNBOUND, "Comparison operands must be bound");
|
|
1696
2026
|
if (isNumericRangeValue(left) && typeof right === "number") {
|
|
@@ -1703,9 +2033,9 @@ function compareValues(kind, left, right) {
|
|
|
1703
2033
|
const rightComparable = right instanceof Date ? right.getTime() : right;
|
|
1704
2034
|
switch (kind) {
|
|
1705
2035
|
case "eq":
|
|
1706
|
-
return leftComparable
|
|
2036
|
+
return sameValue(leftComparable, rightComparable) ? "true" : "false";
|
|
1707
2037
|
case "neq":
|
|
1708
|
-
return leftComparable
|
|
2038
|
+
return sameValue(leftComparable, rightComparable) ? "false" : "true";
|
|
1709
2039
|
case "lt":
|
|
1710
2040
|
case "lte":
|
|
1711
2041
|
case "gt":
|
|
@@ -1715,6 +2045,15 @@ function compareValues(kind, left, right) {
|
|
|
1715
2045
|
return assertNever(kind, "Unhandled comparison kind");
|
|
1716
2046
|
}
|
|
1717
2047
|
}
|
|
2048
|
+
function sameValue(left, right) {
|
|
2049
|
+
if (left === right) {
|
|
2050
|
+
return true;
|
|
2051
|
+
}
|
|
2052
|
+
if (isEntityValue(left) && isEntityValue(right)) {
|
|
2053
|
+
return JSON.stringify(fieldIdentityToken(left)) === JSON.stringify(fieldIdentityToken(right));
|
|
2054
|
+
}
|
|
2055
|
+
return false;
|
|
2056
|
+
}
|
|
1718
2057
|
function compareOrderedValues(kind, left, right) {
|
|
1719
2058
|
assert(typeof left === "number" && typeof right === "number", "Values must be comparable");
|
|
1720
2059
|
switch (kind) {
|
|
@@ -1918,14 +2257,14 @@ function uniqueBindings(bindings) {
|
|
|
1918
2257
|
function canGroundAtom(atom, bindings) {
|
|
1919
2258
|
return Object.values(atom.fields).every((value) => !isVar(value) || bindings.has(value));
|
|
1920
2259
|
}
|
|
1921
|
-
function groundAtomKey(atom) {
|
|
2260
|
+
function groundAtomKey(atom, search) {
|
|
1922
2261
|
const fields = atom.predicate.paramNames.map((name) => fieldIdentityToken(atom.fields[name]));
|
|
1923
|
-
return `${atom.predicate
|
|
2262
|
+
return `${search.index.predicateId(atom.predicate)}:${JSON.stringify(fields)}`;
|
|
1924
2263
|
}
|
|
1925
2264
|
function candidateGroundAtoms(literals, rules, atom, active, search, pathCanHaveUnavailableBindings = false) {
|
|
1926
2265
|
return search.candidates.candidates(
|
|
1927
2266
|
atom,
|
|
1928
|
-
matchingGroundAtoms(
|
|
2267
|
+
matchingGroundAtoms(search.index.literalsFor(atom.predicate), atom),
|
|
1929
2268
|
() => deriveCandidateAtoms(literals, rules, atom, active, search, pathCanHaveUnavailableBindings)
|
|
1930
2269
|
).filter(
|
|
1931
2270
|
(candidate) => isAtomForPredicate(candidate, atom.predicate)
|
|
@@ -1933,7 +2272,7 @@ function candidateGroundAtoms(literals, rules, atom, active, search, pathCanHave
|
|
|
1933
2272
|
}
|
|
1934
2273
|
function deriveCandidateAtoms(literals, rules, atom, active, search, pathCanHaveUnavailableBindings = false) {
|
|
1935
2274
|
const atoms = [];
|
|
1936
|
-
for (const rule of
|
|
2275
|
+
for (const rule of search.index.rulesFor(atom.predicate)) {
|
|
1937
2276
|
const ruleHead = atomForPredicate(rule.head.atom, atom.predicate);
|
|
1938
2277
|
if (ruleHead === void 0) {
|
|
1939
2278
|
continue;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Signed UTC calendar days from `earlier` to `later`, ignoring time of day. */
|
|
2
|
+
export declare function daysBetween(later: Date, earlier: Date): number;
|
|
3
|
+
/** Whole UTC anniversary years, with February 29 anniversaries on March 1 in common years. */
|
|
4
|
+
export declare function yearsBetween(later: Date, earlier: Date): number;
|
|
@@ -5,8 +5,10 @@ import type { AnyRule } from "../types/rule.js";
|
|
|
5
5
|
export declare class CandidateSearch {
|
|
6
6
|
private readonly groups;
|
|
7
7
|
private readonly tables;
|
|
8
|
+
private evaluatingGroups;
|
|
8
9
|
constructor(rules: readonly AnyRule[]);
|
|
9
10
|
candidates(atom: Atom, seeds: readonly GroundAtom[], derive: () => readonly GroundAtom[]): GroundAtom[];
|
|
10
11
|
sameComponent(left: PredicateDeclaration, right: PredicateDeclaration): boolean;
|
|
11
|
-
|
|
12
|
+
/** A judgment depending on these predicates might observe a partial candidate table. */
|
|
13
|
+
evaluatingAmong(predicates: Iterable<PredicateDeclaration>): boolean;
|
|
12
14
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AnyLiteral } from "../types/literal.js";
|
|
2
|
+
import type { PredicateDeclaration } from "../types/predicate.js";
|
|
3
|
+
import type { AnyRule } from "../types/rule.js";
|
|
4
|
+
/** Query-local lookups; predicate identity, not its name, determines dependencies. */
|
|
5
|
+
export declare class ProgramIndex {
|
|
6
|
+
private readonly literals;
|
|
7
|
+
private readonly rules;
|
|
8
|
+
private readonly edges;
|
|
9
|
+
private readonly reachable;
|
|
10
|
+
private readonly predicateIds;
|
|
11
|
+
constructor(literals: readonly AnyLiteral[], rules: readonly AnyRule[]);
|
|
12
|
+
literalsFor(predicate: PredicateDeclaration): readonly AnyLiteral[];
|
|
13
|
+
rulesFor(predicate: PredicateDeclaration): readonly AnyRule[];
|
|
14
|
+
/** Atom keys must distinguish declarations even when they share a display name. */
|
|
15
|
+
predicateId(predicate: PredicateDeclaration): number;
|
|
16
|
+
reaches(predicate: PredicateDeclaration): ReadonlySet<PredicateDeclaration>;
|
|
17
|
+
}
|
|
@@ -10,6 +10,8 @@ export type Edge = Readonly<{
|
|
|
10
10
|
}>;
|
|
11
11
|
export declare function ruleEdges(rule: AnyRule): Edge[];
|
|
12
12
|
export declare function reachablePredicates(start: PredicateDeclaration, edges: readonly Edge[]): Set<PredicateDeclaration>;
|
|
13
|
+
/** Linear-time Tarjan components, including predicates that occur only as targets. */
|
|
14
|
+
export declare function stronglyConnectedComponents(edges: readonly Edge[]): ReadonlyMap<PredicateDeclaration, ReadonlySet<PredicateDeclaration>>;
|
|
13
15
|
export declare function isNegativeLiteral(value: unknown): value is AnyLiteral & {
|
|
14
16
|
sign: "-";
|
|
15
17
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { InferRuntime, PredicateSchema } from "./schema.js";
|
|
1
|
+
import type { Entity, InferRuntime, PredicateSchema } from "./schema.js";
|
|
2
2
|
import type { Var } from "./variable.js";
|
|
3
3
|
import type { PredicateDeclaration } from "./predicate.js";
|
|
4
4
|
/** An atom whose exact schema has been intentionally erased. */
|
|
@@ -52,4 +52,9 @@ export declare function atomEquals(left: AnyAtom, right: AnyAtom): boolean;
|
|
|
52
52
|
export declare function fieldEquals(left: unknown, right: unknown): boolean;
|
|
53
53
|
/** Canonical JSON-safe token for a field value, for use in string identity keys. */
|
|
54
54
|
export declare function fieldIdentityToken(value: unknown): unknown;
|
|
55
|
+
export declare function isEntityValue(value: unknown): value is Entity & {
|
|
56
|
+
constructor: {
|
|
57
|
+
name: string;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
55
60
|
export {};
|
|
@@ -55,8 +55,26 @@ export type SubGoal = ArithmeticGoal<"sub">;
|
|
|
55
55
|
export type MulGoal = ArithmeticGoal<"mul">;
|
|
56
56
|
/** Arithmetic goal binding the quotient of two expressions. */
|
|
57
57
|
export type DivGoal = ArithmeticGoal<"div">;
|
|
58
|
+
/** Arithmetic goal binding the UTC calendar days from the right date to the left. */
|
|
59
|
+
export type DaysBetweenGoal = ArithmeticGoal<"daysBetween">;
|
|
60
|
+
/** Arithmetic goal binding whole anniversary years from the right date to the left. */
|
|
61
|
+
export type YearsBetweenGoal = ArithmeticGoal<"yearsBetween">;
|
|
62
|
+
/** Arithmetic goal binding the lesser of two numeric expressions. */
|
|
63
|
+
export type MinGoal = ArithmeticGoal<"min">;
|
|
64
|
+
/** Arithmetic goal binding the greater of two numeric expressions. */
|
|
65
|
+
export type MaxGoal = ArithmeticGoal<"max">;
|
|
66
|
+
/** Arithmetic goal rounding halves away from zero at the right operand's decimal places. */
|
|
67
|
+
export type RoundToGoal = ArithmeticGoal<"roundTo">;
|
|
68
|
+
/** Arithmetic goal rounding toward negative infinity at the right operand's decimal places. */
|
|
69
|
+
export type FloorToGoal = ArithmeticGoal<"floorTo">;
|
|
70
|
+
/** Arithmetic goal rounding toward positive infinity at the right operand's decimal places. */
|
|
71
|
+
export type CeilToGoal = ArithmeticGoal<"ceilTo">;
|
|
72
|
+
/** The primitive arithmetic operations. */
|
|
73
|
+
export type ArithmeticKind = "add" | "sub" | "mul" | "div" | "daysBetween" | "yearsBetween" | "min" | "max" | "roundTo" | "floorTo" | "ceilTo";
|
|
74
|
+
/** Shared runtime classification for arithmetic formulas. */
|
|
75
|
+
export declare const ARITHMETIC_KINDS: readonly ArithmeticKind[];
|
|
58
76
|
/** Bind the result of a primitive arithmetic operation. */
|
|
59
|
-
export type ArithmeticGoal<Kind extends
|
|
77
|
+
export type ArithmeticGoal<Kind extends ArithmeticKind> = Readonly<{
|
|
60
78
|
readonly kind: Kind;
|
|
61
79
|
readonly bind: Var;
|
|
62
80
|
readonly left: GoalExpression<Scalar>;
|
|
@@ -77,8 +95,8 @@ export type GteGoal = ComparisonGoal<"gte">;
|
|
|
77
95
|
/** Compare two value-producing expressions. */
|
|
78
96
|
export type ComparisonGoal<Kind extends "eq" | "neq" | "lt" | "lte" | "gt" | "gte"> = Readonly<{
|
|
79
97
|
readonly kind: Kind;
|
|
80
|
-
readonly left: GoalExpression
|
|
81
|
-
readonly right: GoalExpression
|
|
98
|
+
readonly left: GoalExpression;
|
|
99
|
+
readonly right: GoalExpression;
|
|
82
100
|
}>;
|
|
83
101
|
/** Sum a numeric expression over rows matched by a body goal. */
|
|
84
102
|
export type SumGoal = Readonly<{
|
|
@@ -96,8 +114,8 @@ export type CountGoal = Readonly<{
|
|
|
96
114
|
readonly completeWhen?: Goal;
|
|
97
115
|
}>;
|
|
98
116
|
/** A normalized rule-body target that the solver/proof system can evaluate. */
|
|
99
|
-
export type Goal = AnyAtomGoal | NotGoal | AndGoal | OrGoal | ExistsGoal | LetGoal | AddGoal | SubGoal | MulGoal | DivGoal | EqGoal | NeqGoal | LtGoal | LteGoal | GtGoal | GteGoal | SumGoal | CountGoal;
|
|
117
|
+
export type Goal = AnyAtomGoal | NotGoal | AndGoal | OrGoal | ExistsGoal | LetGoal | AddGoal | SubGoal | MulGoal | DivGoal | DaysBetweenGoal | YearsBetweenGoal | MinGoal | MaxGoal | RoundToGoal | FloorToGoal | CeilToGoal | EqGoal | NeqGoal | LtGoal | LteGoal | GtGoal | GteGoal | SumGoal | CountGoal;
|
|
100
118
|
/** A goal evaluated by recursively evaluating one or more child goals. */
|
|
101
119
|
export type CompoundGoal = NotGoal | AndGoal | OrGoal | ExistsGoal | LetGoal;
|
|
102
120
|
/** A goal evaluated directly by a primitive operator. */
|
|
103
|
-
export type PrimitiveGoal = AddGoal | SubGoal | MulGoal | DivGoal | EqGoal | NeqGoal | LtGoal | LteGoal | GtGoal | GteGoal | SumGoal | CountGoal;
|
|
121
|
+
export type PrimitiveGoal = AddGoal | SubGoal | MulGoal | DivGoal | DaysBetweenGoal | YearsBetweenGoal | MinGoal | MaxGoal | RoundToGoal | FloorToGoal | CeilToGoal | EqGoal | NeqGoal | LtGoal | LteGoal | GtGoal | GteGoal | SumGoal | CountGoal;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AnyAtomGoal, AtomGoal, CompoundGoal, Goal, GroundAtomGoal, PrimitiveGoal } from "./goal.js";
|
|
1
|
+
import type { AnyAtomGoal, ArithmeticKind, AtomGoal, CompoundGoal, Goal, GroundAtomGoal, PrimitiveGoal } from "./goal.js";
|
|
2
2
|
import type { AnyLiteral, Literal } from "./literal.js";
|
|
3
3
|
import type { AnyRule, Rule } from "./rule.js";
|
|
4
4
|
import type { Bindings } from "./common.js";
|
|
@@ -93,7 +93,7 @@ export type CountPrimitiveSupport = Readonly<{
|
|
|
93
93
|
readonly results: Bindings;
|
|
94
94
|
}>;
|
|
95
95
|
/** Proof support for a primitive goal, narrowed to the support shape for the goal's kind. */
|
|
96
|
-
export type PrimitiveSupportFor<GoalType extends PrimitiveGoal> = PrimitiveSupport | (GoalType["kind"] extends
|
|
96
|
+
export type PrimitiveSupportFor<GoalType extends PrimitiveGoal> = PrimitiveSupport | (GoalType["kind"] extends ArithmeticKind ? ArithmeticPrimitiveSupport : GoalType["kind"] extends "eq" | "neq" | "lt" | "lte" | "gt" | "gte" ? ComparisonPrimitiveSupport : GoalType["kind"] extends "sum" ? SumPrimitiveSupport : GoalType["kind"] extends "count" ? CountPrimitiveSupport : never);
|
|
97
97
|
/** Sources of support allowed for a specific goal shape. */
|
|
98
98
|
export type JudgmentSupportFor<GoalType extends Goal> = GoalType extends AtomGoal<infer Schema> ? EvidenceSupport<Schema> | ClosedWorldAbsenceSupport | RuleSupport<Schema> : GoalType extends AnyAtomGoal ? ErasedEvidenceSupport | ClosedWorldAbsenceSupport | ErasedRuleSupport : GoalType extends PrimitiveGoal ? PrimitiveSupportFor<GoalType> : GoalType extends CompoundGoal ? SubgoalsSupport : never;
|
|
99
99
|
/** The evaluated goal and in-scope variable bindings shared by every judgment. */
|
package/package.json
CHANGED
|
File without changes
|