lemmascript 0.0.1 → 0.2.0

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.
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Lean IR → text. Trivial pretty-printer.
3
+ * No logic, no type decisions — just serialization.
4
+ */
5
+ // ── Lean keyword escaping ────────────────────────────────────
6
+ const LEAN_KEYWORDS = new Set([
7
+ "def", "theorem", "lemma", "example", "structure", "class", "instance",
8
+ "inductive", "where", "match", "with", "if", "then", "else", "do",
9
+ "let", "mut", "return", "for", "in", "while", "break", "continue",
10
+ "import", "open", "section", "namespace", "end", "set_option",
11
+ "variable", "axiom", "constant", "private", "protected", "noncomputable",
12
+ "partial", "unsafe", "macro", "syntax", "by", "fun", "have", "show",
13
+ "at", "from", "to", "deriving", "extends", "true", "false",
14
+ ]);
15
+ function escapeName(name) {
16
+ return LEAN_KEYWORDS.has(name) ? `«${name}»` : name;
17
+ }
18
+ // ── Operator precedence (for parenthesization) ──────────────
19
+ const PREC = {
20
+ "→": 1, "∨": 2, "∧": 3,
21
+ "=": 4, "≠": 4, "≥": 4, "≤": 4, ">": 4, "<": 4,
22
+ "+": 5, "-": 5, "*": 6, "/": 6, "%": 6,
23
+ };
24
+ function prec(op) { return PREC[op] ?? 10; }
25
+ // ── Expression emission ─────────────────────────────────────
26
+ function emitExpr(e, parentPrec) {
27
+ switch (e.kind) {
28
+ case "var": return escapeName(e.name);
29
+ case "num": return `${e.value}`;
30
+ case "bool": return e.value ? "true" : "false";
31
+ case "str": return `"${e.value}"`;
32
+ case "constructor": return `.${e.name}`;
33
+ case "arrayLiteral":
34
+ if (e.elems.length === 0)
35
+ return `#[]`;
36
+ return `#[${e.elems.map(el => emitExpr(el)).join(", ")}]`;
37
+ case "dotCall": {
38
+ const obj = emitExpr(e.obj);
39
+ const wrap = e.obj.kind === "binop" || e.obj.kind === "app" || e.obj.kind === "dotCall";
40
+ const receiver = wrap ? `(${obj})` : obj;
41
+ const args = e.args.map(a => (a.kind === "binop" || a.kind === "unop" || a.kind === "implies" || a.kind === "app") ? `(${emitExpr(a)})` : emitExpr(a));
42
+ return args.length > 0 ? `${receiver}.${e.method} ${args.join(" ")}` : `${receiver}.${e.method}`;
43
+ }
44
+ case "lambda": {
45
+ const params = e.params.map(p => p.name).join(" ");
46
+ // Single return statement → expression lambda
47
+ if (e.body.length === 1 && e.body[0].kind === "return") {
48
+ return `(fun ${params} => ${emitExpr(e.body[0].value)})`;
49
+ }
50
+ // Multi-statement → do block
51
+ return `(fun ${params} => do\n${emitStmts(e.body, 2)})`;
52
+ }
53
+ case "unop":
54
+ if (e.op === "¬")
55
+ return `¬(${emitExpr(e.expr)})`;
56
+ if (e.op === "-" && e.expr.kind === "num")
57
+ return `-${e.expr.value}`;
58
+ return `(-${emitExpr(e.expr)})`;
59
+ case "binop": {
60
+ const s = `${emitExpr(e.left, prec(e.op))} ${e.op} ${emitExpr(e.right, prec(e.op))}`;
61
+ return (parentPrec !== undefined && prec(e.op) < parentPrec) ? `(${s})` : s;
62
+ }
63
+ case "implies": {
64
+ const parts = [...e.premises.map(p => emitExpr(p)), emitExpr(e.conclusion)];
65
+ const s = parts.join(" → ");
66
+ return parentPrec !== undefined ? `(${s})` : s;
67
+ }
68
+ case "app": {
69
+ const args = e.args.map(a => (a.kind === "binop" || a.kind === "unop" || a.kind === "implies" || a.kind === "app") ? `(${emitExpr(a)})` : emitExpr(a));
70
+ return `${e.fn} ${args.join(" ")}`;
71
+ }
72
+ case "field": {
73
+ const obj = emitExpr(e.obj);
74
+ const wrap = e.obj.kind !== "var" && e.obj.kind !== "num" && e.obj.kind !== "bool";
75
+ return wrap ? `(${obj}).${escapeName(e.field)}` : `${obj}.${escapeName(e.field)}`;
76
+ }
77
+ case "toNat": {
78
+ const inner = emitExpr(e.expr);
79
+ const wrap = e.expr.kind !== "var" && e.expr.kind !== "num";
80
+ return wrap ? `(${inner}).toNat` : `${inner}.toNat`;
81
+ }
82
+ case "index":
83
+ return `${emitExpr(e.arr)}[${emitExpr(e.idx)}]!`;
84
+ case "record": {
85
+ const fields = e.fields.map(f => `${escapeName(f.name)} := ${emitExpr(f.value)}`);
86
+ if (e.spread)
87
+ return `{ ${emitExpr(e.spread)} with ${fields.join(", ")} }`;
88
+ return `{ ${fields.join(", ")} }`;
89
+ }
90
+ case "if":
91
+ return `if ${emitExpr(e.cond)} then ${emitExpr(e.then)} else ${emitExpr(e.else)}`;
92
+ case "match": {
93
+ const arms = e.arms.map(a => `| ${a.pattern} => ${emitExpr(a.body)}`);
94
+ return `match ${e.scrutinee} with ${arms.join(" ")}`;
95
+ }
96
+ case "forall": return `∀ ${e.var} : ${e.type}, ${emitExpr(e.body)}`;
97
+ case "exists": return `∃ ${e.var} : ${e.type}, ${emitExpr(e.body)}`;
98
+ case "let": return `let ${e.name} := ${emitExpr(e.value)}\n${emitExpr(e.body)}`;
99
+ }
100
+ }
101
+ // ── Statement emission ──────────────────────────────────────
102
+ function emitStmts(stmts, indent) {
103
+ const pad = " ".repeat(indent);
104
+ return stmts.map(s => emitStmt(s, indent)).join("\n");
105
+ }
106
+ function emitStmt(s, indent) {
107
+ const pad = " ".repeat(indent);
108
+ switch (s.kind) {
109
+ case "let":
110
+ return s.mutable
111
+ ? `${pad}let mut ${escapeName(s.name)} : ${s.type} := ${emitExpr(s.value)}`
112
+ : `${pad}let ${escapeName(s.name)} := ${emitExpr(s.value)}`;
113
+ case "assign": return `${pad}${escapeName(s.target)} := ${emitExpr(s.value)}`;
114
+ case "bind": return `${pad}${escapeName(s.target)} ← ${emitExpr(s.value)}`;
115
+ case "let-bind": return `${pad}let ${s.name} ← ${emitExpr(s.value)}`;
116
+ case "return": return `${pad}return ${emitExpr(s.value)}`;
117
+ case "break": return `${pad}break`;
118
+ case "continue": return `${pad}continue`;
119
+ case "if": {
120
+ let out = `${pad}if ${emitExpr(s.cond)} then\n${emitStmts(s.then, indent + 1)}`;
121
+ if (s.else.length > 0) {
122
+ if (s.else.length === 1 && s.else[0].kind === "if") {
123
+ const ei = s.else[0];
124
+ out += `\n${pad}else if ${emitExpr(ei.cond)} then\n${emitStmts(ei.then, indent + 1)}`;
125
+ if (ei.else.length > 0)
126
+ out += `\n${pad}else\n${emitStmts(ei.else, indent + 1)}`;
127
+ }
128
+ else {
129
+ out += `\n${pad}else\n${emitStmts(s.else, indent + 1)}`;
130
+ }
131
+ }
132
+ return out;
133
+ }
134
+ case "match": {
135
+ const lines = [`${pad}match ${s.scrutinee} with`];
136
+ for (const arm of s.arms) {
137
+ lines.push(`${pad}| ${arm.pattern} =>`);
138
+ lines.push(emitStmts(arm.body, indent + 1));
139
+ }
140
+ return lines.join("\n");
141
+ }
142
+ case "while": {
143
+ const lines = [`${pad}while ${emitExpr(s.cond)}`];
144
+ for (const inv of s.invariants)
145
+ lines.push(`${pad} invariant ${emitExpr(inv)}`);
146
+ if (s.doneWith)
147
+ lines.push(`${pad} done_with ${emitExpr(s.doneWith)}`);
148
+ if (s.decreasing)
149
+ lines.push(`${pad} decreasing ${emitExpr(s.decreasing)}`);
150
+ lines.push(`${pad}do`);
151
+ lines.push(emitStmts(s.body, indent + 1));
152
+ return lines.join("\n");
153
+ }
154
+ case "forin": {
155
+ const lines = [`${pad}for ${s.idx} in [:${emitExpr(s.bound)}]`];
156
+ for (const inv of s.invariants)
157
+ lines.push(`${pad} invariant ${emitExpr(inv)}`);
158
+ lines.push(`${pad}do`);
159
+ lines.push(emitStmts(s.body, indent + 1));
160
+ return lines.join("\n");
161
+ }
162
+ }
163
+ }
164
+ // ── Declaration emission ─────────────────────────────────────
165
+ function emitDecl(d) {
166
+ switch (d.kind) {
167
+ case "inductive": {
168
+ const lines = [`inductive ${d.name} where`];
169
+ for (const c of d.constructors) {
170
+ if (c.fields.length === 0) {
171
+ lines.push(` | ${c.name} : ${d.name}`);
172
+ }
173
+ else {
174
+ const params = c.fields.map(f => `(${escapeName(f.name)} : ${f.type})`).join(" ");
175
+ lines.push(` | ${c.name} ${params} : ${d.name}`);
176
+ }
177
+ }
178
+ if (d.deriving.length > 0)
179
+ lines.push(`deriving ${d.deriving.join(", ")}`);
180
+ return lines.join("\n");
181
+ }
182
+ case "structure": {
183
+ const lines = [`structure ${d.name} where`];
184
+ for (const f of d.fields)
185
+ lines.push(` ${escapeName(f.name)} : ${f.type}`);
186
+ if (d.deriving.length > 0)
187
+ lines.push(`deriving ${d.deriving.join(", ")}`);
188
+ return lines.join("\n");
189
+ }
190
+ case "def": {
191
+ const params = d.params.map(p => `(${escapeName(p.name)} : ${p.type})`).join(" ");
192
+ return `def ${d.name} ${params} : ${d.returnType} :=\n${emitPureExpr(d.body, 1)}`;
193
+ }
194
+ case "method": {
195
+ const params = d.params.map(p => `(${escapeName(p.name)} : ${p.type})`).join(" ");
196
+ const lines = [`method ${d.name} ${params} return (res : ${d.returnType})`];
197
+ for (const r of d.requires)
198
+ lines.push(` require ${emitExpr(r)}`);
199
+ for (const e of d.ensures)
200
+ lines.push(` ensures ${emitExpr(e)}`);
201
+ lines.push(" do");
202
+ lines.push(emitStmts(d.body, 2));
203
+ return lines.join("\n");
204
+ }
205
+ case "namespace": {
206
+ const lines = [`namespace ${d.name}`];
207
+ for (const inner of d.decls)
208
+ lines.push("", emitDecl(inner));
209
+ lines.push("", `end ${d.name}`);
210
+ return lines.join("\n");
211
+ }
212
+ }
213
+ }
214
+ /** Emit a pure expression with indented if/match blocks. */
215
+ function emitPureExpr(e, indent) {
216
+ const pad = " ".repeat(indent);
217
+ switch (e.kind) {
218
+ case "if":
219
+ return `${pad}if ${emitExpr(e.cond)} then\n${emitPureExpr(e.then, indent + 1)}\n${pad}else\n${emitPureExpr(e.else, indent + 1)}`;
220
+ case "match": {
221
+ const lines = [`${pad}match ${e.scrutinee} with`];
222
+ for (const arm of e.arms) {
223
+ lines.push(`${pad}| ${arm.pattern} =>`);
224
+ lines.push(emitPureExpr(arm.body, indent + 1));
225
+ }
226
+ return lines.join("\n");
227
+ }
228
+ case "let":
229
+ return `${pad}let ${e.name} := ${emitExpr(e.value)}\n${emitPureExpr(e.body, indent)}`;
230
+ default:
231
+ return `${pad}${emitExpr(e)}`;
232
+ }
233
+ }
234
+ // ── File emission ────────────────────────────────────────────
235
+ export function emitFile(file) {
236
+ const lines = [];
237
+ if (file.comment) {
238
+ lines.push("/-");
239
+ lines.push(file.comment);
240
+ lines.push("-/");
241
+ }
242
+ for (const imp of file.imports)
243
+ lines.push(`import ${imp}`);
244
+ if (file.options.length > 0)
245
+ lines.push("");
246
+ for (const opt of file.options)
247
+ lines.push(`set_option ${opt.key} ${opt.value}`);
248
+ for (const decl of file.decls) {
249
+ lines.push("");
250
+ lines.push(emitDecl(decl));
251
+ }
252
+ return lines.join("\n") + "\n";
253
+ }