tla-ts 0.0.2 → 0.0.3-dev.a50fc89
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 +56 -10
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +50 -1
- package/dist/index.d.ts +50 -1
- package/dist/index.js +1 -1
- package/dist/parallel-worker.cjs +1 -1
- package/dist/parallel-worker.js +2 -2
- package/dist/parallel.cjs +1 -1
- package/dist/parallel.js +2 -2
- package/dist/run-cli.cjs +20 -2
- package/dist/run-cli.js +20 -2
- package/dist/transfer.cjs +12 -12
- package/dist/transfer.js +12 -12
- package/package.json +3 -1
- package/src/ast.ts +43 -3
- package/src/check.ts +43 -10
- package/src/cli-options.ts +58 -0
- package/src/errors.ts +37 -1
- package/src/evaluate.ts +3 -1
- package/src/lexer.ts +6 -1
- package/src/obligation-action.ts +384 -0
- package/src/obligation-anon.ts +575 -0
- package/src/obligation-deref.ts +637 -0
- package/src/obligation-expression.ts +1060 -0
- package/src/obligation-format.ts +406 -0
- package/src/obligation-generate.ts +1074 -0
- package/src/obligation-levels.ts +329 -0
- package/src/obligation-module.ts +1042 -0
- package/src/obligation-printer.ts +889 -0
- package/src/obligation-proof.ts +76 -0
- package/src/obligation-source.ts +1084 -0
- package/src/obligation-tuples.ts +184 -0
- package/src/obligations.ts +316 -0
- package/src/parser.ts +285 -52
- package/src/run-cli.ts +295 -27
- package/src/standard-levels.ts +3 -3
- package/src/transfer.ts +22 -1
- package/src/values.ts +53 -20
|
@@ -0,0 +1,1074 @@
|
|
|
1
|
+
// A port of tlapm's proof simplification (Proof.Simplify) and obligation generation (Proof.Gen):
|
|
2
|
+
// leaf proofs become obligations whose context is everything the proof may use at that point.
|
|
3
|
+
|
|
4
|
+
import { TlapmBugError, TlaProofError } from "./errors";
|
|
5
|
+
import {
|
|
6
|
+
applyBuiltin,
|
|
7
|
+
at,
|
|
8
|
+
consSubstitution,
|
|
9
|
+
exprifySequent,
|
|
10
|
+
getErrorNote,
|
|
11
|
+
isEqualExpr,
|
|
12
|
+
mapAccumulate,
|
|
13
|
+
normalizeApply,
|
|
14
|
+
shift,
|
|
15
|
+
shiftExpr,
|
|
16
|
+
substituteBounds,
|
|
17
|
+
substituteExpr,
|
|
18
|
+
type Bound,
|
|
19
|
+
type Builtin,
|
|
20
|
+
type Expr,
|
|
21
|
+
type Hyp,
|
|
22
|
+
type Locus,
|
|
23
|
+
type ObligationWarning,
|
|
24
|
+
type Sequent,
|
|
25
|
+
type Visibility,
|
|
26
|
+
mapChildren,
|
|
27
|
+
stripProps,
|
|
28
|
+
} from "./obligation-expression";
|
|
29
|
+
import { format, renderDoc, type Doc } from "./obligation-format";
|
|
30
|
+
import { printExpression } from "./obligation-printer";
|
|
31
|
+
import {
|
|
32
|
+
getStepName,
|
|
33
|
+
getUnique,
|
|
34
|
+
type Counter,
|
|
35
|
+
type Obligation,
|
|
36
|
+
type ObligationKind,
|
|
37
|
+
type Proof,
|
|
38
|
+
type Step,
|
|
39
|
+
type StepNumber,
|
|
40
|
+
type UseDef,
|
|
41
|
+
type Usable,
|
|
42
|
+
} from "./obligation-proof";
|
|
43
|
+
|
|
44
|
+
const AT_NOWHERE: Expr = { kind: "At", isExcept: false };
|
|
45
|
+
|
|
46
|
+
// tlapm builds nodes with x @@ step, which copies all of the step's properties.
|
|
47
|
+
const getProps = (from: Step) => ({
|
|
48
|
+
location: from.location,
|
|
49
|
+
step: from.step,
|
|
50
|
+
isSuppressed: from.isSuppressed,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const isAnonymous = (step: StepNumber | undefined) => step?.kind === "Named" && step.isAnonymous;
|
|
54
|
+
|
|
55
|
+
const getLevel = (step: StepNumber | undefined) => step?.level ?? 0;
|
|
56
|
+
|
|
57
|
+
const getTrue = (from: { location?: Locus }) => at({ kind: "Internal", builtin: "TRUE" }, from);
|
|
58
|
+
|
|
59
|
+
const isBinary = (
|
|
60
|
+
expression: Expr,
|
|
61
|
+
builtin: Builtin,
|
|
62
|
+
): expression is Extract<Expr, { kind: "Apply" }> =>
|
|
63
|
+
expression.kind === "Apply" &&
|
|
64
|
+
expression.operator.kind === "Internal" &&
|
|
65
|
+
expression.operator.builtin === builtin &&
|
|
66
|
+
expression.args.length === 2;
|
|
67
|
+
|
|
68
|
+
const getMessage = (pattern: string, ...args: (string | Doc)[]) =>
|
|
69
|
+
renderDoc(format(pattern, ...args));
|
|
70
|
+
|
|
71
|
+
// Visit.hyps_of_bounds_unditto: dittos repeat the previous domain, and each domain shifts past the
|
|
72
|
+
// names bound before it.
|
|
73
|
+
const getBoundHyps = (bounds: Bound[]): Hyp[] =>
|
|
74
|
+
expandDittoes(bounds).map((bound, index): Hyp => {
|
|
75
|
+
const domain =
|
|
76
|
+
bound.domain.kind === "Domain" ? shiftExpr(index, bound.domain.expression) : undefined;
|
|
77
|
+
return {
|
|
78
|
+
kind: "Fresh",
|
|
79
|
+
name: bound.name,
|
|
80
|
+
shape: 0,
|
|
81
|
+
level: domain ? "Constant" : bound.level,
|
|
82
|
+
domain,
|
|
83
|
+
domainVisibility: "Visible",
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const expandDittoes = (bounds: Bound[]): Bound[] => {
|
|
88
|
+
let domain: Bound["domain"] = { kind: "NoDomain" };
|
|
89
|
+
return bounds.map((bound) => {
|
|
90
|
+
if (bound.domain.kind === "Ditto") return { ...bound, domain };
|
|
91
|
+
domain = bound.domain;
|
|
92
|
+
return bound;
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Each visible fact of the sequent, shifted past the hypotheses after it.
|
|
97
|
+
const getAssumedFacts = (offset: number, sequent: Sequent) =>
|
|
98
|
+
sequent.context.flatMap((hyp, index) =>
|
|
99
|
+
hyp.kind === "Fact" && hyp.visibility === "Visible"
|
|
100
|
+
? [shiftExpr(offset + sequent.context.length - 1 - index, hyp.expression)]
|
|
101
|
+
: [],
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const getStepDefinition = (step: Step, body: Expr): Hyp => {
|
|
105
|
+
if (!step.step) throw new TlapmBugError();
|
|
106
|
+
return {
|
|
107
|
+
kind: "Defn",
|
|
108
|
+
definition: { kind: "Operator", name: getStepName(step.step), body, location: step.location },
|
|
109
|
+
whereDefined: "Proof",
|
|
110
|
+
visibility: "Visible",
|
|
111
|
+
isLocal: true,
|
|
112
|
+
location: step.location,
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const proveAssertion = (context: Hyp[], sequent: Sequent, step: Step) => {
|
|
117
|
+
const visibility: Visibility = isAnonymous(step.step) ? "Visible" : "Hidden";
|
|
118
|
+
const hyps = sequent.context.map((hyp): Hyp =>
|
|
119
|
+
hyp.kind === "Fact" ? { ...hyp, visibility } : hyp,
|
|
120
|
+
);
|
|
121
|
+
const body = at({ kind: "List", bullet: "Refs", items: getAssumedFacts(2, sequent) }, step);
|
|
122
|
+
return {
|
|
123
|
+
context: [
|
|
124
|
+
...context,
|
|
125
|
+
...hyps,
|
|
126
|
+
{
|
|
127
|
+
kind: "Fact",
|
|
128
|
+
expression: getTrue(step),
|
|
129
|
+
visibility: "Hidden",
|
|
130
|
+
location: step.location,
|
|
131
|
+
},
|
|
132
|
+
getStepDefinition(step, body),
|
|
133
|
+
{
|
|
134
|
+
kind: "Fact",
|
|
135
|
+
expression: at({ kind: "Ix", index: 1 }, step),
|
|
136
|
+
visibility: "Hidden",
|
|
137
|
+
location: step.location,
|
|
138
|
+
},
|
|
139
|
+
] satisfies Hyp[],
|
|
140
|
+
active: shiftExpr(3, sequent.active),
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const useAssertion = (
|
|
145
|
+
isSuffices: boolean,
|
|
146
|
+
context: Hyp[],
|
|
147
|
+
goal: Expr,
|
|
148
|
+
sequent: Sequent,
|
|
149
|
+
step: Step,
|
|
150
|
+
) => {
|
|
151
|
+
const visibility: Visibility = isSuffices || isAnonymous(step.step) ? "Visible" : "Hidden";
|
|
152
|
+
return {
|
|
153
|
+
context: [
|
|
154
|
+
...context,
|
|
155
|
+
getStepDefinition(step, at(exprifySequent(sequent), step)),
|
|
156
|
+
{
|
|
157
|
+
kind: "Fact",
|
|
158
|
+
expression: at({ kind: "Ix", index: 1 }, step),
|
|
159
|
+
visibility,
|
|
160
|
+
location: step.location,
|
|
161
|
+
},
|
|
162
|
+
] satisfies Hyp[],
|
|
163
|
+
active: shiftExpr(2, goal),
|
|
164
|
+
};
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const setDefinition = (visibility: Visibility, context: Hyp[], def: UseDef): Hyp[] => {
|
|
168
|
+
if (def.kind !== "Dx") throw new TlaProofError(`Unknown definition ${def.name}`);
|
|
169
|
+
const index = context.length - def.index;
|
|
170
|
+
const hyp = context[index];
|
|
171
|
+
if (!hyp) return context;
|
|
172
|
+
if (hyp.kind !== "Defn") throw new TlaProofError("Indicated point is not a definition");
|
|
173
|
+
return context.map((entry, position) => (position === index ? { ...hyp, visibility } : entry));
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const isDomainMatch = (fact: Expr, domain: Expr) =>
|
|
177
|
+
fact.kind === "Apply" &&
|
|
178
|
+
fact.operator.kind === "Internal" &&
|
|
179
|
+
fact.operator.builtin === "Mem" &&
|
|
180
|
+
fact.args[0]?.kind === "Ix" &&
|
|
181
|
+
fact.args[0].index === 0 &&
|
|
182
|
+
isEqualExpr(domain, fact.args[1]);
|
|
183
|
+
|
|
184
|
+
const setExpression = (
|
|
185
|
+
onWarning: (warning: ObligationWarning) => void,
|
|
186
|
+
visibility: Visibility,
|
|
187
|
+
fact: Expr,
|
|
188
|
+
context: Hyp[],
|
|
189
|
+
): Hyp[] => {
|
|
190
|
+
let isFound = false;
|
|
191
|
+
let current = fact;
|
|
192
|
+
const result = [...context];
|
|
193
|
+
for (let index = context.length - 1; index >= 0; index--) {
|
|
194
|
+
current = shiftExpr(-1, current);
|
|
195
|
+
const hyp = context[index];
|
|
196
|
+
if (hyp.kind === "Fact" && isEqualExpr(hyp.expression, current)) {
|
|
197
|
+
isFound = true;
|
|
198
|
+
result[index] = { ...hyp, visibility };
|
|
199
|
+
} else if (hyp.kind === "Fresh" && hyp.domain && isDomainMatch(current, hyp.domain)) {
|
|
200
|
+
isFound = true;
|
|
201
|
+
result[index] = { ...hyp, domainVisibility: visibility };
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (!isFound) {
|
|
205
|
+
const message = "This expression does not appear in the context and cannot be hidden.";
|
|
206
|
+
onWarning(getErrorNote(fact.location, `${message}\n`));
|
|
207
|
+
throw new TlaProofError(message, { failure: "proof.Gen.set_expr" });
|
|
208
|
+
}
|
|
209
|
+
return result;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// The lines of tlapm's --toolbox and --line options, which select the proofs to generate.
|
|
213
|
+
export interface LineRange {
|
|
214
|
+
start: number;
|
|
215
|
+
end: number;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export interface Generator {
|
|
219
|
+
generate: (sequent: Sequent, proof: Proof) => Proof;
|
|
220
|
+
collect: (proof: Proof) => Obligation[];
|
|
221
|
+
mutate: (
|
|
222
|
+
context: Hyp[],
|
|
223
|
+
mode: "Use" | "Hide",
|
|
224
|
+
usable: Usable,
|
|
225
|
+
location: Locus | undefined,
|
|
226
|
+
) => [Hyp[], Obligation[]];
|
|
227
|
+
simplify: (context: Hyp[], goal: Expr, proof: Proof) => Proof;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// The bounds after a witnessed one, where a ditto takes the witnessed domain.
|
|
231
|
+
const getRemainingBounds = (first: Bound, rest: Bound[]): Bound[] => {
|
|
232
|
+
const shifted = substituteBounds(shift(1), rest)[1];
|
|
233
|
+
return shifted[0].domain.kind === "Ditto" && first.domain.kind === "Domain"
|
|
234
|
+
? [
|
|
235
|
+
{
|
|
236
|
+
...shifted[0],
|
|
237
|
+
domain: { kind: "Domain", expression: shiftExpr(1, first.domain.expression) },
|
|
238
|
+
},
|
|
239
|
+
...shifted.slice(1),
|
|
240
|
+
]
|
|
241
|
+
: shifted;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
// A WITNESS proves each membership and subset it relies on, then the goal from them.
|
|
245
|
+
const getWitnessProof = (step: Step, aux: Expr[]): Proof => {
|
|
246
|
+
if (step.isSuppressed || aux.length === 0) return { kind: "Obvious", ...getProps(step) };
|
|
247
|
+
const steps = aux.map((item, index): Step => {
|
|
248
|
+
const active = shiftExpr(2 * index + 2, item);
|
|
249
|
+
return {
|
|
250
|
+
kind: "Assert",
|
|
251
|
+
sequent: { context: [], active },
|
|
252
|
+
proof: {
|
|
253
|
+
kind: "By",
|
|
254
|
+
usable: { facts: [shiftExpr(3, active)], defs: [] },
|
|
255
|
+
isOnly: false,
|
|
256
|
+
location: item.location,
|
|
257
|
+
},
|
|
258
|
+
location: item.location,
|
|
259
|
+
};
|
|
260
|
+
});
|
|
261
|
+
const facts = [
|
|
262
|
+
at({ kind: "Ix", index: 2 * aux.length + 2 }, step),
|
|
263
|
+
...aux.map((_item, index) => at({ kind: "Ix", index: 2 * (aux.length - index) }, step)),
|
|
264
|
+
];
|
|
265
|
+
const qedProof: Proof = {
|
|
266
|
+
kind: "By",
|
|
267
|
+
usable: { facts, defs: [] },
|
|
268
|
+
isOnly: true,
|
|
269
|
+
...getProps(step),
|
|
270
|
+
};
|
|
271
|
+
return {
|
|
272
|
+
kind: "Steps",
|
|
273
|
+
steps,
|
|
274
|
+
qed: { proof: qedProof, location: qedProof.location },
|
|
275
|
+
...getProps(step),
|
|
276
|
+
};
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const obligate = (
|
|
280
|
+
sequent: Sequent,
|
|
281
|
+
from: { location?: Locus },
|
|
282
|
+
kind: ObligationKind,
|
|
283
|
+
): Obligation => ({ sequent, location: from.location, kind });
|
|
284
|
+
|
|
285
|
+
export interface ProofSummary {
|
|
286
|
+
total: number;
|
|
287
|
+
absent: Locus[];
|
|
288
|
+
omitted: Locus[];
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export const EMPTY_SUMMARY: ProofSummary = { total: 0, absent: [], omitted: [] };
|
|
292
|
+
|
|
293
|
+
export const joinSummaries = (left: ProofSummary, right: ProofSummary): ProofSummary => ({
|
|
294
|
+
total: left.total + right.total,
|
|
295
|
+
absent: [...left.absent, ...right.absent],
|
|
296
|
+
omitted: [...left.omitted, ...right.omitted],
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const getStepSummary = (step: Step): ProofSummary => {
|
|
300
|
+
switch (step.kind) {
|
|
301
|
+
case "Use":
|
|
302
|
+
return { ...EMPTY_SUMMARY, total: step.obligations?.length ?? 0 };
|
|
303
|
+
case "Assert":
|
|
304
|
+
case "Suffices":
|
|
305
|
+
case "Pcase":
|
|
306
|
+
case "Pick":
|
|
307
|
+
return getProofSummary(step.proof);
|
|
308
|
+
default:
|
|
309
|
+
return EMPTY_SUMMARY;
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
export const getProofSummary = (proof: Proof): ProofSummary => {
|
|
314
|
+
if (proof.kind === "Steps") {
|
|
315
|
+
return [...proof.steps.map(getStepSummary), getProofSummary(proof.qed.proof)].reduce(
|
|
316
|
+
joinSummaries,
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
if (proof.kind !== "Omitted" || !proof.obligations || !proof.location)
|
|
320
|
+
return { ...EMPTY_SUMMARY, total: proof.obligations?.length ?? 0 };
|
|
321
|
+
return {
|
|
322
|
+
total: proof.obligations.length,
|
|
323
|
+
absent: proof.omission === "Implicit" ? [proof.location] : [],
|
|
324
|
+
omitted: proof.omission === "Explicit" ? [proof.location] : [],
|
|
325
|
+
};
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const replaceAt = (expression: Expr, replacement: Expr): Expr =>
|
|
329
|
+
expression.kind === "At" && !expression.isExcept
|
|
330
|
+
? at(stripProps(replacement), expression)
|
|
331
|
+
: mapChildren(expression, (inner) => replaceAt(inner, replacement));
|
|
332
|
+
|
|
333
|
+
export const createGenerator = (
|
|
334
|
+
counter: Counter,
|
|
335
|
+
inputFiles: Set<string>,
|
|
336
|
+
lines: LineRange,
|
|
337
|
+
onWarning: (warning: ObligationWarning) => void,
|
|
338
|
+
): Generator => {
|
|
339
|
+
const generate = (sequent: Sequent, proof: Proof): Proof => {
|
|
340
|
+
const location = proof.location;
|
|
341
|
+
if (
|
|
342
|
+
!location ||
|
|
343
|
+
!inputFiles.has(location.file) ||
|
|
344
|
+
location.endLine < lines.start ||
|
|
345
|
+
location.startLine > lines.end
|
|
346
|
+
)
|
|
347
|
+
return proof;
|
|
348
|
+
switch (proof.kind) {
|
|
349
|
+
case "Obvious":
|
|
350
|
+
return { ...proof, obligations: [obligate(sequent, proof, { kind: "Main" })] };
|
|
351
|
+
case "Omitted":
|
|
352
|
+
return {
|
|
353
|
+
...proof,
|
|
354
|
+
obligations: [obligate(sequent, proof, { kind: "Omitted", omission: proof.omission })],
|
|
355
|
+
};
|
|
356
|
+
case "Error":
|
|
357
|
+
return {
|
|
358
|
+
...proof,
|
|
359
|
+
obligations: [obligate(sequent, proof, { kind: "Error", message: proof.message })],
|
|
360
|
+
};
|
|
361
|
+
case "By":
|
|
362
|
+
throw new TlaProofError("Proof.Gen.generate");
|
|
363
|
+
case "Steps": {
|
|
364
|
+
const [inner, steps] = mapAccumulate(sequent, proof.steps, generateStep);
|
|
365
|
+
const qedProof = generate(inner, proof.qed.proof);
|
|
366
|
+
return { ...proof, steps, qed: { ...proof.qed, proof: qedProof } };
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
const generateStep = (sequent: Sequent, step: Step): [Sequent, Step] => {
|
|
372
|
+
switch (step.kind) {
|
|
373
|
+
case "Forget": {
|
|
374
|
+
const count = sequent.context.length;
|
|
375
|
+
const context = sequent.context.map((hyp, index): Hyp =>
|
|
376
|
+
hyp.kind === "Fact" && hyp.visibility === "Visible" && step.count + index < count
|
|
377
|
+
? { ...hyp, visibility: "Hidden" }
|
|
378
|
+
: hyp,
|
|
379
|
+
);
|
|
380
|
+
return [{ ...sequent, context }, step];
|
|
381
|
+
}
|
|
382
|
+
case "Use": {
|
|
383
|
+
const usable = step.usable;
|
|
384
|
+
if (usable.defs.length === 0 && usable.facts.length === 1) {
|
|
385
|
+
const fact = usable.facts[0];
|
|
386
|
+
const obligation = obligate({ ...sequent, active: fact }, step, { kind: "Support" });
|
|
387
|
+
return [
|
|
388
|
+
{
|
|
389
|
+
context: [
|
|
390
|
+
...sequent.context,
|
|
391
|
+
{ kind: "Fact", expression: fact, visibility: "Visible", location: fact.location },
|
|
392
|
+
],
|
|
393
|
+
active: shiftExpr(1, sequent.active),
|
|
394
|
+
},
|
|
395
|
+
{ ...step, obligations: [obligation] },
|
|
396
|
+
];
|
|
397
|
+
}
|
|
398
|
+
if (usable.facts.length > 0)
|
|
399
|
+
throw new TlaProofError("Proof.Gen.gen_step: USE with facts and definitions");
|
|
400
|
+
const context = usable.defs.reduce(
|
|
401
|
+
(current, def) => setDefinition("Visible", current, def),
|
|
402
|
+
sequent.context,
|
|
403
|
+
);
|
|
404
|
+
return [{ ...sequent, context }, step];
|
|
405
|
+
}
|
|
406
|
+
case "Hide": {
|
|
407
|
+
const hidden = step.usable.defs.reduce(
|
|
408
|
+
(current, def) => setDefinition("Hidden", current, def),
|
|
409
|
+
sequent.context,
|
|
410
|
+
);
|
|
411
|
+
const context = step.usable.facts.reduceRight(
|
|
412
|
+
(current, fact) => setExpression(onWarning, "Hidden", fact, current),
|
|
413
|
+
hidden,
|
|
414
|
+
);
|
|
415
|
+
return [{ ...sequent, context }, step];
|
|
416
|
+
}
|
|
417
|
+
case "Define":
|
|
418
|
+
return [
|
|
419
|
+
{
|
|
420
|
+
context: [
|
|
421
|
+
...sequent.context,
|
|
422
|
+
...step.definitions.map((definition): Hyp => ({
|
|
423
|
+
kind: "Defn",
|
|
424
|
+
definition,
|
|
425
|
+
whereDefined: "User",
|
|
426
|
+
visibility: "Visible",
|
|
427
|
+
isLocal: true,
|
|
428
|
+
location: definition.location,
|
|
429
|
+
})),
|
|
430
|
+
],
|
|
431
|
+
active: shiftExpr(step.definitions.length, sequent.active),
|
|
432
|
+
},
|
|
433
|
+
step,
|
|
434
|
+
];
|
|
435
|
+
case "Assert": {
|
|
436
|
+
const proof = generate(proveAssertion(sequent.context, step.sequent, step), step.proof);
|
|
437
|
+
const generated: Step = { ...step, proof };
|
|
438
|
+
return [
|
|
439
|
+
useAssertion(false, sequent.context, sequent.active, step.sequent, generated),
|
|
440
|
+
generated,
|
|
441
|
+
];
|
|
442
|
+
}
|
|
443
|
+
case "Suffices": {
|
|
444
|
+
const proof = generate(
|
|
445
|
+
useAssertion(true, sequent.context, sequent.active, step.sequent, step),
|
|
446
|
+
step.proof,
|
|
447
|
+
);
|
|
448
|
+
const generated: Step = { ...step, proof };
|
|
449
|
+
return [proveAssertion(sequent.context, step.sequent, generated), generated];
|
|
450
|
+
}
|
|
451
|
+
default:
|
|
452
|
+
throw new TlaProofError("Proof.Gen.gen_step");
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
const mutateOne = (
|
|
457
|
+
context: Hyp[],
|
|
458
|
+
mode: "Use" | "Hide",
|
|
459
|
+
usable: Usable,
|
|
460
|
+
location: Locus | undefined,
|
|
461
|
+
): [Hyp[], Obligation[]] => {
|
|
462
|
+
const step: StepNumber = { kind: "Unnamed", level: 0, id: 0 };
|
|
463
|
+
const [sequent, generated] = generateStep(
|
|
464
|
+
{ context, active: { ...AT_NOWHERE, location } },
|
|
465
|
+
mode === "Use"
|
|
466
|
+
? { kind: "Use", usable, isOnly: false, location, step }
|
|
467
|
+
: { kind: "Hide", usable, location, step },
|
|
468
|
+
);
|
|
469
|
+
return [sequent.context, generated.obligations ?? []];
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
// The definitions go first, then each fact on its own, shifting the facts after it.
|
|
473
|
+
const mutate = (
|
|
474
|
+
context: Hyp[],
|
|
475
|
+
mode: "Use" | "Hide",
|
|
476
|
+
usable: Usable,
|
|
477
|
+
location: Locus | undefined,
|
|
478
|
+
): [Hyp[], Obligation[]] => {
|
|
479
|
+
if (usable.defs.length === 0 && usable.facts.length === 0) return [context, []];
|
|
480
|
+
const isDefs = usable.defs.length > 0;
|
|
481
|
+
const [fact, ...facts] = usable.facts;
|
|
482
|
+
const [next, first] = mutateOne(
|
|
483
|
+
context,
|
|
484
|
+
mode,
|
|
485
|
+
isDefs ? { defs: usable.defs, facts: [] } : { defs: [], facts: [fact] },
|
|
486
|
+
location,
|
|
487
|
+
);
|
|
488
|
+
const rest = isDefs ? usable.facts : facts.map((inner) => shiftExpr(1, inner));
|
|
489
|
+
const [last, others] = mutate(next, mode, { defs: [], facts: rest }, location);
|
|
490
|
+
return [last, [...first, ...others]];
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
// collect: a QED's obligations come before its sibling steps', and a step's own before its proof's.
|
|
494
|
+
const collect = (proof: Proof): Obligation[] => {
|
|
495
|
+
const obligations: Obligation[] = [];
|
|
496
|
+
const visitProof = (item: Proof) => {
|
|
497
|
+
if (item.kind === "Steps") {
|
|
498
|
+
visitProof(item.qed.proof);
|
|
499
|
+
item.steps.forEach(visitStep);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
if (item.obligations && !item.isSuppressed) obligations.push(...item.obligations);
|
|
503
|
+
};
|
|
504
|
+
const visitStep = (step: Step) => {
|
|
505
|
+
if (step.kind === "Use" && step.obligations && !step.isSuppressed)
|
|
506
|
+
obligations.push(...step.obligations);
|
|
507
|
+
if (
|
|
508
|
+
step.kind === "Assert" ||
|
|
509
|
+
step.kind === "Suffices" ||
|
|
510
|
+
step.kind === "Pcase" ||
|
|
511
|
+
step.kind === "Pick"
|
|
512
|
+
) {
|
|
513
|
+
visitProof(step.proof);
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
visitProof(proof);
|
|
517
|
+
return obligations;
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
const getAnonymous = (level: number): StepNumber => ({
|
|
521
|
+
kind: "Named",
|
|
522
|
+
level,
|
|
523
|
+
label: `_a${getUnique(counter)}`,
|
|
524
|
+
isAnonymous: true,
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
const renumberNumber = (level: number, step: StepNumber | undefined): StepNumber =>
|
|
528
|
+
step?.kind === "Named" ? { ...step, level } : getAnonymous(level);
|
|
529
|
+
|
|
530
|
+
const renumber = (level: number, proof: Proof): Proof => {
|
|
531
|
+
const renumbered: Proof = { ...proof, step: renumberNumber(level, proof.step) };
|
|
532
|
+
if (renumbered.kind !== "Steps") return renumbered;
|
|
533
|
+
const steps = renumbered.steps.map((step) => renumberStep(level, step));
|
|
534
|
+
return {
|
|
535
|
+
...renumbered,
|
|
536
|
+
steps,
|
|
537
|
+
qed: { ...renumbered.qed, proof: renumber(level + 1, renumbered.qed.proof) },
|
|
538
|
+
};
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
const renumberStep = (level: number, step: Step): Step => {
|
|
542
|
+
const renumbered: Step = { ...step, step: renumberNumber(level, step.step) };
|
|
543
|
+
switch (renumbered.kind) {
|
|
544
|
+
case "Assert":
|
|
545
|
+
case "Suffices":
|
|
546
|
+
case "Pcase":
|
|
547
|
+
case "Pick":
|
|
548
|
+
return { ...renumbered, proof: renumber(level + 1, renumbered.proof) };
|
|
549
|
+
default:
|
|
550
|
+
return renumbered;
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
const exposeConnective = (context: Hyp[], goal: Expr): Expr => {
|
|
555
|
+
const head = goal.kind === "Apply" ? goal.operator : goal;
|
|
556
|
+
if (head.kind === "Ix") {
|
|
557
|
+
const hyp = context[context.length - head.index];
|
|
558
|
+
if (!hyp) throw new TlaProofError("expose_connective");
|
|
559
|
+
if (hyp.kind !== "Defn" || hyp.definition.kind !== "Operator") return goal;
|
|
560
|
+
const body = shiftExpr(head.index, hyp.definition.body);
|
|
561
|
+
if (goal.kind !== "Apply") return exposeConnective(context, body);
|
|
562
|
+
const operator: Expr = { ...body, location: goal.location };
|
|
563
|
+
return exposeConnective(context, at(normalizeApply(operator, goal.args), operator));
|
|
564
|
+
}
|
|
565
|
+
if (
|
|
566
|
+
goal.kind === "Apply" &&
|
|
567
|
+
goal.operator.kind === "Internal" &&
|
|
568
|
+
goal.operator.builtin === "Prime"
|
|
569
|
+
) {
|
|
570
|
+
const inner = exposeConnective(context, goal.args[0]);
|
|
571
|
+
const prime = (expression: Expr) => applyBuiltin("Prime", [expression], expression);
|
|
572
|
+
if (inner.kind === "Quant") {
|
|
573
|
+
return at(
|
|
574
|
+
{
|
|
575
|
+
kind: "Quant",
|
|
576
|
+
quantifier: inner.quantifier,
|
|
577
|
+
bounds: inner.bounds.map((bound) =>
|
|
578
|
+
bound.domain.kind === "Domain"
|
|
579
|
+
? {
|
|
580
|
+
...bound,
|
|
581
|
+
domain: { kind: "Domain", expression: prime(bound.domain.expression) },
|
|
582
|
+
}
|
|
583
|
+
: bound,
|
|
584
|
+
),
|
|
585
|
+
body: prime(inner.body),
|
|
586
|
+
},
|
|
587
|
+
goal,
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
if (isBinary(inner, "Implies") || isBinary(inner, "Mem")) {
|
|
591
|
+
return at(
|
|
592
|
+
{
|
|
593
|
+
kind: "Apply",
|
|
594
|
+
operator: inner.operator,
|
|
595
|
+
args: [prime(inner.args[0]), prime(inner.args[1])],
|
|
596
|
+
},
|
|
597
|
+
goal,
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
return prime(inner);
|
|
601
|
+
}
|
|
602
|
+
if (goal.kind === "Parens") return exposeConnective(context, goal.expression);
|
|
603
|
+
return goal;
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
const getRightOperand = (step: Step): Expr | undefined => {
|
|
607
|
+
if (step.kind !== "Assert") return undefined;
|
|
608
|
+
const active = step.sequent.active;
|
|
609
|
+
return active.kind === "Apply" && active.args.length === 2 ? active.args[1] : undefined;
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
// at_expand: @ in a step stands for the right side of the previous step's assertion.
|
|
613
|
+
const expandAt = (proof: Proof): Proof => {
|
|
614
|
+
if (proof.kind !== "Steps") return proof;
|
|
615
|
+
const steps: Step[] = [];
|
|
616
|
+
proof.steps.forEach((step) => {
|
|
617
|
+
const previous = steps.at(-1);
|
|
618
|
+
const right = previous && getRightOperand(previous);
|
|
619
|
+
if (step.kind !== "Assert" || !right) {
|
|
620
|
+
steps.push(step);
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
const active = replaceAt(step.sequent.active, shiftExpr(2, right));
|
|
624
|
+
steps.push({ ...step, sequent: { ...step.sequent, active } });
|
|
625
|
+
});
|
|
626
|
+
return { ...proof, steps };
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
const simplify = (context: Hyp[], goal: Expr, original: Proof): Proof => {
|
|
630
|
+
const proof = expandAt(original);
|
|
631
|
+
switch (proof.kind) {
|
|
632
|
+
case "Obvious":
|
|
633
|
+
case "Omitted":
|
|
634
|
+
case "Error":
|
|
635
|
+
return proof;
|
|
636
|
+
case "By": {
|
|
637
|
+
const usable = proof.usable;
|
|
638
|
+
const useDefs: Step[] =
|
|
639
|
+
usable.defs.length === 0
|
|
640
|
+
? []
|
|
641
|
+
: [
|
|
642
|
+
{
|
|
643
|
+
kind: "Use",
|
|
644
|
+
usable: { facts: [], defs: usable.defs },
|
|
645
|
+
isOnly: false,
|
|
646
|
+
location: proof.location,
|
|
647
|
+
},
|
|
648
|
+
];
|
|
649
|
+
const useFacts = usable.facts.map((fact, index): Step => ({
|
|
650
|
+
kind: "Use",
|
|
651
|
+
usable: { facts: [shiftExpr(index, fact)], defs: [] },
|
|
652
|
+
isOnly: false,
|
|
653
|
+
location: fact.location,
|
|
654
|
+
isSuppressed: proof.isSuppressed,
|
|
655
|
+
}));
|
|
656
|
+
const forget: Step[] = proof.isOnly
|
|
657
|
+
? [{ kind: "Forget", count: usable.facts.length, location: proof.location }]
|
|
658
|
+
: [];
|
|
659
|
+
const level = getLevel(proof.step);
|
|
660
|
+
const steps = [...useDefs, ...useFacts, ...forget].map((step): Step => ({
|
|
661
|
+
...step,
|
|
662
|
+
step: getAnonymous(level),
|
|
663
|
+
}));
|
|
664
|
+
const qedProof: Proof = {
|
|
665
|
+
kind: "Obvious",
|
|
666
|
+
location: proof.location,
|
|
667
|
+
step: getAnonymous(level),
|
|
668
|
+
isSuppressed: proof.isSuppressed,
|
|
669
|
+
};
|
|
670
|
+
return {
|
|
671
|
+
kind: "Steps",
|
|
672
|
+
steps,
|
|
673
|
+
qed: { proof: qedProof, location: qedProof.location, step: qedProof.step },
|
|
674
|
+
location: proof.location,
|
|
675
|
+
step: proof.step,
|
|
676
|
+
isSuppressed: proof.isSuppressed,
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
case "Steps": {
|
|
680
|
+
let currentContext = context;
|
|
681
|
+
let currentGoal = goal;
|
|
682
|
+
const steps = proof.steps.flatMap((step) => {
|
|
683
|
+
const [nextContext, nextGoal, simplified] = simplifyStep(
|
|
684
|
+
currentContext,
|
|
685
|
+
currentGoal,
|
|
686
|
+
step,
|
|
687
|
+
);
|
|
688
|
+
currentContext = nextContext;
|
|
689
|
+
currentGoal = nextGoal;
|
|
690
|
+
return simplified;
|
|
691
|
+
});
|
|
692
|
+
const qedProof = simplify(currentContext, currentGoal, proof.qed.proof);
|
|
693
|
+
return { ...proof, steps, qed: { ...proof.qed, proof: qedProof } };
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
|
|
698
|
+
const simplifyStep = (context: Hyp[], goal: Expr, step: Step): [Hyp[], Expr, Step[]] => {
|
|
699
|
+
const level = getLevel(step.step);
|
|
700
|
+
switch (step.kind) {
|
|
701
|
+
case "Forget":
|
|
702
|
+
return [context, goal, [step]];
|
|
703
|
+
case "Assert": {
|
|
704
|
+
const inner = proveAssertion(context, step.sequent, step);
|
|
705
|
+
const simplified: Step = {
|
|
706
|
+
...step,
|
|
707
|
+
proof: simplify(inner.context, inner.active, step.proof),
|
|
708
|
+
};
|
|
709
|
+
const next = useAssertion(false, context, goal, step.sequent, simplified);
|
|
710
|
+
return [next.context, next.active, [simplified]];
|
|
711
|
+
}
|
|
712
|
+
case "Suffices": {
|
|
713
|
+
const inner = useAssertion(true, context, goal, step.sequent, step);
|
|
714
|
+
const simplified: Step = {
|
|
715
|
+
...step,
|
|
716
|
+
proof: simplify(inner.context, inner.active, step.proof),
|
|
717
|
+
};
|
|
718
|
+
const next = proveAssertion(context, step.sequent, simplified);
|
|
719
|
+
return [next.context, next.active, [simplified]];
|
|
720
|
+
}
|
|
721
|
+
case "Use": {
|
|
722
|
+
const byProof: Proof = {
|
|
723
|
+
kind: "By",
|
|
724
|
+
usable: step.usable,
|
|
725
|
+
isOnly: step.isOnly,
|
|
726
|
+
location: step.location,
|
|
727
|
+
step: step.step,
|
|
728
|
+
isSuppressed: step.isSuppressed,
|
|
729
|
+
};
|
|
730
|
+
const simplified = simplify(context, goal, byProof);
|
|
731
|
+
if (simplified.kind !== "Steps")
|
|
732
|
+
throw new TlaProofError("simplifying BY does not produce a non-leaf proof!");
|
|
733
|
+
const assumed = step.usable.facts.map((): Hyp => ({
|
|
734
|
+
kind: "Fact",
|
|
735
|
+
expression: AT_NOWHERE,
|
|
736
|
+
visibility: "Visible",
|
|
737
|
+
location: step.location,
|
|
738
|
+
}));
|
|
739
|
+
return [
|
|
740
|
+
[...context, ...assumed],
|
|
741
|
+
shiftExpr(step.usable.facts.length, goal),
|
|
742
|
+
simplified.steps,
|
|
743
|
+
];
|
|
744
|
+
}
|
|
745
|
+
case "Hide": {
|
|
746
|
+
const hidden = step.usable.defs.reduce((current, def) => {
|
|
747
|
+
if (def.kind !== "Dx") throw new TlaProofError("set_vis");
|
|
748
|
+
const index = current.length - def.index;
|
|
749
|
+
const hyp = current[index];
|
|
750
|
+
if (hyp?.kind !== "Defn") {
|
|
751
|
+
const name = hyp ? (hyp.kind === "Fact" ? "??? FACT ???" : hyp.name) : "";
|
|
752
|
+
throw new TlaProofError(
|
|
753
|
+
`USE DEF ${name} invalid because ${name} is not a defined operator`,
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
return current.map((entry, position) =>
|
|
757
|
+
position === index
|
|
758
|
+
? { ...hyp, visibility: "Hidden" as const, location: step.location }
|
|
759
|
+
: entry,
|
|
760
|
+
);
|
|
761
|
+
}, context);
|
|
762
|
+
return [hidden, goal, [step]];
|
|
763
|
+
}
|
|
764
|
+
case "Define":
|
|
765
|
+
return [
|
|
766
|
+
[
|
|
767
|
+
...context,
|
|
768
|
+
...step.definitions.map((definition): Hyp => ({
|
|
769
|
+
kind: "Defn",
|
|
770
|
+
definition,
|
|
771
|
+
whereDefined: "Proof",
|
|
772
|
+
visibility: "Visible",
|
|
773
|
+
isLocal: true,
|
|
774
|
+
location: step.location,
|
|
775
|
+
})),
|
|
776
|
+
],
|
|
777
|
+
shiftExpr(step.definitions.length, goal),
|
|
778
|
+
[step],
|
|
779
|
+
];
|
|
780
|
+
case "Have": {
|
|
781
|
+
const connective = exposeConnective(context, goal);
|
|
782
|
+
const fact: Hyp = {
|
|
783
|
+
kind: "Fact",
|
|
784
|
+
expression: step.expression,
|
|
785
|
+
visibility: "Visible",
|
|
786
|
+
location: step.location,
|
|
787
|
+
};
|
|
788
|
+
const isImplication = isBinary(connective, "Implies");
|
|
789
|
+
const sequent: Sequent = {
|
|
790
|
+
context: [fact],
|
|
791
|
+
active: isImplication ? shiftExpr(1, connective.args[1]) : connective,
|
|
792
|
+
};
|
|
793
|
+
const proof: Proof = isImplication
|
|
794
|
+
? {
|
|
795
|
+
kind: "By",
|
|
796
|
+
usable: { facts: [at({ kind: "Ix", index: 2 }, step)], defs: [] },
|
|
797
|
+
isOnly: true,
|
|
798
|
+
...getProps(step),
|
|
799
|
+
}
|
|
800
|
+
: {
|
|
801
|
+
kind: "Error",
|
|
802
|
+
message: getMessage(
|
|
803
|
+
"@.@[<v2>@[%s@ %s@]@,%a@]@.",
|
|
804
|
+
"Error: this HAVE step is applied to the following goal,",
|
|
805
|
+
"which is not an implication:",
|
|
806
|
+
printExpression(context, goal),
|
|
807
|
+
),
|
|
808
|
+
...getProps(step),
|
|
809
|
+
};
|
|
810
|
+
const suffices = renumberStep(level, {
|
|
811
|
+
kind: "Suffices",
|
|
812
|
+
sequent,
|
|
813
|
+
proof,
|
|
814
|
+
location: step.location,
|
|
815
|
+
step: step.step,
|
|
816
|
+
});
|
|
817
|
+
return simplifyStep(context, goal, suffices);
|
|
818
|
+
}
|
|
819
|
+
case "Take":
|
|
820
|
+
return simplifyStep(
|
|
821
|
+
context,
|
|
822
|
+
goal,
|
|
823
|
+
renumberStep(level, getTakeSuffices(context, goal, step)),
|
|
824
|
+
);
|
|
825
|
+
case "Witness":
|
|
826
|
+
return simplifyStep(
|
|
827
|
+
context,
|
|
828
|
+
goal,
|
|
829
|
+
renumberStep(level, getWitnessSuffices(context, goal, step)),
|
|
830
|
+
);
|
|
831
|
+
case "Pcase": {
|
|
832
|
+
const assertion: Step = {
|
|
833
|
+
kind: "Assert",
|
|
834
|
+
sequent: {
|
|
835
|
+
context: [
|
|
836
|
+
{
|
|
837
|
+
kind: "Fact",
|
|
838
|
+
expression: step.expression,
|
|
839
|
+
visibility: "Visible",
|
|
840
|
+
location: step.location,
|
|
841
|
+
},
|
|
842
|
+
],
|
|
843
|
+
active: shiftExpr(1, goal),
|
|
844
|
+
},
|
|
845
|
+
proof: step.proof,
|
|
846
|
+
...getProps(step),
|
|
847
|
+
};
|
|
848
|
+
return simplifyStep(context, goal, renumberStep(level, assertion));
|
|
849
|
+
}
|
|
850
|
+
case "Pick": {
|
|
851
|
+
const existential: Expr = at(
|
|
852
|
+
{ kind: "Quant", quantifier: "Exists", bounds: step.bounds, body: step.expression },
|
|
853
|
+
step,
|
|
854
|
+
);
|
|
855
|
+
const salted = getSalted(step.step, "ex");
|
|
856
|
+
const [assertContext, assertGoal, asserted] = simplifyStep(context, goal, {
|
|
857
|
+
kind: "Assert",
|
|
858
|
+
sequent: { context: [], active: existential },
|
|
859
|
+
proof: step.proof,
|
|
860
|
+
...getProps(step),
|
|
861
|
+
step: salted,
|
|
862
|
+
});
|
|
863
|
+
const shifted = shiftExpr(2, existential);
|
|
864
|
+
if (shifted.kind !== "Quant") throw new TlaProofError("Proof.Simplify.simplify_step/PICK");
|
|
865
|
+
const hyps = [
|
|
866
|
+
...getBoundHyps(shifted.bounds),
|
|
867
|
+
{
|
|
868
|
+
kind: "Fact",
|
|
869
|
+
expression: shifted.body,
|
|
870
|
+
visibility: "Visible",
|
|
871
|
+
location: shifted.body.location,
|
|
872
|
+
} satisfies Hyp,
|
|
873
|
+
];
|
|
874
|
+
const [nextContext, nextGoal, sufficed] = simplifyStep(assertContext, assertGoal, {
|
|
875
|
+
kind: "Suffices",
|
|
876
|
+
sequent: { context: hyps, active: shiftExpr(hyps.length, assertGoal) },
|
|
877
|
+
proof: { kind: "Omitted", omission: "Elsewhere", ...getProps(step) },
|
|
878
|
+
...getProps(step),
|
|
879
|
+
});
|
|
880
|
+
return [nextContext, nextGoal, [...asserted, ...sufficed]];
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
|
|
885
|
+
const getSalted = (step: StepNumber | undefined, salt: string): StepNumber => ({
|
|
886
|
+
kind: "Named",
|
|
887
|
+
level: getLevel(step),
|
|
888
|
+
label: `${step?.kind === "Named" ? step.label : "_a"}${salt}${getUnique(counter)}`,
|
|
889
|
+
isAnonymous: false,
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
const getTakeSuffices = (context: Hyp[], goal: Expr, step: Step & { kind: "Take" }): Step => {
|
|
893
|
+
const bounds = expandDittoes(step.bounds);
|
|
894
|
+
const stripBounds = (
|
|
895
|
+
amount: number,
|
|
896
|
+
aux: Expr[],
|
|
897
|
+
current: Expr,
|
|
898
|
+
rest: Bound[],
|
|
899
|
+
): { aux: Expr[]; goal: Expr } | string => {
|
|
900
|
+
const bound = rest[0];
|
|
901
|
+
if (!bound) return { aux, goal: current };
|
|
902
|
+
const exposed = exposeConnective(context, current);
|
|
903
|
+
if (
|
|
904
|
+
exposed.kind !== "Quant" ||
|
|
905
|
+
exposed.quantifier !== "Forall" ||
|
|
906
|
+
exposed.bounds.length === 0
|
|
907
|
+
) {
|
|
908
|
+
return getMessage(
|
|
909
|
+
"@.@[<v2>@[<b0>%s@ %s@]@,%a@]@.",
|
|
910
|
+
"Error: this TAKE step is applied to the following goal,",
|
|
911
|
+
"which is not a universal formula:",
|
|
912
|
+
printExpression(context, current),
|
|
913
|
+
);
|
|
914
|
+
}
|
|
915
|
+
const goalBound = exposed.bounds[0];
|
|
916
|
+
const goalBounds = expandDittoes(exposed.bounds).slice(1);
|
|
917
|
+
const domain = bound.domain;
|
|
918
|
+
const goalDomain = goalBound.domain;
|
|
919
|
+
if (domain.kind === "NoDomain" && goalDomain.kind === "Domain") {
|
|
920
|
+
return getMessage(
|
|
921
|
+
"@.@[<b0>%s (%s)@ %s (%s \\in %a)@]@.",
|
|
922
|
+
"Error: the TAKE argument",
|
|
923
|
+
bound.name,
|
|
924
|
+
"does not match the pattern",
|
|
925
|
+
goalBound.name,
|
|
926
|
+
printExpression(context, goalDomain.expression),
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
if (domain.kind === "Domain" && goalDomain.kind === "NoDomain") {
|
|
930
|
+
return getMessage(
|
|
931
|
+
"@.@[<b0>%s (%s \\in %a)@ %s (%s)@]@.",
|
|
932
|
+
"Error: the TAKE argument",
|
|
933
|
+
bound.name,
|
|
934
|
+
printExpression(context, domain.expression),
|
|
935
|
+
"does not match the pattern",
|
|
936
|
+
goalBound.name,
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
const nextAux =
|
|
940
|
+
domain.kind === "Domain" &&
|
|
941
|
+
goalDomain.kind === "Domain" &&
|
|
942
|
+
!isEqualExpr(domain.expression, goalDomain.expression)
|
|
943
|
+
? [
|
|
944
|
+
{
|
|
945
|
+
kind: "Apply",
|
|
946
|
+
operator: { kind: "Internal", builtin: "Subseteq" },
|
|
947
|
+
args: [goalDomain.expression, domain.expression],
|
|
948
|
+
} satisfies Expr,
|
|
949
|
+
...aux,
|
|
950
|
+
]
|
|
951
|
+
: aux;
|
|
952
|
+
if (goalBounds.length === 0) return stripBounds(1, nextAux, exposed.body, rest.slice(1));
|
|
953
|
+
const shiftedBounds =
|
|
954
|
+
rest.length === 1
|
|
955
|
+
? goalBounds.map((item) => substituteBounds(shift(amount), [item])[1][0])
|
|
956
|
+
: goalBounds;
|
|
957
|
+
return stripBounds(
|
|
958
|
+
amount + 1,
|
|
959
|
+
nextAux,
|
|
960
|
+
at(
|
|
961
|
+
{ kind: "Quant", quantifier: "Forall", bounds: shiftedBounds, body: exposed.body },
|
|
962
|
+
exposed,
|
|
963
|
+
),
|
|
964
|
+
rest.slice(1),
|
|
965
|
+
);
|
|
966
|
+
};
|
|
967
|
+
const stripped = stripBounds(1, [], goal, bounds);
|
|
968
|
+
if (typeof stripped === "string") {
|
|
969
|
+
return {
|
|
970
|
+
kind: "Suffices",
|
|
971
|
+
sequent: {
|
|
972
|
+
context: [
|
|
973
|
+
{
|
|
974
|
+
kind: "Fact",
|
|
975
|
+
expression: getTrue(goal),
|
|
976
|
+
visibility: "Visible",
|
|
977
|
+
location: step.location,
|
|
978
|
+
},
|
|
979
|
+
],
|
|
980
|
+
active: goal,
|
|
981
|
+
},
|
|
982
|
+
proof: { kind: "Error", message: stripped, ...getProps(step) },
|
|
983
|
+
...getProps(step),
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
const unique = stripped.aux.filter(
|
|
987
|
+
(item, index) => stripped.aux.findIndex((other) => isEqualExpr(item, other)) === index,
|
|
988
|
+
);
|
|
989
|
+
const facts = [at({ kind: "Ix", index: 2 }, step), ...unique.map((item) => shiftExpr(2, item))];
|
|
990
|
+
return {
|
|
991
|
+
kind: "Suffices",
|
|
992
|
+
sequent: { context: getBoundHyps(bounds), active: stripped.goal },
|
|
993
|
+
proof: { kind: "By", usable: { facts, defs: [] }, isOnly: true, ...getProps(step) },
|
|
994
|
+
...getProps(step),
|
|
995
|
+
};
|
|
996
|
+
};
|
|
997
|
+
|
|
998
|
+
const getWitnessSuffices = (
|
|
999
|
+
context: Hyp[],
|
|
1000
|
+
goal: Expr,
|
|
1001
|
+
step: Step & { kind: "Witness" },
|
|
1002
|
+
): Step => {
|
|
1003
|
+
const aux: Expr[] = [];
|
|
1004
|
+
let error = "";
|
|
1005
|
+
let current = goal;
|
|
1006
|
+
for (const witness of step.expressions) {
|
|
1007
|
+
const exposed = exposeConnective(context, current);
|
|
1008
|
+
if (
|
|
1009
|
+
exposed.kind !== "Quant" ||
|
|
1010
|
+
exposed.quantifier !== "Exists" ||
|
|
1011
|
+
exposed.bounds.length === 0
|
|
1012
|
+
) {
|
|
1013
|
+
error ||= getMessage(
|
|
1014
|
+
"@.@[<v2>@[<b0>%s@ %s@]@,%a@]@.",
|
|
1015
|
+
"Error: this WITNESS step is applied to the following goal,",
|
|
1016
|
+
"which is not an existential formula:",
|
|
1017
|
+
printExpression(context, exposed),
|
|
1018
|
+
);
|
|
1019
|
+
current = exposed;
|
|
1020
|
+
break;
|
|
1021
|
+
}
|
|
1022
|
+
const [first, ...rest] = exposed.bounds;
|
|
1023
|
+
const next =
|
|
1024
|
+
rest.length === 0
|
|
1025
|
+
? exposed.body
|
|
1026
|
+
: at(
|
|
1027
|
+
{
|
|
1028
|
+
kind: "Quant",
|
|
1029
|
+
quantifier: "Exists",
|
|
1030
|
+
bounds: getRemainingBounds(first, rest),
|
|
1031
|
+
body: exposed.body,
|
|
1032
|
+
},
|
|
1033
|
+
exposed,
|
|
1034
|
+
);
|
|
1035
|
+
let value = witness;
|
|
1036
|
+
if (first.domain.kind === "Domain") {
|
|
1037
|
+
const domain = first.domain.expression;
|
|
1038
|
+
const membership = exposeConnective(context, witness);
|
|
1039
|
+
if (isBinary(membership, "Mem")) {
|
|
1040
|
+
const [term, termDomain] = membership.args;
|
|
1041
|
+
const subset = isEqualExpr(termDomain, domain)
|
|
1042
|
+
? []
|
|
1043
|
+
: [applyBuiltin("Subseteq", [termDomain, domain], termDomain)];
|
|
1044
|
+
aux.push(...subset, membership);
|
|
1045
|
+
value = term;
|
|
1046
|
+
} else {
|
|
1047
|
+
error ||= getMessage(
|
|
1048
|
+
"@.@[<b0>%s (%a)@ %s (%s \\in %a)@]@.",
|
|
1049
|
+
"Error: the WITNESS argument",
|
|
1050
|
+
printExpression(context, membership),
|
|
1051
|
+
"does not match the pattern",
|
|
1052
|
+
first.name,
|
|
1053
|
+
printExpression(context, domain),
|
|
1054
|
+
);
|
|
1055
|
+
value = membership;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
const unprimable = applyBuiltin("Unprimable", [value], value);
|
|
1059
|
+
current = substituteExpr(consSubstitution(unprimable, shift(0)), next);
|
|
1060
|
+
}
|
|
1061
|
+
const proof: Proof =
|
|
1062
|
+
error === ""
|
|
1063
|
+
? getWitnessProof(step, aux)
|
|
1064
|
+
: { kind: "Error", message: error, ...getProps(step) };
|
|
1065
|
+
return {
|
|
1066
|
+
kind: "Suffices",
|
|
1067
|
+
sequent: { context: [], active: current },
|
|
1068
|
+
proof,
|
|
1069
|
+
...getProps(step),
|
|
1070
|
+
};
|
|
1071
|
+
};
|
|
1072
|
+
|
|
1073
|
+
return { generate, collect, mutate, simplify };
|
|
1074
|
+
};
|