255 lines
6.9 KiB
TypeScript
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",
|
|
"",
|
|
);
|
|
}
|
|
}
|