slige/compiler/parser.ts

800 lines
23 KiB
TypeScript
Raw Normal View History

2024-12-06 14:17:52 +01:00
import {
2024-12-11 12:36:19 +01:00
Anno,
AssignType,
AstCreator,
2024-12-06 14:17:52 +01:00
BinaryType,
EType,
ETypeKind,
Expr,
ExprKind,
Param,
Stmt,
StmtKind,
UnaryType,
2024-12-06 14:17:52 +01:00
} from "./ast.ts";
2024-12-11 03:11:00 +01:00
import { printStackTrace, Reporter } from "./info.ts";
2024-12-10 21:42:15 +01:00
import { Lexer } from "./lexer.ts";
import { Pos, Token } from "./token.ts";
2024-11-15 15:20:49 +01:00
2024-11-18 10:21:30 +01:00
export class Parser {
2024-11-15 15:20:49 +01:00
private currentToken: Token | null;
public constructor(
private lexer: Lexer,
private astCreator: AstCreator,
private reporter: Reporter,
) {
2024-11-15 15:20:49 +01:00
this.currentToken = lexer.next();
}
2024-12-12 12:04:57 +01:00
public parse(): Stmt[] {
return this.parseStmts();
2024-11-15 15:20:49 +01:00
}
2024-12-12 12:04:57 +01:00
private parseStmts(): Stmt[] {
const 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 (
["{", "if", "loop", "while", "for"].some((tt) => this.test(tt))
) {
2024-12-12 12:04:57 +01:00
const expr = this.parseMultiLineBlockExpr();
stmts.push(this.stmt({ type: "expr", expr }, expr.pos));
} else {
stmts.push(this.parseAssign());
this.eatSemicolon();
}
}
return stmts;
2024-12-06 14:17:52 +01:00
}
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
}
if (this.test("while")) {
return this.parseWhile();
}
if (this.test("for")) {
return this.parseFor();
}
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();
}
2024-12-12 12:04:57 +01:00
private parseExpr(): Expr {
2024-12-12 15:37:56 +01:00
return this.parseBinary();
2024-11-15 15:20:49 +01:00
}
2024-12-12 12:04:57 +01:00
private parseBlock(): Expr {
2024-11-15 15:20:49 +01:00
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 (
["{", "if", "loop", "while", "for"].some((tt) => this.test(tt))
) {
2024-12-13 12:12:16 +01:00
const expr = this.parseMultiLineBlockExpr();
2024-11-15 15:20:49 +01:00
if (this.test("}")) {
this.step();
2024-12-13 12:12:16 +01:00
return this.expr({ type: "block", stmts, expr }, expr.pos);
2024-11-15 15:20:49 +01:00
}
stmts.push(this.stmt({ type: "expr", expr }, expr.pos));
} else {
const expr = this.parseExpr();
if (this.test("=") || this.test("+=") || this.test("-=")) {
const assignType = this.current().type as AssignType;
2024-11-15 15:20:49 +01:00
this.step();
const value = this.parseExpr();
this.eatSemicolon();
2024-12-06 14:17:52 +01:00
stmts.push(
this.stmt(
{
type: "assign",
assignType,
subject: expr,
value,
},
2024-12-13 12:12:16 +01:00
expr.pos,
2024-12-06 14:17:52 +01:00
),
);
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 '}'");
2024-12-13 12:15:32 +01:00
return this.expr({ type: "error" }, this.pos());
2024-11-15 15:20:49 +01:00
}
}
}
this.report("expected '}'");
return this.expr({ type: "error" }, pos);
}
2024-12-12 12:04:57 +01:00
private parseFn(): Stmt {
2024-11-15 15:20:49 +01:00
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-12-11 12:36:19 +01:00
2024-12-11 13:03:01 +01:00
let anno: Anno | null = null;
2024-12-11 12:36:19 +01:00
if (this.test("#")) {
2024-12-11 13:03:01 +01:00
anno = this.parseAnno();
2024-12-11 12:36:19 +01:00
}
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-11 13:03:01 +01:00
return this.stmt(
{
type: "fn",
ident,
params,
returnType: returnType !== null ? returnType : undefined,
body,
anno: anno != null ? anno : undefined,
},
pos,
);
2024-11-15 15:20:49 +01:00
}
2024-12-12 12:04:57 +01:00
private parseAnnoArgs(): Expr[] {
2024-12-11 12:36:19 +01:00
this.step();
if (!this.test("(")) {
this.report("expected '('");
return [];
}
this.step();
const annoArgs: Expr[] = [];
if (!this.test(")")) {
annoArgs.push(this.parseExpr());
while (this.test(",")) {
this.step();
if (this.test(")")) {
break;
}
annoArgs.push(this.parseExpr());
}
}
if (!this.test(")")) {
this.report("expected ')'");
return [];
}
this.step();
return annoArgs;
}
2024-12-12 12:04:57 +01:00
private parseAnno(): Anno | null {
2024-12-11 13:03:01 +01:00
const pos = this.pos();
2024-12-11 12:36:19 +01:00
this.step();
if (!this.test("[")) {
this.report("expected '['");
2024-12-11 13:03:01 +01:00
return null;
2024-12-11 12:36:19 +01:00
}
this.step();
2024-12-11 13:03:01 +01:00
if (!this.test("ident")) {
this.report("expected identifier");
return null;
2024-12-11 12:36:19 +01:00
}
2024-12-11 13:03:01 +01:00
const ident = this.current().identValue!;
const values = this.parseAnnoArgs();
if (!this.test("]")) {
this.report("expected ']'");
return null;
2024-12-11 12:36:19 +01:00
}
this.step();
2024-12-11 13:03:01 +01:00
return { ident, pos, values };
2024-12-11 12:36:19 +01:00
}
2024-12-12 12:04:57 +01:00
private parseFnParams(): Param[] {
2024-11-15 15:20:49 +01:00
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-12 12:04:57 +01:00
private 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(":")) {
2024-12-09 13:57:48 +01:00
this.step();
2024-12-06 14:17:52 +01:00
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 };
}
2024-12-12 12:04:57 +01:00
private parseLet(): Stmt {
2024-11-15 15:20:49 +01:00
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);
}
2024-12-12 12:04:57 +01:00
private parseAssign(): Stmt {
2024-11-15 15:20:49 +01:00
const pos = this.pos();
const subject = this.parseExpr();
if (this.test("=") || this.test("+=") || this.test("-=")) {
const assignType = this.current().type as AssignType;
this.step();
const value = this.parseExpr();
return this.stmt({
type: "assign",
assignType,
subject,
value,
}, pos);
2024-11-15 15:20:49 +01:00
}
return this.stmt({ type: "expr", expr: subject }, pos);
2024-11-15 15:20:49 +01:00
}
2024-12-12 12:04:57 +01:00
private parseReturn(): Stmt {
2024-11-15 15:20:49 +01:00
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);
}
2024-12-12 12:04:57 +01:00
private parseBreak(): Stmt {
2024-11-15 15:20:49 +01:00
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);
}
2024-12-12 12:04:57 +01:00
private parseLoop(): Expr {
2024-11-15 15:20:49 +01:00
const pos = this.pos();
this.step();
if (!this.test("{")) {
this.report("expected '{'");
2024-11-15 15:20:49 +01:00
return this.expr({ type: "error" }, pos);
}
const body = this.parseExpr();
return this.expr({ type: "loop", body }, pos);
}
private parseWhile(): Expr {
const pos = this.pos();
this.step();
const cond = this.parseExpr();
if (!this.test("{")) {
this.report("expected '{'");
return this.expr({ type: "error" }, pos);
}
const body = this.parseExpr();
return this.expr({ type: "while", cond, body }, pos);
}
private parseFor(): Expr {
const pos = this.pos();
this.step();
if (this.test("(")) {
return this.parseForClassicTail(pos);
}
const paramRes = this.parseParam();
if (!paramRes.ok) {
return this.expr({ type: "error" }, pos);
}
const param = paramRes.value;
if (!this.test("in")) {
this.report("expected 'in'");
return this.expr({ type: "error" }, pos);
}
this.step();
const value = this.parseExpr();
if (!this.test("{")) {
this.report("expected '{'");
return this.expr({ type: "error" }, pos);
}
const body = this.parseExpr();
return this.expr({ type: "for_in", param, value, body }, pos);
}
private parseForClassicTail(pos: Pos): Expr {
this.step();
let decl: Stmt | undefined;
if (!this.test(";")) {
decl = this.parseLet();
}
if (!this.test(";")) {
this.report("expected ';'");
return this.expr({ type: "error" }, pos);
}
this.step();
let cond: Expr | undefined;
if (!this.test(";")) {
cond = this.parseExpr();
}
if (!this.test(";")) {
this.report("expected ';'");
return this.expr({ type: "error" }, pos);
}
this.step();
let incr: Stmt | undefined;
if (!this.test(")")) {
incr = this.parseAssign();
}
if (!this.test(")")) {
this.report("expected '}'");
return this.expr({ type: "error" }, pos);
}
this.step();
if (!this.test("{")) {
this.report("expected '{'");
return this.expr({ type: "error" }, pos);
}
const body = this.parseExpr();
return this.expr({ type: "for", decl, cond, incr, body }, pos);
}
2024-12-12 12:04:57 +01:00
private parseIf(): Expr {
2024-11-15 15:20:49 +01:00
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);
}
2024-12-12 15:37:56 +01:00
private parseBinary(): Expr {
return this.parseOr();
}
private parseOr(): Expr {
2024-11-15 15:20:49 +01:00
const pos = this.pos();
2024-12-12 15:37:56 +01:00
let left = this.parseAnd();
while (true) {
if (this.test("or")) {
left = this.parBinTail(left, pos, this.parseAnd, "or");
} else {
break;
}
2024-11-15 15:20:49 +01:00
}
2024-12-12 15:37:56 +01:00
return left;
}
private parseAnd(): Expr {
const pos = this.pos();
let left = this.parseEquality();
while (true) {
if (this.test("and")) {
left = this.parBinTail(left, pos, this.parseEquality, "and");
} else {
break;
2024-11-20 15:41:20 +01:00
}
}
2024-12-12 15:37:56 +01:00
return left;
}
private parseEquality(): Expr {
const pos = this.pos();
const left = this.parseComparison();
if (this.test("==")) {
return this.parBinTail(left, pos, this.parseComparison, "==");
}
if (this.test("!=")) {
return this.parBinTail(left, pos, this.parseComparison, "!=");
}
return left;
}
private parseComparison(): Expr {
const pos = this.pos();
const left = this.parseAddSub();
if (this.test("<")) {
return this.parBinTail(left, pos, this.parseAddSub, "<");
}
if (this.test(">")) {
return this.parBinTail(left, pos, this.parseAddSub, ">");
}
if (this.test("<=")) {
return this.parBinTail(left, pos, this.parseAddSub, "<=");
}
if (this.test(">=")) {
return this.parBinTail(left, pos, this.parseAddSub, ">=");
}
return left;
}
private parseAddSub(): Expr {
const pos = this.pos();
let left = this.parseMulDiv();
while (true) {
if (this.test("+")) {
left = this.parBinTail(left, pos, this.parseMulDiv, "+");
continue;
}
2024-12-12 16:07:59 +01:00
if (this.test("-")) {
2024-12-12 15:37:56 +01:00
left = this.parBinTail(left, pos, this.parseMulDiv, "-");
continue;
}
break;
}
return left;
}
private parseMulDiv(): Expr {
const pos = this.pos();
let left = this.parsePrefix();
while (true) {
if (this.test("*")) {
left = this.parBinTail(left, pos, this.parsePrefix, "*");
continue;
}
if (this.test("/")) {
left = this.parBinTail(left, pos, this.parsePrefix, "/");
continue;
}
break;
}
return left;
}
private parBinTail(
left: Expr,
pos: Pos,
parseRight: (this: Parser) => Expr,
binaryType: BinaryType,
): Expr {
this.step();
const right = parseRight.call(this);
return this.expr(
{ type: "binary", binaryType, left, right },
pos,
);
2024-11-15 15:20:49 +01:00
}
2024-12-12 15:37:56 +01:00
private parsePrefix(): Expr {
const pos = this.pos();
if (this.test("not") || this.test("-")) {
const unaryType = this.current().type as UnaryType;
2024-11-15 15:20:49 +01:00
this.step();
2024-12-12 15:37:56 +01:00
const subject = this.parsePrefix();
return this.expr({ type: "unary", unaryType, subject }, pos);
2024-12-06 14:17:52 +01:00
}
2024-12-12 15:37:56 +01:00
return this.parsePostfix();
2024-11-15 15:20:49 +01:00
}
2024-12-12 12:04:57 +01:00
private parsePostfix(): Expr {
2024-11-15 15:20:49 +01:00
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;
}
2024-12-12 12:04:57 +01:00
private parseOperand(): Expr {
2024-11-15 15:20:49 +01:00
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-12 12:04:57 +01:00
private parseEType(): EType {
2024-12-06 14:17:52 +01:00
const pos = this.pos();
if (this.test("ident")) {
const ident = this.current().identValue!;
2024-12-10 21:42:15 +01:00
this.step();
2024-12-06 14:17:52 +01:00
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);
}
2024-12-12 12:04:57 +01:00
private parseETypeStructFields(): Param[] {
2024-12-06 14:17:52 +01:00
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-12-12 12:04:57 +01:00
private step() {
this.currentToken = this.lexer.next();
}
private done(): boolean {
return this.currentToken == null;
}
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}`);
this.reporter.reportError({
msg,
pos,
reporter: "Parser",
});
printStackTrace();
}
private stmt(kind: StmtKind, pos: Pos): Stmt {
return this.astCreator.stmt(kind, pos);
2024-12-12 12:04:57 +01:00
}
private expr(kind: ExprKind, pos: Pos): Expr {
return this.astCreator.expr(kind, pos);
2024-12-12 12:04:57 +01:00
}
private etype(kind: ETypeKind, pos: Pos): EType {
return this.astCreator.etype(kind, pos);
2024-12-12 12:04:57 +01:00
}
2024-11-15 15:20:49 +01:00
}