@hatchingpoint/point 0.0.6 → 0.0.8
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 -21
- package/README.md +34 -34
- package/package.json +34 -30
- package/src/cli.ts +7 -7
- package/src/core/ast.ts +162 -162
- package/src/core/check.ts +412 -412
- package/src/core/cli.ts +497 -497
- package/src/core/context.ts +181 -181
- package/src/core/emit-javascript.ts +124 -124
- package/src/core/emit-typescript.ts +166 -166
- package/src/core/format.ts +6 -6
- package/src/core/incremental.ts +53 -53
- package/src/core/index.ts +12 -12
- package/src/core/lexer.ts +234 -234
- package/src/core/semantic-source.ts +26 -26
- package/src/core/serialize.ts +18 -18
- package/src/core/test-only/core-text-parser.ts +400 -400
- package/src/core/test-only/format-core.ts +120 -120
- package/src/core/test-only/index.ts +3 -3
- package/src/core/test-only/legacy-lowering.ts +1030 -1030
- package/src/index.ts +1 -1
- package/src/semantic/ast.ts +230 -230
- package/src/semantic/callables.ts +51 -51
- package/src/semantic/context.ts +347 -347
- package/src/semantic/desugar.ts +665 -665
- package/src/semantic/expressions.ts +347 -347
- package/src/semantic/format.ts +222 -222
- package/src/semantic/index.ts +10 -10
- package/src/semantic/metadata.ts +37 -37
- package/src/semantic/naming.ts +33 -33
- package/src/semantic/parse.ts +945 -945
- package/src/semantic/serialize.ts +18 -18
|
@@ -13,403 +13,403 @@ import type {
|
|
|
13
13
|
} from "../ast.ts";
|
|
14
14
|
import { lexPointCore, type PointCoreToken, type PointCoreTokenType } from "../lexer.ts";
|
|
15
15
|
|
|
16
|
-
export class PointCoreParserError extends Error {
|
|
17
|
-
constructor(message: string, public readonly token: PointCoreToken) {
|
|
18
|
-
super(`${message} at ${token.span.start.line}:${token.span.start.column}`);
|
|
19
|
-
this.name = "PointCoreParserError";
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function parsePointCore(source: string): PointCoreProgram {
|
|
24
|
-
const parser = new CoreParser(lexPointCore(source));
|
|
25
|
-
return parser.parseProgram();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
class CoreParser {
|
|
29
|
-
private current = 0;
|
|
30
|
-
|
|
31
|
-
constructor(private readonly tokens: PointCoreToken[]) {}
|
|
32
|
-
|
|
33
|
-
parseProgram(): PointCoreProgram {
|
|
34
|
-
let moduleName: string | undefined;
|
|
35
|
-
const declarations: PointCoreDeclaration[] = [];
|
|
36
|
-
const start = this.peek().span.start;
|
|
37
|
-
while (!this.check("eof")) {
|
|
38
|
-
if (this.matchKeyword("module")) {
|
|
39
|
-
moduleName = this.consume("identifier", "Expected module name").value;
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
declarations.push(this.parseDeclaration());
|
|
43
|
-
}
|
|
44
|
-
return {
|
|
45
|
-
kind: "coreProgram",
|
|
46
|
-
module: moduleName,
|
|
47
|
-
declarations,
|
|
48
|
-
span: { start, end: this.peek().span.end },
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
private parseDeclaration(): PointCoreDeclaration {
|
|
53
|
-
if (this.matchKeyword("import")) return this.parseImport();
|
|
54
|
-
if (this.matchKeyword("external")) return this.parseExternal();
|
|
55
|
-
if (this.checkKeyword("let") || this.checkKeyword("var")) return this.parseValueDeclaration();
|
|
56
|
-
if (this.matchKeyword("fn")) return this.parseFunction();
|
|
57
|
-
if (this.matchKeyword("type")) return this.parseTypeDeclaration();
|
|
58
|
-
throw new PointCoreParserError("Expected import, let, var, fn, or type declaration", this.peek());
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
private parseImport(): PointCoreDeclaration {
|
|
62
|
-
const start = this.previous().span.start;
|
|
63
|
-
this.consume("leftBrace", "Expected import list");
|
|
64
|
-
const names: string[] = [];
|
|
65
|
-
do {
|
|
66
|
-
names.push(this.consume("identifier", "Expected imported name").value);
|
|
67
|
-
} while (this.match("comma"));
|
|
68
|
-
this.consume("rightBrace", "Expected end of import list");
|
|
69
|
-
this.consumeKeyword("from");
|
|
70
|
-
const from = this.consume("string", "Expected import source").value;
|
|
71
|
-
return { kind: "import", names, from, span: { start, end: this.previous().span.end } };
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
private parseValueDeclaration(): PointCoreValueDeclaration {
|
|
75
|
-
const keyword = this.advance();
|
|
76
|
-
const name = this.consume("identifier", "Expected value name");
|
|
77
|
-
this.consume("colon", `Expected type annotation for ${name.value}`);
|
|
78
|
-
const type = this.parseTypeExpression();
|
|
79
|
-
this.consume("equals", `Expected initializer for ${name.value}`);
|
|
80
|
-
const value = this.parseExpression();
|
|
81
|
-
return {
|
|
82
|
-
kind: "value",
|
|
83
|
-
mutable: keyword.value === "var",
|
|
84
|
-
name: name.value,
|
|
85
|
-
type,
|
|
86
|
-
value,
|
|
87
|
-
span: { start: keyword.span.start, end: value.span?.end ?? this.previous().span.end },
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
private parseFunction(): PointCoreFunctionDeclaration {
|
|
92
|
-
const start = this.previous().span.start;
|
|
93
|
-
const name = this.consume("identifier", "Expected function name").value;
|
|
94
|
-
this.consume("leftParen", "Expected function parameters");
|
|
95
|
-
const params = this.parseParameters();
|
|
96
|
-
this.consume("rightParen", "Expected end of function parameters");
|
|
97
|
-
this.consume("colon", "Expected function return type");
|
|
98
|
-
const returnType = this.parseTypeExpression();
|
|
99
|
-
this.consume("leftBrace", "Expected function body");
|
|
100
|
-
const body: PointCoreStatement[] = [];
|
|
101
|
-
while (!this.check("rightBrace")) body.push(this.parseStatement());
|
|
102
|
-
this.consume("rightBrace", "Expected end of function body");
|
|
103
|
-
return { kind: "function", name, params, returnType, body, span: { start, end: this.previous().span.end } };
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
private parseTypeDeclaration(): PointCoreDeclaration {
|
|
107
|
-
const start = this.previous().span.start;
|
|
108
|
-
const name = this.consume("identifier", "Expected type name").value;
|
|
109
|
-
this.consume("leftBrace", "Expected type body");
|
|
110
|
-
const fields: PointCoreParameter[] = [];
|
|
111
|
-
while (!this.check("rightBrace")) {
|
|
112
|
-
fields.push(this.parseParameter());
|
|
113
|
-
}
|
|
114
|
-
this.consume("rightBrace", "Expected end of type body");
|
|
115
|
-
return { kind: "type", name, fields, span: { start, end: this.previous().span.end } };
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
private parseStatement(): PointCoreStatement {
|
|
119
|
-
if (this.matchKeyword("return")) {
|
|
120
|
-
const start = this.previous().span.start;
|
|
121
|
-
if (this.check("rightBrace")) return { kind: "return", span: { start, end: this.previous().span.end } };
|
|
122
|
-
const value = this.parseExpression();
|
|
123
|
-
return { kind: "return", value, span: { start, end: value.span?.end ?? this.previous().span.end } };
|
|
124
|
-
}
|
|
125
|
-
if (this.matchKeyword("if")) return this.parseIfStatement();
|
|
126
|
-
if (this.matchKeyword("for")) return this.parseForStatement();
|
|
127
|
-
if (this.checkKeyword("let") || this.checkKeyword("var")) return this.parseValueDeclaration();
|
|
128
|
-
if (this.check("identifier") && (this.checkNext("equals") || this.checkNext("plusEquals") || this.checkNext("minusEquals"))) {
|
|
129
|
-
return this.parseAssignment();
|
|
130
|
-
}
|
|
131
|
-
const value = this.parseExpression();
|
|
132
|
-
return { kind: "expression", value, span: value.span };
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
private parseAssignment(): PointCoreStatement {
|
|
136
|
-
const name = this.consume("identifier", "Expected assignment target");
|
|
137
|
-
const operator = this.match("plusEquals") ? "+=" : this.match("minusEquals") ? "-=" : "=";
|
|
138
|
-
if (operator === "=") this.consume("equals", "Expected assignment operator");
|
|
139
|
-
const value = this.parseExpression();
|
|
140
|
-
return {
|
|
141
|
-
kind: "assignment",
|
|
142
|
-
name: name.value,
|
|
143
|
-
operator,
|
|
144
|
-
value,
|
|
145
|
-
span: { start: name.span.start, end: value.span?.end ?? this.previous().span.end },
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
private parseIfStatement(): PointCoreStatement {
|
|
150
|
-
const start = this.previous().span.start;
|
|
151
|
-
const condition = this.parseExpression();
|
|
152
|
-
const thenBody = this.parseBlockStatements("Expected if body");
|
|
153
|
-
let elseBody: PointCoreStatement[] = [];
|
|
154
|
-
if (this.matchKeyword("else")) {
|
|
155
|
-
elseBody = this.parseBlockStatements("Expected else body");
|
|
156
|
-
}
|
|
157
|
-
return {
|
|
158
|
-
kind: "if",
|
|
159
|
-
condition,
|
|
160
|
-
thenBody,
|
|
161
|
-
elseBody,
|
|
162
|
-
span: { start, end: this.previous().span.end },
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
private parseExternal(): PointCoreExternalDeclaration {
|
|
167
|
-
const start = this.previous().span.start;
|
|
168
|
-
this.consumeKeyword("fn");
|
|
169
|
-
const name = this.consume("identifier", "Expected external function name").value;
|
|
170
|
-
this.consume("leftParen", "Expected external function parameters");
|
|
171
|
-
const params = this.parseParameters();
|
|
172
|
-
this.consume("rightParen", "Expected end of external function parameters");
|
|
173
|
-
this.consume("colon", "Expected external function return type");
|
|
174
|
-
const returnType = this.parseTypeExpression();
|
|
175
|
-
this.consumeKeyword("from");
|
|
176
|
-
const from = this.consume("string", "Expected external import source").value;
|
|
177
|
-
let importName: string | undefined;
|
|
178
|
-
if (this.matchKeyword("as")) importName = this.consume("identifier", "Expected imported external name").value;
|
|
179
|
-
return { kind: "external", name, params, returnType, from, importName, span: { start, end: this.previous().span.end } };
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
private parseForStatement(): PointCoreStatement {
|
|
183
|
-
const start = this.previous().span.start;
|
|
184
|
-
const itemName = this.consume("identifier", "Expected loop item name").value;
|
|
185
|
-
this.consumeKeyword("in");
|
|
186
|
-
const iterable = this.parseExpression();
|
|
187
|
-
const body = this.parseBlockStatements("Expected for body");
|
|
188
|
-
return { kind: "for", itemName, iterable, body, span: { start, end: this.previous().span.end } };
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
private parseBlockStatements(message: string): PointCoreStatement[] {
|
|
192
|
-
this.consume("leftBrace", message);
|
|
193
|
-
const statements: PointCoreStatement[] = [];
|
|
194
|
-
while (!this.check("rightBrace")) statements.push(this.parseStatement());
|
|
195
|
-
this.consume("rightBrace", "Expected end of block");
|
|
196
|
-
return statements;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
private parseParameters(): PointCoreParameter[] {
|
|
200
|
-
const params: PointCoreParameter[] = [];
|
|
201
|
-
if (this.check("rightParen")) return params;
|
|
202
|
-
do {
|
|
203
|
-
params.push(this.parseParameter());
|
|
204
|
-
} while (this.match("comma"));
|
|
205
|
-
return params;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
private parseParameter(): PointCoreParameter {
|
|
209
|
-
const name = this.consume("identifier", "Expected parameter name");
|
|
210
|
-
this.consume("colon", `Expected type annotation for ${name.value}`);
|
|
211
|
-
const type = this.parseTypeExpression();
|
|
212
|
-
return { name: name.value, type, span: { start: name.span.start, end: type.span?.end ?? name.span.end } };
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
private parseTypeExpression(): PointCoreTypeExpression {
|
|
216
|
-
const token = this.consume("identifier", "Expected type name");
|
|
217
|
-
const args: PointCoreTypeExpression[] = [];
|
|
218
|
-
if (this.match("less")) {
|
|
219
|
-
do {
|
|
220
|
-
args.push(this.parseTypeExpression());
|
|
221
|
-
} while (this.match("comma"));
|
|
222
|
-
this.consume("greater", "Expected end of type arguments");
|
|
223
|
-
}
|
|
224
|
-
return { kind: "typeRef", name: token.value, args, span: { start: token.span.start, end: this.previous().span.end } };
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
private parseExpression(): PointCoreExpression {
|
|
228
|
-
return this.parseBinaryExpression(0);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
private parseBinaryExpression(minPrecedence: number): PointCoreExpression {
|
|
232
|
-
let left = this.parsePrimaryExpression();
|
|
233
|
-
while (true) {
|
|
234
|
-
const operator = this.peekBinaryOperator();
|
|
235
|
-
if (!operator) break;
|
|
236
|
-
const precedence = precedenceFor(operator);
|
|
237
|
-
if (precedence < minPrecedence) break;
|
|
238
|
-
this.advance();
|
|
239
|
-
const right = this.parseBinaryExpression(precedence + 1);
|
|
240
|
-
left = {
|
|
241
|
-
kind: "binary",
|
|
242
|
-
operator,
|
|
243
|
-
left,
|
|
244
|
-
right,
|
|
245
|
-
span: { start: left.span?.start ?? this.previous().span.start, end: right.span?.end ?? this.previous().span.end },
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
return left;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
private parsePrimaryExpression(): PointCoreExpression {
|
|
252
|
-
let expression = this.parseAtomExpression();
|
|
253
|
-
while (this.match("dot")) {
|
|
254
|
-
const name = this.consume("identifier", "Expected property name");
|
|
255
|
-
expression = {
|
|
256
|
-
kind: "property",
|
|
257
|
-
target: expression,
|
|
258
|
-
name: name.value,
|
|
259
|
-
span: { start: expression.span?.start ?? name.span.start, end: name.span.end },
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
return expression;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
private parseAtomExpression(): PointCoreExpression {
|
|
266
|
-
if (this.check("string")) {
|
|
267
|
-
const token = this.advance();
|
|
268
|
-
return { kind: "literal", value: token.value, span: token.span };
|
|
269
|
-
}
|
|
270
|
-
if (this.check("number")) {
|
|
271
|
-
const token = this.advance();
|
|
272
|
-
return { kind: "literal", value: Number(token.value), span: token.span };
|
|
273
|
-
}
|
|
274
|
-
if (this.match("leftParen")) {
|
|
275
|
-
const start = this.previous().span.start;
|
|
276
|
-
const expression = this.parseExpression();
|
|
277
|
-
this.consume("rightParen", "Expected end of grouped expression");
|
|
278
|
-
return { ...expression, span: { start, end: this.previous().span.end } };
|
|
279
|
-
}
|
|
280
|
-
if (this.match("leftBracket")) return this.parseListExpression();
|
|
281
|
-
if (this.match("leftBrace")) return this.parseRecordExpression();
|
|
282
|
-
const identifier = this.consume("identifier", "Expected expression");
|
|
283
|
-
if (identifier.value === "true") return { kind: "literal", value: true, span: identifier.span };
|
|
284
|
-
if (identifier.value === "false") return { kind: "literal", value: false, span: identifier.span };
|
|
285
|
-
if (identifier.value === "none" || identifier.value === "null") return { kind: "literal", value: null, span: identifier.span };
|
|
286
|
-
if (identifier.value === "await") {
|
|
287
|
-
const value = this.parsePrimaryExpression();
|
|
288
|
-
return { kind: "await", value, span: { start: identifier.span.start, end: value.span?.end ?? identifier.span.end } };
|
|
289
|
-
}
|
|
290
|
-
if (this.match("leftParen")) {
|
|
291
|
-
const args: PointCoreExpression[] = [];
|
|
292
|
-
if (!this.check("rightParen")) {
|
|
293
|
-
do {
|
|
294
|
-
args.push(this.parseExpression());
|
|
295
|
-
} while (this.match("comma"));
|
|
296
|
-
}
|
|
297
|
-
this.consume("rightParen", "Expected end of call arguments");
|
|
298
|
-
return {
|
|
299
|
-
kind: "call",
|
|
300
|
-
callee: identifier.value,
|
|
301
|
-
args,
|
|
302
|
-
span: { start: identifier.span.start, end: this.previous().span.end },
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
return { kind: "identifier", name: identifier.value, span: identifier.span };
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
private parseListExpression(): PointCoreExpression {
|
|
309
|
-
const start = this.previous().span.start;
|
|
310
|
-
const items: PointCoreExpression[] = [];
|
|
311
|
-
if (!this.check("rightBracket")) {
|
|
312
|
-
do {
|
|
313
|
-
items.push(this.parseExpression());
|
|
314
|
-
} while (this.match("comma"));
|
|
315
|
-
}
|
|
316
|
-
this.consume("rightBracket", "Expected end of list");
|
|
317
|
-
return { kind: "list", items, span: { start, end: this.previous().span.end } };
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
private parseRecordExpression(): PointCoreExpression {
|
|
321
|
-
const start = this.previous().span.start;
|
|
322
|
-
const fields: PointCoreRecordField[] = [];
|
|
323
|
-
if (!this.check("rightBrace")) {
|
|
324
|
-
do {
|
|
325
|
-
const name = this.consume("identifier", "Expected record field name");
|
|
326
|
-
this.consume("colon", `Expected value for record field ${name.value}`);
|
|
327
|
-
const value = this.parseExpression();
|
|
328
|
-
fields.push({
|
|
329
|
-
name: name.value,
|
|
330
|
-
value,
|
|
331
|
-
span: { start: name.span.start, end: value.span?.end ?? name.span.end },
|
|
332
|
-
});
|
|
333
|
-
} while (this.match("comma"));
|
|
334
|
-
}
|
|
335
|
-
this.consume("rightBrace", "Expected end of record");
|
|
336
|
-
return { kind: "record", fields, span: { start, end: this.previous().span.end } };
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
private peekBinaryOperator(): PointCoreBinaryOperator | null {
|
|
340
|
-
const token = this.peek();
|
|
341
|
-
if (token.type === "plus") return "+";
|
|
342
|
-
if (token.type === "minus") return "-";
|
|
343
|
-
if (token.type === "star") return "*";
|
|
344
|
-
if (token.type === "slash") return "/";
|
|
345
|
-
if (token.type === "equalsEquals") return "==";
|
|
346
|
-
if (token.type === "bangEquals") return "!=";
|
|
347
|
-
if (token.type === "less") return "<";
|
|
348
|
-
if (token.type === "lessEquals") return "<=";
|
|
349
|
-
if (token.type === "greater") return ">";
|
|
350
|
-
if (token.type === "greaterEquals") return ">=";
|
|
351
|
-
if (token.type === "identifier" && (token.value === "and" || token.value === "or")) return token.value;
|
|
352
|
-
return null;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
private consumeKeyword(keyword: string) {
|
|
356
|
-
const token = this.consume("identifier", `Expected ${keyword}`);
|
|
357
|
-
if (token.value !== keyword) throw new PointCoreParserError(`Expected ${keyword}`, token);
|
|
358
|
-
return token;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
private matchKeyword(keyword: string) {
|
|
362
|
-
if (!this.checkKeyword(keyword)) return false;
|
|
363
|
-
this.advance();
|
|
364
|
-
return true;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
private checkKeyword(keyword: string) {
|
|
368
|
-
return this.check("identifier") && this.peek().value === keyword;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
private consume(type: PointCoreTokenType, message: string) {
|
|
372
|
-
if (this.check(type)) return this.advance();
|
|
373
|
-
throw new PointCoreParserError(message, this.peek());
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
private match(type: PointCoreTokenType) {
|
|
377
|
-
if (!this.check(type)) return false;
|
|
378
|
-
this.advance();
|
|
379
|
-
return true;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
private check(type: PointCoreTokenType) {
|
|
383
|
-
return this.peek().type === type;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
private checkNext(type: PointCoreTokenType) {
|
|
387
|
-
return this.peek(1).type === type;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
private advance() {
|
|
391
|
-
if (!this.check("eof")) this.current += 1;
|
|
392
|
-
return this.previous();
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
private peek(offset = 0) {
|
|
396
|
-
return this.tokens[this.current + offset] ?? this.tokens[this.tokens.length - 1]!;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
private previous() {
|
|
400
|
-
return this.tokens[this.current - 1] ?? this.tokens[0]!;
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
function precedenceFor(operator: PointCoreBinaryOperator): number {
|
|
405
|
-
if (operator === "or") return 1;
|
|
406
|
-
if (operator === "and") return 2;
|
|
407
|
-
if (operator === "==" || operator === "!=") return 3;
|
|
408
|
-
if (operator === "<" || operator === "<=" || operator === ">" || operator === ">=") return 4;
|
|
409
|
-
if (operator === "+" || operator === "-") return 5;
|
|
410
|
-
return 6;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
export function mergeSpans(start: PointSourceSpan, end: PointSourceSpan): PointSourceSpan {
|
|
414
|
-
return { start: start.start, end: end.end };
|
|
415
|
-
}
|
|
16
|
+
export class PointCoreParserError extends Error {
|
|
17
|
+
constructor(message: string, public readonly token: PointCoreToken) {
|
|
18
|
+
super(`${message} at ${token.span.start.line}:${token.span.start.column}`);
|
|
19
|
+
this.name = "PointCoreParserError";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function parsePointCore(source: string): PointCoreProgram {
|
|
24
|
+
const parser = new CoreParser(lexPointCore(source));
|
|
25
|
+
return parser.parseProgram();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
class CoreParser {
|
|
29
|
+
private current = 0;
|
|
30
|
+
|
|
31
|
+
constructor(private readonly tokens: PointCoreToken[]) {}
|
|
32
|
+
|
|
33
|
+
parseProgram(): PointCoreProgram {
|
|
34
|
+
let moduleName: string | undefined;
|
|
35
|
+
const declarations: PointCoreDeclaration[] = [];
|
|
36
|
+
const start = this.peek().span.start;
|
|
37
|
+
while (!this.check("eof")) {
|
|
38
|
+
if (this.matchKeyword("module")) {
|
|
39
|
+
moduleName = this.consume("identifier", "Expected module name").value;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
declarations.push(this.parseDeclaration());
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
kind: "coreProgram",
|
|
46
|
+
module: moduleName,
|
|
47
|
+
declarations,
|
|
48
|
+
span: { start, end: this.peek().span.end },
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private parseDeclaration(): PointCoreDeclaration {
|
|
53
|
+
if (this.matchKeyword("import")) return this.parseImport();
|
|
54
|
+
if (this.matchKeyword("external")) return this.parseExternal();
|
|
55
|
+
if (this.checkKeyword("let") || this.checkKeyword("var")) return this.parseValueDeclaration();
|
|
56
|
+
if (this.matchKeyword("fn")) return this.parseFunction();
|
|
57
|
+
if (this.matchKeyword("type")) return this.parseTypeDeclaration();
|
|
58
|
+
throw new PointCoreParserError("Expected import, let, var, fn, or type declaration", this.peek());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private parseImport(): PointCoreDeclaration {
|
|
62
|
+
const start = this.previous().span.start;
|
|
63
|
+
this.consume("leftBrace", "Expected import list");
|
|
64
|
+
const names: string[] = [];
|
|
65
|
+
do {
|
|
66
|
+
names.push(this.consume("identifier", "Expected imported name").value);
|
|
67
|
+
} while (this.match("comma"));
|
|
68
|
+
this.consume("rightBrace", "Expected end of import list");
|
|
69
|
+
this.consumeKeyword("from");
|
|
70
|
+
const from = this.consume("string", "Expected import source").value;
|
|
71
|
+
return { kind: "import", names, from, span: { start, end: this.previous().span.end } };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private parseValueDeclaration(): PointCoreValueDeclaration {
|
|
75
|
+
const keyword = this.advance();
|
|
76
|
+
const name = this.consume("identifier", "Expected value name");
|
|
77
|
+
this.consume("colon", `Expected type annotation for ${name.value}`);
|
|
78
|
+
const type = this.parseTypeExpression();
|
|
79
|
+
this.consume("equals", `Expected initializer for ${name.value}`);
|
|
80
|
+
const value = this.parseExpression();
|
|
81
|
+
return {
|
|
82
|
+
kind: "value",
|
|
83
|
+
mutable: keyword.value === "var",
|
|
84
|
+
name: name.value,
|
|
85
|
+
type,
|
|
86
|
+
value,
|
|
87
|
+
span: { start: keyword.span.start, end: value.span?.end ?? this.previous().span.end },
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private parseFunction(): PointCoreFunctionDeclaration {
|
|
92
|
+
const start = this.previous().span.start;
|
|
93
|
+
const name = this.consume("identifier", "Expected function name").value;
|
|
94
|
+
this.consume("leftParen", "Expected function parameters");
|
|
95
|
+
const params = this.parseParameters();
|
|
96
|
+
this.consume("rightParen", "Expected end of function parameters");
|
|
97
|
+
this.consume("colon", "Expected function return type");
|
|
98
|
+
const returnType = this.parseTypeExpression();
|
|
99
|
+
this.consume("leftBrace", "Expected function body");
|
|
100
|
+
const body: PointCoreStatement[] = [];
|
|
101
|
+
while (!this.check("rightBrace")) body.push(this.parseStatement());
|
|
102
|
+
this.consume("rightBrace", "Expected end of function body");
|
|
103
|
+
return { kind: "function", name, params, returnType, body, span: { start, end: this.previous().span.end } };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private parseTypeDeclaration(): PointCoreDeclaration {
|
|
107
|
+
const start = this.previous().span.start;
|
|
108
|
+
const name = this.consume("identifier", "Expected type name").value;
|
|
109
|
+
this.consume("leftBrace", "Expected type body");
|
|
110
|
+
const fields: PointCoreParameter[] = [];
|
|
111
|
+
while (!this.check("rightBrace")) {
|
|
112
|
+
fields.push(this.parseParameter());
|
|
113
|
+
}
|
|
114
|
+
this.consume("rightBrace", "Expected end of type body");
|
|
115
|
+
return { kind: "type", name, fields, span: { start, end: this.previous().span.end } };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private parseStatement(): PointCoreStatement {
|
|
119
|
+
if (this.matchKeyword("return")) {
|
|
120
|
+
const start = this.previous().span.start;
|
|
121
|
+
if (this.check("rightBrace")) return { kind: "return", span: { start, end: this.previous().span.end } };
|
|
122
|
+
const value = this.parseExpression();
|
|
123
|
+
return { kind: "return", value, span: { start, end: value.span?.end ?? this.previous().span.end } };
|
|
124
|
+
}
|
|
125
|
+
if (this.matchKeyword("if")) return this.parseIfStatement();
|
|
126
|
+
if (this.matchKeyword("for")) return this.parseForStatement();
|
|
127
|
+
if (this.checkKeyword("let") || this.checkKeyword("var")) return this.parseValueDeclaration();
|
|
128
|
+
if (this.check("identifier") && (this.checkNext("equals") || this.checkNext("plusEquals") || this.checkNext("minusEquals"))) {
|
|
129
|
+
return this.parseAssignment();
|
|
130
|
+
}
|
|
131
|
+
const value = this.parseExpression();
|
|
132
|
+
return { kind: "expression", value, span: value.span };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private parseAssignment(): PointCoreStatement {
|
|
136
|
+
const name = this.consume("identifier", "Expected assignment target");
|
|
137
|
+
const operator = this.match("plusEquals") ? "+=" : this.match("minusEquals") ? "-=" : "=";
|
|
138
|
+
if (operator === "=") this.consume("equals", "Expected assignment operator");
|
|
139
|
+
const value = this.parseExpression();
|
|
140
|
+
return {
|
|
141
|
+
kind: "assignment",
|
|
142
|
+
name: name.value,
|
|
143
|
+
operator,
|
|
144
|
+
value,
|
|
145
|
+
span: { start: name.span.start, end: value.span?.end ?? this.previous().span.end },
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private parseIfStatement(): PointCoreStatement {
|
|
150
|
+
const start = this.previous().span.start;
|
|
151
|
+
const condition = this.parseExpression();
|
|
152
|
+
const thenBody = this.parseBlockStatements("Expected if body");
|
|
153
|
+
let elseBody: PointCoreStatement[] = [];
|
|
154
|
+
if (this.matchKeyword("else")) {
|
|
155
|
+
elseBody = this.parseBlockStatements("Expected else body");
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
kind: "if",
|
|
159
|
+
condition,
|
|
160
|
+
thenBody,
|
|
161
|
+
elseBody,
|
|
162
|
+
span: { start, end: this.previous().span.end },
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private parseExternal(): PointCoreExternalDeclaration {
|
|
167
|
+
const start = this.previous().span.start;
|
|
168
|
+
this.consumeKeyword("fn");
|
|
169
|
+
const name = this.consume("identifier", "Expected external function name").value;
|
|
170
|
+
this.consume("leftParen", "Expected external function parameters");
|
|
171
|
+
const params = this.parseParameters();
|
|
172
|
+
this.consume("rightParen", "Expected end of external function parameters");
|
|
173
|
+
this.consume("colon", "Expected external function return type");
|
|
174
|
+
const returnType = this.parseTypeExpression();
|
|
175
|
+
this.consumeKeyword("from");
|
|
176
|
+
const from = this.consume("string", "Expected external import source").value;
|
|
177
|
+
let importName: string | undefined;
|
|
178
|
+
if (this.matchKeyword("as")) importName = this.consume("identifier", "Expected imported external name").value;
|
|
179
|
+
return { kind: "external", name, params, returnType, from, importName, span: { start, end: this.previous().span.end } };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private parseForStatement(): PointCoreStatement {
|
|
183
|
+
const start = this.previous().span.start;
|
|
184
|
+
const itemName = this.consume("identifier", "Expected loop item name").value;
|
|
185
|
+
this.consumeKeyword("in");
|
|
186
|
+
const iterable = this.parseExpression();
|
|
187
|
+
const body = this.parseBlockStatements("Expected for body");
|
|
188
|
+
return { kind: "for", itemName, iterable, body, span: { start, end: this.previous().span.end } };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private parseBlockStatements(message: string): PointCoreStatement[] {
|
|
192
|
+
this.consume("leftBrace", message);
|
|
193
|
+
const statements: PointCoreStatement[] = [];
|
|
194
|
+
while (!this.check("rightBrace")) statements.push(this.parseStatement());
|
|
195
|
+
this.consume("rightBrace", "Expected end of block");
|
|
196
|
+
return statements;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private parseParameters(): PointCoreParameter[] {
|
|
200
|
+
const params: PointCoreParameter[] = [];
|
|
201
|
+
if (this.check("rightParen")) return params;
|
|
202
|
+
do {
|
|
203
|
+
params.push(this.parseParameter());
|
|
204
|
+
} while (this.match("comma"));
|
|
205
|
+
return params;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private parseParameter(): PointCoreParameter {
|
|
209
|
+
const name = this.consume("identifier", "Expected parameter name");
|
|
210
|
+
this.consume("colon", `Expected type annotation for ${name.value}`);
|
|
211
|
+
const type = this.parseTypeExpression();
|
|
212
|
+
return { name: name.value, type, span: { start: name.span.start, end: type.span?.end ?? name.span.end } };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private parseTypeExpression(): PointCoreTypeExpression {
|
|
216
|
+
const token = this.consume("identifier", "Expected type name");
|
|
217
|
+
const args: PointCoreTypeExpression[] = [];
|
|
218
|
+
if (this.match("less")) {
|
|
219
|
+
do {
|
|
220
|
+
args.push(this.parseTypeExpression());
|
|
221
|
+
} while (this.match("comma"));
|
|
222
|
+
this.consume("greater", "Expected end of type arguments");
|
|
223
|
+
}
|
|
224
|
+
return { kind: "typeRef", name: token.value, args, span: { start: token.span.start, end: this.previous().span.end } };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private parseExpression(): PointCoreExpression {
|
|
228
|
+
return this.parseBinaryExpression(0);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private parseBinaryExpression(minPrecedence: number): PointCoreExpression {
|
|
232
|
+
let left = this.parsePrimaryExpression();
|
|
233
|
+
while (true) {
|
|
234
|
+
const operator = this.peekBinaryOperator();
|
|
235
|
+
if (!operator) break;
|
|
236
|
+
const precedence = precedenceFor(operator);
|
|
237
|
+
if (precedence < minPrecedence) break;
|
|
238
|
+
this.advance();
|
|
239
|
+
const right = this.parseBinaryExpression(precedence + 1);
|
|
240
|
+
left = {
|
|
241
|
+
kind: "binary",
|
|
242
|
+
operator,
|
|
243
|
+
left,
|
|
244
|
+
right,
|
|
245
|
+
span: { start: left.span?.start ?? this.previous().span.start, end: right.span?.end ?? this.previous().span.end },
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
return left;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private parsePrimaryExpression(): PointCoreExpression {
|
|
252
|
+
let expression = this.parseAtomExpression();
|
|
253
|
+
while (this.match("dot")) {
|
|
254
|
+
const name = this.consume("identifier", "Expected property name");
|
|
255
|
+
expression = {
|
|
256
|
+
kind: "property",
|
|
257
|
+
target: expression,
|
|
258
|
+
name: name.value,
|
|
259
|
+
span: { start: expression.span?.start ?? name.span.start, end: name.span.end },
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
return expression;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private parseAtomExpression(): PointCoreExpression {
|
|
266
|
+
if (this.check("string")) {
|
|
267
|
+
const token = this.advance();
|
|
268
|
+
return { kind: "literal", value: token.value, span: token.span };
|
|
269
|
+
}
|
|
270
|
+
if (this.check("number")) {
|
|
271
|
+
const token = this.advance();
|
|
272
|
+
return { kind: "literal", value: Number(token.value), span: token.span };
|
|
273
|
+
}
|
|
274
|
+
if (this.match("leftParen")) {
|
|
275
|
+
const start = this.previous().span.start;
|
|
276
|
+
const expression = this.parseExpression();
|
|
277
|
+
this.consume("rightParen", "Expected end of grouped expression");
|
|
278
|
+
return { ...expression, span: { start, end: this.previous().span.end } };
|
|
279
|
+
}
|
|
280
|
+
if (this.match("leftBracket")) return this.parseListExpression();
|
|
281
|
+
if (this.match("leftBrace")) return this.parseRecordExpression();
|
|
282
|
+
const identifier = this.consume("identifier", "Expected expression");
|
|
283
|
+
if (identifier.value === "true") return { kind: "literal", value: true, span: identifier.span };
|
|
284
|
+
if (identifier.value === "false") return { kind: "literal", value: false, span: identifier.span };
|
|
285
|
+
if (identifier.value === "none" || identifier.value === "null") return { kind: "literal", value: null, span: identifier.span };
|
|
286
|
+
if (identifier.value === "await") {
|
|
287
|
+
const value = this.parsePrimaryExpression();
|
|
288
|
+
return { kind: "await", value, span: { start: identifier.span.start, end: value.span?.end ?? identifier.span.end } };
|
|
289
|
+
}
|
|
290
|
+
if (this.match("leftParen")) {
|
|
291
|
+
const args: PointCoreExpression[] = [];
|
|
292
|
+
if (!this.check("rightParen")) {
|
|
293
|
+
do {
|
|
294
|
+
args.push(this.parseExpression());
|
|
295
|
+
} while (this.match("comma"));
|
|
296
|
+
}
|
|
297
|
+
this.consume("rightParen", "Expected end of call arguments");
|
|
298
|
+
return {
|
|
299
|
+
kind: "call",
|
|
300
|
+
callee: identifier.value,
|
|
301
|
+
args,
|
|
302
|
+
span: { start: identifier.span.start, end: this.previous().span.end },
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
return { kind: "identifier", name: identifier.value, span: identifier.span };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
private parseListExpression(): PointCoreExpression {
|
|
309
|
+
const start = this.previous().span.start;
|
|
310
|
+
const items: PointCoreExpression[] = [];
|
|
311
|
+
if (!this.check("rightBracket")) {
|
|
312
|
+
do {
|
|
313
|
+
items.push(this.parseExpression());
|
|
314
|
+
} while (this.match("comma"));
|
|
315
|
+
}
|
|
316
|
+
this.consume("rightBracket", "Expected end of list");
|
|
317
|
+
return { kind: "list", items, span: { start, end: this.previous().span.end } };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private parseRecordExpression(): PointCoreExpression {
|
|
321
|
+
const start = this.previous().span.start;
|
|
322
|
+
const fields: PointCoreRecordField[] = [];
|
|
323
|
+
if (!this.check("rightBrace")) {
|
|
324
|
+
do {
|
|
325
|
+
const name = this.consume("identifier", "Expected record field name");
|
|
326
|
+
this.consume("colon", `Expected value for record field ${name.value}`);
|
|
327
|
+
const value = this.parseExpression();
|
|
328
|
+
fields.push({
|
|
329
|
+
name: name.value,
|
|
330
|
+
value,
|
|
331
|
+
span: { start: name.span.start, end: value.span?.end ?? name.span.end },
|
|
332
|
+
});
|
|
333
|
+
} while (this.match("comma"));
|
|
334
|
+
}
|
|
335
|
+
this.consume("rightBrace", "Expected end of record");
|
|
336
|
+
return { kind: "record", fields, span: { start, end: this.previous().span.end } };
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
private peekBinaryOperator(): PointCoreBinaryOperator | null {
|
|
340
|
+
const token = this.peek();
|
|
341
|
+
if (token.type === "plus") return "+";
|
|
342
|
+
if (token.type === "minus") return "-";
|
|
343
|
+
if (token.type === "star") return "*";
|
|
344
|
+
if (token.type === "slash") return "/";
|
|
345
|
+
if (token.type === "equalsEquals") return "==";
|
|
346
|
+
if (token.type === "bangEquals") return "!=";
|
|
347
|
+
if (token.type === "less") return "<";
|
|
348
|
+
if (token.type === "lessEquals") return "<=";
|
|
349
|
+
if (token.type === "greater") return ">";
|
|
350
|
+
if (token.type === "greaterEquals") return ">=";
|
|
351
|
+
if (token.type === "identifier" && (token.value === "and" || token.value === "or")) return token.value;
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
private consumeKeyword(keyword: string) {
|
|
356
|
+
const token = this.consume("identifier", `Expected ${keyword}`);
|
|
357
|
+
if (token.value !== keyword) throw new PointCoreParserError(`Expected ${keyword}`, token);
|
|
358
|
+
return token;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
private matchKeyword(keyword: string) {
|
|
362
|
+
if (!this.checkKeyword(keyword)) return false;
|
|
363
|
+
this.advance();
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
private checkKeyword(keyword: string) {
|
|
368
|
+
return this.check("identifier") && this.peek().value === keyword;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
private consume(type: PointCoreTokenType, message: string) {
|
|
372
|
+
if (this.check(type)) return this.advance();
|
|
373
|
+
throw new PointCoreParserError(message, this.peek());
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
private match(type: PointCoreTokenType) {
|
|
377
|
+
if (!this.check(type)) return false;
|
|
378
|
+
this.advance();
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
private check(type: PointCoreTokenType) {
|
|
383
|
+
return this.peek().type === type;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
private checkNext(type: PointCoreTokenType) {
|
|
387
|
+
return this.peek(1).type === type;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
private advance() {
|
|
391
|
+
if (!this.check("eof")) this.current += 1;
|
|
392
|
+
return this.previous();
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
private peek(offset = 0) {
|
|
396
|
+
return this.tokens[this.current + offset] ?? this.tokens[this.tokens.length - 1]!;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
private previous() {
|
|
400
|
+
return this.tokens[this.current - 1] ?? this.tokens[0]!;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function precedenceFor(operator: PointCoreBinaryOperator): number {
|
|
405
|
+
if (operator === "or") return 1;
|
|
406
|
+
if (operator === "and") return 2;
|
|
407
|
+
if (operator === "==" || operator === "!=") return 3;
|
|
408
|
+
if (operator === "<" || operator === "<=" || operator === ">" || operator === ">=") return 4;
|
|
409
|
+
if (operator === "+" || operator === "-") return 5;
|
|
410
|
+
return 6;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export function mergeSpans(start: PointSourceSpan, end: PointSourceSpan): PointSourceSpan {
|
|
414
|
+
return { start: start.start, end: end.end };
|
|
415
|
+
}
|