tla-ts 0.0.2 → 0.0.3-dev.763cc71
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/parallel-worker.ts +26 -7
- 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,1060 @@
|
|
|
1
|
+
// tlapm's expression IR (E_t) with de Bruijn indices, and its substitutions (E_subst) and
|
|
2
|
+
// alpha-equivalence (E_eq).
|
|
3
|
+
|
|
4
|
+
import { TlaProofError } from "./errors";
|
|
5
|
+
|
|
6
|
+
export interface Locus {
|
|
7
|
+
file: string;
|
|
8
|
+
startLine: number;
|
|
9
|
+
startColumn: number;
|
|
10
|
+
endLine: number;
|
|
11
|
+
endColumn: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ObligationWarning {
|
|
15
|
+
text: string;
|
|
16
|
+
channel: "toolbox" | "toolboxOnly" | "stderr" | "stdout";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const getLocusPosition = (locus: Locus) => {
|
|
20
|
+
const end = locus.endColumn - 1;
|
|
21
|
+
if (locus.startLine !== locus.endLine)
|
|
22
|
+
return `line ${locus.startLine}, character ${locus.startColumn} to line ${locus.endLine}, character ${end}`;
|
|
23
|
+
return locus.startColumn >= end
|
|
24
|
+
? `line ${locus.startLine}, character ${locus.startColumn}`
|
|
25
|
+
: `line ${locus.startLine}, characters ${locus.startColumn}-${end}`;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const getLocusText = (locus: Locus) =>
|
|
29
|
+
`File ${JSON.stringify(locus.file)}, ${getLocusPosition(locus)}`;
|
|
30
|
+
|
|
31
|
+
export const getAtPrefix = (locus: Locus | undefined) =>
|
|
32
|
+
`${locus ? getLocusText(locus) : "<unknown location>"}:\n`;
|
|
33
|
+
|
|
34
|
+
export const getErrorNote = (locus: Locus | undefined, message: string): ObligationWarning => ({
|
|
35
|
+
text: `${getAtPrefix(locus)}Error: ${message}`,
|
|
36
|
+
channel: "stderr",
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export const getLocusPrefix = (locus: Locus | undefined) =>
|
|
40
|
+
locus ? `${getLocusText(locus)} :\n` : "";
|
|
41
|
+
|
|
42
|
+
export type Builtin =
|
|
43
|
+
| "TRUE"
|
|
44
|
+
| "FALSE"
|
|
45
|
+
| "Implies"
|
|
46
|
+
| "Equiv"
|
|
47
|
+
| "Conj"
|
|
48
|
+
| "Disj"
|
|
49
|
+
| "Neg"
|
|
50
|
+
| "Eq"
|
|
51
|
+
| "Neq"
|
|
52
|
+
| "STRING"
|
|
53
|
+
| "BOOLEAN"
|
|
54
|
+
| "SUBSET"
|
|
55
|
+
| "UNION"
|
|
56
|
+
| "DOMAIN"
|
|
57
|
+
| "Subseteq"
|
|
58
|
+
| "Mem"
|
|
59
|
+
| "Notmem"
|
|
60
|
+
| "Setminus"
|
|
61
|
+
| "Cap"
|
|
62
|
+
| "Cup"
|
|
63
|
+
| "Prime"
|
|
64
|
+
| "StrongPrime"
|
|
65
|
+
| "Leadsto"
|
|
66
|
+
| "ENABLED"
|
|
67
|
+
| "UNCHANGED"
|
|
68
|
+
| "Cdot"
|
|
69
|
+
| "Actplus"
|
|
70
|
+
| "BoxTrue"
|
|
71
|
+
| "BoxFalse"
|
|
72
|
+
| "Diamond"
|
|
73
|
+
| "Nat"
|
|
74
|
+
| "Int"
|
|
75
|
+
| "Real"
|
|
76
|
+
| "Plus"
|
|
77
|
+
| "Minus"
|
|
78
|
+
| "Uminus"
|
|
79
|
+
| "Times"
|
|
80
|
+
| "Ratio"
|
|
81
|
+
| "Quotient"
|
|
82
|
+
| "Remainder"
|
|
83
|
+
| "Exp"
|
|
84
|
+
| "Infinity"
|
|
85
|
+
| "Lteq"
|
|
86
|
+
| "Lt"
|
|
87
|
+
| "Gteq"
|
|
88
|
+
| "Gt"
|
|
89
|
+
| "Divides"
|
|
90
|
+
| "Range"
|
|
91
|
+
| "Seq"
|
|
92
|
+
| "Len"
|
|
93
|
+
| "BSeq"
|
|
94
|
+
| "Cat"
|
|
95
|
+
| "Append"
|
|
96
|
+
| "Head"
|
|
97
|
+
| "Tail"
|
|
98
|
+
| "SubSeq"
|
|
99
|
+
| "SelectSeq"
|
|
100
|
+
| "OneArg"
|
|
101
|
+
| "Extend"
|
|
102
|
+
| "Print"
|
|
103
|
+
| "PrintT"
|
|
104
|
+
| "Assert"
|
|
105
|
+
| "JavaTime"
|
|
106
|
+
| "TLCGet"
|
|
107
|
+
| "TLCSet"
|
|
108
|
+
| "Permutations"
|
|
109
|
+
| "SortSeq"
|
|
110
|
+
| "RandomElement"
|
|
111
|
+
| "Any"
|
|
112
|
+
| "ToString"
|
|
113
|
+
| "Unprimable"
|
|
114
|
+
| "Irregular";
|
|
115
|
+
|
|
116
|
+
export type Shape = number;
|
|
117
|
+
|
|
118
|
+
export type Level = "Constant" | "State" | "Action" | "Temporal" | "Unknown";
|
|
119
|
+
|
|
120
|
+
export type BoundDomain =
|
|
121
|
+
| { kind: "NoDomain" }
|
|
122
|
+
| { kind: "Domain"; expression: Expr }
|
|
123
|
+
| { kind: "Ditto" };
|
|
124
|
+
|
|
125
|
+
export interface Bound {
|
|
126
|
+
name: string;
|
|
127
|
+
level: Level;
|
|
128
|
+
domain: BoundDomain;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export type Selector =
|
|
132
|
+
| { kind: "Down" }
|
|
133
|
+
| { kind: "Number"; value: number }
|
|
134
|
+
| { kind: "Left" }
|
|
135
|
+
| { kind: "Right" }
|
|
136
|
+
| { kind: "At" }
|
|
137
|
+
| { kind: "Instance"; args: Expr[] }
|
|
138
|
+
| { kind: "Label"; label: string; args: Expr[] };
|
|
139
|
+
|
|
140
|
+
export type ExceptPoint = { kind: "Dot"; field: string } | { kind: "Apply"; expression: Expr };
|
|
141
|
+
|
|
142
|
+
interface ExceptSpec {
|
|
143
|
+
trail: ExceptPoint[];
|
|
144
|
+
value: Expr;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export type ParenForm =
|
|
148
|
+
| { kind: "Syntax" }
|
|
149
|
+
| { kind: "Nlabel"; label: string; names: string[] }
|
|
150
|
+
| { kind: "Xlabel"; label: string; indices: { name: string; index: number }[] };
|
|
151
|
+
|
|
152
|
+
export interface Param {
|
|
153
|
+
name: string;
|
|
154
|
+
shape: Shape;
|
|
155
|
+
location?: Locus;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// isAtomic is Expr.Deref.atomic: a resolved subexpression reference, which has no subexpressions.
|
|
159
|
+
interface ExprProps {
|
|
160
|
+
location?: Locus;
|
|
161
|
+
isAtomic?: boolean;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export type Expr = ExprCore & ExprProps;
|
|
165
|
+
|
|
166
|
+
export type ExprCore =
|
|
167
|
+
| { kind: "Ix"; index: number }
|
|
168
|
+
| { kind: "Opaque"; name: string }
|
|
169
|
+
| { kind: "Internal"; builtin: Builtin }
|
|
170
|
+
| { kind: "Lambda"; params: Param[]; body: Expr }
|
|
171
|
+
| { kind: "Sequent"; sequent: Sequent }
|
|
172
|
+
| { kind: "Bang"; expression: Expr; selectors: Selector[] }
|
|
173
|
+
| { kind: "Apply"; operator: Expr; args: Expr[] }
|
|
174
|
+
| { kind: "With"; expression: Expr; method: string }
|
|
175
|
+
| { kind: "If"; condition: Expr; consequent: Expr; alternative: Expr }
|
|
176
|
+
| { kind: "List"; bullet: "And" | "Or" | "Refs"; items: Expr[] }
|
|
177
|
+
| { kind: "Let"; definitions: Defn[]; body: Expr }
|
|
178
|
+
| { kind: "Quant"; quantifier: "Forall" | "Exists"; bounds: Bound[]; body: Expr }
|
|
179
|
+
| { kind: "Tquant"; quantifier: "Forall" | "Exists"; names: string[]; body: Expr }
|
|
180
|
+
| { kind: "Choose"; name: string; domain: Expr | undefined; body: Expr }
|
|
181
|
+
| { kind: "SetSt"; name: string; domain: Expr; body: Expr }
|
|
182
|
+
| { kind: "SetOf"; element: Expr; bounds: Bound[] }
|
|
183
|
+
| { kind: "SetEnum"; elements: Expr[] }
|
|
184
|
+
| { kind: "Product"; factors: Expr[] }
|
|
185
|
+
| { kind: "Tuple"; elements: Expr[] }
|
|
186
|
+
| { kind: "Fcn"; bounds: Bound[]; body: Expr }
|
|
187
|
+
| { kind: "FcnApp"; function: Expr; args: Expr[] }
|
|
188
|
+
| { kind: "Arrow"; domain: Expr; range: Expr }
|
|
189
|
+
| { kind: "Rect"; fields: Field[] }
|
|
190
|
+
| { kind: "Record"; fields: Field[] }
|
|
191
|
+
| { kind: "Except"; function: Expr; specs: ExceptSpec[] }
|
|
192
|
+
| { kind: "Dot"; expression: Expr; field: string }
|
|
193
|
+
| { kind: "Sub"; modal: "Box" | "Dia"; action: Expr; subscript: Expr }
|
|
194
|
+
| { kind: "Tsub"; modal: "Box" | "Dia"; action: Expr; subscript: Expr }
|
|
195
|
+
| { kind: "Fair"; fairness: "Weak" | "Strong"; subscript: Expr; action: Expr }
|
|
196
|
+
| { kind: "Case"; arms: { condition: Expr; body: Expr }[]; other: Expr | undefined }
|
|
197
|
+
| { kind: "String"; value: string }
|
|
198
|
+
| { kind: "Num"; integer: string; fraction: string }
|
|
199
|
+
| { kind: "At"; isExcept: boolean }
|
|
200
|
+
| { kind: "Parens"; expression: Expr; form: ParenForm };
|
|
201
|
+
|
|
202
|
+
interface Field {
|
|
203
|
+
name: string;
|
|
204
|
+
value: Expr;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export interface InstanceDefinition {
|
|
208
|
+
args: string[];
|
|
209
|
+
module: string;
|
|
210
|
+
substitutions: { name: string; expression: Expr }[];
|
|
211
|
+
location?: Locus;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export type Defn = DefnCore & { location?: Locus };
|
|
215
|
+
|
|
216
|
+
type DefnCore =
|
|
217
|
+
| { kind: "Recursive"; name: string; shape: Shape }
|
|
218
|
+
| { kind: "Operator"; name: string; body: Expr }
|
|
219
|
+
| { kind: "Instance"; name: string; instance: InstanceDefinition }
|
|
220
|
+
| { kind: "Bpragma"; name: string; body: Expr };
|
|
221
|
+
|
|
222
|
+
export type WhereDefined = "Builtin" | "Proof" | "User";
|
|
223
|
+
|
|
224
|
+
export type Visibility = "Visible" | "Hidden";
|
|
225
|
+
|
|
226
|
+
export type Hyp = HypCore & { location?: Locus };
|
|
227
|
+
|
|
228
|
+
type HypCore =
|
|
229
|
+
| {
|
|
230
|
+
kind: "Fresh";
|
|
231
|
+
name: string;
|
|
232
|
+
shape: Shape;
|
|
233
|
+
level: Level;
|
|
234
|
+
domain: Expr | undefined;
|
|
235
|
+
domainVisibility: Visibility;
|
|
236
|
+
}
|
|
237
|
+
| { kind: "Flex"; name: string }
|
|
238
|
+
| {
|
|
239
|
+
kind: "Defn";
|
|
240
|
+
definition: Defn;
|
|
241
|
+
whereDefined: WhereDefined;
|
|
242
|
+
visibility: Visibility;
|
|
243
|
+
isLocal: boolean;
|
|
244
|
+
}
|
|
245
|
+
| { kind: "Fact"; expression: Expr; visibility: Visibility };
|
|
246
|
+
|
|
247
|
+
export interface Sequent {
|
|
248
|
+
context: Hyp[];
|
|
249
|
+
active: Expr;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export type Substitution =
|
|
253
|
+
| { kind: "Shift"; amount: number }
|
|
254
|
+
| { kind: "Cons"; expression: Expr; rest: Substitution }
|
|
255
|
+
| { kind: "Compose"; outer: Substitution; inner: Substitution }
|
|
256
|
+
| { kind: "Bump"; amount: number; rest: Substitution };
|
|
257
|
+
|
|
258
|
+
export const shift = (amount: number): Substitution => ({ kind: "Shift", amount });
|
|
259
|
+
|
|
260
|
+
export const consSubstitution = (expression: Expr, rest: Substitution): Substitution => ({
|
|
261
|
+
kind: "Cons",
|
|
262
|
+
expression,
|
|
263
|
+
rest,
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// Binds the expressions in order, so the last one becomes index 1.
|
|
267
|
+
export const consAll = (expressions: Expr[], rest: Substitution) =>
|
|
268
|
+
expressions.reduce((inner, expression) => consSubstitution(expression, inner), rest);
|
|
269
|
+
|
|
270
|
+
export const bumpBy = (amount: number, substitution: Substitution): Substitution =>
|
|
271
|
+
substitution.kind === "Bump"
|
|
272
|
+
? { kind: "Bump", amount: substitution.amount + amount, rest: substitution.rest }
|
|
273
|
+
: { kind: "Bump", amount, rest: substitution };
|
|
274
|
+
|
|
275
|
+
// tlapm's core @@ from, which takes the properties of from.
|
|
276
|
+
export const at = (core: ExprCore, from: ExprProps): Expr => {
|
|
277
|
+
const located = from.location ? { ...core, location: from.location } : core;
|
|
278
|
+
return from.isAtomic ? { ...located, isAtomic: true } : located;
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
export const getHypName = (hyp: Hyp) => {
|
|
282
|
+
switch (hyp.kind) {
|
|
283
|
+
case "Fresh":
|
|
284
|
+
case "Flex":
|
|
285
|
+
return hyp.name;
|
|
286
|
+
case "Defn":
|
|
287
|
+
return hyp.definition.name;
|
|
288
|
+
case "Fact":
|
|
289
|
+
return undefined;
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
export const applyBuiltin = (builtin: Builtin, args: Expr[], from: ExprProps): Expr =>
|
|
294
|
+
at({ kind: "Apply", operator: at({ kind: "Internal", builtin }, from), args }, from);
|
|
295
|
+
|
|
296
|
+
export const exprifySequent = (sequent: Sequent): ExprCore =>
|
|
297
|
+
sequent.context.length === 0 ? sequent.active : { kind: "Sequent", sequent };
|
|
298
|
+
|
|
299
|
+
// A persistent context, newest hypothesis first, so binders extend it without copying.
|
|
300
|
+
export interface Scope {
|
|
301
|
+
hyp: Hyp;
|
|
302
|
+
rest: Scope | undefined;
|
|
303
|
+
size: number;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export const adjoin = (scope: Scope | undefined, hyp: Hyp): Scope => ({
|
|
307
|
+
hyp,
|
|
308
|
+
rest: scope,
|
|
309
|
+
size: (scope?.size ?? 0) + 1,
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
export const adjoinAll = (scope: Scope | undefined, hyps: Hyp[]) =>
|
|
313
|
+
hyps.reduce<Scope | undefined>(adjoin, scope);
|
|
314
|
+
|
|
315
|
+
export const getScopeHyps = (scope: Scope | undefined): Hyp[] => {
|
|
316
|
+
const hyps: Hyp[] = [];
|
|
317
|
+
for (let current = scope; current; current = current.rest) hyps.push(current.hyp);
|
|
318
|
+
return hyps.reverse();
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
export const findNamed = (scope: Scope | undefined, name: string) => {
|
|
322
|
+
let depth = 0;
|
|
323
|
+
for (let current = scope; current; current = current.rest) {
|
|
324
|
+
if (getHypName(current.hyp) === name) return { depth, hyp: current.hyp };
|
|
325
|
+
depth++;
|
|
326
|
+
}
|
|
327
|
+
return undefined;
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
export const getScopeAt = (scope: Scope | undefined, depth: number) => {
|
|
331
|
+
let current = scope;
|
|
332
|
+
for (let index = 0; index < depth && current; index++) current = current.rest;
|
|
333
|
+
return current;
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// Maps the items in order, threading a state from each item to the next.
|
|
337
|
+
export const mapAccumulate = <State, Item, Result>(
|
|
338
|
+
state: State,
|
|
339
|
+
items: Item[],
|
|
340
|
+
step: (state: State, item: Item) => [State, Result],
|
|
341
|
+
): [State, Result[]] => {
|
|
342
|
+
const results: Result[] = [];
|
|
343
|
+
const final = items.reduce((current, item) => {
|
|
344
|
+
const [next, result] = step(current, item);
|
|
345
|
+
results.push(result);
|
|
346
|
+
return next;
|
|
347
|
+
}, state);
|
|
348
|
+
return [final, results];
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
export const getFresh = (name: string, level: Level, shape = 0): Hyp => ({
|
|
352
|
+
kind: "Fresh",
|
|
353
|
+
name,
|
|
354
|
+
shape,
|
|
355
|
+
level,
|
|
356
|
+
domain: undefined,
|
|
357
|
+
domainVisibility: "Visible",
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
export const getBoundedFresh = (name: string, domain: Expr): Hyp => ({
|
|
361
|
+
kind: "Fresh",
|
|
362
|
+
name,
|
|
363
|
+
shape: 0,
|
|
364
|
+
level: "Constant",
|
|
365
|
+
domain,
|
|
366
|
+
domainVisibility: "Visible",
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
const applyIndex = (substitution: Substitution, index: number, from: Expr): Expr => {
|
|
370
|
+
switch (substitution.kind) {
|
|
371
|
+
case "Shift":
|
|
372
|
+
return at({ kind: "Ix", index: substitution.amount + index }, from);
|
|
373
|
+
case "Cons":
|
|
374
|
+
return index === 1
|
|
375
|
+
? at(stripProps(substitution.expression), from)
|
|
376
|
+
: applyIndex(substitution.rest, index - 1, from);
|
|
377
|
+
case "Compose":
|
|
378
|
+
return substituteExpr(substitution.outer, applyIndex(substitution.inner, index, from));
|
|
379
|
+
case "Bump":
|
|
380
|
+
return index <= substitution.amount
|
|
381
|
+
? at({ kind: "Ix", index }, from)
|
|
382
|
+
: substituteExpr(
|
|
383
|
+
shift(substitution.amount),
|
|
384
|
+
applyIndex(substitution.rest, index - substitution.amount, from),
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
export const substituteExprs = (substitution: Substitution, expressions: Expr[]) =>
|
|
390
|
+
expressions.map((expression) => substituteExpr(substitution, expression));
|
|
391
|
+
|
|
392
|
+
// Beta-reduces a lambda applied to arguments, as E_subst.normalize does.
|
|
393
|
+
export const normalizeApply = (operator: Expr, args: Expr[]): ExprCore => {
|
|
394
|
+
if (operator.kind === "Lambda") {
|
|
395
|
+
if (operator.params.length !== args.length) throw new TlaProofError("Arity mismatch");
|
|
396
|
+
return stripProps(substituteExpr(consAll(args, shift(0)), operator.body));
|
|
397
|
+
}
|
|
398
|
+
if (operator.kind === "Apply") {
|
|
399
|
+
return { kind: "Apply", operator: operator.operator, args: [...operator.args, ...args] };
|
|
400
|
+
}
|
|
401
|
+
return args.length === 0 ? stripProps(operator) : { kind: "Apply", operator, args };
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
// The node without its properties, as tlapm's e.core.
|
|
405
|
+
export const stripProps = (expression: Expr): ExprCore => {
|
|
406
|
+
if (!expression.location && !expression.isAtomic) return expression;
|
|
407
|
+
const { location: _location, isAtomic: _isAtomic, ...core } = expression;
|
|
408
|
+
return core;
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
export const substituteBounds = (
|
|
412
|
+
substitution: Substitution,
|
|
413
|
+
bounds: Bound[],
|
|
414
|
+
): [Substitution, Bound[]] => [
|
|
415
|
+
bumpBy(bounds.length, substitution),
|
|
416
|
+
mapBoundDomains(bounds, (domain) => substituteExpr(substitution, domain)),
|
|
417
|
+
];
|
|
418
|
+
|
|
419
|
+
export const substituteDefn = (substitution: Substitution, definition: Defn): Defn => {
|
|
420
|
+
switch (definition.kind) {
|
|
421
|
+
case "Recursive":
|
|
422
|
+
case "Operator":
|
|
423
|
+
case "Bpragma":
|
|
424
|
+
return mapDefnBody(definition, (body) => substituteExpr(substitution, body));
|
|
425
|
+
case "Instance": {
|
|
426
|
+
const inner = bumpBy(definition.instance.args.length, substitution);
|
|
427
|
+
return {
|
|
428
|
+
...definition,
|
|
429
|
+
instance: {
|
|
430
|
+
...definition.instance,
|
|
431
|
+
substitutions: definition.instance.substitutions.map((entry) => ({
|
|
432
|
+
name: entry.name,
|
|
433
|
+
expression: substituteExpr(inner, entry.expression),
|
|
434
|
+
})),
|
|
435
|
+
},
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
export const substituteHyp = (substitution: Substitution, hyp: Hyp): Hyp => {
|
|
442
|
+
switch (hyp.kind) {
|
|
443
|
+
case "Fresh":
|
|
444
|
+
return hyp.domain ? { ...hyp, domain: substituteExpr(substitution, hyp.domain) } : hyp;
|
|
445
|
+
case "Flex":
|
|
446
|
+
return hyp;
|
|
447
|
+
case "Defn":
|
|
448
|
+
return { ...hyp, definition: substituteDefn(substitution, hyp.definition) };
|
|
449
|
+
case "Fact":
|
|
450
|
+
return { ...hyp, expression: substituteExpr(substitution, hyp.expression) };
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
export const substituteSequent = (substitution: Substitution, sequent: Sequent): Sequent => {
|
|
455
|
+
const [inner, context] = mapAccumulate(substitution, sequent.context, (current, hyp) => [
|
|
456
|
+
bumpBy(1, current),
|
|
457
|
+
substituteHyp(current, hyp),
|
|
458
|
+
]);
|
|
459
|
+
return { context, active: substituteExpr(inner, sequent.active) };
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
export const substituteExpr = (substitution: Substitution, expression: Expr): Expr => {
|
|
463
|
+
const recurse = (inner: Expr) => substituteExpr(substitution, inner);
|
|
464
|
+
switch (expression.kind) {
|
|
465
|
+
case "Internal":
|
|
466
|
+
case "Opaque":
|
|
467
|
+
case "String":
|
|
468
|
+
case "Num":
|
|
469
|
+
case "At":
|
|
470
|
+
return expression;
|
|
471
|
+
case "Ix":
|
|
472
|
+
return applyIndex(substitution, expression.index, expression);
|
|
473
|
+
case "Apply":
|
|
474
|
+
return at(
|
|
475
|
+
normalizeApply(
|
|
476
|
+
recurse(expression.operator),
|
|
477
|
+
substituteExprs(substitution, expression.args),
|
|
478
|
+
),
|
|
479
|
+
expression,
|
|
480
|
+
);
|
|
481
|
+
case "Bang":
|
|
482
|
+
return {
|
|
483
|
+
...expression,
|
|
484
|
+
expression: recurse(expression.expression),
|
|
485
|
+
selectors: expression.selectors.map((selector) => mapSelectorArgs(selector, recurse)),
|
|
486
|
+
};
|
|
487
|
+
case "Lambda":
|
|
488
|
+
return {
|
|
489
|
+
...expression,
|
|
490
|
+
body: substituteExpr(bumpBy(expression.params.length, substitution), expression.body),
|
|
491
|
+
};
|
|
492
|
+
case "Let": {
|
|
493
|
+
const [inner, definitions] = mapAccumulate(
|
|
494
|
+
substitution,
|
|
495
|
+
expression.definitions,
|
|
496
|
+
(current, definition) => [bumpBy(1, current), substituteDefn(current, definition)],
|
|
497
|
+
);
|
|
498
|
+
return { ...expression, definitions, body: substituteExpr(inner, expression.body) };
|
|
499
|
+
}
|
|
500
|
+
case "Sequent":
|
|
501
|
+
return { ...expression, sequent: substituteSequent(substitution, expression.sequent) };
|
|
502
|
+
case "With":
|
|
503
|
+
return { ...expression, expression: recurse(expression.expression) };
|
|
504
|
+
case "If":
|
|
505
|
+
return {
|
|
506
|
+
...expression,
|
|
507
|
+
condition: recurse(expression.condition),
|
|
508
|
+
consequent: recurse(expression.consequent),
|
|
509
|
+
alternative: recurse(expression.alternative),
|
|
510
|
+
};
|
|
511
|
+
case "List":
|
|
512
|
+
return { ...expression, items: substituteExprs(substitution, expression.items) };
|
|
513
|
+
case "Quant": {
|
|
514
|
+
const [inner, bounds] = substituteBounds(substitution, expression.bounds);
|
|
515
|
+
return { ...expression, bounds, body: substituteExpr(inner, expression.body) };
|
|
516
|
+
}
|
|
517
|
+
case "Tquant":
|
|
518
|
+
return {
|
|
519
|
+
...expression,
|
|
520
|
+
body: substituteExpr(bumpBy(expression.names.length, substitution), expression.body),
|
|
521
|
+
};
|
|
522
|
+
case "Choose":
|
|
523
|
+
return {
|
|
524
|
+
...expression,
|
|
525
|
+
domain: expression.domain && recurse(expression.domain),
|
|
526
|
+
body: substituteExpr(bumpBy(1, substitution), expression.body),
|
|
527
|
+
};
|
|
528
|
+
case "SetSt":
|
|
529
|
+
return {
|
|
530
|
+
...expression,
|
|
531
|
+
domain: recurse(expression.domain),
|
|
532
|
+
body: substituteExpr(bumpBy(1, substitution), expression.body),
|
|
533
|
+
};
|
|
534
|
+
case "SetOf": {
|
|
535
|
+
const [inner, bounds] = substituteBounds(substitution, expression.bounds);
|
|
536
|
+
return { ...expression, bounds, element: substituteExpr(inner, expression.element) };
|
|
537
|
+
}
|
|
538
|
+
case "SetEnum":
|
|
539
|
+
return { ...expression, elements: substituteExprs(substitution, expression.elements) };
|
|
540
|
+
case "Arrow":
|
|
541
|
+
return {
|
|
542
|
+
...expression,
|
|
543
|
+
domain: recurse(expression.domain),
|
|
544
|
+
range: recurse(expression.range),
|
|
545
|
+
};
|
|
546
|
+
case "Fcn": {
|
|
547
|
+
const [inner, bounds] = substituteBounds(substitution, expression.bounds);
|
|
548
|
+
return { ...expression, bounds, body: substituteExpr(inner, expression.body) };
|
|
549
|
+
}
|
|
550
|
+
case "FcnApp":
|
|
551
|
+
return {
|
|
552
|
+
...expression,
|
|
553
|
+
function: recurse(expression.function),
|
|
554
|
+
args: substituteExprs(substitution, expression.args),
|
|
555
|
+
};
|
|
556
|
+
case "Product":
|
|
557
|
+
return { ...expression, factors: substituteExprs(substitution, expression.factors) };
|
|
558
|
+
case "Tuple":
|
|
559
|
+
return { ...expression, elements: substituteExprs(substitution, expression.elements) };
|
|
560
|
+
case "Rect":
|
|
561
|
+
case "Record":
|
|
562
|
+
return {
|
|
563
|
+
...expression,
|
|
564
|
+
fields: expression.fields.map((field) => ({
|
|
565
|
+
name: field.name,
|
|
566
|
+
value: recurse(field.value),
|
|
567
|
+
})),
|
|
568
|
+
};
|
|
569
|
+
case "Except":
|
|
570
|
+
return {
|
|
571
|
+
...expression,
|
|
572
|
+
function: recurse(expression.function),
|
|
573
|
+
specs: mapExceptSpecs(expression.specs, recurse),
|
|
574
|
+
};
|
|
575
|
+
case "Dot":
|
|
576
|
+
return { ...expression, expression: recurse(expression.expression) };
|
|
577
|
+
case "Sub":
|
|
578
|
+
case "Tsub":
|
|
579
|
+
return {
|
|
580
|
+
...expression,
|
|
581
|
+
action: recurse(expression.action),
|
|
582
|
+
subscript: recurse(expression.subscript),
|
|
583
|
+
};
|
|
584
|
+
case "Fair":
|
|
585
|
+
return {
|
|
586
|
+
...expression,
|
|
587
|
+
subscript: recurse(expression.subscript),
|
|
588
|
+
action: recurse(expression.action),
|
|
589
|
+
};
|
|
590
|
+
case "Case":
|
|
591
|
+
return {
|
|
592
|
+
...expression,
|
|
593
|
+
arms: expression.arms.map((arm) => ({
|
|
594
|
+
condition: recurse(arm.condition),
|
|
595
|
+
body: recurse(arm.body),
|
|
596
|
+
})),
|
|
597
|
+
other: expression.other && recurse(expression.other),
|
|
598
|
+
};
|
|
599
|
+
case "Parens":
|
|
600
|
+
return { ...expression, expression: recurse(expression.expression) };
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
export const shiftExpr = (amount: number, expression: Expr) =>
|
|
605
|
+
amount === 0 ? expression : substituteExpr(shift(amount), expression);
|
|
606
|
+
|
|
607
|
+
const isEqualList = <Item>(
|
|
608
|
+
left: Item[],
|
|
609
|
+
right: Item[],
|
|
610
|
+
isEqual: (left: Item, right: Item) => boolean,
|
|
611
|
+
) => left.length === right.length && left.every((item, index) => isEqual(item, right[index]));
|
|
612
|
+
|
|
613
|
+
const isEqualFields = (left: Field[], right: Field[]) => {
|
|
614
|
+
const leftMap = new Map(left.map((field) => [field.name, field.value]));
|
|
615
|
+
const rightMap = new Map(right.map((field) => [field.name, field.value]));
|
|
616
|
+
if (leftMap.size !== rightMap.size) return false;
|
|
617
|
+
for (const [name, value] of leftMap) {
|
|
618
|
+
const other = rightMap.get(name);
|
|
619
|
+
if (!other || !isEqualExpr(value, other)) return false;
|
|
620
|
+
}
|
|
621
|
+
return true;
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
const isEqualBound = (left: Bound, right: Bound) =>
|
|
625
|
+
left.level === right.level &&
|
|
626
|
+
left.domain.kind === right.domain.kind &&
|
|
627
|
+
(left.domain.kind !== "Domain" ||
|
|
628
|
+
(right.domain.kind === "Domain" &&
|
|
629
|
+
isEqualExpr(left.domain.expression, right.domain.expression)));
|
|
630
|
+
|
|
631
|
+
const isEqualSelector = (left: Selector, right: Selector) => {
|
|
632
|
+
if (left.kind !== right.kind) return false;
|
|
633
|
+
switch (left.kind) {
|
|
634
|
+
case "Number":
|
|
635
|
+
return right.kind === "Number" && left.value === right.value;
|
|
636
|
+
case "Instance":
|
|
637
|
+
return right.kind === "Instance" && isEqualList(left.args, right.args, isEqualExpr);
|
|
638
|
+
case "Label":
|
|
639
|
+
return (
|
|
640
|
+
right.kind === "Label" &&
|
|
641
|
+
left.label === right.label &&
|
|
642
|
+
isEqualList(left.args, right.args, isEqualExpr)
|
|
643
|
+
);
|
|
644
|
+
default:
|
|
645
|
+
return true;
|
|
646
|
+
}
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
const isEqualDefn = (left: Defn, right: Defn): boolean => {
|
|
650
|
+
if (left.kind === "Operator" && right.kind === "Operator")
|
|
651
|
+
return isEqualExpr(left.body, right.body);
|
|
652
|
+
if (left.kind === "Bpragma" && right.kind === "Bpragma")
|
|
653
|
+
return isEqualExpr(left.body, right.body);
|
|
654
|
+
if (left.kind === "Instance" && right.kind === "Instance") {
|
|
655
|
+
const leftSubstitutions = left.instance.substitutions.map((entry): Field => ({
|
|
656
|
+
name: entry.name,
|
|
657
|
+
value: entry.expression,
|
|
658
|
+
}));
|
|
659
|
+
const rightSubstitutions = right.instance.substitutions.map((entry): Field => ({
|
|
660
|
+
name: entry.name,
|
|
661
|
+
value: entry.expression,
|
|
662
|
+
}));
|
|
663
|
+
return (
|
|
664
|
+
isEqualList(
|
|
665
|
+
left.instance.args,
|
|
666
|
+
right.instance.args,
|
|
667
|
+
(leftArg, rightArg) => leftArg === rightArg,
|
|
668
|
+
) &&
|
|
669
|
+
left.instance.module === right.instance.module &&
|
|
670
|
+
isEqualFields(leftSubstitutions, rightSubstitutions)
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
return false;
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
export const isEqualHyp = (left: Hyp, right: Hyp): boolean => {
|
|
677
|
+
if (left.kind === "Fresh" && right.kind === "Fresh") {
|
|
678
|
+
if (left.level !== right.level || left.shape !== right.shape) return false;
|
|
679
|
+
if (!left.domain || !right.domain) return !left.domain && !right.domain;
|
|
680
|
+
return isEqualExpr(left.domain, right.domain);
|
|
681
|
+
}
|
|
682
|
+
if (left.kind === "Flex" && right.kind === "Flex") return true;
|
|
683
|
+
if (left.kind === "Defn" && right.kind === "Defn") {
|
|
684
|
+
return isEqualDefn(left.definition, right.definition) && left.isLocal === right.isLocal;
|
|
685
|
+
}
|
|
686
|
+
if (left.kind === "Fact" && right.kind === "Fact") {
|
|
687
|
+
return isEqualExpr(left.expression, right.expression);
|
|
688
|
+
}
|
|
689
|
+
return false;
|
|
690
|
+
};
|
|
691
|
+
|
|
692
|
+
const isEqualSequent = (left: Sequent, right: Sequent) =>
|
|
693
|
+
isEqualList(left.context, right.context, isEqualHyp) && isEqualExpr(left.active, right.active);
|
|
694
|
+
|
|
695
|
+
// Alpha-equivalence, including E_eq's quirk of comparing only the first CASE arm.
|
|
696
|
+
export const isEqualExpr = (left: Expr, right: Expr): boolean => {
|
|
697
|
+
if (left.kind === "Parens") return isEqualExpr(left.expression, right);
|
|
698
|
+
if (right.kind === "Parens") return isEqualExpr(left, right.expression);
|
|
699
|
+
switch (left.kind) {
|
|
700
|
+
case "Ix":
|
|
701
|
+
return right.kind === "Ix" && left.index === right.index;
|
|
702
|
+
case "Opaque":
|
|
703
|
+
return right.kind === "Opaque" && left.name === right.name;
|
|
704
|
+
case "Internal":
|
|
705
|
+
return right.kind === "Internal" && left.builtin === right.builtin;
|
|
706
|
+
case "Lambda":
|
|
707
|
+
return (
|
|
708
|
+
right.kind === "Lambda" &&
|
|
709
|
+
left.params.length === right.params.length &&
|
|
710
|
+
isEqualExpr(left.body, right.body)
|
|
711
|
+
);
|
|
712
|
+
case "Apply":
|
|
713
|
+
return (
|
|
714
|
+
right.kind === "Apply" &&
|
|
715
|
+
isEqualExpr(left.operator, right.operator) &&
|
|
716
|
+
isEqualList(left.args, right.args, isEqualExpr)
|
|
717
|
+
);
|
|
718
|
+
case "Sequent":
|
|
719
|
+
return right.kind === "Sequent" && isEqualSequent(left.sequent, right.sequent);
|
|
720
|
+
case "Bang":
|
|
721
|
+
return (
|
|
722
|
+
right.kind === "Bang" &&
|
|
723
|
+
isEqualExpr(left.expression, right.expression) &&
|
|
724
|
+
isEqualList(left.selectors, right.selectors, isEqualSelector)
|
|
725
|
+
);
|
|
726
|
+
case "With":
|
|
727
|
+
return right.kind === "With" && isEqualExpr(left.expression, right.expression);
|
|
728
|
+
case "Let":
|
|
729
|
+
return (
|
|
730
|
+
right.kind === "Let" &&
|
|
731
|
+
isEqualList(left.definitions, right.definitions, isEqualDefn) &&
|
|
732
|
+
isEqualExpr(left.body, right.body)
|
|
733
|
+
);
|
|
734
|
+
case "If":
|
|
735
|
+
return (
|
|
736
|
+
right.kind === "If" &&
|
|
737
|
+
isEqualExpr(left.condition, right.condition) &&
|
|
738
|
+
isEqualExpr(left.consequent, right.consequent) &&
|
|
739
|
+
isEqualExpr(left.alternative, right.alternative)
|
|
740
|
+
);
|
|
741
|
+
case "List":
|
|
742
|
+
return (
|
|
743
|
+
right.kind === "List" &&
|
|
744
|
+
left.bullet === right.bullet &&
|
|
745
|
+
isEqualList(left.items, right.items, isEqualExpr)
|
|
746
|
+
);
|
|
747
|
+
case "Quant":
|
|
748
|
+
return (
|
|
749
|
+
right.kind === "Quant" &&
|
|
750
|
+
left.quantifier === right.quantifier &&
|
|
751
|
+
isEqualList(left.bounds, right.bounds, isEqualBound) &&
|
|
752
|
+
isEqualExpr(left.body, right.body)
|
|
753
|
+
);
|
|
754
|
+
case "Tquant":
|
|
755
|
+
return (
|
|
756
|
+
right.kind === "Tquant" &&
|
|
757
|
+
left.quantifier === right.quantifier &&
|
|
758
|
+
isEqualList(left.names, right.names, (leftName, rightName) => leftName === rightName) &&
|
|
759
|
+
isEqualExpr(left.body, right.body)
|
|
760
|
+
);
|
|
761
|
+
case "Choose":
|
|
762
|
+
if (right.kind !== "Choose") return false;
|
|
763
|
+
if (left.domain && right.domain) {
|
|
764
|
+
return isEqualExpr(left.domain, right.domain) && isEqualExpr(left.body, right.body);
|
|
765
|
+
}
|
|
766
|
+
return !left.domain && !right.domain && isEqualExpr(left.body, right.body);
|
|
767
|
+
case "SetSt":
|
|
768
|
+
return (
|
|
769
|
+
right.kind === "SetSt" &&
|
|
770
|
+
isEqualExpr(left.domain, right.domain) &&
|
|
771
|
+
isEqualExpr(left.body, right.body)
|
|
772
|
+
);
|
|
773
|
+
case "SetOf":
|
|
774
|
+
return (
|
|
775
|
+
right.kind === "SetOf" &&
|
|
776
|
+
isEqualExpr(left.element, right.element) &&
|
|
777
|
+
isEqualList(left.bounds, right.bounds, isEqualBound)
|
|
778
|
+
);
|
|
779
|
+
case "SetEnum":
|
|
780
|
+
return right.kind === "SetEnum" && isEqualList(left.elements, right.elements, isEqualExpr);
|
|
781
|
+
case "Arrow":
|
|
782
|
+
return (
|
|
783
|
+
right.kind === "Arrow" &&
|
|
784
|
+
isEqualExpr(left.domain, right.domain) &&
|
|
785
|
+
isEqualExpr(left.range, right.range)
|
|
786
|
+
);
|
|
787
|
+
case "Fcn":
|
|
788
|
+
return (
|
|
789
|
+
right.kind === "Fcn" &&
|
|
790
|
+
isEqualList(left.bounds, right.bounds, isEqualBound) &&
|
|
791
|
+
isEqualExpr(left.body, right.body)
|
|
792
|
+
);
|
|
793
|
+
case "FcnApp":
|
|
794
|
+
return (
|
|
795
|
+
right.kind === "FcnApp" &&
|
|
796
|
+
isEqualList([left.function, ...left.args], [right.function, ...right.args], isEqualExpr)
|
|
797
|
+
);
|
|
798
|
+
case "Tuple":
|
|
799
|
+
return right.kind === "Tuple" && isEqualList(left.elements, right.elements, isEqualExpr);
|
|
800
|
+
case "Product":
|
|
801
|
+
return right.kind === "Product" && isEqualList(left.factors, right.factors, isEqualExpr);
|
|
802
|
+
case "Rect":
|
|
803
|
+
return right.kind === "Rect" && isEqualFields(left.fields, right.fields);
|
|
804
|
+
case "Record":
|
|
805
|
+
return right.kind === "Record" && isEqualFields(left.fields, right.fields);
|
|
806
|
+
case "Except":
|
|
807
|
+
return (
|
|
808
|
+
right.kind === "Except" &&
|
|
809
|
+
isEqualExpr(left.function, right.function) &&
|
|
810
|
+
isEqualList(
|
|
811
|
+
left.specs,
|
|
812
|
+
right.specs,
|
|
813
|
+
(leftSpec, rightSpec) =>
|
|
814
|
+
isEqualList(leftSpec.trail, rightSpec.trail, (leftPoint, rightPoint) =>
|
|
815
|
+
leftPoint.kind === "Dot"
|
|
816
|
+
? rightPoint.kind === "Dot" && leftPoint.field === rightPoint.field
|
|
817
|
+
: rightPoint.kind === "Apply" &&
|
|
818
|
+
isEqualExpr(leftPoint.expression, rightPoint.expression),
|
|
819
|
+
) && isEqualExpr(leftSpec.value, rightSpec.value),
|
|
820
|
+
)
|
|
821
|
+
);
|
|
822
|
+
case "Dot":
|
|
823
|
+
return (
|
|
824
|
+
right.kind === "Dot" &&
|
|
825
|
+
isEqualExpr(left.expression, right.expression) &&
|
|
826
|
+
left.field === right.field
|
|
827
|
+
);
|
|
828
|
+
case "Sub":
|
|
829
|
+
case "Tsub":
|
|
830
|
+
return (
|
|
831
|
+
right.kind === left.kind &&
|
|
832
|
+
left.modal === right.modal &&
|
|
833
|
+
isEqualExpr(left.action, right.action) &&
|
|
834
|
+
isEqualExpr(left.subscript, right.subscript)
|
|
835
|
+
);
|
|
836
|
+
case "Fair":
|
|
837
|
+
return (
|
|
838
|
+
right.kind === "Fair" &&
|
|
839
|
+
left.fairness === right.fairness &&
|
|
840
|
+
isEqualExpr(left.subscript, right.subscript) &&
|
|
841
|
+
isEqualExpr(left.action, right.action)
|
|
842
|
+
);
|
|
843
|
+
case "Case": {
|
|
844
|
+
if (right.kind !== "Case") return false;
|
|
845
|
+
const isArmsEqual =
|
|
846
|
+
left.arms.length === 0 || right.arms.length === 0
|
|
847
|
+
? left.arms.length === right.arms.length
|
|
848
|
+
: isEqualExpr(left.arms[0].condition, right.arms[0].condition) &&
|
|
849
|
+
isEqualExpr(left.arms[0].body, right.arms[0].body);
|
|
850
|
+
if (!isArmsEqual) return false;
|
|
851
|
+
if (left.other && right.other) return isEqualExpr(left.other, right.other);
|
|
852
|
+
return !left.other && !right.other;
|
|
853
|
+
}
|
|
854
|
+
case "String":
|
|
855
|
+
return right.kind === "String" && left.value === right.value;
|
|
856
|
+
case "Num":
|
|
857
|
+
return (
|
|
858
|
+
right.kind === "Num" && left.integer === right.integer && left.fraction === right.fraction
|
|
859
|
+
);
|
|
860
|
+
case "At":
|
|
861
|
+
return right.kind === "At" && left.isExcept === right.isExcept;
|
|
862
|
+
}
|
|
863
|
+
};
|
|
864
|
+
|
|
865
|
+
export const mapBoundDomains = (bounds: Bound[], map: (domain: Expr) => Expr): Bound[] =>
|
|
866
|
+
bounds.map((bound) =>
|
|
867
|
+
bound.domain.kind === "Domain"
|
|
868
|
+
? { ...bound, domain: { kind: "Domain", expression: map(bound.domain.expression) } }
|
|
869
|
+
: bound,
|
|
870
|
+
);
|
|
871
|
+
|
|
872
|
+
export const mapSelectorArgs = (selector: Selector, map: (arg: Expr) => Expr): Selector =>
|
|
873
|
+
selector.kind === "Instance" || selector.kind === "Label"
|
|
874
|
+
? { ...selector, args: selector.args.map(map) }
|
|
875
|
+
: selector;
|
|
876
|
+
|
|
877
|
+
export const mapExceptSpecs = (specs: ExceptSpec[], map: (inner: Expr) => Expr): ExceptSpec[] =>
|
|
878
|
+
specs.map((spec) => {
|
|
879
|
+
const value = map(spec.value);
|
|
880
|
+
const trail = spec.trail.map((point): ExceptPoint =>
|
|
881
|
+
point.kind === "Apply" ? { kind: "Apply", expression: map(point.expression) } : point,
|
|
882
|
+
);
|
|
883
|
+
return { trail, value };
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
export const mapDefnBody = (definition: Defn, map: (body: Expr) => Expr): Defn =>
|
|
887
|
+
definition.kind === "Operator" || definition.kind === "Bpragma"
|
|
888
|
+
? { ...definition, body: map(definition.body) }
|
|
889
|
+
: definition;
|
|
890
|
+
|
|
891
|
+
const mapHyp = (hyp: Hyp, map: (inner: Expr) => Expr, hyps: "visible" | "all"): Hyp => {
|
|
892
|
+
if (hyp.kind === "Fresh") return hyp.domain ? { ...hyp, domain: map(hyp.domain) } : hyp;
|
|
893
|
+
if (hyp.kind === "Flex" || (hyps === "visible" && hyp.visibility === "Hidden")) return hyp;
|
|
894
|
+
if (hyp.kind === "Defn") return { ...hyp, definition: mapDefnBody(hyp.definition, map) };
|
|
895
|
+
return { ...hyp, expression: map(hyp.expression) };
|
|
896
|
+
};
|
|
897
|
+
|
|
898
|
+
// Visit.map, threading a state through each binder: a scope or a substitution. Visit.map_visible_hyp
|
|
899
|
+
// binds hidden hypotheses without visiting them, and Visit.map visits them too. Children map in
|
|
900
|
+
// OCaml evaluation order, last constructor argument first, which decides the error tlapm reports.
|
|
901
|
+
export const mapScoped = <State>(
|
|
902
|
+
state: State,
|
|
903
|
+
expression: Expr,
|
|
904
|
+
map: (state: State, inner: Expr) => Expr,
|
|
905
|
+
bind: (state: State, hyp: Hyp) => State,
|
|
906
|
+
hyps: "visible" | "all" = "visible",
|
|
907
|
+
): Expr => {
|
|
908
|
+
const recurse = (inner: Expr) => map(state, inner);
|
|
909
|
+
const bindBounds = (bounds: Bound[]) =>
|
|
910
|
+
bounds.reduce((current, bound) => bind(current, getFresh(bound.name, bound.level)), state);
|
|
911
|
+
switch (expression.kind) {
|
|
912
|
+
case "Ix":
|
|
913
|
+
case "Internal":
|
|
914
|
+
case "Opaque":
|
|
915
|
+
case "String":
|
|
916
|
+
case "Num":
|
|
917
|
+
case "At":
|
|
918
|
+
return expression;
|
|
919
|
+
case "Lambda": {
|
|
920
|
+
const inner = expression.params.reduce((current, param) => {
|
|
921
|
+
const fresh = getFresh(param.name, "Unknown", param.shape);
|
|
922
|
+
return bind(current, param.location ? { ...fresh, location: param.location } : fresh);
|
|
923
|
+
}, state);
|
|
924
|
+
return { ...expression, body: map(inner, expression.body) };
|
|
925
|
+
}
|
|
926
|
+
case "Sequent": {
|
|
927
|
+
const [inner, context] = mapAccumulate(state, expression.sequent.context, (current, hyp) => {
|
|
928
|
+
const mapped = mapHyp(hyp, (child) => map(current, child), hyps);
|
|
929
|
+
return [bind(current, mapped), mapped];
|
|
930
|
+
});
|
|
931
|
+
return { ...expression, sequent: { context, active: map(inner, expression.sequent.active) } };
|
|
932
|
+
}
|
|
933
|
+
case "Let": {
|
|
934
|
+
const [inner, definitions] = mapAccumulate(
|
|
935
|
+
state,
|
|
936
|
+
expression.definitions,
|
|
937
|
+
(current, definition) => {
|
|
938
|
+
const mapped = mapDefnBody(definition, (body) => map(current, body));
|
|
939
|
+
const hyp: Hyp = {
|
|
940
|
+
kind: "Defn",
|
|
941
|
+
definition: mapped,
|
|
942
|
+
whereDefined: "User",
|
|
943
|
+
visibility: "Visible",
|
|
944
|
+
isLocal: true,
|
|
945
|
+
};
|
|
946
|
+
return [bind(current, hyp), mapped];
|
|
947
|
+
},
|
|
948
|
+
);
|
|
949
|
+
return { ...expression, definitions, body: map(inner, expression.body) };
|
|
950
|
+
}
|
|
951
|
+
case "Quant":
|
|
952
|
+
case "Fcn": {
|
|
953
|
+
const bounds = mapBoundDomains(expression.bounds, recurse);
|
|
954
|
+
return { ...expression, bounds, body: map(bindBounds(bounds), expression.body) };
|
|
955
|
+
}
|
|
956
|
+
case "SetOf": {
|
|
957
|
+
const bounds = mapBoundDomains(expression.bounds, recurse);
|
|
958
|
+
return { ...expression, bounds, element: map(bindBounds(bounds), expression.element) };
|
|
959
|
+
}
|
|
960
|
+
case "Tquant": {
|
|
961
|
+
const inner = expression.names.reduce(
|
|
962
|
+
(current, name) => bind(current, { kind: "Flex", name }),
|
|
963
|
+
state,
|
|
964
|
+
);
|
|
965
|
+
return { ...expression, body: map(inner, expression.body) };
|
|
966
|
+
}
|
|
967
|
+
case "Choose": {
|
|
968
|
+
const domain = expression.domain && recurse(expression.domain);
|
|
969
|
+
const hyp = domain
|
|
970
|
+
? getBoundedFresh(expression.name, domain)
|
|
971
|
+
: getFresh(expression.name, "Constant");
|
|
972
|
+
return { ...expression, domain, body: map(bind(state, hyp), expression.body) };
|
|
973
|
+
}
|
|
974
|
+
case "SetSt": {
|
|
975
|
+
const domain = recurse(expression.domain);
|
|
976
|
+
const hyp = getBoundedFresh(expression.name, domain);
|
|
977
|
+
return { ...expression, domain, body: map(bind(state, hyp), expression.body) };
|
|
978
|
+
}
|
|
979
|
+
case "Bang": {
|
|
980
|
+
const selectors = expression.selectors.map((selector) => mapSelectorArgs(selector, recurse));
|
|
981
|
+
return { ...expression, expression: recurse(expression.expression), selectors };
|
|
982
|
+
}
|
|
983
|
+
case "Apply": {
|
|
984
|
+
const args = expression.args.map(recurse);
|
|
985
|
+
return { ...expression, operator: recurse(expression.operator), args };
|
|
986
|
+
}
|
|
987
|
+
case "With":
|
|
988
|
+
case "Dot":
|
|
989
|
+
case "Parens":
|
|
990
|
+
return { ...expression, expression: recurse(expression.expression) };
|
|
991
|
+
case "If": {
|
|
992
|
+
const alternative = recurse(expression.alternative);
|
|
993
|
+
const consequent = recurse(expression.consequent);
|
|
994
|
+
return { ...expression, condition: recurse(expression.condition), consequent, alternative };
|
|
995
|
+
}
|
|
996
|
+
case "List":
|
|
997
|
+
return { ...expression, items: expression.items.map(recurse) };
|
|
998
|
+
case "SetEnum":
|
|
999
|
+
case "Tuple":
|
|
1000
|
+
return { ...expression, elements: expression.elements.map(recurse) };
|
|
1001
|
+
case "Product":
|
|
1002
|
+
return { ...expression, factors: expression.factors.map(recurse) };
|
|
1003
|
+
case "FcnApp": {
|
|
1004
|
+
const args = expression.args.map(recurse);
|
|
1005
|
+
return { ...expression, function: recurse(expression.function), args };
|
|
1006
|
+
}
|
|
1007
|
+
case "Arrow": {
|
|
1008
|
+
const range = recurse(expression.range);
|
|
1009
|
+
return { ...expression, domain: recurse(expression.domain), range };
|
|
1010
|
+
}
|
|
1011
|
+
case "Rect":
|
|
1012
|
+
case "Record":
|
|
1013
|
+
return {
|
|
1014
|
+
...expression,
|
|
1015
|
+
fields: expression.fields.map((field) => ({
|
|
1016
|
+
name: field.name,
|
|
1017
|
+
value: recurse(field.value),
|
|
1018
|
+
})),
|
|
1019
|
+
};
|
|
1020
|
+
case "Except": {
|
|
1021
|
+
const specs = mapExceptSpecs(expression.specs, recurse);
|
|
1022
|
+
return { ...expression, function: recurse(expression.function), specs };
|
|
1023
|
+
}
|
|
1024
|
+
case "Sub":
|
|
1025
|
+
case "Tsub":
|
|
1026
|
+
case "Fair":
|
|
1027
|
+
return {
|
|
1028
|
+
...expression,
|
|
1029
|
+
subscript: recurse(expression.subscript),
|
|
1030
|
+
action: recurse(expression.action),
|
|
1031
|
+
};
|
|
1032
|
+
case "Case": {
|
|
1033
|
+
const other = expression.other && recurse(expression.other);
|
|
1034
|
+
const arms = expression.arms.map((arm) => {
|
|
1035
|
+
const body = recurse(arm.body);
|
|
1036
|
+
return { condition: recurse(arm.condition), body };
|
|
1037
|
+
});
|
|
1038
|
+
return { ...expression, arms, other };
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
};
|
|
1042
|
+
|
|
1043
|
+
// Visit.map without a state: every child, the hidden hypotheses of an ASSUME included.
|
|
1044
|
+
export const mapChildren = (expression: Expr, map: (inner: Expr) => Expr): Expr =>
|
|
1045
|
+
mapScoped(
|
|
1046
|
+
undefined,
|
|
1047
|
+
expression,
|
|
1048
|
+
(_state, inner) => map(inner),
|
|
1049
|
+
() => undefined,
|
|
1050
|
+
"all",
|
|
1051
|
+
);
|
|
1052
|
+
|
|
1053
|
+
export const getChildren = (expression: Expr) => {
|
|
1054
|
+
const children: Expr[] = [];
|
|
1055
|
+
mapChildren(expression, (inner) => {
|
|
1056
|
+
children.push(inner);
|
|
1057
|
+
return inner;
|
|
1058
|
+
});
|
|
1059
|
+
return children;
|
|
1060
|
+
};
|