arc-lang 0.5.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 +148 -0
- package/dist/ast.d.ts +298 -0
- package/dist/ast.js +2 -0
- package/dist/build.d.ts +7 -0
- package/dist/build.js +138 -0
- package/dist/codegen-js.d.ts +2 -0
- package/dist/codegen-js.js +168 -0
- package/dist/codegen.d.ts +2 -0
- package/dist/codegen.js +364 -0
- package/dist/errors.d.ts +52 -0
- package/dist/errors.js +229 -0
- package/dist/formatter.d.ts +5 -0
- package/dist/formatter.js +361 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +165 -0
- package/dist/interpreter.d.ts +39 -0
- package/dist/interpreter.js +668 -0
- package/dist/ir.d.ts +126 -0
- package/dist/ir.js +610 -0
- package/dist/lexer.d.ts +79 -0
- package/dist/lexer.js +335 -0
- package/dist/linter.d.ts +15 -0
- package/dist/linter.js +382 -0
- package/dist/lsp.d.ts +1 -0
- package/dist/lsp.js +253 -0
- package/dist/modules.d.ts +24 -0
- package/dist/modules.js +115 -0
- package/dist/optimizer.d.ts +17 -0
- package/dist/optimizer.js +481 -0
- package/dist/package-manager.d.ts +31 -0
- package/dist/package-manager.js +180 -0
- package/dist/parser.d.ts +42 -0
- package/dist/parser.js +779 -0
- package/dist/repl.d.ts +1 -0
- package/dist/repl.js +120 -0
- package/dist/security.d.ts +48 -0
- package/dist/security.js +198 -0
- package/dist/semantic.d.ts +7 -0
- package/dist/semantic.js +327 -0
- package/dist/typechecker.d.ts +7 -0
- package/dist/typechecker.js +132 -0
- package/dist/version.d.ts +26 -0
- package/dist/version.js +71 -0
- package/package.json +51 -0
package/dist/parser.js
ADDED
|
@@ -0,0 +1,779 @@
|
|
|
1
|
+
// Arc Language Parser - Recursive Descent with Pratt Parsing
|
|
2
|
+
import { TokenType } from "./lexer.js";
|
|
3
|
+
export class ParseError extends Error {
|
|
4
|
+
loc;
|
|
5
|
+
constructor(msg, loc) {
|
|
6
|
+
super(`Parse error at line ${loc.line}, col ${loc.col}: ${msg}`);
|
|
7
|
+
this.loc = loc;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export class Parser {
|
|
11
|
+
pos = 0;
|
|
12
|
+
tokens;
|
|
13
|
+
constructor(tokens) {
|
|
14
|
+
// Filter out newlines for simpler parsing (treat as whitespace)
|
|
15
|
+
this.tokens = tokens.filter(t => t.type !== TokenType.Newline);
|
|
16
|
+
}
|
|
17
|
+
peek() { return this.tokens[this.pos] ?? this.tokens[this.tokens.length - 1]; }
|
|
18
|
+
at(type) { return this.peek().type === type; }
|
|
19
|
+
loc() { return { line: this.peek().line, col: this.peek().col }; }
|
|
20
|
+
advance() {
|
|
21
|
+
const t = this.peek();
|
|
22
|
+
if (this.pos < this.tokens.length - 1)
|
|
23
|
+
this.pos++;
|
|
24
|
+
return t;
|
|
25
|
+
}
|
|
26
|
+
expect(type, msg) {
|
|
27
|
+
if (!this.at(type)) {
|
|
28
|
+
const t = this.peek();
|
|
29
|
+
throw new ParseError(msg ?? `Expected ${TokenType[type]}, got ${TokenType[t.type]} '${t.value}'`, this.loc());
|
|
30
|
+
}
|
|
31
|
+
return this.advance();
|
|
32
|
+
}
|
|
33
|
+
skipSemicolons() {
|
|
34
|
+
while (this.at(TokenType.Semicolon))
|
|
35
|
+
this.advance();
|
|
36
|
+
}
|
|
37
|
+
parse() {
|
|
38
|
+
const stmts = [];
|
|
39
|
+
this.skipSemicolons();
|
|
40
|
+
while (!this.at(TokenType.EOF)) {
|
|
41
|
+
stmts.push(this.parseStmt());
|
|
42
|
+
this.skipSemicolons();
|
|
43
|
+
}
|
|
44
|
+
return { kind: "Program", stmts };
|
|
45
|
+
}
|
|
46
|
+
parseStmt() {
|
|
47
|
+
this.skipSemicolons();
|
|
48
|
+
const t = this.peek();
|
|
49
|
+
switch (t.type) {
|
|
50
|
+
case TokenType.Let: return this.parseLet();
|
|
51
|
+
case TokenType.Pub: return this.parsePub();
|
|
52
|
+
case TokenType.Fn: return this.parseFn(false);
|
|
53
|
+
case TokenType.Async: return this.parseAsync();
|
|
54
|
+
case TokenType.For: return this.parseFor();
|
|
55
|
+
case TokenType.Do: return this.parseDo();
|
|
56
|
+
case TokenType.Use: return this.parseUse();
|
|
57
|
+
case TokenType.Type: return this.parseType();
|
|
58
|
+
default: {
|
|
59
|
+
const exprLoc = this.loc();
|
|
60
|
+
const expr = this.parseExpr();
|
|
61
|
+
// Check for assignment: expr = value
|
|
62
|
+
if (this.at(TokenType.Assign)) {
|
|
63
|
+
this.advance();
|
|
64
|
+
const value = this.parseExpr();
|
|
65
|
+
if (expr.kind === "Identifier") {
|
|
66
|
+
return { kind: "AssignStmt", target: expr.name, value, loc: exprLoc };
|
|
67
|
+
}
|
|
68
|
+
if (expr.kind === "MemberExpr") {
|
|
69
|
+
return { kind: "MemberAssignStmt", object: expr.object, property: expr.property, value, loc: exprLoc };
|
|
70
|
+
}
|
|
71
|
+
if (expr.kind === "IndexExpr") {
|
|
72
|
+
return { kind: "IndexAssignStmt", object: expr.object, index: expr.index, value, loc: exprLoc };
|
|
73
|
+
}
|
|
74
|
+
throw new ParseError("Invalid assignment target", exprLoc);
|
|
75
|
+
}
|
|
76
|
+
return { kind: "ExprStmt", expr, loc: this.loc() };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
parsePub() {
|
|
81
|
+
const loc = this.loc();
|
|
82
|
+
this.advance(); // consume 'pub'
|
|
83
|
+
if (this.at(TokenType.Fn))
|
|
84
|
+
return this.parseFn(false, undefined, true);
|
|
85
|
+
if (this.at(TokenType.Let))
|
|
86
|
+
return this.parseLet(true);
|
|
87
|
+
if (this.at(TokenType.Type))
|
|
88
|
+
return this.parseType(true);
|
|
89
|
+
if (this.at(TokenType.Async)) {
|
|
90
|
+
this.advance();
|
|
91
|
+
return this.parseFn(true, loc, true);
|
|
92
|
+
}
|
|
93
|
+
throw new ParseError("Expected fn, let, or type after pub", loc);
|
|
94
|
+
}
|
|
95
|
+
parseLet(pub = false) {
|
|
96
|
+
const loc = this.loc();
|
|
97
|
+
this.expect(TokenType.Let);
|
|
98
|
+
let mutable = false;
|
|
99
|
+
if (this.at(TokenType.Mut)) {
|
|
100
|
+
this.advance();
|
|
101
|
+
mutable = true;
|
|
102
|
+
}
|
|
103
|
+
let name;
|
|
104
|
+
if (this.at(TokenType.LBrace)) {
|
|
105
|
+
// Object destructuring
|
|
106
|
+
this.advance();
|
|
107
|
+
const names = [];
|
|
108
|
+
while (!this.at(TokenType.RBrace)) {
|
|
109
|
+
names.push(this.expect(TokenType.Ident).value);
|
|
110
|
+
if (this.at(TokenType.Comma))
|
|
111
|
+
this.advance();
|
|
112
|
+
}
|
|
113
|
+
this.expect(TokenType.RBrace);
|
|
114
|
+
name = { kind: "DestructureTarget", type: "object", names };
|
|
115
|
+
}
|
|
116
|
+
else if (this.at(TokenType.LBracket)) {
|
|
117
|
+
this.advance();
|
|
118
|
+
const names = [];
|
|
119
|
+
while (!this.at(TokenType.RBracket)) {
|
|
120
|
+
names.push(this.expect(TokenType.Ident).value);
|
|
121
|
+
if (this.at(TokenType.Comma))
|
|
122
|
+
this.advance();
|
|
123
|
+
}
|
|
124
|
+
this.expect(TokenType.RBracket);
|
|
125
|
+
name = { kind: "DestructureTarget", type: "array", names };
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
name = this.expect(TokenType.Ident).value;
|
|
129
|
+
}
|
|
130
|
+
this.expect(TokenType.Assign);
|
|
131
|
+
const value = this.parseExpr();
|
|
132
|
+
return { kind: "LetStmt", name, mutable, pub, value, loc };
|
|
133
|
+
}
|
|
134
|
+
parseAsync() {
|
|
135
|
+
const loc = this.loc();
|
|
136
|
+
this.expect(TokenType.Async);
|
|
137
|
+
if (this.at(TokenType.Fn)) {
|
|
138
|
+
return this.parseFn(true, loc);
|
|
139
|
+
}
|
|
140
|
+
// async { body } as expression statement
|
|
141
|
+
const body = this.parseBlock();
|
|
142
|
+
const expr = { kind: "AsyncExpr", body, loc };
|
|
143
|
+
// Check for assignment context — but since we came from parseStmt default would handle it
|
|
144
|
+
// Just wrap as ExprStmt
|
|
145
|
+
return { kind: "ExprStmt", expr, loc };
|
|
146
|
+
}
|
|
147
|
+
parseFn(isAsync = false, asyncLoc, pub = false) {
|
|
148
|
+
const loc = asyncLoc ?? this.loc();
|
|
149
|
+
this.expect(TokenType.Fn);
|
|
150
|
+
const name = this.expect(TokenType.Ident).value;
|
|
151
|
+
this.expect(TokenType.LParen);
|
|
152
|
+
const params = [];
|
|
153
|
+
while (!this.at(TokenType.RParen)) {
|
|
154
|
+
params.push(this.expect(TokenType.Ident).value);
|
|
155
|
+
if (this.at(TokenType.Comma))
|
|
156
|
+
this.advance();
|
|
157
|
+
}
|
|
158
|
+
this.expect(TokenType.RParen);
|
|
159
|
+
let body;
|
|
160
|
+
if (this.at(TokenType.FatArrow)) {
|
|
161
|
+
this.advance();
|
|
162
|
+
body = this.parseExpr();
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
body = this.parseBlock();
|
|
166
|
+
}
|
|
167
|
+
return { kind: "FnStmt", name, params, body, isAsync, pub, loc };
|
|
168
|
+
}
|
|
169
|
+
parseFor() {
|
|
170
|
+
const loc = this.loc();
|
|
171
|
+
this.expect(TokenType.For);
|
|
172
|
+
const variable = this.expect(TokenType.Ident).value;
|
|
173
|
+
this.expect(TokenType.In);
|
|
174
|
+
const iterable = this.parseExpr();
|
|
175
|
+
const body = this.parseBlock();
|
|
176
|
+
return { kind: "ForStmt", variable, iterable, body, loc };
|
|
177
|
+
}
|
|
178
|
+
parseDo() {
|
|
179
|
+
const loc = this.loc();
|
|
180
|
+
this.expect(TokenType.Do);
|
|
181
|
+
const body = this.parseBlock();
|
|
182
|
+
const isWhile = this.at(TokenType.While);
|
|
183
|
+
if (!isWhile)
|
|
184
|
+
this.expect(TokenType.Until);
|
|
185
|
+
else
|
|
186
|
+
this.advance();
|
|
187
|
+
const condition = this.parseExpr();
|
|
188
|
+
return { kind: "DoStmt", body, condition, isWhile, loc };
|
|
189
|
+
}
|
|
190
|
+
parseUse() {
|
|
191
|
+
const loc = this.loc();
|
|
192
|
+
this.expect(TokenType.Use);
|
|
193
|
+
const path = [this.expect(TokenType.Ident).value];
|
|
194
|
+
while (this.at(TokenType.Slash)) {
|
|
195
|
+
this.advance();
|
|
196
|
+
path.push(this.expect(TokenType.Ident).value);
|
|
197
|
+
}
|
|
198
|
+
// Also support legacy dot separator
|
|
199
|
+
while (this.at(TokenType.Dot)) {
|
|
200
|
+
this.advance();
|
|
201
|
+
path.push(this.expect(TokenType.Ident).value);
|
|
202
|
+
}
|
|
203
|
+
let imports;
|
|
204
|
+
let wildcard;
|
|
205
|
+
if (this.at(TokenType.Colon)) {
|
|
206
|
+
this.advance();
|
|
207
|
+
if (this.at(TokenType.Star)) {
|
|
208
|
+
this.advance();
|
|
209
|
+
wildcard = true;
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
imports = [this.expect(TokenType.Ident).value];
|
|
213
|
+
while (this.at(TokenType.Comma)) {
|
|
214
|
+
this.advance();
|
|
215
|
+
imports.push(this.expect(TokenType.Ident).value);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return { kind: "UseStmt", path, imports, wildcard, loc };
|
|
220
|
+
}
|
|
221
|
+
parseType(pub = false) {
|
|
222
|
+
const loc = this.loc();
|
|
223
|
+
this.expect(TokenType.Type);
|
|
224
|
+
const name = this.expect(TokenType.Ident).value;
|
|
225
|
+
this.expect(TokenType.Assign);
|
|
226
|
+
const def = this.parseTypeExpr();
|
|
227
|
+
return { kind: "TypeStmt", name, pub, def, loc };
|
|
228
|
+
}
|
|
229
|
+
parseTypeExpr() {
|
|
230
|
+
let typeExpr = this.parseTypeAtom();
|
|
231
|
+
// Check for enum/union: Type | Type
|
|
232
|
+
if (this.at(TokenType.Bar)) {
|
|
233
|
+
const variants = [typeExpr];
|
|
234
|
+
while (this.at(TokenType.Bar)) {
|
|
235
|
+
this.advance();
|
|
236
|
+
variants.push(this.parseTypeAtom());
|
|
237
|
+
}
|
|
238
|
+
// Check if all are NamedType — treat as enum if they look like variants
|
|
239
|
+
const allNamed = variants.every(v => v.kind === "NamedType");
|
|
240
|
+
if (allNamed) {
|
|
241
|
+
return { kind: "UnionType", variants };
|
|
242
|
+
}
|
|
243
|
+
return { kind: "UnionType", variants };
|
|
244
|
+
}
|
|
245
|
+
// Check for constraints: where / matching
|
|
246
|
+
if (this.at(TokenType.Where)) {
|
|
247
|
+
this.advance();
|
|
248
|
+
const predicate = this.parseExpr();
|
|
249
|
+
return { kind: "ConstrainedType", base: typeExpr, constraint: "where", predicate };
|
|
250
|
+
}
|
|
251
|
+
if (this.at(TokenType.Matching)) {
|
|
252
|
+
this.advance();
|
|
253
|
+
// Expect regex token or string
|
|
254
|
+
let predicate;
|
|
255
|
+
if (this.at(TokenType.Regex)) {
|
|
256
|
+
const regexVal = this.advance().value;
|
|
257
|
+
predicate = { kind: "StringLiteral", value: regexVal, loc: this.loc() };
|
|
258
|
+
}
|
|
259
|
+
else if (this.at(TokenType.String)) {
|
|
260
|
+
predicate = { kind: "StringLiteral", value: this.advance().value, loc: this.loc() };
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
throw new ParseError("Expected regex or string after 'matching'", this.loc());
|
|
264
|
+
}
|
|
265
|
+
return { kind: "ConstrainedType", base: typeExpr, constraint: "matching", predicate };
|
|
266
|
+
}
|
|
267
|
+
return typeExpr;
|
|
268
|
+
}
|
|
269
|
+
parseTypeAtom() {
|
|
270
|
+
// Record type: { name: Type, ... }
|
|
271
|
+
if (this.at(TokenType.LBrace)) {
|
|
272
|
+
this.advance();
|
|
273
|
+
const fields = [];
|
|
274
|
+
while (!this.at(TokenType.RBrace) && !this.at(TokenType.EOF)) {
|
|
275
|
+
const fieldName = this.expect(TokenType.Ident).value;
|
|
276
|
+
this.expect(TokenType.Colon);
|
|
277
|
+
const fieldType = this.parseTypeExpr();
|
|
278
|
+
fields.push({ name: fieldName, type: fieldType });
|
|
279
|
+
if (this.at(TokenType.Comma))
|
|
280
|
+
this.advance();
|
|
281
|
+
}
|
|
282
|
+
this.expect(TokenType.RBrace);
|
|
283
|
+
return { kind: "RecordType", fields };
|
|
284
|
+
}
|
|
285
|
+
// Function type: (Type, ...) -> Type
|
|
286
|
+
if (this.at(TokenType.LParen)) {
|
|
287
|
+
const saved = this.pos;
|
|
288
|
+
try {
|
|
289
|
+
this.advance();
|
|
290
|
+
const params = [];
|
|
291
|
+
while (!this.at(TokenType.RParen) && !this.at(TokenType.EOF)) {
|
|
292
|
+
params.push(this.parseTypeExpr());
|
|
293
|
+
if (this.at(TokenType.Comma))
|
|
294
|
+
this.advance();
|
|
295
|
+
}
|
|
296
|
+
this.expect(TokenType.RParen);
|
|
297
|
+
if (this.at(TokenType.Arrow)) {
|
|
298
|
+
this.advance();
|
|
299
|
+
const ret = this.parseTypeExpr();
|
|
300
|
+
return { kind: "FunctionType", params, ret };
|
|
301
|
+
}
|
|
302
|
+
// Not a function type, restore
|
|
303
|
+
this.pos = saved;
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
this.pos = saved;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// Named type (possibly with enum variant params or generics)
|
|
310
|
+
if (this.at(TokenType.Ident)) {
|
|
311
|
+
const name = this.advance().value;
|
|
312
|
+
// Generic: Name<Type, ...>
|
|
313
|
+
if (this.at(TokenType.Lt)) {
|
|
314
|
+
this.advance();
|
|
315
|
+
const params = [];
|
|
316
|
+
while (!this.at(TokenType.Gt) && !this.at(TokenType.EOF)) {
|
|
317
|
+
params.push(this.parseTypeExpr());
|
|
318
|
+
if (this.at(TokenType.Comma))
|
|
319
|
+
this.advance();
|
|
320
|
+
}
|
|
321
|
+
this.expect(TokenType.Gt);
|
|
322
|
+
return { kind: "GenericType", name, params };
|
|
323
|
+
}
|
|
324
|
+
// Enum variant with params: Name(Type, ...)
|
|
325
|
+
if (this.at(TokenType.LParen)) {
|
|
326
|
+
// This is part of enum parsing — but at atom level just return named
|
|
327
|
+
// Enum variants are handled at the union level
|
|
328
|
+
// Actually let's peek if this is in a union context
|
|
329
|
+
// For now, parse variant params
|
|
330
|
+
this.advance();
|
|
331
|
+
const params = [];
|
|
332
|
+
while (!this.at(TokenType.RParen) && !this.at(TokenType.EOF)) {
|
|
333
|
+
params.push(this.parseTypeExpr());
|
|
334
|
+
if (this.at(TokenType.Comma))
|
|
335
|
+
this.advance();
|
|
336
|
+
}
|
|
337
|
+
this.expect(TokenType.RParen);
|
|
338
|
+
return { kind: "EnumType", variants: [{ name, params }] };
|
|
339
|
+
}
|
|
340
|
+
return { kind: "NamedType", name };
|
|
341
|
+
}
|
|
342
|
+
throw new ParseError(`Expected type expression, got ${TokenType[this.peek().type]}`, this.loc());
|
|
343
|
+
}
|
|
344
|
+
parseBlock() {
|
|
345
|
+
const loc = this.loc();
|
|
346
|
+
this.expect(TokenType.LBrace);
|
|
347
|
+
const stmts = [];
|
|
348
|
+
this.skipSemicolons();
|
|
349
|
+
while (!this.at(TokenType.RBrace) && !this.at(TokenType.EOF)) {
|
|
350
|
+
stmts.push(this.parseStmt());
|
|
351
|
+
this.skipSemicolons();
|
|
352
|
+
}
|
|
353
|
+
this.expect(TokenType.RBrace);
|
|
354
|
+
return { kind: "BlockExpr", stmts, loc };
|
|
355
|
+
}
|
|
356
|
+
// ---- Pratt Expression Parsing ----
|
|
357
|
+
parseExpr(minPrec = 0) {
|
|
358
|
+
let left = this.parsePrefix();
|
|
359
|
+
while (true) {
|
|
360
|
+
const prec = this.infixPrec();
|
|
361
|
+
if (prec < minPrec)
|
|
362
|
+
break;
|
|
363
|
+
const t = this.peek();
|
|
364
|
+
if (t.type === TokenType.Pipe) {
|
|
365
|
+
this.advance();
|
|
366
|
+
const right = this.parseExpr(prec + 1);
|
|
367
|
+
left = { kind: "PipelineExpr", left, right, loc: left.loc };
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
if (t.type === TokenType.Range) {
|
|
371
|
+
this.advance();
|
|
372
|
+
const right = this.parseExpr(prec + 1);
|
|
373
|
+
left = { kind: "RangeExpr", start: left, end: right, loc: left.loc };
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
// Postfix: function call
|
|
377
|
+
if (t.type === TokenType.LParen) {
|
|
378
|
+
this.advance();
|
|
379
|
+
const args = [];
|
|
380
|
+
while (!this.at(TokenType.RParen)) {
|
|
381
|
+
args.push(this.parseExpr());
|
|
382
|
+
if (this.at(TokenType.Comma))
|
|
383
|
+
this.advance();
|
|
384
|
+
}
|
|
385
|
+
this.expect(TokenType.RParen);
|
|
386
|
+
left = { kind: "CallExpr", callee: left, args, loc: left.loc };
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
// Postfix: index
|
|
390
|
+
if (t.type === TokenType.LBracket) {
|
|
391
|
+
this.advance();
|
|
392
|
+
const index = this.parseExpr();
|
|
393
|
+
this.expect(TokenType.RBracket);
|
|
394
|
+
left = { kind: "IndexExpr", object: left, index, loc: left.loc };
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
// Postfix: member access
|
|
398
|
+
if (t.type === TokenType.Dot) {
|
|
399
|
+
this.advance();
|
|
400
|
+
const prop = this.expect(TokenType.Ident).value;
|
|
401
|
+
left = { kind: "MemberExpr", object: left, property: prop, loc: left.loc };
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
// Binary operators
|
|
405
|
+
const op = this.binaryOp();
|
|
406
|
+
if (op) {
|
|
407
|
+
this.advance();
|
|
408
|
+
const right = this.parseExpr(prec + 1);
|
|
409
|
+
left = { kind: "BinaryExpr", op, left, right, loc: left.loc };
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
break;
|
|
413
|
+
}
|
|
414
|
+
return left;
|
|
415
|
+
}
|
|
416
|
+
infixPrec() {
|
|
417
|
+
const t = this.peek().type;
|
|
418
|
+
switch (t) {
|
|
419
|
+
case TokenType.Or: return 1;
|
|
420
|
+
case TokenType.And: return 2;
|
|
421
|
+
case TokenType.Eq:
|
|
422
|
+
case TokenType.Neq:
|
|
423
|
+
case TokenType.Lt:
|
|
424
|
+
case TokenType.Gt:
|
|
425
|
+
case TokenType.Lte:
|
|
426
|
+
case TokenType.Gte: return 3;
|
|
427
|
+
case TokenType.Concat: return 4;
|
|
428
|
+
case TokenType.Plus:
|
|
429
|
+
case TokenType.Minus: return 5;
|
|
430
|
+
case TokenType.Star:
|
|
431
|
+
case TokenType.Slash:
|
|
432
|
+
case TokenType.Percent: return 6;
|
|
433
|
+
case TokenType.Power: return 7;
|
|
434
|
+
case TokenType.Range: return 8;
|
|
435
|
+
case TokenType.Dot:
|
|
436
|
+
case TokenType.LBracket:
|
|
437
|
+
case TokenType.LParen: return 10;
|
|
438
|
+
case TokenType.Pipe: return 0; // lowest
|
|
439
|
+
default: return -1;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
binaryOp() {
|
|
443
|
+
const t = this.peek().type;
|
|
444
|
+
const map = {
|
|
445
|
+
[TokenType.Plus]: "+", [TokenType.Minus]: "-", [TokenType.Star]: "*",
|
|
446
|
+
[TokenType.Slash]: "/", [TokenType.Percent]: "%", [TokenType.Power]: "**",
|
|
447
|
+
[TokenType.Eq]: "==", [TokenType.Neq]: "!=", [TokenType.Lt]: "<",
|
|
448
|
+
[TokenType.Gt]: ">", [TokenType.Lte]: "<=", [TokenType.Gte]: ">=",
|
|
449
|
+
[TokenType.And]: "and", [TokenType.Or]: "or", [TokenType.Concat]: "++",
|
|
450
|
+
};
|
|
451
|
+
return map[t] ?? null;
|
|
452
|
+
}
|
|
453
|
+
parsePrefix() {
|
|
454
|
+
const t = this.peek();
|
|
455
|
+
const loc = this.loc();
|
|
456
|
+
// Unary operators
|
|
457
|
+
if (t.type === TokenType.Minus) {
|
|
458
|
+
this.advance();
|
|
459
|
+
return { kind: "UnaryExpr", op: "-", operand: this.parseExpr(8), loc };
|
|
460
|
+
}
|
|
461
|
+
if (t.type === TokenType.Not) {
|
|
462
|
+
this.advance();
|
|
463
|
+
return { kind: "UnaryExpr", op: "not", operand: this.parseExpr(8), loc };
|
|
464
|
+
}
|
|
465
|
+
// Literals
|
|
466
|
+
if (t.type === TokenType.Int) {
|
|
467
|
+
this.advance();
|
|
468
|
+
return { kind: "IntLiteral", value: parseInt(t.value), loc };
|
|
469
|
+
}
|
|
470
|
+
if (t.type === TokenType.Float) {
|
|
471
|
+
this.advance();
|
|
472
|
+
return { kind: "FloatLiteral", value: parseFloat(t.value), loc };
|
|
473
|
+
}
|
|
474
|
+
if (t.type === TokenType.True) {
|
|
475
|
+
this.advance();
|
|
476
|
+
return { kind: "BoolLiteral", value: true, loc };
|
|
477
|
+
}
|
|
478
|
+
if (t.type === TokenType.False) {
|
|
479
|
+
this.advance();
|
|
480
|
+
return { kind: "BoolLiteral", value: false, loc };
|
|
481
|
+
}
|
|
482
|
+
if (t.type === TokenType.NilKw) {
|
|
483
|
+
this.advance();
|
|
484
|
+
return { kind: "NilLiteral", loc };
|
|
485
|
+
}
|
|
486
|
+
if (t.type === TokenType.String) {
|
|
487
|
+
this.advance();
|
|
488
|
+
return { kind: "StringLiteral", value: t.value, loc };
|
|
489
|
+
}
|
|
490
|
+
// String interpolation
|
|
491
|
+
if (t.type === TokenType.StringInterpStart) {
|
|
492
|
+
return this.parseStringInterp();
|
|
493
|
+
}
|
|
494
|
+
// Identifier (possibly lambda)
|
|
495
|
+
if (t.type === TokenType.Ident) {
|
|
496
|
+
// Check for lambda: ident => expr
|
|
497
|
+
if (this.tokens[this.pos + 1]?.type === TokenType.FatArrow) {
|
|
498
|
+
const param = this.advance().value;
|
|
499
|
+
this.advance(); // =>
|
|
500
|
+
const body = this.parseExpr();
|
|
501
|
+
return { kind: "LambdaExpr", params: [param], body, loc };
|
|
502
|
+
}
|
|
503
|
+
this.advance();
|
|
504
|
+
return { kind: "Identifier", name: t.value, loc };
|
|
505
|
+
}
|
|
506
|
+
// Parenthesized expression or multi-param lambda
|
|
507
|
+
if (t.type === TokenType.LParen) {
|
|
508
|
+
this.advance();
|
|
509
|
+
// Check for lambda: (a, b) => expr
|
|
510
|
+
if (this.at(TokenType.RParen)) {
|
|
511
|
+
this.advance();
|
|
512
|
+
if (this.at(TokenType.FatArrow)) {
|
|
513
|
+
this.advance();
|
|
514
|
+
const body = this.parseExpr();
|
|
515
|
+
return { kind: "LambdaExpr", params: [], body, loc };
|
|
516
|
+
}
|
|
517
|
+
// Empty parens as nil?
|
|
518
|
+
return { kind: "NilLiteral", loc };
|
|
519
|
+
}
|
|
520
|
+
// Try to detect lambda
|
|
521
|
+
const saved = this.pos;
|
|
522
|
+
let isLambda = false;
|
|
523
|
+
try {
|
|
524
|
+
const names = [];
|
|
525
|
+
if (this.at(TokenType.Ident)) {
|
|
526
|
+
names.push(this.advance().value);
|
|
527
|
+
while (this.at(TokenType.Comma)) {
|
|
528
|
+
this.advance();
|
|
529
|
+
names.push(this.expect(TokenType.Ident).value);
|
|
530
|
+
}
|
|
531
|
+
if (this.at(TokenType.RParen)) {
|
|
532
|
+
this.advance();
|
|
533
|
+
if (this.at(TokenType.FatArrow)) {
|
|
534
|
+
this.advance();
|
|
535
|
+
const body = this.parseExpr();
|
|
536
|
+
return { kind: "LambdaExpr", params: names, body, loc };
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
catch { }
|
|
542
|
+
this.pos = saved;
|
|
543
|
+
const expr = this.parseExpr();
|
|
544
|
+
this.expect(TokenType.RParen);
|
|
545
|
+
return expr;
|
|
546
|
+
}
|
|
547
|
+
// List literal or comprehension
|
|
548
|
+
if (t.type === TokenType.LBracket) {
|
|
549
|
+
return this.parseListOrComprehension();
|
|
550
|
+
}
|
|
551
|
+
// Map literal or block
|
|
552
|
+
if (t.type === TokenType.LBrace) {
|
|
553
|
+
return this.parseMapOrBlock();
|
|
554
|
+
}
|
|
555
|
+
// If expression
|
|
556
|
+
if (t.type === TokenType.If) {
|
|
557
|
+
return this.parseIf();
|
|
558
|
+
}
|
|
559
|
+
// Match expression
|
|
560
|
+
if (t.type === TokenType.Match) {
|
|
561
|
+
return this.parseMatch();
|
|
562
|
+
}
|
|
563
|
+
// Async expression: async { body }
|
|
564
|
+
if (t.type === TokenType.Async) {
|
|
565
|
+
this.advance();
|
|
566
|
+
const body = this.parseBlock();
|
|
567
|
+
return { kind: "AsyncExpr", body, loc };
|
|
568
|
+
}
|
|
569
|
+
// Await expression: await expr
|
|
570
|
+
if (t.type === TokenType.Await) {
|
|
571
|
+
this.advance();
|
|
572
|
+
const expr = this.parseExpr(8); // high precedence
|
|
573
|
+
return { kind: "AwaitExpr", expr, loc };
|
|
574
|
+
}
|
|
575
|
+
// Fetch expression: fetch [expr1, expr2, ...]
|
|
576
|
+
if (t.type === TokenType.Fetch) {
|
|
577
|
+
this.advance();
|
|
578
|
+
this.expect(TokenType.LBracket);
|
|
579
|
+
const targets = [];
|
|
580
|
+
while (!this.at(TokenType.RBracket)) {
|
|
581
|
+
targets.push(this.parseExpr());
|
|
582
|
+
if (this.at(TokenType.Comma))
|
|
583
|
+
this.advance();
|
|
584
|
+
}
|
|
585
|
+
this.expect(TokenType.RBracket);
|
|
586
|
+
return { kind: "FetchExpr", targets, loc };
|
|
587
|
+
}
|
|
588
|
+
// Tool call: @GET "url" or @ident(args)
|
|
589
|
+
if (t.type === TokenType.At) {
|
|
590
|
+
return this.parseToolCall();
|
|
591
|
+
}
|
|
592
|
+
throw new ParseError(`Unexpected token: ${TokenType[t.type]} '${t.value}'`, loc);
|
|
593
|
+
}
|
|
594
|
+
parseStringInterp() {
|
|
595
|
+
const loc = this.loc();
|
|
596
|
+
this.advance(); // StringInterpStart
|
|
597
|
+
const parts = [];
|
|
598
|
+
while (!this.at(TokenType.StringInterpEnd) && !this.at(TokenType.EOF)) {
|
|
599
|
+
if (this.at(TokenType.StringInterpPart)) {
|
|
600
|
+
parts.push(this.advance().value);
|
|
601
|
+
}
|
|
602
|
+
else if (this.at(TokenType.Ident)) {
|
|
603
|
+
// This is an interpolated expression identifier
|
|
604
|
+
const identToken = this.advance();
|
|
605
|
+
// Try to parse a more complex expression from the token value
|
|
606
|
+
parts.push({ kind: "Identifier", name: identToken.value, loc: { line: identToken.line, col: identToken.col } });
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
this.advance(); // skip unexpected
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
if (this.at(TokenType.StringInterpEnd))
|
|
613
|
+
this.advance();
|
|
614
|
+
return { kind: "StringInterp", parts, loc };
|
|
615
|
+
}
|
|
616
|
+
parseListOrComprehension() {
|
|
617
|
+
const loc = this.loc();
|
|
618
|
+
this.expect(TokenType.LBracket);
|
|
619
|
+
if (this.at(TokenType.RBracket)) {
|
|
620
|
+
this.advance();
|
|
621
|
+
return { kind: "ListLiteral", elements: [], loc };
|
|
622
|
+
}
|
|
623
|
+
const first = this.parseExpr();
|
|
624
|
+
// Check for list comprehension: [expr for x in iter if cond]
|
|
625
|
+
if (this.at(TokenType.For)) {
|
|
626
|
+
this.advance();
|
|
627
|
+
const variable = this.expect(TokenType.Ident).value;
|
|
628
|
+
this.expect(TokenType.In);
|
|
629
|
+
const iterable = this.parseExpr();
|
|
630
|
+
let filter;
|
|
631
|
+
if (this.at(TokenType.If)) {
|
|
632
|
+
this.advance();
|
|
633
|
+
filter = this.parseExpr();
|
|
634
|
+
}
|
|
635
|
+
this.expect(TokenType.RBracket);
|
|
636
|
+
return { kind: "ListComprehension", expr: first, variable, iterable, filter, loc };
|
|
637
|
+
}
|
|
638
|
+
// Regular list
|
|
639
|
+
const elements = [first];
|
|
640
|
+
while (this.at(TokenType.Comma)) {
|
|
641
|
+
this.advance();
|
|
642
|
+
if (this.at(TokenType.RBracket))
|
|
643
|
+
break;
|
|
644
|
+
elements.push(this.parseExpr());
|
|
645
|
+
}
|
|
646
|
+
this.expect(TokenType.RBracket);
|
|
647
|
+
return { kind: "ListLiteral", elements, loc };
|
|
648
|
+
}
|
|
649
|
+
parseMapOrBlock() {
|
|
650
|
+
const loc = this.loc();
|
|
651
|
+
// Peek ahead to determine if map literal (key: value) or block
|
|
652
|
+
const saved = this.pos;
|
|
653
|
+
this.advance(); // skip {
|
|
654
|
+
// Empty braces = empty map
|
|
655
|
+
if (this.at(TokenType.RBrace)) {
|
|
656
|
+
this.advance();
|
|
657
|
+
return { kind: "MapLiteral", entries: [], loc };
|
|
658
|
+
}
|
|
659
|
+
// Check if it's a map: identifier followed by colon
|
|
660
|
+
if (this.at(TokenType.Ident) && this.tokens[this.pos + 1]?.type === TokenType.Colon) {
|
|
661
|
+
// It's a map literal
|
|
662
|
+
const entries = [];
|
|
663
|
+
while (!this.at(TokenType.RBrace) && !this.at(TokenType.EOF)) {
|
|
664
|
+
const key = this.expect(TokenType.Ident).value;
|
|
665
|
+
this.expect(TokenType.Colon);
|
|
666
|
+
const value = this.parseExpr();
|
|
667
|
+
entries.push({ key, value });
|
|
668
|
+
if (this.at(TokenType.Comma))
|
|
669
|
+
this.advance();
|
|
670
|
+
}
|
|
671
|
+
this.expect(TokenType.RBrace);
|
|
672
|
+
return { kind: "MapLiteral", entries, loc };
|
|
673
|
+
}
|
|
674
|
+
// It's a block
|
|
675
|
+
this.pos = saved;
|
|
676
|
+
return this.parseBlock();
|
|
677
|
+
}
|
|
678
|
+
parseIf() {
|
|
679
|
+
const loc = this.loc();
|
|
680
|
+
this.expect(TokenType.If);
|
|
681
|
+
const condition = this.parseExpr(0);
|
|
682
|
+
const then = this.parseBlock();
|
|
683
|
+
let else_;
|
|
684
|
+
if (this.at(TokenType.El)) {
|
|
685
|
+
this.advance();
|
|
686
|
+
if (this.at(TokenType.If)) {
|
|
687
|
+
else_ = this.parseIf();
|
|
688
|
+
}
|
|
689
|
+
else {
|
|
690
|
+
else_ = this.parseBlock();
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
return { kind: "IfExpr", condition, then, else_, loc };
|
|
694
|
+
}
|
|
695
|
+
parseMatch() {
|
|
696
|
+
const loc = this.loc();
|
|
697
|
+
this.expect(TokenType.Match);
|
|
698
|
+
const subject = this.parseExpr();
|
|
699
|
+
this.expect(TokenType.LBrace);
|
|
700
|
+
const arms = [];
|
|
701
|
+
while (!this.at(TokenType.RBrace) && !this.at(TokenType.EOF)) {
|
|
702
|
+
const pattern = this.parsePattern();
|
|
703
|
+
let guard;
|
|
704
|
+
if (this.at(TokenType.If)) {
|
|
705
|
+
this.advance();
|
|
706
|
+
guard = this.parseExpr();
|
|
707
|
+
}
|
|
708
|
+
this.expect(TokenType.FatArrow);
|
|
709
|
+
const body = this.parseExpr();
|
|
710
|
+
arms.push({ pattern, guard, body });
|
|
711
|
+
if (this.at(TokenType.Comma))
|
|
712
|
+
this.advance();
|
|
713
|
+
}
|
|
714
|
+
this.expect(TokenType.RBrace);
|
|
715
|
+
return { kind: "MatchExpr", subject, arms, loc };
|
|
716
|
+
}
|
|
717
|
+
parsePattern() {
|
|
718
|
+
const loc = this.loc();
|
|
719
|
+
const t = this.peek();
|
|
720
|
+
if (t.type === TokenType.Ident && t.value === "_") {
|
|
721
|
+
this.advance();
|
|
722
|
+
return { kind: "WildcardPattern", loc };
|
|
723
|
+
}
|
|
724
|
+
if (t.type === TokenType.Int || t.type === TokenType.Float) {
|
|
725
|
+
this.advance();
|
|
726
|
+
return { kind: "LiteralPattern", value: parseFloat(t.value), loc };
|
|
727
|
+
}
|
|
728
|
+
if (t.type === TokenType.String) {
|
|
729
|
+
this.advance();
|
|
730
|
+
return { kind: "LiteralPattern", value: t.value, loc };
|
|
731
|
+
}
|
|
732
|
+
if (t.type === TokenType.True) {
|
|
733
|
+
this.advance();
|
|
734
|
+
return { kind: "LiteralPattern", value: true, loc };
|
|
735
|
+
}
|
|
736
|
+
if (t.type === TokenType.False) {
|
|
737
|
+
this.advance();
|
|
738
|
+
return { kind: "LiteralPattern", value: false, loc };
|
|
739
|
+
}
|
|
740
|
+
if (t.type === TokenType.NilKw) {
|
|
741
|
+
this.advance();
|
|
742
|
+
return { kind: "LiteralPattern", value: null, loc };
|
|
743
|
+
}
|
|
744
|
+
if (t.type === TokenType.Ident) {
|
|
745
|
+
this.advance();
|
|
746
|
+
return { kind: "BindingPattern", name: t.value, loc };
|
|
747
|
+
}
|
|
748
|
+
throw new ParseError(`Expected pattern, got ${TokenType[t.type]}`, loc);
|
|
749
|
+
}
|
|
750
|
+
parseToolCall() {
|
|
751
|
+
const loc = this.loc();
|
|
752
|
+
this.expect(TokenType.At);
|
|
753
|
+
const method = this.expect(TokenType.Ident).value;
|
|
754
|
+
// @ident(args) - custom tool call
|
|
755
|
+
if (this.at(TokenType.LParen)) {
|
|
756
|
+
this.advance();
|
|
757
|
+
const args = [];
|
|
758
|
+
while (!this.at(TokenType.RParen)) {
|
|
759
|
+
args.push(this.parseExpr());
|
|
760
|
+
if (this.at(TokenType.Comma))
|
|
761
|
+
this.advance();
|
|
762
|
+
}
|
|
763
|
+
this.expect(TokenType.RParen);
|
|
764
|
+
const arg = args.length === 1 ? args[0] :
|
|
765
|
+
{ kind: "ListLiteral", elements: args, loc };
|
|
766
|
+
return { kind: "ToolCallExpr", method, arg, loc };
|
|
767
|
+
}
|
|
768
|
+
// @METHOD "url" {body?}
|
|
769
|
+
const arg = this.parseExpr(9); // high precedence to grab just the string
|
|
770
|
+
let body;
|
|
771
|
+
if (this.at(TokenType.LBrace)) {
|
|
772
|
+
body = this.parseMapOrBlock();
|
|
773
|
+
}
|
|
774
|
+
return { kind: "ToolCallExpr", method, arg, body, loc };
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
export function parse(tokens) {
|
|
778
|
+
return new Parser(tokens).parse();
|
|
779
|
+
}
|