lang10/src/parse.ts
2025-12-10 22:46:32 +01:00

255 lines
6.9 KiB
TypeScript

import { AstBuilder, Block, Expr, File, Param, Pat, Stmt, Ty } from "./ast.ts";
import { Tok } from "./tok.ts";
const t = new AstBuilder();
export class Parser {
private i = 0;
private eaten?: Tok;
constructor(
private toks: Tok[],
) {}
parseFile(): File {
const stmts: Stmt[] = [];
while (!this.done) {
stmts.push(this.parseItem());
}
return t.file(stmts);
}
parseItem(): Stmt {
const line = this.line();
if (this.test("fn")) {
return this.parseFn();
} else if (this.test("let")) {
return this.parseFn();
} else {
this.expect("item");
this.step();
return t.stmt(line, "error", {});
}
}
parseBlock(): Block {
const line = this.line();
this.step();
const stmts: Stmt[] = [];
let expr: Expr | undefined = undefined;
while (!this.done && !this.test("}")) {
const line = this.line();
if (this.test("fn")) {
stmts.push(this.parseFn());
} else if (this.test("let")) {
stmts.push(this.parseLet());
this.expect(";");
} else {
const lhs = this.parseExpr();
if (this.eat("=")) {
const rhs = this.parseExpr();
this.expect(";");
stmts.push(
t.stmt(line, "assign", { place: lhs, expr: rhs }),
);
} else if (this.eat(";")) {
stmts.push(t.stmt(line, "expr", { expr: lhs }));
} else if (this.test("}")) {
expr = lhs;
break;
} else {
this.expect(";");
}
}
}
this.expect("}");
return t.block(line, stmts, expr);
}
parseFn(): Stmt {
const line = this.line();
this.step();
if (!this.expect("ident")) {
return t.stmt(line, "error", {});
}
const ident = this.eaten!.value!;
const params: Param[] = [];
if (!this.expect("(")) {
return t.stmt(line, "error", {});
}
if (!this.done && !this.test(")")) {
params.push(this.parseParam());
while (this.eat(",")) {
if (this.done || this.test(")")) {
break;
}
params.push(this.parseParam());
}
}
if (!this.expect(")")) {
return t.stmt(line, "error", {});
}
let retTy: Ty | undefined = undefined;
if (this.eat("->")) {
retTy = this.parseTy();
}
if (!this.test("{")) {
this.expect("{");
return t.stmt(line, "error", {});
}
const body = this.parseBlock();
return t.stmt(line, "fn", { ident, params, retTy, body });
}
parseLet(): Stmt {
const line = this.line();
this.step();
const param = this.parseParam();
if (!this.expect("=")) {
return t.stmt(line, "error", {});
}
const init = this.parseExpr();
return t.stmt(line, "let", { param, init });
}
parseExpr(): Expr {
return this.parsePostfix();
}
parsePostfix(): Expr {
let expr = this.parseOp();
while (true) {
const line = this.line();
if (this.eat("(")) {
const args: Expr[] = [];
if (!this.done && !this.test(")")) {
args.push(this.parseExpr());
while (this.eat(",")) {
if (this.done || this.test(")")) {
break;
}
args.push(this.parseExpr());
}
}
if (!this.expect(")")) {
return t.expr(line, "error", {});
}
expr = t.expr(line, "call", { expr, args });
} else {
break;
}
}
return expr;
}
parseOp(): Expr {
const line = this.line();
if (this.eat("ident")) {
const ident = this.eaten!.value!;
return t.expr(line, "ident", { ident });
} else if (this.eat("int")) {
const value = this.eaten!.value!;
return t.expr(line, "int", { value });
} else if (this.eat("char")) {
const value = this.eaten!.value!;
return t.expr(line, "int", { value });
} else if (this.eat("str")) {
const value = this.eaten!.value!;
return t.expr(line, "int", { value });
} else {
this.expect("expr");
this.step();
return t.expr(line, "error", {});
}
}
parseParam(): Param {
const line = this.line();
const pat = this.parsePat();
let ty: Ty | undefined = undefined;
if (this.eat(":")) {
ty = this.parseTy();
}
return t.param(line, pat, ty);
}
parsePat(): Pat {
const line = this.line();
if (this.eat("ident")) {
const ident = this.eaten!.value!;
return t.pat(line, "ident", { ident });
} else {
this.expect("pat");
this.step();
return t.pat(line, "error", {});
}
}
parseTy(): Ty {
const line = this.line();
if (this.eat("ident")) {
const ident = this.eaten!.value!;
return t.ty(line, "ident", { ident });
} else {
this.expect("ty");
this.step();
return t.ty(line, "error", {});
}
}
private expect(type: string): boolean {
const line = this.line();
if (!this.eat(type)) {
if (this.done) {
this.error(line, `expected '${type}', got 'eof'`);
} else {
this.error(line, `expected '${type}', got '${this.tok.type}'`);
}
return false;
}
return true;
}
private eat(type: string): boolean {
if (this.test(type)) {
this.eaten = this.tok;
this.step();
return true;
}
return false;
}
private test(type: string): boolean {
return !this.done && this.tok.type === type;
}
private step() {
this.i += 1;
}
private line(): number {
return this.tok.line;
}
private get tok(): Tok {
return this.toks[this.i];
}
private get done(): boolean {
return this.i >= this.toks.length;
}
private error(line: number, message: string) {
console.error(
`%cerror%c: ${message}\n %c--> line ${line}%c`,
"font-weight: bold; color: red",
"font-weight: bold; color: while",
"color: cyan",
"",
);
}
}