slige/compiler/Parser.ts

559 lines
17 KiB
TypeScript
Raw Normal View History

2024-12-06 14:17:52 +01:00
import {
BinaryType,
EType,
ETypeKind,
Expr,
ExprKind,
Param,
Stmt,
StmtKind,
} from "./ast.ts";
2024-11-15 15:20:49 +01:00
import { Lexer } from "./Lexer.ts";
import { Pos, Token } from "./Token.ts";
2024-11-18 10:21:30 +01:00
export class Parser {
2024-11-15 15:20:49 +01:00
private currentToken: Token | null;
private nextNodeId = 0;
public constructor(private lexer: Lexer) {
this.currentToken = lexer.next();
}
2024-12-06 14:17:52 +01:00
private step() {
this.currentToken = this.lexer.next();
}
public done(): boolean {
return this.currentToken == null;
}
private current(): Token {
return this.currentToken!;
}
2024-11-15 15:20:49 +01:00
private pos(): Pos {
2024-12-06 14:17:52 +01:00
if (this.done()) {
2024-11-15 15:20:49 +01:00
return this.lexer.currentPos();
2024-12-06 14:17:52 +01:00
}
2024-11-15 15:20:49 +01:00
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-12-06 14:17:52 +01:00
class ReportNotAnError extends Error {
constructor() {
super("ReportNotAnError");
}
}
2024-11-20 15:41:20 +01:00
try {
throw new ReportNotAnError();
} catch (error) {
2024-12-06 14:17:52 +01:00
if (!(error instanceof ReportNotAnError)) {
2024-11-20 15:41:20 +01:00
throw error;
2024-12-06 14:17:52 +01:00
}
console.log(error);
2024-11-20 15:41:20 +01:00
}
2024-11-15 15:20:49 +01: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 };
}
2024-12-06 14:17:52 +01:00
private etype(kind: ETypeKind, pos: Pos): EType {
const id = this.nextNodeId;
this.nextNodeId += 1;
return { kind, pos, id };
}
2024-11-15 15:20:49 +01:00
private parseMultiLineBlockExpr(): Expr {
const pos = this.pos();
2024-12-06 14:17:52 +01:00
if (this.test("{")) {
2024-11-15 15:20:49 +01:00
return this.parseBlock();
2024-12-06 14:17:52 +01:00
}
if (this.test("if")) {
2024-11-15 15:20:49 +01:00
return this.parseIf();
2024-12-06 14:17:52 +01:00
}
if (this.test("loop")) {
2024-11-15 15:20:49 +01:00
return this.parseLoop();
2024-12-06 14:17:52 +01:00
}
2024-11-15 15:20:49 +01:00
this.report("expected expr");
return this.expr({ type: "error" }, pos);
}
private parseSingleLineBlockStmt(): Stmt {
const pos = this.pos();
2024-12-06 14:17:52 +01:00
if (this.test("let")) {
2024-11-15 15:20:49 +01:00
return this.parseLet();
2024-12-06 14:17:52 +01:00
}
if (this.test("return")) {
2024-11-15 15:20:49 +01:00
return this.parseReturn();
2024-12-06 14:17:52 +01:00
}
if (this.test("break")) {
2024-11-15 15:20:49 +01:00
return this.parseBreak();
2024-12-06 14:17:52 +01:00
}
2024-11-15 15:20:49 +01:00
this.report("expected stmt");
return this.stmt({ type: "error" }, pos);
}
private eatSemicolon() {
if (!this.test(";")) {
2024-12-06 14:17:52 +01:00
this.report(
`expected ';', got '${this.currentToken?.type ?? "eof"}'`,
);
2024-11-15 15:20:49 +01: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-12-06 14:17:52 +01:00
} else if (
this.test("return") || this.test("break") || this.test("let")
) {
2024-11-21 14:38:33 +01:00
stmts.push(this.parseSingleLineBlockStmt());
this.eatSemicolon();
2024-12-06 14:17:52 +01:00
} else if (this.test("fn")) {
2024-11-15 15:20:49 +01: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();
2024-12-06 14:17:52 +01:00
stmts.push(
this.stmt(
{ type: "assign", subject: expr, value },
pos,
),
);
2024-11-15 15:20:49 +01:00
} 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());
2024-12-06 14:17:52 +01:00
} else if (
this.test("let") || this.test("return") || this.test("break")
) {
2024-11-15 15:20:49 +01:00
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));
2024-12-06 14:17:52 +01:00
} else {
2024-11-15 15:20:49 +01:00
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();
2024-12-06 14:17:52 +01:00
let returnType: EType | null = null;
if (this.test("->")) {
this.step();
returnType = this.parseEType();
}
2024-11-15 15:20:49 +01:00
if (!this.test("{")) {
this.report("expected block");
return this.stmt({ type: "error" }, pos);
}
const body = this.parseBlock();
2024-12-06 14:17:52 +01:00
if (returnType === null) {
return this.stmt({ type: "fn", ident, params, body }, pos);
}
return this.stmt({ type: "fn", ident, params, returnType, body }, pos);
2024-11-15 15:20:49 +01:00
}
public parseFnParams(): Param[] {
this.step();
if (this.test(")")) {
this.step();
return [];
}
2024-12-06 14:17:52 +01:00
const params: Param[] = [];
2024-11-15 15:20:49 +01:00
const paramResult = this.parseParam();
2024-12-06 14:17:52 +01:00
if (!paramResult.ok) {
2024-11-15 15:20:49 +01:00
return [];
2024-12-06 14:17:52 +01:00
}
2024-11-15 15:20:49 +01:00
params.push(paramResult.value);
while (this.test(",")) {
this.step();
2024-12-06 14:17:52 +01:00
if (this.test(")")) {
2024-11-15 15:20:49 +01:00
break;
2024-12-06 14:17:52 +01:00
}
2024-11-15 15:20:49 +01:00
const paramResult = this.parseParam();
2024-12-06 14:17:52 +01:00
if (!paramResult.ok) {
2024-11-15 15:20:49 +01:00
return [];
2024-12-06 14:17:52 +01:00
}
2024-11-15 15:20:49 +01:00
params.push(paramResult.value);
}
if (!this.test(")")) {
this.report("expected ')'");
return params;
}
this.step();
return params;
}
2024-12-06 14:17:52 +01:00
public parseParam(): { ok: true; value: Param } | { ok: false } {
2024-11-15 15:20:49 +01:00
const pos = this.pos();
if (this.test("ident")) {
const ident = this.current().identValue!;
this.step();
2024-12-06 14:17:52 +01:00
if (this.test(":")) {
const etype = this.parseEType();
return { ok: true, value: { ident, etype, pos } };
}
2024-11-15 15:20:49 +01:00
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();
2024-12-06 14:17:52 +01:00
if (!paramResult.ok) {
2024-11-15 15:20:49 +01:00
return this.stmt({ type: "error" }, pos);
2024-12-06 14:17:52 +01:00
}
2024-11-15 15:20:49 +01:00
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-12-06 14:17:52 +01:00
for (
const binaryType of [
"+",
"*",
"==",
"-",
"/",
"!=",
"<",
">",
"<=",
">=",
"or",
"and",
]
) {
const subject = this.parseBinary(binaryType as BinaryType, pos);
2024-11-20 15:41:20 +01:00
if (subject !== null) {
2024-12-06 14:17:52 +01:00
return subject;
2024-11-20 15:41:20 +01:00
}
}
2024-11-15 15:20:49 +01:00
return this.parsePostfix();
}
2024-11-20 15:41:20 +01:00
public parseBinary(binaryType: BinaryType, pos: Pos): Expr | null {
2024-11-15 15:20:49 +01: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-12-06 14:17:52 +01:00
}
return null;
2024-11-15 15:20:49 +01: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();
2024-12-06 14:17:52 +01:00
if (this.test(")")) {
2024-11-15 15:20:49 +01:00
break;
2024-12-06 14:17:52 +01:00
}
2024-11-15 15:20:49 +01:00
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();
2024-12-06 14:17:52 +01:00
return this.expr({ type: "null" }, pos);
2024-11-15 15:20:49 +01:00
}
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);
}
2024-12-06 14:17:52 +01:00
if (this.test("{")) {
2024-11-15 15:20:49 +01:00
return this.parseBlock();
2024-12-06 14:17:52 +01:00
}
if (this.test("if")) {
2024-11-15 15:20:49 +01:00
return this.parseIf();
2024-12-06 14:17:52 +01:00
}
if (this.test("loop")) {
2024-11-15 15:20:49 +01:00
return this.parseLoop();
2024-12-06 14:17:52 +01:00
}
2024-11-15 15:20:49 +01:00
this.report("expected expr", pos);
this.step();
return this.expr({ type: "error" }, pos);
}
2024-12-06 14:17:52 +01:00
public parseEType(): EType {
const pos = this.pos();
if (this.test("ident")) {
const ident = this.current().identValue!;
return this.etype({ type: "ident", value: ident }, pos);
}
if (this.test("[")) {
this.step();
const inner = this.parseEType();
if (!this.test("]")) {
this.report("expected ']'", pos);
return this.etype({ type: "error" }, pos);
}
this.step();
return this.etype({ type: "array", inner }, pos);
}
if (this.test("struct")) {
this.step();
if (!this.test("{")) {
this.report("expected '{'");
return this.etype({ type: "error" }, pos);
}
const fields = this.parseETypeStructFields();
return this.etype({ type: "struct", fields }, pos);
}
this.report("expected type");
return this.etype({ type: "error" }, pos);
}
public parseETypeStructFields(): Param[] {
this.step();
if (this.test("}")) {
this.step();
return [];
}
const 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;
}
2024-11-15 15:20:49 +01:00
}