2024-11-15 14:20:49 +00:00
|
|
|
import { Expr, ExprKind, Param, Stmt, StmtKind, BinaryType} from "./ast.ts";
|
|
|
|
import { Lexer } from "./Lexer.ts";
|
|
|
|
import { Pos, Token } from "./Token.ts";
|
|
|
|
|
2024-11-18 09:21:30 +00:00
|
|
|
export class Parser {
|
2024-11-15 14:20:49 +00:00
|
|
|
private currentToken: Token | null;
|
|
|
|
private nextNodeId = 0;
|
|
|
|
|
|
|
|
public constructor(private lexer: Lexer) {
|
|
|
|
this.currentToken = lexer.next();
|
|
|
|
}
|
|
|
|
|
|
|
|
private step() { this.currentToken = this.lexer.next() }
|
2024-11-18 09:21:30 +00:00
|
|
|
public done(): boolean { return this.currentToken == null; }
|
2024-11-15 14:20:49 +00:00
|
|
|
private current(): Token { return this.currentToken!; }
|
|
|
|
private pos(): Pos {
|
|
|
|
if (this.done())
|
|
|
|
return this.lexer.currentPos();
|
|
|
|
return this.current().pos;
|
|
|
|
}
|
|
|
|
|
|
|
|
private test(type: string): boolean {
|
|
|
|
return !this.done() && this.current().type === type;
|
|
|
|
}
|
|
|
|
|
|
|
|
private report(msg: string, pos = this.pos()) {
|
|
|
|
console.log(`Parser: ${msg} at ${pos.line}:${pos.col}`);
|
2024-11-20 14:41:20 +00:00
|
|
|
class ReportNotAnError extends Error { constructor() { super("ReportNotAnError"); } }
|
|
|
|
try {
|
|
|
|
throw new ReportNotAnError();
|
|
|
|
} catch (error) {
|
|
|
|
if (!(error instanceof ReportNotAnError))
|
|
|
|
throw error;
|
|
|
|
console.log(error)
|
|
|
|
}
|
2024-11-15 14:20:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private stmt(kind: StmtKind, pos: Pos): Stmt {
|
|
|
|
const id = this.nextNodeId;
|
|
|
|
this.nextNodeId += 1;
|
|
|
|
return { kind, pos, id };
|
|
|
|
}
|
|
|
|
|
|
|
|
private expr(kind: ExprKind, pos: Pos): Expr {
|
|
|
|
const id = this.nextNodeId;
|
|
|
|
this.nextNodeId += 1;
|
|
|
|
return { kind, pos, id };
|
|
|
|
}
|
|
|
|
|
|
|
|
private parseMultiLineBlockExpr(): Expr {
|
|
|
|
const pos = this.pos();
|
|
|
|
if (this.test("{"))
|
|
|
|
return this.parseBlock();
|
|
|
|
if (this.test("if"))
|
|
|
|
return this.parseIf();
|
|
|
|
if (this.test("loop"))
|
|
|
|
return this.parseLoop();
|
|
|
|
this.report("expected expr");
|
|
|
|
return this.expr({ type: "error" }, pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
private parseSingleLineBlockStmt(): Stmt {
|
|
|
|
const pos = this.pos();
|
|
|
|
if (this.test("let"))
|
|
|
|
return this.parseLet();
|
|
|
|
if (this.test("return"))
|
|
|
|
return this.parseReturn();
|
|
|
|
if (this.test("break"))
|
|
|
|
return this.parseBreak();
|
|
|
|
this.report("expected stmt");
|
|
|
|
return this.stmt({ type: "error" }, pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
private eatSemicolon() {
|
|
|
|
if (!this.test(";")) {
|
2024-11-20 14:41:20 +00:00
|
|
|
this.report(`expected ';', got '${this.currentToken?.type ?? "eof"}'`);
|
2024-11-15 14:20:49 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.step();
|
|
|
|
}
|
|
|
|
|
|
|
|
public parseExpr(): Expr {
|
|
|
|
return this.parsePrefix();
|
|
|
|
}
|
|
|
|
|
|
|
|
public parseBlock(): Expr {
|
|
|
|
const pos = this.pos();
|
|
|
|
this.step();
|
|
|
|
let stmts: Stmt[] = [];
|
|
|
|
while (!this.done()) {
|
|
|
|
if (this.test("}")) {
|
|
|
|
this.step();
|
|
|
|
return this.expr({ type: "block", stmts }, pos);
|
2024-11-21 13:38:33 +00:00
|
|
|
} else if (this.test("return") || this.test("break") || this.test("let")) {
|
|
|
|
stmts.push(this.parseSingleLineBlockStmt());
|
|
|
|
this.eatSemicolon();
|
|
|
|
}
|
|
|
|
else if (this.test("fn")) {
|
2024-11-15 14:20:49 +00:00
|
|
|
stmts.push(this.parseSingleLineBlockStmt());
|
|
|
|
stmts.push(this.parseFn());
|
|
|
|
} else if (this.test("{") || this.test("if") || this.test("loop")) {
|
|
|
|
let expr = this.parseMultiLineBlockExpr();
|
|
|
|
if (this.test("}")) {
|
|
|
|
this.step();
|
|
|
|
return this.expr({ type: "block", stmts, expr }, pos);
|
|
|
|
}
|
|
|
|
stmts.push(this.stmt({ type: "expr", expr }, expr.pos));
|
|
|
|
} else {
|
|
|
|
const expr = this.parseExpr();
|
|
|
|
if (this.test("=")) {
|
|
|
|
this.step();
|
|
|
|
const value = this.parseExpr();
|
|
|
|
this.eatSemicolon();
|
|
|
|
stmts.push(this.stmt({ type: "assign", subject: expr, value }, pos));
|
|
|
|
} else if (this.test(";")) {
|
|
|
|
this.step();
|
|
|
|
stmts.push(this.stmt({ type: "expr", expr }, expr.pos));
|
|
|
|
} else if (this.test("}")) {
|
|
|
|
this.step();
|
|
|
|
return this.expr({ type: "block", stmts, expr }, pos);
|
|
|
|
} else {
|
|
|
|
this.report("expected ';' or '}'");
|
|
|
|
return this.expr({ type: "error" }, pos);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.report("expected '}'");
|
|
|
|
return this.expr({ type: "error" }, pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
public parseStmts(): Stmt[] {
|
|
|
|
let stmts: Stmt[] = [];
|
|
|
|
while (!this.done()) {
|
|
|
|
if (this.test("fn")) {
|
|
|
|
stmts.push(this.parseFn());
|
|
|
|
} else if (this.test("let") || this.test("return") || this.test("break")) {
|
|
|
|
stmts.push(this.parseSingleLineBlockStmt());
|
|
|
|
this.eatSemicolon();
|
|
|
|
} else if (this.test("{") || this.test("if") || this.test("loop")) {
|
|
|
|
const expr = this.parseMultiLineBlockExpr();
|
|
|
|
stmts.push(this.stmt({ type: "expr", expr }, expr.pos));
|
|
|
|
} else {
|
|
|
|
stmts.push(this.parseAssign());
|
|
|
|
this.eatSemicolon();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return stmts;
|
|
|
|
}
|
|
|
|
|
|
|
|
public parseFn(): Stmt {
|
|
|
|
const pos = this.pos();
|
|
|
|
this.step();
|
|
|
|
if (!this.test("ident")) {
|
|
|
|
this.report("expected ident");
|
|
|
|
return this.stmt({ type: "error" }, pos);
|
|
|
|
}
|
|
|
|
const ident = this.current().identValue!;
|
|
|
|
this.step();
|
|
|
|
if (!this.test("(")) {
|
|
|
|
this.report("expected '('");
|
|
|
|
return this.stmt({ type: "error" }, pos);
|
|
|
|
}
|
|
|
|
const params = this.parseFnParams();
|
|
|
|
if (!this.test("{")) {
|
|
|
|
this.report("expected block");
|
|
|
|
return this.stmt({ type: "error" }, pos);
|
|
|
|
}
|
|
|
|
const body = this.parseBlock();
|
|
|
|
return this.stmt({ type: "fn", ident, params, body }, pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
public parseFnParams(): Param[] {
|
|
|
|
this.step();
|
|
|
|
if (this.test(")")) {
|
|
|
|
this.step();
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
let params: Param[] = [];
|
|
|
|
const paramResult = this.parseParam();
|
|
|
|
if (!paramResult.ok)
|
|
|
|
return [];
|
|
|
|
params.push(paramResult.value);
|
|
|
|
while (this.test(",")) {
|
|
|
|
this.step();
|
|
|
|
if (this.test(")"))
|
|
|
|
break;
|
|
|
|
const paramResult = this.parseParam();
|
|
|
|
if (!paramResult.ok)
|
|
|
|
return [];
|
|
|
|
params.push(paramResult.value);
|
|
|
|
}
|
|
|
|
if (!this.test(")")) {
|
|
|
|
this.report("expected ')'");
|
|
|
|
return params;
|
|
|
|
}
|
|
|
|
this.step();
|
|
|
|
return params;
|
|
|
|
}
|
|
|
|
|
|
|
|
public parseParam(): { ok: true, value: Param } | { ok: false } {
|
|
|
|
const pos = this.pos();
|
|
|
|
if (this.test("ident")) {
|
|
|
|
const ident = this.current().identValue!;
|
|
|
|
this.step();
|
|
|
|
return { ok: true, value: { ident, pos } };
|
|
|
|
}
|
|
|
|
this.report("expected param");
|
|
|
|
return { ok: false };
|
|
|
|
}
|
|
|
|
|
|
|
|
public parseLet(): Stmt {
|
|
|
|
const pos = this.pos();
|
|
|
|
this.step();
|
|
|
|
const paramResult = this.parseParam();
|
|
|
|
if (!paramResult.ok)
|
|
|
|
return this.stmt({ type: "error" }, pos);
|
|
|
|
const param = paramResult.value;
|
|
|
|
if (!this.test("=")) {
|
|
|
|
this.report("expected '='");
|
|
|
|
return this.stmt({ type: "error" }, pos);
|
|
|
|
}
|
|
|
|
this.step();
|
|
|
|
const value = this.parseExpr();
|
|
|
|
return this.stmt({ type: "let", param, value }, pos);
|
|
|
|
}
|
|
|
|
public parseAssign(): Stmt {
|
|
|
|
const pos = this.pos();
|
|
|
|
const subject = this.parseExpr();
|
|
|
|
if (!this.test("=")) {
|
|
|
|
return this.stmt({ type: "expr", expr: subject }, pos);
|
|
|
|
}
|
|
|
|
this.step();
|
|
|
|
const value = this.parseExpr();
|
|
|
|
return this.stmt({ type: "assign", subject, value }, pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
public parseReturn(): Stmt {
|
|
|
|
const pos = this.pos();
|
|
|
|
this.step();
|
|
|
|
if (this.test(";")) {
|
|
|
|
return this.stmt({ type: "return" }, pos);
|
|
|
|
}
|
|
|
|
const expr = this.parseExpr();
|
|
|
|
return this.stmt({ type: "return", expr }, pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
public parseBreak(): Stmt {
|
|
|
|
const pos = this.pos();
|
|
|
|
this.step();
|
|
|
|
if (this.test(";")) {
|
|
|
|
return this.stmt({ type: "break" }, pos);
|
|
|
|
}
|
|
|
|
const expr = this.parseExpr();
|
|
|
|
return this.stmt({ type: "break", expr }, pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
public parseLoop(): Expr {
|
|
|
|
const pos = this.pos();
|
|
|
|
this.step();
|
|
|
|
if (!this.test("{")) {
|
|
|
|
this.report("expected '}'");
|
|
|
|
return this.expr({ type: "error" }, pos);
|
|
|
|
}
|
|
|
|
const body = this.parseExpr();
|
|
|
|
return this.expr({ type: "loop", body }, pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
public parseIf(): Expr {
|
|
|
|
const pos = this.pos();
|
|
|
|
this.step();
|
|
|
|
const cond = this.parseExpr();
|
|
|
|
if (!this.test("{")) {
|
|
|
|
this.report("expected block");
|
|
|
|
return this.expr({ type: "error" }, pos);
|
|
|
|
}
|
|
|
|
const truthy = this.parseBlock();
|
|
|
|
if (!this.test("else")) {
|
|
|
|
return this.expr({ type: "if", cond, truthy }, pos);
|
|
|
|
}
|
|
|
|
this.step();
|
|
|
|
if (this.test("if")) {
|
|
|
|
const falsy = this.parseIf();
|
|
|
|
return this.expr({ type: "if", cond, truthy, falsy }, pos);
|
|
|
|
}
|
|
|
|
if (!this.test("{")) {
|
|
|
|
this.report("expected block");
|
|
|
|
return this.expr({ type: "error" }, pos);
|
|
|
|
}
|
|
|
|
const falsy = this.parseBlock();
|
|
|
|
return this.expr({ type: "if", cond, truthy, falsy }, pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
public parsePrefix(): Expr {
|
|
|
|
const pos = this.pos();
|
|
|
|
if (this.test("not")) {
|
|
|
|
this.step();
|
|
|
|
const subject = this.parsePrefix();
|
|
|
|
return this.expr({ type: "unary", unaryType: "not", subject }, pos);
|
|
|
|
}
|
2024-11-20 14:41:20 +00:00
|
|
|
for (const binaryType of ["+", "*", "==", "-", "/", "!=", "<", ">", "<=", ">=", "or", "and"]) {
|
|
|
|
const subject = this.parseBinary(binaryType as BinaryType, pos)
|
|
|
|
if (subject !== null) {
|
|
|
|
return subject
|
|
|
|
}
|
|
|
|
}
|
2024-11-15 14:20:49 +00:00
|
|
|
return this.parsePostfix();
|
|
|
|
}
|
|
|
|
|
2024-11-20 14:41:20 +00:00
|
|
|
public parseBinary(binaryType: BinaryType, pos: Pos): Expr | null {
|
2024-11-15 14:20:49 +00:00
|
|
|
if (this.test(binaryType)) {
|
|
|
|
this.step();
|
|
|
|
const left = this.parsePrefix();
|
|
|
|
const right = this.parsePrefix();
|
|
|
|
return this.expr({ type: "binary", binaryType, left, right }, pos);
|
|
|
|
}
|
2024-11-20 14:41:20 +00:00
|
|
|
return null
|
2024-11-15 14:20:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public parsePostfix(): Expr {
|
|
|
|
let subject = this.parseOperand();
|
|
|
|
while (true) {
|
|
|
|
const pos = this.pos();
|
|
|
|
if (this.test(".")) {
|
|
|
|
this.step();
|
|
|
|
if (!this.test("ident")) {
|
|
|
|
this.report("expected ident");
|
|
|
|
return this.expr({ type: "error" }, pos);
|
|
|
|
}
|
|
|
|
const value = this.current().identValue!;
|
|
|
|
this.step();
|
|
|
|
subject = this.expr({ type: "field", subject, value }, pos);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (this.test("[")) {
|
|
|
|
this.step();
|
|
|
|
const value = this.parseExpr();
|
|
|
|
if (!this.test("]")) {
|
|
|
|
this.report("expected ']'");
|
|
|
|
return this.expr({ type: "error" }, pos);
|
|
|
|
}
|
|
|
|
this.step();
|
|
|
|
subject = this.expr({ type: "index", subject, value }, pos);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (this.test("(")) {
|
|
|
|
this.step();
|
|
|
|
let args: Expr[] = [];
|
|
|
|
if (!this.test(")")) {
|
|
|
|
args.push(this.parseExpr());
|
|
|
|
while (this.test(",")) {
|
|
|
|
this.step();
|
|
|
|
if (this.test(")"))
|
|
|
|
break;
|
|
|
|
args.push(this.parseExpr());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!this.test(")")) {
|
|
|
|
this.report("expected ')'");
|
|
|
|
return this.expr({ type: "error" }, pos);
|
|
|
|
}
|
|
|
|
this.step();
|
|
|
|
subject = this.expr({ type: "call", subject, args }, pos);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return subject;
|
|
|
|
}
|
|
|
|
|
|
|
|
public parseOperand(): Expr {
|
|
|
|
const pos = this.pos();
|
|
|
|
if (this.test("ident")) {
|
|
|
|
const value = this.current().identValue!;
|
|
|
|
this.step();
|
|
|
|
return this.expr({ type: "ident", value }, pos);
|
|
|
|
}
|
|
|
|
if (this.test("int")) {
|
|
|
|
const value = this.current().intValue!;
|
|
|
|
this.step();
|
|
|
|
return this.expr({ type: "int", value }, pos);
|
|
|
|
}
|
|
|
|
if (this.test("string")) {
|
|
|
|
const value = this.current().stringValue!;
|
|
|
|
this.step();
|
|
|
|
return this.expr({ type: "string", value }, pos);
|
|
|
|
}
|
|
|
|
if (this.test("false")) {
|
|
|
|
this.step();
|
|
|
|
return this.expr({ type: "bool", value: false }, pos);
|
|
|
|
}
|
|
|
|
if (this.test("true")) {
|
|
|
|
this.step();
|
|
|
|
return this.expr({ type: "bool", value: true }, pos);
|
|
|
|
}
|
|
|
|
if (this.test("null")) {
|
|
|
|
this.step();
|
|
|
|
return this.expr({ type: "null"}, pos);
|
|
|
|
}
|
|
|
|
if (this.test("(")) {
|
|
|
|
this.step();
|
|
|
|
const expr = this.parseExpr();
|
|
|
|
if (!this.test(")")) {
|
|
|
|
this.report("expected ')'");
|
|
|
|
return this.expr({ type: "error" }, pos);
|
|
|
|
}
|
|
|
|
this.step();
|
|
|
|
return this.expr({ type: "group", expr }, pos);
|
|
|
|
}
|
|
|
|
if (this.test("{"))
|
|
|
|
return this.parseBlock();
|
|
|
|
if (this.test("if"))
|
|
|
|
return this.parseIf();
|
|
|
|
if (this.test("loop"))
|
|
|
|
return this.parseLoop();
|
|
|
|
|
|
|
|
this.report("expected expr", pos);
|
|
|
|
this.step();
|
|
|
|
return this.expr({ type: "error" }, pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|