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.
- package/LICENSE +21 -0
- package/README.md +94 -3
- package/package.json +30 -20
- package/tools/dist/dafny-commands.js +98 -0
- package/tools/dist/dafny-emit.js +738 -0
- package/tools/dist/emit.js +253 -0
- package/tools/dist/extract.js +806 -0
- package/tools/dist/ir.js +7 -0
- package/tools/dist/lean-commands.js +35 -0
- package/tools/dist/lean-emit.js +393 -0
- package/tools/dist/lsc.js +119 -0
- package/tools/dist/rawir.js +10 -0
- package/tools/dist/resolve.js +717 -0
- package/tools/dist/specparser.js +305 -0
- package/tools/dist/transform.js +1091 -0
- package/tools/dist/typedir.js +7 -0
- package/tools/dist/types.js +71 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -4
- package/src/index.ts +0 -1
- package/tsconfig.json +0 -14
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spec expression parser.
|
|
3
|
+
* Parses //@ annotation expressions into RawExpr AST nodes.
|
|
4
|
+
*/
|
|
5
|
+
const MULTI_OPS = ["==>", "===", "!==", "==", "!=", ">=", "<=", "&&", "||"];
|
|
6
|
+
function tokenize(input) {
|
|
7
|
+
const tokens = [];
|
|
8
|
+
let i = 0;
|
|
9
|
+
while (i < input.length) {
|
|
10
|
+
if (/\s/.test(input[i])) {
|
|
11
|
+
i++;
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
if (input[i] === "\\" && input.slice(i + 1, i + 7) === "result") {
|
|
15
|
+
tokens.push({ type: "result", value: undefined });
|
|
16
|
+
i += 7;
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (input[i] === '"' || input[i] === "'") {
|
|
20
|
+
const quote = input[i];
|
|
21
|
+
i++;
|
|
22
|
+
let s = "";
|
|
23
|
+
while (i < input.length && input[i] !== quote)
|
|
24
|
+
s += input[i++];
|
|
25
|
+
if (i < input.length)
|
|
26
|
+
i++;
|
|
27
|
+
tokens.push({ type: "str", value: s });
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (/[0-9]/.test(input[i])) {
|
|
31
|
+
let value;
|
|
32
|
+
if (input[i] === "0" && i + 1 < input.length && input[i + 1] === "x") {
|
|
33
|
+
i += 2;
|
|
34
|
+
let hex = "";
|
|
35
|
+
while (i < input.length && /[0-9a-fA-F]/.test(input[i]))
|
|
36
|
+
hex += input[i++];
|
|
37
|
+
value = parseInt(hex, 16);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
let dec = "";
|
|
41
|
+
while (i < input.length && /[0-9]/.test(input[i]))
|
|
42
|
+
dec += input[i++];
|
|
43
|
+
value = parseInt(dec, 10);
|
|
44
|
+
}
|
|
45
|
+
if (i < input.length && input[i] === "n")
|
|
46
|
+
i++;
|
|
47
|
+
tokens.push({ type: "num", value });
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (/[a-zA-Z_]/.test(input[i])) {
|
|
51
|
+
let id = "";
|
|
52
|
+
while (i < input.length && /[a-zA-Z_0-9]/.test(input[i]))
|
|
53
|
+
id += input[i++];
|
|
54
|
+
tokens.push({ type: "ident", value: id });
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
let matched = false;
|
|
58
|
+
for (const op of MULTI_OPS) {
|
|
59
|
+
if (input.slice(i, i + op.length) === op) {
|
|
60
|
+
tokens.push({ type: "op", value: op });
|
|
61
|
+
i += op.length;
|
|
62
|
+
matched = true;
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (matched)
|
|
67
|
+
continue;
|
|
68
|
+
const ch = input[i];
|
|
69
|
+
if ("+-*/%><!".includes(ch)) {
|
|
70
|
+
tokens.push({ type: "op", value: ch });
|
|
71
|
+
}
|
|
72
|
+
else if ("()[],:.{}".includes(ch)) {
|
|
73
|
+
tokens.push({ type: "punc", value: ch });
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
throw new Error(`Unexpected '${ch}' at ${i} in: ${input}`);
|
|
77
|
+
}
|
|
78
|
+
i++;
|
|
79
|
+
}
|
|
80
|
+
return tokens;
|
|
81
|
+
}
|
|
82
|
+
// ── Parser ───────────────────────────────────────────────────
|
|
83
|
+
class Parser {
|
|
84
|
+
tokens;
|
|
85
|
+
pos = 0;
|
|
86
|
+
constructor(tokens) {
|
|
87
|
+
this.tokens = tokens;
|
|
88
|
+
}
|
|
89
|
+
peek() { return this.tokens[this.pos]; }
|
|
90
|
+
advance() { return this.tokens[this.pos++]; }
|
|
91
|
+
expect(type, value) {
|
|
92
|
+
const t = this.advance();
|
|
93
|
+
if (!t || t.type !== type || (value !== undefined && t.value !== value))
|
|
94
|
+
throw new Error(`Expected ${type}${value ? ` '${value}'` : ""}, got ${t ? JSON.stringify(t) : "EOF"}`);
|
|
95
|
+
return t;
|
|
96
|
+
}
|
|
97
|
+
match(type, value) {
|
|
98
|
+
const t = this.peek();
|
|
99
|
+
if (t && t.type === type && (value === undefined || t.value === value)) {
|
|
100
|
+
this.pos++;
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
parse() {
|
|
106
|
+
const r = this.parseImplies();
|
|
107
|
+
if (this.pos < this.tokens.length)
|
|
108
|
+
throw new Error(`Unexpected: ${JSON.stringify(this.peek())}`);
|
|
109
|
+
return r;
|
|
110
|
+
}
|
|
111
|
+
parseImplies() {
|
|
112
|
+
const left = this.parseOr();
|
|
113
|
+
if (this.match("op", "==>"))
|
|
114
|
+
return { kind: "binop", op: "==>", left, right: this.parseImplies() };
|
|
115
|
+
return left;
|
|
116
|
+
}
|
|
117
|
+
parseOr() {
|
|
118
|
+
let left = this.parseAnd();
|
|
119
|
+
while (this.match("op", "||"))
|
|
120
|
+
left = { kind: "binop", op: "||", left, right: this.parseAnd() };
|
|
121
|
+
return left;
|
|
122
|
+
}
|
|
123
|
+
parseAnd() {
|
|
124
|
+
let left = this.parseCmp();
|
|
125
|
+
while (this.match("op", "&&"))
|
|
126
|
+
left = { kind: "binop", op: "&&", left, right: this.parseCmp() };
|
|
127
|
+
return left;
|
|
128
|
+
}
|
|
129
|
+
parseCmp() {
|
|
130
|
+
const left = this.parseAdd();
|
|
131
|
+
const t = this.peek();
|
|
132
|
+
if (t?.type === "op" && ["===", "!==", "==", "!=", ">=", "<=", ">", "<"].includes(t.value)) {
|
|
133
|
+
this.advance();
|
|
134
|
+
// Normalize == to ===, != to !== so downstream sees one spelling
|
|
135
|
+
const op = t.value === "==" ? "===" : t.value === "!=" ? "!==" : t.value;
|
|
136
|
+
return { kind: "binop", op, left, right: this.parseAdd() };
|
|
137
|
+
}
|
|
138
|
+
return left;
|
|
139
|
+
}
|
|
140
|
+
parseAdd() {
|
|
141
|
+
let left = this.parseMul();
|
|
142
|
+
while (this.peek()?.type === "op" && ["+", "-"].includes(this.peek().value)) {
|
|
143
|
+
const op = this.advance().value;
|
|
144
|
+
left = { kind: "binop", op, left, right: this.parseMul() };
|
|
145
|
+
}
|
|
146
|
+
return left;
|
|
147
|
+
}
|
|
148
|
+
parseMul() {
|
|
149
|
+
let left = this.parseUnary();
|
|
150
|
+
while (this.peek()?.type === "op" && ["*", "/", "%"].includes(this.peek().value)) {
|
|
151
|
+
const op = this.advance().value;
|
|
152
|
+
left = { kind: "binop", op, left, right: this.parseUnary() };
|
|
153
|
+
}
|
|
154
|
+
return left;
|
|
155
|
+
}
|
|
156
|
+
parseUnary() {
|
|
157
|
+
if (this.match("op", "!"))
|
|
158
|
+
return { kind: "unop", op: "!", expr: this.parseUnary() };
|
|
159
|
+
if (this.peek()?.type === "op" && this.peek().value === "-") {
|
|
160
|
+
const prev = this.pos > 0 ? this.tokens[this.pos - 1] : undefined;
|
|
161
|
+
if (!prev || prev.type === "op" || (prev.type === "punc" && prev.value !== ")")) {
|
|
162
|
+
this.advance();
|
|
163
|
+
return { kind: "unop", op: "-", expr: this.parseUnary() };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return this.parsePostfix();
|
|
167
|
+
}
|
|
168
|
+
parsePostfix() {
|
|
169
|
+
let expr = this.parseAtom();
|
|
170
|
+
while (true) {
|
|
171
|
+
if (this.match("punc", ".")) {
|
|
172
|
+
expr = { kind: "field", obj: expr, field: this.expect("ident").value };
|
|
173
|
+
}
|
|
174
|
+
else if (this.match("punc", "[")) {
|
|
175
|
+
const idx = this.parseImplies();
|
|
176
|
+
this.expect("punc", "]");
|
|
177
|
+
expr = { kind: "index", obj: expr, idx };
|
|
178
|
+
}
|
|
179
|
+
else if (this.match("punc", "(")) {
|
|
180
|
+
const args = [];
|
|
181
|
+
if (!this.match("punc", ")")) {
|
|
182
|
+
args.push(this.parseImplies());
|
|
183
|
+
while (this.match("punc", ","))
|
|
184
|
+
args.push(this.parseImplies());
|
|
185
|
+
this.expect("punc", ")");
|
|
186
|
+
}
|
|
187
|
+
expr = { kind: "call", fn: expr, args };
|
|
188
|
+
}
|
|
189
|
+
else
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
return expr;
|
|
193
|
+
}
|
|
194
|
+
parseAtom() {
|
|
195
|
+
const t = this.peek();
|
|
196
|
+
if (!t)
|
|
197
|
+
throw new Error("Unexpected end of expression");
|
|
198
|
+
if (t.type === "result") {
|
|
199
|
+
this.advance();
|
|
200
|
+
return { kind: "result" };
|
|
201
|
+
}
|
|
202
|
+
if (t.type === "num") {
|
|
203
|
+
this.advance();
|
|
204
|
+
return { kind: "num", value: t.value };
|
|
205
|
+
}
|
|
206
|
+
if (t.type === "str") {
|
|
207
|
+
this.advance();
|
|
208
|
+
return { kind: "str", value: t.value };
|
|
209
|
+
}
|
|
210
|
+
if (t.type === "ident") {
|
|
211
|
+
if (t.value === "true") {
|
|
212
|
+
this.advance();
|
|
213
|
+
return { kind: "bool", value: true };
|
|
214
|
+
}
|
|
215
|
+
if (t.value === "false") {
|
|
216
|
+
this.advance();
|
|
217
|
+
return { kind: "bool", value: false };
|
|
218
|
+
}
|
|
219
|
+
// new Set<T>() / new Map<K,V>()
|
|
220
|
+
if (t.value === "new") {
|
|
221
|
+
this.advance();
|
|
222
|
+
const name = this.expect("ident").value;
|
|
223
|
+
if (name !== "Set" && name !== "Map")
|
|
224
|
+
throw new Error(`Unsupported constructor: new ${name}`);
|
|
225
|
+
// Skip <T> or <K,V> type arguments
|
|
226
|
+
let tsType = name;
|
|
227
|
+
if (this.match("op", "<")) {
|
|
228
|
+
let depth = 1;
|
|
229
|
+
let typeArgs = "";
|
|
230
|
+
while (depth > 0) {
|
|
231
|
+
const next = this.advance();
|
|
232
|
+
if (next.value === "<")
|
|
233
|
+
depth++;
|
|
234
|
+
else if (next.value === ">") {
|
|
235
|
+
depth--;
|
|
236
|
+
if (depth === 0)
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
typeArgs += next.value;
|
|
240
|
+
}
|
|
241
|
+
tsType = `${name}<${typeArgs}>`;
|
|
242
|
+
}
|
|
243
|
+
this.expect("punc", "(");
|
|
244
|
+
this.expect("punc", ")");
|
|
245
|
+
return { kind: "emptyCollection", collectionType: name, tsType };
|
|
246
|
+
}
|
|
247
|
+
if (t.value === "forall" || t.value === "exists") {
|
|
248
|
+
const q = t.value;
|
|
249
|
+
this.advance();
|
|
250
|
+
this.expect("punc", "(");
|
|
251
|
+
const v = this.expect("ident").value;
|
|
252
|
+
let varType = "int";
|
|
253
|
+
if (this.match("punc", ":")) {
|
|
254
|
+
const ty = this.expect("ident").value;
|
|
255
|
+
if (ty !== "nat" && ty !== "int")
|
|
256
|
+
throw new Error(`Unknown type '${ty}'`);
|
|
257
|
+
varType = ty;
|
|
258
|
+
}
|
|
259
|
+
this.expect("punc", ",");
|
|
260
|
+
const body = this.parseImplies();
|
|
261
|
+
this.expect("punc", ")");
|
|
262
|
+
return { kind: q, var: v, varType, body };
|
|
263
|
+
}
|
|
264
|
+
this.advance();
|
|
265
|
+
return { kind: "var", name: t.value };
|
|
266
|
+
}
|
|
267
|
+
if (t.type === "punc" && t.value === "(") {
|
|
268
|
+
this.advance();
|
|
269
|
+
const expr = this.parseImplies();
|
|
270
|
+
this.expect("punc", ")");
|
|
271
|
+
return expr;
|
|
272
|
+
}
|
|
273
|
+
if (t.type === "punc" && t.value === "[") {
|
|
274
|
+
this.advance();
|
|
275
|
+
const elems = [];
|
|
276
|
+
if (!this.match("punc", "]")) {
|
|
277
|
+
elems.push(this.parseImplies());
|
|
278
|
+
while (this.match("punc", ","))
|
|
279
|
+
elems.push(this.parseImplies());
|
|
280
|
+
this.expect("punc", "]");
|
|
281
|
+
}
|
|
282
|
+
return { kind: "arrayLiteral", elems };
|
|
283
|
+
}
|
|
284
|
+
if (t.type === "punc" && t.value === "{") {
|
|
285
|
+
this.advance();
|
|
286
|
+
const fields = [];
|
|
287
|
+
if (!this.match("punc", "}")) {
|
|
288
|
+
const name = this.expect("ident").value;
|
|
289
|
+
this.expect("punc", ":");
|
|
290
|
+
fields.push({ name, value: this.parseImplies() });
|
|
291
|
+
while (this.match("punc", ",")) {
|
|
292
|
+
const n = this.expect("ident").value;
|
|
293
|
+
this.expect("punc", ":");
|
|
294
|
+
fields.push({ name: n, value: this.parseImplies() });
|
|
295
|
+
}
|
|
296
|
+
this.expect("punc", "}");
|
|
297
|
+
}
|
|
298
|
+
return { kind: "record", spread: null, fields };
|
|
299
|
+
}
|
|
300
|
+
throw new Error(`Unexpected: ${JSON.stringify(t)}`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
export function parseExpr(input) {
|
|
304
|
+
return new Parser(tokenize(input)).parse();
|
|
305
|
+
}
|