@bramas/simulant-lean-gen 0.0.0-stage → 0.1.1
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/LICENCE.txt +515 -0
- package/dist/check-proofs.js +76 -0
- package/dist/component.js +196 -0
- package/dist/differential.js +159 -0
- package/dist/library.js +30 -0
- package/dist/model.js +391 -0
- package/dist/names.js +3 -0
- package/dist/translate.js +398 -0
- package/package.json +54 -3
- package/src/check-proofs.ts +83 -0
- package/src/component.ts +195 -0
- package/src/differential.ts +159 -0
- package/src/library.ts +35 -0
- package/src/model.ts +387 -0
- package/src/names.ts +5 -0
- package/src/translate.ts +421 -0
- package/README.md +0 -3
package/src/translate.ts
ADDED
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translation of IR expressions to Lean terms (plan, Section 5.1).
|
|
3
|
+
*
|
|
4
|
+
* A deterministic expression becomes a plain term; an expression with a
|
|
5
|
+
* choice becomes a term of type `ND T`, built with `do` and `←`, in the
|
|
6
|
+
* interpreter's evaluation order, so that witnesses coincide. Choice labels
|
|
7
|
+
* are only for display and are dropped.
|
|
8
|
+
*/
|
|
9
|
+
import { BUILTINS } from '@bramas/simulant-lang/builtins';
|
|
10
|
+
import type { Def, Expr, Json, Type } from '@bramas/simulant-lang/ir';
|
|
11
|
+
import { fieldType, isCollection, MOVES, resolvePub, showType, T, typeEq, VERDICTS } from '@bramas/simulant-lang/types';
|
|
12
|
+
|
|
13
|
+
export class GenError extends Error {}
|
|
14
|
+
|
|
15
|
+
export type Tr = { code: string; t: Type; nd: boolean };
|
|
16
|
+
|
|
17
|
+
const KEYWORDS = new Set([
|
|
18
|
+
'at', 'by', 'do', 'else', 'end', 'fun', 'from', 'have', 'if', 'in', 'let', 'match', 'open', 'show', 'then', 'with',
|
|
19
|
+
'where', 'structure', 'class', 'instance', 'def', 'theorem', 'namespace', 'section', 'variable', 'import', 'mut',
|
|
20
|
+
'return', 'for', 'unless', 'try', 'catch', 'finally', 'break', 'continue', 'Type', 'Prop', 'Sort', 'local', 'deriving',
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
export function ident(name: string): string {
|
|
24
|
+
return KEYWORDS.has(name) || !/^[A-Za-z_][A-Za-z0-9_']*$/.test(name) ? `«${name}»` : name;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** How generated code refers to the composition it is part of. */
|
|
28
|
+
export interface Env {
|
|
29
|
+
/** Lean types of nodes and agents, the ring size and the number of agents. */
|
|
30
|
+
node: string;
|
|
31
|
+
agent: string;
|
|
32
|
+
N: string;
|
|
33
|
+
k: string;
|
|
34
|
+
/** Name of a record or enum type. */
|
|
35
|
+
typeName(t: Type): string;
|
|
36
|
+
/** The Lean type of the algorithm's public state. */
|
|
37
|
+
pub: string;
|
|
38
|
+
/** `C.world.f`, given the code of the configuration. */
|
|
39
|
+
worldField(config: string, field: string): string;
|
|
40
|
+
agentFlag(agent: string, flag: string): string;
|
|
41
|
+
agentField(agent: string, field: string): string;
|
|
42
|
+
decisionAction(decision: string, action: string): string;
|
|
43
|
+
/** Experiment parameters (initialisation and domains only). */
|
|
44
|
+
param(name: string): string;
|
|
45
|
+
paramType(name: string): Type;
|
|
46
|
+
/** An effect, in a component's hook. */
|
|
47
|
+
effect?(world: [string, string][], raise: [string, { pred?: string; set?: string }][]): string;
|
|
48
|
+
/** Variables of the round available in hooks and sensors. */
|
|
49
|
+
ts?: string;
|
|
50
|
+
ds?: string;
|
|
51
|
+
layout: {
|
|
52
|
+
pub: Type; mem: Type; worldType: Type;
|
|
53
|
+
flags: { name: string }[]; agentFields: { name: string; type: Type }[]; actions: { name: string; type: Type }[];
|
|
54
|
+
};
|
|
55
|
+
defs: Map<string, Def>;
|
|
56
|
+
defName(name: string): string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type Scope = { vars: Map<string, { code: string; t: Type }>; config?: string };
|
|
60
|
+
|
|
61
|
+
export class Translator {
|
|
62
|
+
private fresh = 0;
|
|
63
|
+
|
|
64
|
+
constructor(readonly env: Env) {}
|
|
65
|
+
|
|
66
|
+
resolve(t: Type): Type {
|
|
67
|
+
return resolvePub(t, this.env.layout.pub);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
type(t: Type): string {
|
|
71
|
+
const env = this.env;
|
|
72
|
+
switch (t.k) {
|
|
73
|
+
case 'unit': return 'Unit';
|
|
74
|
+
case 'bool': case 'frame': return 'Bool';
|
|
75
|
+
case 'nat': return 'Nat';
|
|
76
|
+
case 'int': return 'Int';
|
|
77
|
+
case 'string': return 'String';
|
|
78
|
+
case 'node': return env.node;
|
|
79
|
+
case 'agent': return env.agent;
|
|
80
|
+
case 'move': return 'Move';
|
|
81
|
+
case 'verdict': return 'Verdict';
|
|
82
|
+
case 'pub': return env.pub;
|
|
83
|
+
case 'option': return `Option (${this.type(t.of)})`;
|
|
84
|
+
case 'list': case 'multiset': return `List (${this.type(t.of)})`;
|
|
85
|
+
case 'enum': case 'record': return env.typeName(t);
|
|
86
|
+
default: throw new GenError(`no Lean type for ${showType(t)}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
ctor(t: Type, c: string): string {
|
|
91
|
+
if (t.k === 'move') return `Move.${c}`;
|
|
92
|
+
if (t.k === 'verdict') return `Verdict.${c}`;
|
|
93
|
+
if (t.k === 'enum') return `${this.env.typeName(t)}.${ident(c)}`;
|
|
94
|
+
throw new GenError(`no constructors for ${showType(t)}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
lit(v: Json, t: Type): string {
|
|
98
|
+
t = this.resolve(t);
|
|
99
|
+
switch (t.k) {
|
|
100
|
+
case 'unit': return '()';
|
|
101
|
+
case 'bool': case 'frame': return String(v);
|
|
102
|
+
case 'nat': return String(v);
|
|
103
|
+
case 'int': return `(${v} : Int)`;
|
|
104
|
+
case 'string': return JSON.stringify(v);
|
|
105
|
+
case 'node': return `(Fin.ofNat ${this.env.N} ${v})`;
|
|
106
|
+
case 'move': case 'verdict': case 'enum': return this.ctor(t, v as string);
|
|
107
|
+
case 'option': return v === null ? `(none : ${this.type(t)})` : `(some ${this.lit(v, t.of)})`;
|
|
108
|
+
case 'list': case 'multiset': return `([${(v as Json[]).map(x => this.lit(x, t.of)).join(', ')}] : ${this.type(t)})`;
|
|
109
|
+
case 'record': {
|
|
110
|
+
const o = v as Record<string, Json>;
|
|
111
|
+
return `({ ${t.fields.map(f => `${ident(f.name)} := ${this.lit(o[f.name], f.type)}`).join(', ')} } : ${this.type(t)})`;
|
|
112
|
+
}
|
|
113
|
+
default: throw new GenError(`no literals of type ${showType(t)}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private name(prefix: string) {
|
|
118
|
+
return `_${prefix}${++this.fresh}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** The code of `tr` as a term of type `ND`. */
|
|
122
|
+
nd(tr: Tr): string {
|
|
123
|
+
return tr.nd ? tr.code : `(pure ${wrap(tr.code)})`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Evaluates `parts` (in order) and combines their values; binds those with choices. */
|
|
127
|
+
private seq(parts: Tr[], t: Type, build: (codes: string[]) => string): Tr {
|
|
128
|
+
if (parts.every(p => !p.nd)) return { code: build(parts.map(p => p.code)), t, nd: false };
|
|
129
|
+
const binds: string[] = [];
|
|
130
|
+
const codes = parts.map(p => {
|
|
131
|
+
if (!p.nd) return p.code;
|
|
132
|
+
const n = this.name('v');
|
|
133
|
+
binds.push(`let ${n} ← ${p.code}`);
|
|
134
|
+
return n;
|
|
135
|
+
});
|
|
136
|
+
return { code: `(do ${binds.join('; ')}; pure ${wrap(build(codes))})`, t, nd: true };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
root(e: Expr, vars: [string, string, Type][], config?: string): Tr {
|
|
140
|
+
const scope: Scope = { vars: new Map(vars.map(([n, code, t]) => [n, { code, t: this.resolve(t) }])), config };
|
|
141
|
+
return this.tr(e, scope);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
tr(e: Expr, scope: Scope): Tr {
|
|
145
|
+
const env = this.env;
|
|
146
|
+
switch (e.e) {
|
|
147
|
+
case 'lit':
|
|
148
|
+
return { code: this.lit(e.value, e.type), t: this.resolve(e.type), nd: false };
|
|
149
|
+
case 'var': {
|
|
150
|
+
const v = scope.vars.get(e.name) ?? fail(`unknown variable ${e.name}`);
|
|
151
|
+
return { code: v.code, t: v.t, nd: false };
|
|
152
|
+
}
|
|
153
|
+
case 'param':
|
|
154
|
+
return { code: env.param(e.name), t: env.paramType(e.name), nd: false };
|
|
155
|
+
case 'let': {
|
|
156
|
+
const v = this.tr(e.value, scope);
|
|
157
|
+
const n = ident(e.name);
|
|
158
|
+
const inner: Scope = { vars: new Map(scope.vars).set(e.name, { code: n, t: v.t }), config: v.t.k === 'config' ? n : scope.config };
|
|
159
|
+
const b = this.tr(e.body, inner);
|
|
160
|
+
if (!v.nd) return { code: `(let ${n} := ${v.code}; ${b.code})`, t: b.t, nd: b.nd };
|
|
161
|
+
return { code: `(do let ${n} ← ${v.code}; ${this.nd(b)})`, t: b.t, nd: true };
|
|
162
|
+
}
|
|
163
|
+
case 'if': {
|
|
164
|
+
const [c, a, b] = [this.tr(e.cond, scope), this.tr(e.then, scope), this.tr(e.else, scope)];
|
|
165
|
+
if (!c.nd && !a.nd && !b.nd) return { code: `(if ${c.code} then ${a.code} else ${b.code})`, t: a.t, nd: false };
|
|
166
|
+
const body = (cond: string) => `if ${cond} then ${this.nd(a)} else ${this.nd(b)}`;
|
|
167
|
+
if (!c.nd) return { code: `(${body(c.code)})`, t: a.t, nd: true };
|
|
168
|
+
const n = this.name('c');
|
|
169
|
+
return { code: `(do let ${n} ← ${c.code}; ${body(n)})`, t: a.t, nd: true };
|
|
170
|
+
}
|
|
171
|
+
case 'match': {
|
|
172
|
+
const on = this.tr(e.on, scope);
|
|
173
|
+
const cases = Object.entries(e.cases).map(([c, b]) => [c, this.tr(b, scope)] as const);
|
|
174
|
+
const other = e.else ? this.tr(e.else, scope) : undefined;
|
|
175
|
+
const branches = [...cases.map(([, b]) => b), ...(other ? [other] : [])];
|
|
176
|
+
const nd = branches.some(b => b.nd);
|
|
177
|
+
const code = (x: string) => `(match ${x} with ${cases.map(([c, b]) => `| ${this.ctor(on.t, c)} => ${nd ? this.nd(b) : b.code}`).join(' ')}${other ? ` | _ => ${nd ? this.nd(other) : other.code}` : ''})`;
|
|
178
|
+
if (!on.nd) return { code: code(on.code), t: branches[0].t, nd };
|
|
179
|
+
const n = this.name('m');
|
|
180
|
+
return { code: `(do let ${n} ← ${on.code}; ${code(n)})`, t: branches[0].t, nd: true };
|
|
181
|
+
}
|
|
182
|
+
case 'fn':
|
|
183
|
+
return fail('a function is only allowed as an argument of a builtin');
|
|
184
|
+
case 'record': {
|
|
185
|
+
const names = Object.keys(e.fields);
|
|
186
|
+
const parts = names.map(n => this.tr(e.fields[n], scope));
|
|
187
|
+
const t = T.record(names.map((name, i) => ({ name, type: parts[i].t })));
|
|
188
|
+
return this.seq(parts, t, cs => `({ ${names.map((n, i) => `${ident(n)} := ${cs[i]}`).join(', ')} } : ${this.type(t)})`);
|
|
189
|
+
}
|
|
190
|
+
case 'with': {
|
|
191
|
+
const on = this.tr(e.on, scope);
|
|
192
|
+
const names = Object.keys(e.fields);
|
|
193
|
+
const parts = [on, ...names.map(n => this.tr(e.fields[n], scope))];
|
|
194
|
+
return this.seq(parts, on.t, ([o, ...cs]) => `{ ${wrap(o)} with ${names.map((n, i) => `${ident(n)} := ${cs[i]}`).join(', ')} }`);
|
|
195
|
+
}
|
|
196
|
+
case 'get':
|
|
197
|
+
return this.get(e, scope);
|
|
198
|
+
case 'choose': {
|
|
199
|
+
const from = this.tr(e.from, scope);
|
|
200
|
+
if (!isCollection(from.t)) fail('choose: expected a collection');
|
|
201
|
+
const such = e.such ? this.lambda(e.such, [from.t.of], scope).code : undefined;
|
|
202
|
+
const site = JSON.stringify(e.site);
|
|
203
|
+
const pick = (l: string) => such ? `ND.chooseSuch ${site} ${wrap(l)} ${such}` : `ND.choose ${site} ${wrap(l)}`;
|
|
204
|
+
if (!from.nd) return { code: `(${pick(from.code)})`, t: from.t.of, nd: true };
|
|
205
|
+
const n = this.name('l');
|
|
206
|
+
return { code: `(do let ${n} ← ${from.code}; ${pick(n)})`, t: from.t.of, nd: true };
|
|
207
|
+
}
|
|
208
|
+
case 'effect': {
|
|
209
|
+
if (!env.effect) fail('effects are only generated in components');
|
|
210
|
+
const world = Object.entries(e.world ?? {}).map(([n, x]) => [n, this.tr(x, scope)] as const);
|
|
211
|
+
const raises = Object.entries(e.raise ?? {}).map(([flag, x]) => {
|
|
212
|
+
if (x.e === 'fn') return [flag, { pred: this.lambda(x, [T.agent], scope).code }] as const;
|
|
213
|
+
return [flag, { set: this.tr(x, scope) }] as const;
|
|
214
|
+
});
|
|
215
|
+
const parts = [...world.map(([, t]) => t), ...raises.flatMap(([, r]) => 'set' in r ? [r.set!] : [])];
|
|
216
|
+
return this.seq(parts, T.effect, cs => {
|
|
217
|
+
let j = world.length;
|
|
218
|
+
return env.effect!(
|
|
219
|
+
world.map(([n], i) => [n, cs[i]]),
|
|
220
|
+
raises.map(([flag, r]) => [flag, 'pred' in r ? { pred: r.pred } : { set: cs[j++] }]),
|
|
221
|
+
);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
case 'call':
|
|
225
|
+
return BUILTINS[e.fn] ? this.builtin(e, scope) : this.callDef(e, scope);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private get(e: Extract<Expr, { e: 'get' }>, scope: Scope): Tr {
|
|
230
|
+
const env = this.env;
|
|
231
|
+
// `C.world.f`
|
|
232
|
+
if (e.on.e === 'get' && e.on.field === 'world') {
|
|
233
|
+
const C = this.tr(e.on.on, scope);
|
|
234
|
+
if (C.t.k === 'config') {
|
|
235
|
+
const f = env.layout.worldType.k === 'record' ? fieldType(env.layout.worldType, e.field) : undefined;
|
|
236
|
+
if (!f) fail(`unknown world field ${e.field}`);
|
|
237
|
+
return this.seq([C], this.resolve(f), ([c]) => env.worldField(c, e.field));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const on = this.tr(e.on, scope);
|
|
241
|
+
switch (on.t.k) {
|
|
242
|
+
case 'record': {
|
|
243
|
+
const ft = fieldType(on.t, e.field) ?? fail(`${showType(on.t)} has no field ${e.field}`);
|
|
244
|
+
return this.seq([on], ft, ([o]) => `${wrap(o)}.${ident(e.field)}`);
|
|
245
|
+
}
|
|
246
|
+
case 'config':
|
|
247
|
+
if (e.field === 'agents') return { code: `(List.finRange ${env.k})`, t: T.multiset(T.agent), nd: false };
|
|
248
|
+
return fail(`configurations have no field ${e.field} here`);
|
|
249
|
+
case 'agent': {
|
|
250
|
+
const C = scope.config ?? fail(`reading ${e.field} of an agent needs a configuration`);
|
|
251
|
+
const at = (a: string) => `${C}.agents[${a}]`;
|
|
252
|
+
const lay = env.layout;
|
|
253
|
+
if (e.field === 'pos') return this.seq([on], T.node, ([a]) => `${at(a)}.pos`);
|
|
254
|
+
if (e.field === 'pub') return this.seq([on], lay.pub, ([a]) => `${at(a)}.pub`);
|
|
255
|
+
if (e.field === 'mem') return this.seq([on], lay.mem, ([a]) => `${at(a)}.mem`);
|
|
256
|
+
if (lay.flags.some(f => f.name === e.field)) return this.seq([on], T.bool, ([a]) => env.agentFlag(at(a), e.field));
|
|
257
|
+
const f = lay.agentFields.find(f => f.name === e.field) ?? fail(`agents have no field ${e.field}`);
|
|
258
|
+
return this.seq([on], f.type, ([a]) => env.agentField(at(a), e.field));
|
|
259
|
+
}
|
|
260
|
+
case 'decision': {
|
|
261
|
+
if (e.field === 'move' || e.field === 'decided') {
|
|
262
|
+
return this.seq([on], e.field === 'move' ? T.move : T.bool, ([d]) => `${wrap(d)}.${e.field}`);
|
|
263
|
+
}
|
|
264
|
+
const a = env.layout.actions.find(a => a.name === e.field) ?? fail(`decisions have no action ${e.field}`);
|
|
265
|
+
return this.seq([on], a.type, ([d]) => env.decisionAction(d, e.field));
|
|
266
|
+
}
|
|
267
|
+
default:
|
|
268
|
+
return fail(`${showType(on.t)} has no fields`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** A deterministic lambda, with typed parameters. */
|
|
273
|
+
lambda(e: Expr, params: Type[], scope: Scope): { code: string; ret: Type } {
|
|
274
|
+
if (e.e !== 'fn') fail('expected a function');
|
|
275
|
+
const vars = new Map(scope.vars);
|
|
276
|
+
const binders = e.params.map((p, i) => {
|
|
277
|
+
const t = this.resolve(params[i]);
|
|
278
|
+
const n = ident(p.name);
|
|
279
|
+
vars.set(p.name, { code: n, t });
|
|
280
|
+
return `(${n} : ${this.type(t)})`;
|
|
281
|
+
});
|
|
282
|
+
const body = this.tr(e.body, { vars, config: scope.config });
|
|
283
|
+
if (body.nd) fail('a function passed to a builtin cannot contain choices');
|
|
284
|
+
return { code: `(fun ${binders.join(' ')} => ${body.code})`, ret: body.t };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private callDef(e: Extract<Expr, { e: 'call' }>, scope: Scope): Tr {
|
|
288
|
+
const def = this.env.defs.get(e.fn) ?? fail(`unknown function ${e.fn}`);
|
|
289
|
+
const args = e.args.map(a => this.tr(a, scope));
|
|
290
|
+
const ret = this.resolve(def.ret);
|
|
291
|
+
const call = (cs: string[]) => `(${this.env.defName(e.fn)} ${cs.map(wrap).join(' ')})`;
|
|
292
|
+
// Definitions are generated before their callers, which marks those with choices.
|
|
293
|
+
if (!(def as Def & { nd?: boolean }).nd) return this.seq(args, ret, call);
|
|
294
|
+
return this.bindSeq(args, ret, call);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** Like `seq`, when the combination is itself a term of type `ND`. */
|
|
298
|
+
private bindSeq(parts: Tr[], t: Type, build: (codes: string[]) => string): Tr {
|
|
299
|
+
const binds: string[] = [];
|
|
300
|
+
const codes = parts.map(p => {
|
|
301
|
+
if (!p.nd) return p.code;
|
|
302
|
+
const n = this.name('v');
|
|
303
|
+
binds.push(`let ${n} ← ${p.code}`);
|
|
304
|
+
return n;
|
|
305
|
+
});
|
|
306
|
+
return { code: binds.length ? `(do ${binds.join('; ')}; ${build(codes)})` : build(codes), t, nd: true };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private builtin(e: Extract<Expr, { e: 'call' }>, scope: Scope): Tr {
|
|
310
|
+
const env = this.env;
|
|
311
|
+
const sig = BUILTINS[e.fn];
|
|
312
|
+
const types: Type[] = [];
|
|
313
|
+
const parts: Tr[] = [];
|
|
314
|
+
const lambdas: (string | undefined)[] = [];
|
|
315
|
+
e.args.forEach((arg, i) => {
|
|
316
|
+
const lam = sig.lambdas?.[i];
|
|
317
|
+
if (lam) {
|
|
318
|
+
const params = lam(types);
|
|
319
|
+
if (typeof params === 'string') fail(params);
|
|
320
|
+
const l = this.lambda(arg, params, scope);
|
|
321
|
+
types.push(T.fn(params, l.ret));
|
|
322
|
+
lambdas.push(l.code);
|
|
323
|
+
} else {
|
|
324
|
+
const p = this.tr(arg, scope);
|
|
325
|
+
types.push(p.t);
|
|
326
|
+
parts.push(p);
|
|
327
|
+
lambdas.push(undefined);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
const ret = sig.ret(types);
|
|
331
|
+
if (typeof ret === 'string') fail(ret);
|
|
332
|
+
const C = scope.config;
|
|
333
|
+
return this.seq(parts, ret, vals => {
|
|
334
|
+
let j = 0;
|
|
335
|
+
const a = lambdas.map(l => l ?? vals[j++]);
|
|
336
|
+
const w = a.map(wrap);
|
|
337
|
+
const t0 = types[0];
|
|
338
|
+
switch (e.fn) {
|
|
339
|
+
case 'not': return `(!${w[0]})`;
|
|
340
|
+
case 'and': return `(${w.join(' && ')})`;
|
|
341
|
+
case 'or': return `(${w.join(' || ')})`;
|
|
342
|
+
case 'implies': return `(!${w[0]} || ${w[1]})`;
|
|
343
|
+
case 'eq': return `(${w[0]} == ${w[1]})`;
|
|
344
|
+
case 'ne': return `(${w[0]} != ${w[1]})`;
|
|
345
|
+
case 'lt': return `(decide (${w[0]} < ${w[1]}))`;
|
|
346
|
+
case 'le': return `(decide (${w[0]} ≤ ${w[1]}))`;
|
|
347
|
+
case 'gt': return `(decide (${w[0]} > ${w[1]}))`;
|
|
348
|
+
case 'ge': return `(decide (${w[0]} ≥ ${w[1]}))`;
|
|
349
|
+
case 'add': return `(${w[0]} + ${w[1]})`;
|
|
350
|
+
case 'sub': return `(${w[0]} - ${w[1]})`;
|
|
351
|
+
case 'mul': return `(${w[0]} * ${w[1]})`;
|
|
352
|
+
case 'div': return `(${w[0]} / ${w[1]})`;
|
|
353
|
+
case 'mod': return `(${w[0]} % ${w[1]})`;
|
|
354
|
+
case 'min': return `(min ${w[0]} ${w[1]})`;
|
|
355
|
+
case 'max': return `(max ${w[0]} ${w[1]})`;
|
|
356
|
+
case 'toInt': return `(Int.ofNat ${w[0]})`;
|
|
357
|
+
case 'some': return `(some ${w[0]})`;
|
|
358
|
+
case 'isSome': return `${w[0]}.isSome`;
|
|
359
|
+
case 'isNone': return `${w[0]}.isNone`;
|
|
360
|
+
case 'getD': return `(${w[0]}.getD ${w[1]})`;
|
|
361
|
+
case 'length': return `${w[0]}.length`;
|
|
362
|
+
case 'isEmpty': return `${w[0]}.isEmpty`;
|
|
363
|
+
case 'filter': return `(${w[0]}.filter ${w[1]})`;
|
|
364
|
+
case 'any': return `(${w[0]}.any ${w[1]})`;
|
|
365
|
+
case 'all': return `(${w[0]}.all ${w[1]})`;
|
|
366
|
+
case 'count': return `(${w[0]}.countP ${w[1]})`;
|
|
367
|
+
case 'map': return `(${w[0]}.map ${w[1]})`;
|
|
368
|
+
case 'sum': return `${w[0]}.sum`;
|
|
369
|
+
case 'mem': return `(${w[1]}.contains ${w[0]})`;
|
|
370
|
+
case 'range': return `(List.range ${w[0]})`;
|
|
371
|
+
case 'toMultiset': return a[0];
|
|
372
|
+
case 'subsets': case 'subsetsUpTo':
|
|
373
|
+
if (t0.k === 'multiset' && (t0 as { of: Type }).of.k !== 'agent') {
|
|
374
|
+
fail(`${e.fn} of a multiset of ${showType((t0 as { of: Type }).of)}: equal elements are not merged in Lean yet`);
|
|
375
|
+
}
|
|
376
|
+
return e.fn === 'subsets' ? `(B.subsets ${w[0]})` : `(B.subsetsUpTo ${w[1]} ${w[0]})`;
|
|
377
|
+
case 'dedup': return `${w[0]}.eraseDups`;
|
|
378
|
+
case 'diff': return `(B.diff ${w[0]} ${w[1]})`;
|
|
379
|
+
case 'append': return `(${w[0]} ++ ${w[1]})`;
|
|
380
|
+
case 'list': return `[${a.join(', ')}]`;
|
|
381
|
+
case 'at': return `(${w[0]}[${a[1]}]!)`;
|
|
382
|
+
case 'ofFn': return `(B.ofFn ${w[0]} ${w[1]})`;
|
|
383
|
+
case 'agents': return `(List.finRange ${env.k})`;
|
|
384
|
+
case 'nodes': return `(List.finRange ${env.N})`;
|
|
385
|
+
case 'size': return env.N;
|
|
386
|
+
case 'correct': return `(FlagSet.correct ${C ?? fail('correct needs a configuration')}.agents[${a[0]}].flags)`;
|
|
387
|
+
case 'present': return `(FlagSet.present ${C ?? fail('present needs a configuration')}.agents[${a[0]}].flags)`;
|
|
388
|
+
case 'decisionOf': return `(${env.ds ?? fail('decisionOf outside beforeMove')} ${w[0]})`;
|
|
389
|
+
case 'target': return `(Ring.target ${w[0]} ${w[1]})`;
|
|
390
|
+
case 'advance': return `(Ring.advance ${w[0]} ${w[1]} ${w[2]})`;
|
|
391
|
+
case 'edgePresent': return `(Ring.canMove ${env.ts ?? fail('no topology state here')} ${w[0]} ${w[1]})`;
|
|
392
|
+
case 'offset': return `(Ring.offset ${w[0]} ${w[1]} ${w[2]})`;
|
|
393
|
+
case 'toGlobal': return `(toGlobal ${w[0]} ${w[1]})`;
|
|
394
|
+
case 'opposite': return `(Move.flip ${w[0]})`;
|
|
395
|
+
case 'frames': return '[false, true]';
|
|
396
|
+
case 'node': return `(Fin.ofNat ${env.N} ${w[0]})`;
|
|
397
|
+
default: return fail(`builtin ${e.fn} has no Lean translation`);
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export function fail(msg: string): never {
|
|
404
|
+
throw new GenError(msg);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/** Parenthesizes a term unless it is atomic. */
|
|
408
|
+
export function wrap(code: string): string {
|
|
409
|
+
return /^[A-Za-z0-9_.'«»!]+$/.test(code) || /^\(.*\)$/s.test(code) && balanced(code.slice(1, -1)) ? code : `(${code})`;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function balanced(s: string): boolean {
|
|
413
|
+
let depth = 0;
|
|
414
|
+
for (const ch of s) {
|
|
415
|
+
if (ch === '(') depth++;
|
|
416
|
+
else if (ch === ')' && --depth < 0) return false;
|
|
417
|
+
}
|
|
418
|
+
return depth === 0;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export { MOVES, VERDICTS, typeEq };
|
package/README.md
DELETED