@polintpro/proposit-core 0.2.5 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +150 -27
- package/dist/cli/commands/analysis.js +1 -1
- package/dist/cli/commands/analysis.js.map +1 -1
- package/dist/cli/commands/expressions.d.ts.map +1 -1
- package/dist/cli/commands/expressions.js +21 -3
- package/dist/cli/commands/expressions.js.map +1 -1
- package/dist/cli/commands/premises.d.ts.map +1 -1
- package/dist/cli/commands/premises.js +28 -11
- package/dist/cli/commands/premises.js.map +1 -1
- package/dist/cli/engine.d.ts +1 -1
- package/dist/cli/engine.d.ts.map +1 -1
- package/dist/cli/engine.js +17 -9
- package/dist/cli/engine.js.map +1 -1
- package/dist/cli/import.d.ts.map +1 -1
- package/dist/cli/import.js +10 -6
- package/dist/cli/import.js.map +1 -1
- package/dist/cli/schemata.d.ts +3 -0
- package/dist/cli/schemata.d.ts.map +1 -1
- package/dist/cli/schemata.js +2 -1
- package/dist/cli/schemata.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/consts.d.ts.map +1 -1
- package/dist/lib/consts.js +2 -1
- package/dist/lib/consts.js.map +1 -1
- package/dist/lib/core/ArgumentEngine.d.ts +64 -13
- package/dist/lib/core/ArgumentEngine.d.ts.map +1 -1
- package/dist/lib/core/ArgumentEngine.js +243 -23
- package/dist/lib/core/ArgumentEngine.js.map +1 -1
- package/dist/lib/core/ExpressionManager.d.ts +18 -8
- package/dist/lib/core/ExpressionManager.d.ts.map +1 -1
- package/dist/lib/core/ExpressionManager.js +42 -16
- package/dist/lib/core/ExpressionManager.js.map +1 -1
- package/dist/lib/core/PremiseEngine.d.ts +200 -0
- package/dist/lib/core/PremiseEngine.d.ts.map +1 -0
- package/dist/lib/core/PremiseEngine.js +872 -0
- package/dist/lib/core/PremiseEngine.js.map +1 -0
- package/dist/lib/core/VariableManager.d.ts +15 -3
- package/dist/lib/core/VariableManager.d.ts.map +1 -1
- package/dist/lib/core/VariableManager.js +34 -13
- package/dist/lib/core/VariableManager.js.map +1 -1
- package/dist/lib/core/diff.d.ts +2 -2
- package/dist/lib/core/diff.d.ts.map +1 -1
- package/dist/lib/core/diff.js +12 -16
- package/dist/lib/core/diff.js.map +1 -1
- package/dist/lib/core/relationships.d.ts +2 -2
- package/dist/lib/core/relationships.d.ts.map +1 -1
- package/dist/lib/core/relationships.js.map +1 -1
- package/dist/lib/index.d.ts +7 -2
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +2 -2
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/schemata/propositional.d.ts +8 -29
- package/dist/lib/schemata/propositional.d.ts.map +1 -1
- package/dist/lib/schemata/propositional.js +4 -10
- package/dist/lib/schemata/propositional.js.map +1 -1
- package/dist/lib/types/checksum.d.ts +1 -1
- package/dist/lib/types/checksum.d.ts.map +1 -1
- package/dist/lib/types/evaluation.d.ts +1 -9
- package/dist/lib/types/evaluation.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,872 @@
|
|
|
1
|
+
import { DefaultMap } from "../utils.js";
|
|
2
|
+
import { sortedCopyById, sortedUnique } from "../utils/collections.js";
|
|
3
|
+
import { buildDirectionalVacuity, kleeneAnd, kleeneIff, kleeneImplies, kleeneNot, kleeneOr, makeErrorIssue, makeValidationResult, } from "./evaluation/shared.js";
|
|
4
|
+
import { DEFAULT_CHECKSUM_CONFIG } from "../consts.js";
|
|
5
|
+
import { ChangeCollector } from "./ChangeCollector.js";
|
|
6
|
+
import { canonicalSerialize, computeHash, entityChecksum } from "./checksum.js";
|
|
7
|
+
import { ExpressionManager } from "./ExpressionManager.js";
|
|
8
|
+
import { VariableManager } from "./VariableManager.js";
|
|
9
|
+
export class PremiseEngine {
|
|
10
|
+
premise;
|
|
11
|
+
rootExpressionId;
|
|
12
|
+
variables;
|
|
13
|
+
expressions;
|
|
14
|
+
expressionsByVariableId;
|
|
15
|
+
argument;
|
|
16
|
+
checksumConfig;
|
|
17
|
+
checksumDirty = true;
|
|
18
|
+
cachedChecksum;
|
|
19
|
+
expressionIndex;
|
|
20
|
+
constructor(premise, deps, config) {
|
|
21
|
+
this.premise = { ...premise };
|
|
22
|
+
this.argument = deps.argument;
|
|
23
|
+
this.checksumConfig = config?.checksumConfig;
|
|
24
|
+
this.rootExpressionId = undefined;
|
|
25
|
+
this.variables = deps.variables;
|
|
26
|
+
this.expressions = new ExpressionManager(config);
|
|
27
|
+
this.expressionsByVariableId = new DefaultMap(() => new Set());
|
|
28
|
+
this.expressionIndex = deps.expressionIndex;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Deletes all expressions that reference the given variable ID,
|
|
32
|
+
* including their subtrees. Operator collapse runs after each removal.
|
|
33
|
+
* Returns all removed expressions in the changeset.
|
|
34
|
+
*/
|
|
35
|
+
deleteExpressionsUsingVariable(variableId) {
|
|
36
|
+
const expressionIds = this.expressionsByVariableId.get(variableId);
|
|
37
|
+
if (expressionIds.size === 0) {
|
|
38
|
+
return { result: [], changes: {} };
|
|
39
|
+
}
|
|
40
|
+
const collector = new ChangeCollector();
|
|
41
|
+
// Copy the set since removeExpression mutates expressionsByVariableId
|
|
42
|
+
const removed = [];
|
|
43
|
+
for (const exprId of [...expressionIds]) {
|
|
44
|
+
// The expression may already have been removed as part of a
|
|
45
|
+
// prior subtree deletion or operator collapse in this loop.
|
|
46
|
+
if (!this.expressions.getExpression(exprId))
|
|
47
|
+
continue;
|
|
48
|
+
const { result, changes } = this.removeExpression(exprId, true);
|
|
49
|
+
if (result)
|
|
50
|
+
removed.push(result);
|
|
51
|
+
if (changes.expressions) {
|
|
52
|
+
for (const e of changes.expressions.removed) {
|
|
53
|
+
collector.removedExpression(e);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Expressions in the collector already have checksums attached
|
|
58
|
+
// (from ExpressionManager which stores expressions with checksums).
|
|
59
|
+
const changes = collector.toChangeset();
|
|
60
|
+
this.syncExpressionIndex(changes);
|
|
61
|
+
return {
|
|
62
|
+
result: removed,
|
|
63
|
+
changes,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Adds an expression to this premise's tree.
|
|
68
|
+
*
|
|
69
|
+
* If the expression has `parentId: null` it becomes the root; only one
|
|
70
|
+
* root is permitted per premise. If `parentId` is non-null the parent
|
|
71
|
+
* must already exist within this premise.
|
|
72
|
+
*
|
|
73
|
+
* All other structural rules (`implies`/`iff` root-only, child limits,
|
|
74
|
+
* position uniqueness) are enforced by the underlying `ExpressionManager`.
|
|
75
|
+
*
|
|
76
|
+
* @throws If the premise already has a root expression and this one is also a root.
|
|
77
|
+
* @throws If the expression's parent does not exist in this premise.
|
|
78
|
+
* @throws If the expression is a variable reference and the variable has not been registered.
|
|
79
|
+
* @throws If the expression does not belong to this argument.
|
|
80
|
+
*/
|
|
81
|
+
addExpression(expression) {
|
|
82
|
+
this.assertBelongsToArgument(expression.argumentId, expression.argumentVersion);
|
|
83
|
+
if (expression.type === "variable" &&
|
|
84
|
+
!this.variables.hasVariable(expression.variableId)) {
|
|
85
|
+
throw new Error(`Variable expression "${expression.id}" references non-existent variable "${expression.variableId}".`);
|
|
86
|
+
}
|
|
87
|
+
if (expression.parentId === null) {
|
|
88
|
+
if (this.rootExpressionId !== undefined) {
|
|
89
|
+
throw new Error(`Premise "${this.premise.id}" already has a root expression.`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
if (!this.expressions.getExpression(expression.parentId)) {
|
|
94
|
+
throw new Error(`Parent expression "${expression.parentId}" does not exist in this premise.`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const collector = new ChangeCollector();
|
|
98
|
+
this.expressions.setCollector(collector);
|
|
99
|
+
try {
|
|
100
|
+
// Delegate structural validation (operator type checks, position
|
|
101
|
+
// uniqueness, child limits) to ExpressionManager.
|
|
102
|
+
this.expressions.addExpression(expression);
|
|
103
|
+
if (expression.parentId === null) {
|
|
104
|
+
this.rootExpressionId = expression.id;
|
|
105
|
+
}
|
|
106
|
+
if (expression.type === "variable") {
|
|
107
|
+
this.expressionsByVariableId
|
|
108
|
+
.get(expression.variableId)
|
|
109
|
+
.add(expression.id);
|
|
110
|
+
}
|
|
111
|
+
this.markDirty();
|
|
112
|
+
const changes = collector.toChangeset();
|
|
113
|
+
this.syncExpressionIndex(changes);
|
|
114
|
+
return {
|
|
115
|
+
result: this.expressions.getExpression(expression.id),
|
|
116
|
+
changes,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
this.expressions.setCollector(null);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Adds an expression as the last child of the given parent, with
|
|
125
|
+
* position computed automatically.
|
|
126
|
+
*
|
|
127
|
+
* If `parentId` is `null`, the expression becomes the root.
|
|
128
|
+
*
|
|
129
|
+
* @throws If the premise already has a root and parentId is null.
|
|
130
|
+
* @throws If the expression does not belong to this argument.
|
|
131
|
+
* @throws If the expression is a variable reference and the variable has not been registered.
|
|
132
|
+
*/
|
|
133
|
+
appendExpression(parentId, expression) {
|
|
134
|
+
this.assertBelongsToArgument(expression.argumentId, expression.argumentVersion);
|
|
135
|
+
if (expression.type === "variable" &&
|
|
136
|
+
!this.variables.hasVariable(expression.variableId)) {
|
|
137
|
+
throw new Error(`Variable expression "${expression.id}" references non-existent variable "${expression.variableId}".`);
|
|
138
|
+
}
|
|
139
|
+
if (parentId === null) {
|
|
140
|
+
if (this.rootExpressionId !== undefined) {
|
|
141
|
+
throw new Error(`Premise "${this.premise.id}" already has a root expression.`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
if (!this.expressions.getExpression(parentId)) {
|
|
146
|
+
throw new Error(`Parent expression "${parentId}" does not exist in this premise.`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const collector = new ChangeCollector();
|
|
150
|
+
this.expressions.setCollector(collector);
|
|
151
|
+
try {
|
|
152
|
+
this.expressions.appendExpression(parentId, expression);
|
|
153
|
+
if (parentId === null) {
|
|
154
|
+
this.syncRootExpressionId();
|
|
155
|
+
}
|
|
156
|
+
if (expression.type === "variable") {
|
|
157
|
+
this.expressionsByVariableId
|
|
158
|
+
.get(expression.variableId)
|
|
159
|
+
.add(expression.id);
|
|
160
|
+
}
|
|
161
|
+
this.markDirty();
|
|
162
|
+
const changes = collector.toChangeset();
|
|
163
|
+
this.syncExpressionIndex(changes);
|
|
164
|
+
return {
|
|
165
|
+
result: this.expressions.getExpression(expression.id),
|
|
166
|
+
changes,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
finally {
|
|
170
|
+
this.expressions.setCollector(null);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Adds an expression immediately before or after an existing sibling,
|
|
175
|
+
* with position computed automatically.
|
|
176
|
+
*
|
|
177
|
+
* @throws If the sibling does not exist in this premise.
|
|
178
|
+
* @throws If the expression does not belong to this argument.
|
|
179
|
+
* @throws If the expression is a variable reference and the variable has not been registered.
|
|
180
|
+
*/
|
|
181
|
+
addExpressionRelative(siblingId, relativePosition, expression) {
|
|
182
|
+
this.assertBelongsToArgument(expression.argumentId, expression.argumentVersion);
|
|
183
|
+
if (expression.type === "variable" &&
|
|
184
|
+
!this.variables.hasVariable(expression.variableId)) {
|
|
185
|
+
throw new Error(`Variable expression "${expression.id}" references non-existent variable "${expression.variableId}".`);
|
|
186
|
+
}
|
|
187
|
+
if (!this.expressions.getExpression(siblingId)) {
|
|
188
|
+
throw new Error(`Expression "${siblingId}" not found in this premise.`);
|
|
189
|
+
}
|
|
190
|
+
const collector = new ChangeCollector();
|
|
191
|
+
this.expressions.setCollector(collector);
|
|
192
|
+
try {
|
|
193
|
+
this.expressions.addExpressionRelative(siblingId, relativePosition, expression);
|
|
194
|
+
if (expression.type === "variable") {
|
|
195
|
+
this.expressionsByVariableId
|
|
196
|
+
.get(expression.variableId)
|
|
197
|
+
.add(expression.id);
|
|
198
|
+
}
|
|
199
|
+
this.markDirty();
|
|
200
|
+
const changes = collector.toChangeset();
|
|
201
|
+
this.syncExpressionIndex(changes);
|
|
202
|
+
return {
|
|
203
|
+
result: this.expressions.getExpression(expression.id),
|
|
204
|
+
changes,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
this.expressions.setCollector(null);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Updates mutable fields of an existing expression in this premise.
|
|
213
|
+
*
|
|
214
|
+
* Only `position`, `variableId`, and `operator` may be updated. Structural
|
|
215
|
+
* fields (`id`, `parentId`, `type`, `argumentId`, `argumentVersion`,
|
|
216
|
+
* `checksum`) are forbidden — enforced by the underlying
|
|
217
|
+
* `ExpressionManager`.
|
|
218
|
+
*
|
|
219
|
+
* If `variableId` changes, the internal `expressionsByVariableId` index is
|
|
220
|
+
* updated so that cascade deletion (`deleteExpressionsUsingVariable`) stays
|
|
221
|
+
* correct.
|
|
222
|
+
*
|
|
223
|
+
* @throws If the expression does not exist in this premise.
|
|
224
|
+
* @throws If `variableId` references a non-existent variable.
|
|
225
|
+
*/
|
|
226
|
+
updateExpression(expressionId, updates) {
|
|
227
|
+
const existing = this.expressions.getExpression(expressionId);
|
|
228
|
+
if (!existing) {
|
|
229
|
+
throw new Error(`Expression "${expressionId}" not found in premise "${this.premise.id}".`);
|
|
230
|
+
}
|
|
231
|
+
if (updates.variableId !== undefined) {
|
|
232
|
+
if (!this.variables.hasVariable(updates.variableId)) {
|
|
233
|
+
throw new Error(`Variable expression "${expressionId}" references non-existent variable "${updates.variableId}".`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const collector = new ChangeCollector();
|
|
237
|
+
this.expressions.setCollector(collector);
|
|
238
|
+
try {
|
|
239
|
+
const oldVariableId = existing.type === "variable" ? existing.variableId : undefined;
|
|
240
|
+
const updated = this.expressions.updateExpression(expressionId, updates);
|
|
241
|
+
if (updates.variableId !== undefined &&
|
|
242
|
+
oldVariableId !== undefined &&
|
|
243
|
+
oldVariableId !== updates.variableId) {
|
|
244
|
+
this.expressionsByVariableId
|
|
245
|
+
.get(oldVariableId)
|
|
246
|
+
?.delete(expressionId);
|
|
247
|
+
this.expressionsByVariableId
|
|
248
|
+
.get(updates.variableId)
|
|
249
|
+
.add(expressionId);
|
|
250
|
+
}
|
|
251
|
+
const changeset = collector.toChangeset();
|
|
252
|
+
if (changeset.expressions !== undefined) {
|
|
253
|
+
this.markDirty();
|
|
254
|
+
}
|
|
255
|
+
this.syncExpressionIndex(changeset);
|
|
256
|
+
return {
|
|
257
|
+
result: updated,
|
|
258
|
+
changes: changeset,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
finally {
|
|
262
|
+
this.expressions.setCollector(null);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Removes an expression and its entire descendant subtree, then collapses
|
|
267
|
+
* any ancestor operators with fewer than two children (same semantics as
|
|
268
|
+
* before). Returns the removed root expression, or `undefined` if not
|
|
269
|
+
* found.
|
|
270
|
+
*
|
|
271
|
+
* `rootExpressionId` is recomputed after every removal because operator
|
|
272
|
+
* collapse can silently promote a new expression into the root slot.
|
|
273
|
+
*/
|
|
274
|
+
removeExpression(expressionId, deleteSubtree) {
|
|
275
|
+
// Snapshot the expression before removal (for result).
|
|
276
|
+
const snapshot = this.expressions.getExpression(expressionId);
|
|
277
|
+
const collector = new ChangeCollector();
|
|
278
|
+
this.expressions.setCollector(collector);
|
|
279
|
+
try {
|
|
280
|
+
if (!snapshot) {
|
|
281
|
+
return {
|
|
282
|
+
result: undefined,
|
|
283
|
+
changes: collector.toChangeset(),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
if (deleteSubtree) {
|
|
287
|
+
// Snapshot the subtree before deletion so we can clean up
|
|
288
|
+
// expressionsByVariableId for cascade-deleted descendants — they are
|
|
289
|
+
// not individually surfaced by ExpressionManager.removeExpression.
|
|
290
|
+
const subtree = this.collectSubtree(expressionId);
|
|
291
|
+
this.expressions.removeExpression(expressionId, true);
|
|
292
|
+
for (const expr of subtree) {
|
|
293
|
+
if (expr.type === "variable") {
|
|
294
|
+
this.expressionsByVariableId
|
|
295
|
+
.get(expr.variableId)
|
|
296
|
+
?.delete(expr.id);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
// Only clean up expressionsByVariableId for the removed
|
|
302
|
+
// expression itself — children survive promotion.
|
|
303
|
+
if (snapshot.type === "variable") {
|
|
304
|
+
this.expressionsByVariableId
|
|
305
|
+
.get(snapshot.variableId)
|
|
306
|
+
?.delete(snapshot.id);
|
|
307
|
+
}
|
|
308
|
+
this.expressions.removeExpression(expressionId, false);
|
|
309
|
+
}
|
|
310
|
+
this.syncRootExpressionId();
|
|
311
|
+
this.markDirty();
|
|
312
|
+
const changes = collector.toChangeset();
|
|
313
|
+
this.syncExpressionIndex(changes);
|
|
314
|
+
return {
|
|
315
|
+
result: snapshot,
|
|
316
|
+
changes,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
finally {
|
|
320
|
+
this.expressions.setCollector(null);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Splices a new expression between existing nodes in the tree. The new
|
|
325
|
+
* expression inherits the tree slot of the anchor node
|
|
326
|
+
* (`leftNodeId ?? rightNodeId`).
|
|
327
|
+
*
|
|
328
|
+
* `rootExpressionId` is recomputed after every insertion because the
|
|
329
|
+
* anchor may have been the root.
|
|
330
|
+
*
|
|
331
|
+
* See `ArgumentEngine.insertExpression` for the full contract; the same
|
|
332
|
+
* rules apply here.
|
|
333
|
+
*
|
|
334
|
+
* @throws If the expression does not belong to this argument.
|
|
335
|
+
* @throws If the expression is a variable reference and the variable has not been registered.
|
|
336
|
+
*/
|
|
337
|
+
insertExpression(expression, leftNodeId, rightNodeId) {
|
|
338
|
+
this.assertBelongsToArgument(expression.argumentId, expression.argumentVersion);
|
|
339
|
+
if (expression.type === "variable" &&
|
|
340
|
+
!this.variables.hasVariable(expression.variableId)) {
|
|
341
|
+
throw new Error(`Variable expression "${expression.id}" references non-existent variable "${expression.variableId}".`);
|
|
342
|
+
}
|
|
343
|
+
const collector = new ChangeCollector();
|
|
344
|
+
this.expressions.setCollector(collector);
|
|
345
|
+
try {
|
|
346
|
+
this.expressions.insertExpression(expression, leftNodeId, rightNodeId);
|
|
347
|
+
if (expression.type === "variable") {
|
|
348
|
+
this.expressionsByVariableId
|
|
349
|
+
.get(expression.variableId)
|
|
350
|
+
.add(expression.id);
|
|
351
|
+
}
|
|
352
|
+
this.syncRootExpressionId();
|
|
353
|
+
this.markDirty();
|
|
354
|
+
const changes = collector.toChangeset();
|
|
355
|
+
this.syncExpressionIndex(changes);
|
|
356
|
+
return {
|
|
357
|
+
result: this.expressions.getExpression(expression.id),
|
|
358
|
+
changes,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
finally {
|
|
362
|
+
this.expressions.setCollector(null);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Returns an expression by ID, or `undefined` if not found in this
|
|
367
|
+
* premise.
|
|
368
|
+
*/
|
|
369
|
+
getExpression(id) {
|
|
370
|
+
return this.expressions.getExpression(id);
|
|
371
|
+
}
|
|
372
|
+
getId() {
|
|
373
|
+
return this.premise.id;
|
|
374
|
+
}
|
|
375
|
+
getExtras() {
|
|
376
|
+
const { id: _id, argumentId: _argumentId, argumentVersion: _argumentVersion, checksum: _checksum, ...extras } = this.premise;
|
|
377
|
+
return { ...extras };
|
|
378
|
+
}
|
|
379
|
+
setExtras(extras) {
|
|
380
|
+
// Strip old extras and replace with new ones
|
|
381
|
+
const { id, argumentId, argumentVersion, checksum } = this
|
|
382
|
+
.premise;
|
|
383
|
+
this.premise = {
|
|
384
|
+
...extras,
|
|
385
|
+
id,
|
|
386
|
+
argumentId,
|
|
387
|
+
argumentVersion,
|
|
388
|
+
...(checksum !== undefined ? { checksum } : {}),
|
|
389
|
+
};
|
|
390
|
+
this.markDirty();
|
|
391
|
+
return { result: this.getExtras(), changes: {} };
|
|
392
|
+
}
|
|
393
|
+
getRootExpressionId() {
|
|
394
|
+
return this.rootExpressionId;
|
|
395
|
+
}
|
|
396
|
+
getRootExpression() {
|
|
397
|
+
if (this.rootExpressionId === undefined) {
|
|
398
|
+
return undefined;
|
|
399
|
+
}
|
|
400
|
+
return this.expressions.getExpression(this.rootExpressionId);
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Returns all argument-level variables (from the shared VariableManager)
|
|
404
|
+
* sorted by ID. Since the VariableManager is shared across all premises,
|
|
405
|
+
* this returns every registered variable — not just those referenced by
|
|
406
|
+
* expressions in this premise.
|
|
407
|
+
*/
|
|
408
|
+
getVariables() {
|
|
409
|
+
return sortedCopyById(this.variables.toArray());
|
|
410
|
+
}
|
|
411
|
+
getExpressions() {
|
|
412
|
+
return sortedCopyById(this.expressions.toArray());
|
|
413
|
+
}
|
|
414
|
+
getChildExpressions(parentId) {
|
|
415
|
+
return this.expressions.getChildExpressions(parentId);
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Returns `true` if the root expression is an `implies` or `iff` operator,
|
|
419
|
+
* meaning this premise expresses a logical inference relationship.
|
|
420
|
+
*/
|
|
421
|
+
isInference() {
|
|
422
|
+
const root = this.getRootExpression();
|
|
423
|
+
return (root?.type === "operator" &&
|
|
424
|
+
(root.operator === "implies" || root.operator === "iff"));
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Returns `true` if this premise does not have an inference operator at its
|
|
428
|
+
* root (i.e. it is a constraint premise). Equivalent to `!isInference()`.
|
|
429
|
+
*/
|
|
430
|
+
isConstraint() {
|
|
431
|
+
return !this.isInference();
|
|
432
|
+
}
|
|
433
|
+
validateEvaluability() {
|
|
434
|
+
const issues = [];
|
|
435
|
+
const roots = this.expressions.getChildExpressions(null);
|
|
436
|
+
if (this.expressions.toArray().length === 0) {
|
|
437
|
+
issues.push(makeErrorIssue({
|
|
438
|
+
code: "PREMISE_EMPTY",
|
|
439
|
+
message: `Premise "${this.premise.id}" has no expressions to evaluate.`,
|
|
440
|
+
premiseId: this.premise.id,
|
|
441
|
+
}));
|
|
442
|
+
return makeValidationResult(issues);
|
|
443
|
+
}
|
|
444
|
+
if (roots.length === 0) {
|
|
445
|
+
issues.push(makeErrorIssue({
|
|
446
|
+
code: "PREMISE_ROOT_MISSING",
|
|
447
|
+
message: `Premise "${this.premise.id}" has expressions but no root expression.`,
|
|
448
|
+
premiseId: this.premise.id,
|
|
449
|
+
}));
|
|
450
|
+
}
|
|
451
|
+
if (this.rootExpressionId === undefined) {
|
|
452
|
+
issues.push(makeErrorIssue({
|
|
453
|
+
code: "PREMISE_ROOT_MISSING",
|
|
454
|
+
message: `Premise "${this.premise.id}" does not have rootExpressionId set.`,
|
|
455
|
+
premiseId: this.premise.id,
|
|
456
|
+
}));
|
|
457
|
+
}
|
|
458
|
+
else if (!this.expressions.getExpression(this.rootExpressionId)) {
|
|
459
|
+
issues.push(makeErrorIssue({
|
|
460
|
+
code: "PREMISE_ROOT_MISMATCH",
|
|
461
|
+
message: `Premise "${this.premise.id}" rootExpressionId "${this.rootExpressionId}" does not exist.`,
|
|
462
|
+
premiseId: this.premise.id,
|
|
463
|
+
expressionId: this.rootExpressionId,
|
|
464
|
+
}));
|
|
465
|
+
}
|
|
466
|
+
else if (roots[0] && roots[0].id !== this.rootExpressionId) {
|
|
467
|
+
issues.push(makeErrorIssue({
|
|
468
|
+
code: "PREMISE_ROOT_MISMATCH",
|
|
469
|
+
message: `Premise "${this.premise.id}" rootExpressionId "${this.rootExpressionId}" does not match actual root "${roots[0].id}".`,
|
|
470
|
+
premiseId: this.premise.id,
|
|
471
|
+
expressionId: this.rootExpressionId,
|
|
472
|
+
}));
|
|
473
|
+
}
|
|
474
|
+
for (const expr of this.expressions.toArray()) {
|
|
475
|
+
if (expr.type === "variable" &&
|
|
476
|
+
!this.variables.hasVariable(expr.variableId)) {
|
|
477
|
+
issues.push(makeErrorIssue({
|
|
478
|
+
code: "EXPR_VARIABLE_UNDECLARED",
|
|
479
|
+
message: `Expression "${expr.id}" references undeclared variable "${expr.variableId}".`,
|
|
480
|
+
premiseId: this.premise.id,
|
|
481
|
+
expressionId: expr.id,
|
|
482
|
+
variableId: expr.variableId,
|
|
483
|
+
}));
|
|
484
|
+
}
|
|
485
|
+
if (expr.type !== "operator" && expr.type !== "formula") {
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
const children = this.expressions.getChildExpressions(expr.id);
|
|
489
|
+
if (expr.type === "formula") {
|
|
490
|
+
if (children.length !== 1) {
|
|
491
|
+
issues.push(makeErrorIssue({
|
|
492
|
+
code: "EXPR_CHILD_COUNT_INVALID",
|
|
493
|
+
message: `Formula expression "${expr.id}" must have exactly 1 child; found ${children.length}.`,
|
|
494
|
+
premiseId: this.premise.id,
|
|
495
|
+
expressionId: expr.id,
|
|
496
|
+
}));
|
|
497
|
+
}
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
if (expr.operator === "not" && children.length !== 1) {
|
|
501
|
+
issues.push(makeErrorIssue({
|
|
502
|
+
code: "EXPR_CHILD_COUNT_INVALID",
|
|
503
|
+
message: `Operator "${expr.id}" (not) must have exactly 1 child; found ${children.length}.`,
|
|
504
|
+
premiseId: this.premise.id,
|
|
505
|
+
expressionId: expr.id,
|
|
506
|
+
}));
|
|
507
|
+
}
|
|
508
|
+
if ((expr.operator === "implies" || expr.operator === "iff") &&
|
|
509
|
+
children.length !== 2) {
|
|
510
|
+
issues.push(makeErrorIssue({
|
|
511
|
+
code: "EXPR_CHILD_COUNT_INVALID",
|
|
512
|
+
message: `Operator "${expr.id}" (${expr.operator}) must have exactly 2 children; found ${children.length}.`,
|
|
513
|
+
premiseId: this.premise.id,
|
|
514
|
+
expressionId: expr.id,
|
|
515
|
+
}));
|
|
516
|
+
}
|
|
517
|
+
if ((expr.operator === "and" || expr.operator === "or") &&
|
|
518
|
+
children.length < 2) {
|
|
519
|
+
issues.push(makeErrorIssue({
|
|
520
|
+
code: "EXPR_CHILD_COUNT_INVALID",
|
|
521
|
+
message: `Operator "${expr.id}" (${expr.operator}) must have at least 2 children; found ${children.length}.`,
|
|
522
|
+
premiseId: this.premise.id,
|
|
523
|
+
expressionId: expr.id,
|
|
524
|
+
}));
|
|
525
|
+
}
|
|
526
|
+
if (expr.operator === "implies" || expr.operator === "iff") {
|
|
527
|
+
const childPositions = new Set(children.map((child) => child.position));
|
|
528
|
+
if (!childPositions.has(0) || !childPositions.has(1)) {
|
|
529
|
+
issues.push(makeErrorIssue({
|
|
530
|
+
code: "EXPR_BINARY_POSITIONS_INVALID",
|
|
531
|
+
message: `Operator "${expr.id}" (${expr.operator}) must have children at positions 0 and 1.`,
|
|
532
|
+
premiseId: this.premise.id,
|
|
533
|
+
expressionId: expr.id,
|
|
534
|
+
}));
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return makeValidationResult(issues);
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Evaluates the premise under a three-valued expression assignment.
|
|
542
|
+
*
|
|
543
|
+
* Variable values are looked up in `assignment.variables` using Kleene
|
|
544
|
+
* three-valued logic (`null` = unknown). Missing variables default to `null`.
|
|
545
|
+
* Expressions listed in `assignment.rejectedExpressionIds` evaluate to
|
|
546
|
+
* `false` and their children are not evaluated.
|
|
547
|
+
*
|
|
548
|
+
* For inference premises (`implies`/`iff`), an `inferenceDiagnostic` is
|
|
549
|
+
* computed with three-valued fields unless the root is rejected.
|
|
550
|
+
*/
|
|
551
|
+
evaluate(assignment, options) {
|
|
552
|
+
const validation = this.validateEvaluability();
|
|
553
|
+
if (!validation.ok) {
|
|
554
|
+
throw new Error(`Premise "${this.premise.id}" is not evaluable: ${validation.issues
|
|
555
|
+
.map((issue) => issue.code)
|
|
556
|
+
.join(", ")}`);
|
|
557
|
+
}
|
|
558
|
+
const rootExpressionId = this.rootExpressionId;
|
|
559
|
+
const referencedVariableIds = sortedUnique(this.expressions
|
|
560
|
+
.toArray()
|
|
561
|
+
.filter((expr) => expr.type === "variable")
|
|
562
|
+
.map((expr) => expr.variableId));
|
|
563
|
+
if (options?.strictUnknownKeys || options?.requireExactCoverage) {
|
|
564
|
+
const knownVariableIds = new Set(referencedVariableIds);
|
|
565
|
+
const unknownKeys = Object.keys(assignment.variables).filter((variableId) => !knownVariableIds.has(variableId));
|
|
566
|
+
if (unknownKeys.length > 0) {
|
|
567
|
+
throw new Error(`Assignment contains unknown variable IDs for premise "${this.premise.id}": ${unknownKeys.join(", ")}`);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
const expressionValues = {};
|
|
571
|
+
const evaluateExpression = (expressionId) => {
|
|
572
|
+
const expression = this.expressions.getExpression(expressionId);
|
|
573
|
+
if (!expression) {
|
|
574
|
+
throw new Error(`Expression "${expressionId}" was not found.`);
|
|
575
|
+
}
|
|
576
|
+
if (assignment.rejectedExpressionIds.includes(expression.id)) {
|
|
577
|
+
expressionValues[expression.id] = false;
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
580
|
+
if (expression.type === "variable") {
|
|
581
|
+
const value = assignment.variables[expression.variableId] ?? null;
|
|
582
|
+
expressionValues[expression.id] = value;
|
|
583
|
+
return value;
|
|
584
|
+
}
|
|
585
|
+
const children = this.expressions.getChildExpressions(expression.id);
|
|
586
|
+
let value;
|
|
587
|
+
if (expression.type === "formula") {
|
|
588
|
+
value = evaluateExpression(children[0].id);
|
|
589
|
+
expressionValues[expression.id] = value;
|
|
590
|
+
return value;
|
|
591
|
+
}
|
|
592
|
+
switch (expression.operator) {
|
|
593
|
+
case "not":
|
|
594
|
+
value = kleeneNot(evaluateExpression(children[0].id));
|
|
595
|
+
break;
|
|
596
|
+
case "and":
|
|
597
|
+
value = children.reduce((acc, child) => kleeneAnd(acc, evaluateExpression(child.id)), true);
|
|
598
|
+
break;
|
|
599
|
+
case "or":
|
|
600
|
+
value = children.reduce((acc, child) => kleeneOr(acc, evaluateExpression(child.id)), false);
|
|
601
|
+
break;
|
|
602
|
+
case "implies": {
|
|
603
|
+
const left = children.find((child) => child.position === 0);
|
|
604
|
+
const right = children.find((child) => child.position === 1);
|
|
605
|
+
value = kleeneImplies(evaluateExpression(left.id), evaluateExpression(right.id));
|
|
606
|
+
break;
|
|
607
|
+
}
|
|
608
|
+
case "iff": {
|
|
609
|
+
const left = children.find((child) => child.position === 0);
|
|
610
|
+
const right = children.find((child) => child.position === 1);
|
|
611
|
+
value = kleeneIff(evaluateExpression(left.id), evaluateExpression(right.id));
|
|
612
|
+
break;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
expressionValues[expression.id] = value;
|
|
616
|
+
return value;
|
|
617
|
+
};
|
|
618
|
+
const rootValue = evaluateExpression(rootExpressionId);
|
|
619
|
+
const variableValues = {};
|
|
620
|
+
for (const variableId of referencedVariableIds) {
|
|
621
|
+
variableValues[variableId] =
|
|
622
|
+
assignment.variables[variableId] ?? null;
|
|
623
|
+
}
|
|
624
|
+
let inferenceDiagnostic;
|
|
625
|
+
if (this.isInference() &&
|
|
626
|
+
!assignment.rejectedExpressionIds.includes(rootExpressionId)) {
|
|
627
|
+
const root = this.expressions.getExpression(rootExpressionId);
|
|
628
|
+
if (root?.type === "operator") {
|
|
629
|
+
const children = this.expressions.getChildExpressions(root.id);
|
|
630
|
+
const left = children.find((child) => child.position === 0);
|
|
631
|
+
const right = children.find((child) => child.position === 1);
|
|
632
|
+
if (left && right) {
|
|
633
|
+
const leftValue = expressionValues[left.id];
|
|
634
|
+
const rightValue = expressionValues[right.id];
|
|
635
|
+
if (root.operator === "implies") {
|
|
636
|
+
inferenceDiagnostic = {
|
|
637
|
+
kind: "implies",
|
|
638
|
+
premiseId: this.premise.id,
|
|
639
|
+
rootExpressionId,
|
|
640
|
+
leftValue,
|
|
641
|
+
rightValue,
|
|
642
|
+
rootValue,
|
|
643
|
+
antecedentTrue: leftValue,
|
|
644
|
+
consequentTrue: rightValue,
|
|
645
|
+
isVacuouslyTrue: kleeneNot(leftValue),
|
|
646
|
+
fired: leftValue,
|
|
647
|
+
firedAndHeld: kleeneAnd(leftValue, rightValue),
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
else if (root.operator === "iff") {
|
|
651
|
+
const leftToRight = buildDirectionalVacuity(leftValue, rightValue);
|
|
652
|
+
const rightToLeft = buildDirectionalVacuity(rightValue, leftValue);
|
|
653
|
+
inferenceDiagnostic = {
|
|
654
|
+
kind: "iff",
|
|
655
|
+
premiseId: this.premise.id,
|
|
656
|
+
rootExpressionId,
|
|
657
|
+
leftValue,
|
|
658
|
+
rightValue,
|
|
659
|
+
rootValue,
|
|
660
|
+
leftToRight,
|
|
661
|
+
rightToLeft,
|
|
662
|
+
bothSidesTrue: kleeneAnd(leftValue, rightValue),
|
|
663
|
+
bothSidesFalse: kleeneAnd(kleeneNot(leftValue), kleeneNot(rightValue)),
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return {
|
|
670
|
+
premiseId: this.premise.id,
|
|
671
|
+
premiseType: this.isInference() ? "inference" : "constraint",
|
|
672
|
+
rootExpressionId,
|
|
673
|
+
rootValue,
|
|
674
|
+
expressionValues,
|
|
675
|
+
variableValues,
|
|
676
|
+
inferenceDiagnostic,
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Returns a human-readable string of this premise's expression tree using
|
|
681
|
+
* standard logical notation (∧ ∨ ¬ → ↔). Missing operands are rendered
|
|
682
|
+
* as `(?)`. Returns an empty string when the premise has no expressions.
|
|
683
|
+
*/
|
|
684
|
+
toDisplayString() {
|
|
685
|
+
if (this.rootExpressionId === undefined) {
|
|
686
|
+
return "";
|
|
687
|
+
}
|
|
688
|
+
return this.renderExpression(this.rootExpressionId);
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Returns the set of variable IDs referenced by expressions in this premise.
|
|
692
|
+
* Only variables that appear in `type: "variable"` expression nodes are
|
|
693
|
+
* included — not all variables in the shared VariableManager.
|
|
694
|
+
*/
|
|
695
|
+
getReferencedVariableIds() {
|
|
696
|
+
const ids = new Set();
|
|
697
|
+
for (const expr of this.expressions.toArray()) {
|
|
698
|
+
if (expr.type === "variable") {
|
|
699
|
+
ids.add(expr.variableId);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return ids;
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Returns a serializable TPremise representation of this premise.
|
|
706
|
+
* Contains only premise identity/metadata and checksum — use
|
|
707
|
+
* getRootExpressionId(), getExpressions(), getReferencedVariableIds()
|
|
708
|
+
* for runtime state.
|
|
709
|
+
*/
|
|
710
|
+
toPremiseData() {
|
|
711
|
+
return {
|
|
712
|
+
...this.premise,
|
|
713
|
+
checksum: this.checksum(),
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Returns a premise-level checksum combining all entity checksums.
|
|
718
|
+
* Computed lazily -- only recalculated when state has changed.
|
|
719
|
+
*/
|
|
720
|
+
checksum() {
|
|
721
|
+
if (this.checksumDirty || this.cachedChecksum === undefined) {
|
|
722
|
+
this.cachedChecksum = this.computeChecksum();
|
|
723
|
+
this.checksumDirty = false;
|
|
724
|
+
}
|
|
725
|
+
return this.cachedChecksum;
|
|
726
|
+
}
|
|
727
|
+
// -------------------------------------------------------------------------
|
|
728
|
+
// Private helpers
|
|
729
|
+
// -------------------------------------------------------------------------
|
|
730
|
+
computeChecksum() {
|
|
731
|
+
const checksumMap = {};
|
|
732
|
+
// Premise's own entity checksum
|
|
733
|
+
const premiseFields = this.checksumConfig?.premiseFields ??
|
|
734
|
+
DEFAULT_CHECKSUM_CONFIG.premiseFields;
|
|
735
|
+
checksumMap[this.premise.id] = entityChecksum({ id: this.premise.id }, premiseFields);
|
|
736
|
+
// All owned expression checksums
|
|
737
|
+
for (const expr of this.expressions.toArray()) {
|
|
738
|
+
checksumMap[expr.id] = expr.checksum;
|
|
739
|
+
}
|
|
740
|
+
return computeHash(canonicalSerialize(checksumMap));
|
|
741
|
+
}
|
|
742
|
+
/** Invalidate the cached checksum so the next call recomputes it. */
|
|
743
|
+
markDirty() {
|
|
744
|
+
this.checksumDirty = true;
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Re-reads the single root from ExpressionManager after any operation
|
|
748
|
+
* that may have caused operator collapse to silently change the root.
|
|
749
|
+
*/
|
|
750
|
+
syncRootExpressionId() {
|
|
751
|
+
const roots = this.expressions.getChildExpressions(null);
|
|
752
|
+
this.rootExpressionId = roots[0]?.id;
|
|
753
|
+
}
|
|
754
|
+
collectSubtree(rootId) {
|
|
755
|
+
const result = [];
|
|
756
|
+
const stack = [rootId];
|
|
757
|
+
while (stack.length > 0) {
|
|
758
|
+
const id = stack.pop();
|
|
759
|
+
const expr = this.expressions.getExpression(id);
|
|
760
|
+
if (!expr)
|
|
761
|
+
continue;
|
|
762
|
+
result.push(expr);
|
|
763
|
+
for (const child of this.expressions.getChildExpressions(id)) {
|
|
764
|
+
stack.push(child.id);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
return result;
|
|
768
|
+
}
|
|
769
|
+
assertBelongsToArgument(argumentId, argumentVersion) {
|
|
770
|
+
if (argumentId !== this.argument.id) {
|
|
771
|
+
throw new Error(`Entity argumentId "${argumentId}" does not match engine argument ID "${this.argument.id}".`);
|
|
772
|
+
}
|
|
773
|
+
if (argumentVersion !== this.argument.version) {
|
|
774
|
+
throw new Error(`Entity argumentVersion "${argumentVersion}" does not match engine argument version "${this.argument.version}".`);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
renderExpression(expressionId) {
|
|
778
|
+
const expression = this.expressions.getExpression(expressionId);
|
|
779
|
+
if (!expression) {
|
|
780
|
+
throw new Error(`Expression "${expressionId}" was not found.`);
|
|
781
|
+
}
|
|
782
|
+
if (expression.type === "variable") {
|
|
783
|
+
const variable = this.variables.getVariable(expression.variableId);
|
|
784
|
+
if (!variable) {
|
|
785
|
+
throw new Error(`Variable "${expression.variableId}" for expression "${expressionId}" was not found.`);
|
|
786
|
+
}
|
|
787
|
+
return variable.symbol;
|
|
788
|
+
}
|
|
789
|
+
if (expression.type === "formula") {
|
|
790
|
+
const children = this.expressions.getChildExpressions(expression.id);
|
|
791
|
+
if (children.length === 0) {
|
|
792
|
+
return "(?)";
|
|
793
|
+
}
|
|
794
|
+
return `(${this.renderExpression(children[0].id)})`;
|
|
795
|
+
}
|
|
796
|
+
const children = this.expressions.getChildExpressions(expression.id);
|
|
797
|
+
if (expression.operator === "not") {
|
|
798
|
+
if (children.length === 0) {
|
|
799
|
+
return `${this.operatorSymbol(expression.operator)} (?)`;
|
|
800
|
+
}
|
|
801
|
+
return `${this.operatorSymbol(expression.operator)}(${this.renderExpression(children[0].id)})`;
|
|
802
|
+
}
|
|
803
|
+
if (children.length === 0) {
|
|
804
|
+
return "(?)";
|
|
805
|
+
}
|
|
806
|
+
const renderedChildren = children.map((child) => this.renderExpression(child.id));
|
|
807
|
+
return `(${renderedChildren.join(` ${this.operatorSymbol(expression.operator)} `)})`;
|
|
808
|
+
}
|
|
809
|
+
operatorSymbol(operator) {
|
|
810
|
+
switch (operator) {
|
|
811
|
+
case "and":
|
|
812
|
+
return "∧";
|
|
813
|
+
case "or":
|
|
814
|
+
return "∨";
|
|
815
|
+
case "implies":
|
|
816
|
+
return "→";
|
|
817
|
+
case "iff":
|
|
818
|
+
return "↔";
|
|
819
|
+
case "not":
|
|
820
|
+
return "¬";
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
/** Returns a serializable snapshot of the premise's owned state. */
|
|
824
|
+
snapshot() {
|
|
825
|
+
const exprSnapshot = this.expressions.snapshot();
|
|
826
|
+
return {
|
|
827
|
+
premise: { ...this.premise },
|
|
828
|
+
rootExpressionId: this.rootExpressionId,
|
|
829
|
+
expressions: exprSnapshot,
|
|
830
|
+
config: {
|
|
831
|
+
checksumConfig: this.checksumConfig,
|
|
832
|
+
positionConfig: exprSnapshot.config?.positionConfig,
|
|
833
|
+
},
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
/** Creates a new PremiseEngine from a previously captured snapshot. */
|
|
837
|
+
static fromSnapshot(snapshot, argument, variables, expressionIndex) {
|
|
838
|
+
const pe = new PremiseEngine(snapshot.premise, { argument, variables, expressionIndex }, snapshot.config);
|
|
839
|
+
// Restore expressions from the snapshot
|
|
840
|
+
pe.expressions = ExpressionManager.fromSnapshot(snapshot.expressions);
|
|
841
|
+
// Restore rootExpressionId from snapshot
|
|
842
|
+
pe.rootExpressionId = snapshot.rootExpressionId;
|
|
843
|
+
// Rebuild the expressionsByVariableId index
|
|
844
|
+
pe.rebuildVariableIndex();
|
|
845
|
+
// Populate the shared expression index if provided
|
|
846
|
+
if (expressionIndex) {
|
|
847
|
+
for (const expr of pe.expressions.toArray()) {
|
|
848
|
+
expressionIndex.set(expr.id, pe.getId());
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
return pe;
|
|
852
|
+
}
|
|
853
|
+
syncExpressionIndex(changes) {
|
|
854
|
+
if (!this.expressionIndex || !changes.expressions)
|
|
855
|
+
return;
|
|
856
|
+
for (const expr of changes.expressions.added) {
|
|
857
|
+
this.expressionIndex.set(expr.id, this.premise.id);
|
|
858
|
+
}
|
|
859
|
+
for (const expr of changes.expressions.removed) {
|
|
860
|
+
this.expressionIndex.delete(expr.id);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
rebuildVariableIndex() {
|
|
864
|
+
this.expressionsByVariableId = new DefaultMap(() => new Set());
|
|
865
|
+
for (const expr of this.expressions.toArray()) {
|
|
866
|
+
if (expr.type === "variable") {
|
|
867
|
+
this.expressionsByVariableId.get(expr.variableId).add(expr.id);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
//# sourceMappingURL=PremiseEngine.js.map
|