mirror of
https://git.sfja.dk/Mikkel/slige.git
synced 2025-01-18 10:36:31 +00:00
1097 lines
32 KiB
TypeScript
1097 lines
32 KiB
TypeScript
import {
|
|
AssignType,
|
|
AstCreator,
|
|
BinaryType,
|
|
EType,
|
|
ETypeKind,
|
|
Expr,
|
|
ExprKind,
|
|
Field,
|
|
GenericParam,
|
|
Param,
|
|
Stmt,
|
|
StmtDetails,
|
|
StmtKind,
|
|
UnaryType,
|
|
} from "./ast.ts";
|
|
import { printStackTrace, Reporter } from "./info.ts";
|
|
import { Lexer } from "./lexer.ts";
|
|
import { Pos, Token } from "./token.ts";
|
|
|
|
type Res<T> = { ok: true; value: T } | { ok: false; pos?: Pos };
|
|
|
|
export class Parser {
|
|
private currentToken: Token | null;
|
|
|
|
public constructor(
|
|
private lexer: Lexer,
|
|
private astCreator: AstCreator,
|
|
private reporter: Reporter,
|
|
) {
|
|
this.currentToken = lexer.next();
|
|
}
|
|
|
|
public parse(): Stmt[] {
|
|
return this.parseStmts();
|
|
}
|
|
|
|
private parseStmts(): Stmt[] {
|
|
const stmts: Stmt[] = [];
|
|
while (!this.done()) {
|
|
stmts.push(this.parseStmt());
|
|
}
|
|
return stmts;
|
|
}
|
|
|
|
private parseStmt(): Stmt {
|
|
if (
|
|
["#", "pub", "mod", "fn"].some((tt) => this.test(tt))
|
|
) {
|
|
return this.parseItemStmt();
|
|
} else if (
|
|
["let", "type_alias", "return", "break"].some((tt) => this.test(tt))
|
|
) {
|
|
const expr = this.parseSingleLineBlockStmt();
|
|
this.eatSemicolon();
|
|
return expr;
|
|
} else if (
|
|
["{", "if", "loop", "while", "for"].some((tt) => this.test(tt))
|
|
) {
|
|
const expr = this.parseMultiLineBlockExpr();
|
|
return (this.stmt({ type: "expr", expr }, expr.pos));
|
|
} else {
|
|
const expr = this.parseAssign();
|
|
this.eatSemicolon();
|
|
return expr;
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
if (this.test("while")) {
|
|
return this.parseWhile();
|
|
}
|
|
if (this.test("for")) {
|
|
return this.parseFor();
|
|
}
|
|
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("type_alias")) {
|
|
return this.parseTypeAlias();
|
|
}
|
|
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(";")) {
|
|
this.report(
|
|
`expected ';', got '${this.currentToken?.type ?? "eof"}'`,
|
|
);
|
|
return;
|
|
}
|
|
this.step();
|
|
}
|
|
|
|
private parseExpr(): Expr {
|
|
return this.parseBinary();
|
|
}
|
|
|
|
private parseBlock(): Expr {
|
|
const pos = this.pos();
|
|
this.step();
|
|
const stmts: Stmt[] = [];
|
|
while (!this.done()) {
|
|
if (this.test("}")) {
|
|
this.step();
|
|
return this.expr({ type: "block", stmts }, pos);
|
|
} else if (
|
|
["#", "pub", "mod", "fn"].some((tt) => this.test(tt))
|
|
) {
|
|
stmts.push(this.parseItemStmt());
|
|
} else if (
|
|
["let", "type_alias", "return", "break"]
|
|
.some((tt) => this.test(tt))
|
|
) {
|
|
stmts.push(this.parseSingleLineBlockStmt());
|
|
this.eatSemicolon();
|
|
} else if (
|
|
["{", "if", "loop", "while", "for"].some((tt) => this.test(tt))
|
|
) {
|
|
const expr = this.parseMultiLineBlockExpr();
|
|
if (this.test("}")) {
|
|
this.step();
|
|
return this.expr({ type: "block", stmts, expr }, expr.pos);
|
|
}
|
|
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;
|
|
this.step();
|
|
const value = this.parseExpr();
|
|
this.eatSemicolon();
|
|
stmts.push(
|
|
this.stmt(
|
|
{
|
|
type: "assign",
|
|
assignType,
|
|
subject: expr,
|
|
value,
|
|
},
|
|
expr.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" }, this.pos());
|
|
}
|
|
}
|
|
}
|
|
this.report("expected '}'");
|
|
return this.expr({ type: "error" }, pos);
|
|
}
|
|
|
|
private parseItemStmt(
|
|
pos = this.pos(),
|
|
details: StmtDetails = {
|
|
pub: false,
|
|
annos: [],
|
|
},
|
|
): Stmt {
|
|
const spos = this.pos();
|
|
if (this.test("#") && !details.pub) {
|
|
this.step();
|
|
if (!this.test("[")) {
|
|
this.report("expected '['");
|
|
return this.stmt({ type: "error" }, spos);
|
|
}
|
|
this.step();
|
|
if (!this.test("ident")) {
|
|
this.report("expected 'ident'");
|
|
return this.stmt({ type: "error" }, spos);
|
|
}
|
|
const ident = this.current().identValue!;
|
|
this.step();
|
|
const args: Expr[] = [];
|
|
if (this.test("(")) {
|
|
this.step();
|
|
if (!this.done() && !this.test(")")) {
|
|
args.push(this.parseExpr());
|
|
while (this.test(",")) {
|
|
this.step();
|
|
if (this.done() || this.test(")")) {
|
|
break;
|
|
}
|
|
args.push(this.parseExpr());
|
|
}
|
|
}
|
|
if (!this.test(")")) {
|
|
this.report("expected ')'");
|
|
return this.stmt({ type: "error" }, spos);
|
|
}
|
|
this.step();
|
|
}
|
|
if (!this.test("]")) {
|
|
this.report("expected ']'");
|
|
return this.stmt({ type: "error" }, spos);
|
|
}
|
|
this.step();
|
|
const anno = { ident, args, pos: spos };
|
|
return this.parseItemStmt(pos, {
|
|
...details,
|
|
annos: [...details.annos, anno],
|
|
});
|
|
} else if (this.test("pub") && !details.pub) {
|
|
this.step();
|
|
return this.parseItemStmt(pos, { ...details, pub: true });
|
|
} else if (this.test("mod")) {
|
|
return this.parseMod(details);
|
|
} else if (this.test("fn")) {
|
|
return this.parseFn(details);
|
|
} else {
|
|
this.report("expected item statement");
|
|
return this.stmt({ type: "error" }, pos);
|
|
}
|
|
}
|
|
|
|
private parseMod(details: StmtDetails): 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.eatSemicolon();
|
|
return this.stmt({ type: "mod_file", ident, filePath: ident }, pos);
|
|
}
|
|
if (this.test("string")) {
|
|
const filePath = this.current().stringValue!;
|
|
this.step();
|
|
this.eatSemicolon();
|
|
return this.stmt({ type: "mod_file", ident, filePath }, pos);
|
|
}
|
|
|
|
if (!this.test("{")) {
|
|
this.report("expected '{' or 'string'");
|
|
return this.stmt({ type: "error" }, pos);
|
|
}
|
|
this.step();
|
|
|
|
const stmts: Stmt[] = [];
|
|
while (!this.done() && !this.test("}")) {
|
|
stmts.push(this.parseStmt());
|
|
}
|
|
|
|
if (!this.test("}")) {
|
|
this.report("expected '}'");
|
|
return this.stmt({ type: "error" }, pos);
|
|
}
|
|
this.step();
|
|
|
|
return this.stmt({ type: "mod_block", ident, stmts }, pos, details);
|
|
}
|
|
|
|
private parseFn(details: StmtDetails): 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();
|
|
let genericParams: GenericParam[] | undefined;
|
|
if (this.test("<")) {
|
|
genericParams = this.parseFnETypeParams();
|
|
}
|
|
if (!this.test("(")) {
|
|
this.report("expected '('");
|
|
return this.stmt({ type: "error" }, pos);
|
|
}
|
|
const params = this.parseFnParams();
|
|
let returnType: EType | undefined;
|
|
if (this.test("->")) {
|
|
this.step();
|
|
returnType = this.parseEType();
|
|
}
|
|
|
|
if (!this.test("{")) {
|
|
this.report("expected block");
|
|
return this.stmt({ type: "error" }, pos);
|
|
}
|
|
const body = this.parseBlock();
|
|
return this.stmt(
|
|
{
|
|
type: "fn",
|
|
ident,
|
|
genericParams,
|
|
params,
|
|
returnType,
|
|
body,
|
|
},
|
|
pos,
|
|
details,
|
|
);
|
|
}
|
|
|
|
private parseFnETypeParams(): GenericParam[] {
|
|
return this.parseDelimitedList(this.parseETypeParam, ">", ",");
|
|
}
|
|
|
|
private parseETypeParam(index: number): Res<GenericParam> {
|
|
const pos = this.pos();
|
|
if (this.test("ident")) {
|
|
const ident = this.current().identValue!;
|
|
this.step();
|
|
return {
|
|
ok: true,
|
|
value: this.astCreator.genericParam({ index, ident, pos }),
|
|
};
|
|
}
|
|
this.report("expected generic parameter");
|
|
return { ok: false };
|
|
}
|
|
|
|
private parseFnParams(): Param[] {
|
|
return this.parseDelimitedList(this.parseParam, ")", ",");
|
|
}
|
|
|
|
private parseDelimitedList<T>(
|
|
parseElem: (this: Parser, index: number) => Res<T>,
|
|
endToken: string,
|
|
delimiter: string,
|
|
): T[] {
|
|
this.step();
|
|
if (this.test(endToken)) {
|
|
this.step();
|
|
return [];
|
|
}
|
|
let i = 0;
|
|
const elems: T[] = [];
|
|
const elemRes = parseElem.call(this, i);
|
|
if (!elemRes.ok) {
|
|
return [];
|
|
}
|
|
elems.push(elemRes.value);
|
|
i += 1;
|
|
while (this.test(delimiter)) {
|
|
this.step();
|
|
if (this.test(endToken)) {
|
|
break;
|
|
}
|
|
const elemRes = parseElem.call(this, i);
|
|
if (!elemRes.ok) {
|
|
return [];
|
|
}
|
|
elems.push(elemRes.value);
|
|
i += 1;
|
|
}
|
|
if (!this.test(endToken)) {
|
|
this.report(`expected '${endToken}'`);
|
|
return elems;
|
|
}
|
|
this.step();
|
|
return elems;
|
|
}
|
|
|
|
private parseParam(index?: number): Res<Param> {
|
|
const pos = this.pos();
|
|
if (this.test("ident") || this.test("mut")) {
|
|
let mut = false;
|
|
if (this.test("mut")) {
|
|
mut = true;
|
|
this.step();
|
|
}
|
|
const ident = this.current().identValue!;
|
|
this.step();
|
|
if (this.test(":")) {
|
|
this.step();
|
|
const etype = this.parseEType();
|
|
return {
|
|
ok: true,
|
|
value: this.astCreator.param({
|
|
index,
|
|
ident,
|
|
mut,
|
|
etype,
|
|
pos,
|
|
}),
|
|
};
|
|
}
|
|
return {
|
|
ok: true,
|
|
value: this.astCreator.param({ index, ident, mut, pos }),
|
|
};
|
|
}
|
|
this.report("expected param");
|
|
return { ok: false };
|
|
}
|
|
|
|
private 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);
|
|
}
|
|
|
|
private parseTypeAlias(): Stmt {
|
|
const pos = this.pos();
|
|
this.step();
|
|
const paramResult = this.parseParam();
|
|
if (!paramResult.ok) {
|
|
return this.stmt({ type: "error" }, pos);
|
|
}
|
|
const param = paramResult.value;
|
|
return this.stmt({ type: "type_alias", param }, pos);
|
|
}
|
|
|
|
private parseAssign(): Stmt {
|
|
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);
|
|
}
|
|
return this.stmt({ type: "expr", expr: subject }, pos);
|
|
}
|
|
|
|
private 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);
|
|
}
|
|
|
|
private 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);
|
|
}
|
|
|
|
private 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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
private parseArray(): Expr {
|
|
const pos = this.pos();
|
|
this.step();
|
|
const exprs: Expr[] = [];
|
|
if (!this.test("]")) {
|
|
exprs.push(this.parseExpr());
|
|
while (this.test(",")) {
|
|
this.step();
|
|
if (this.done() || this.test("]")) {
|
|
break;
|
|
}
|
|
exprs.push(this.parseExpr());
|
|
}
|
|
}
|
|
if (!this.test("]")) {
|
|
this.report("expected ']'");
|
|
return this.expr({ type: "error" }, pos);
|
|
}
|
|
this.step();
|
|
return this.expr({ type: "array", exprs }, pos);
|
|
}
|
|
|
|
private parseStruct(): Expr {
|
|
const pos = this.pos();
|
|
this.step();
|
|
if (!this.test("{")) {
|
|
this.report("expected '{'");
|
|
return this.expr({ type: "error" }, pos);
|
|
}
|
|
this.step();
|
|
const fields: Field[] = [];
|
|
if (!this.test("}")) {
|
|
const res = this.parseStructField();
|
|
if (!res.ok) {
|
|
return this.expr({ type: "error" }, res.pos!);
|
|
}
|
|
fields.push(res.value);
|
|
while (this.test(",")) {
|
|
this.step();
|
|
if (this.done() || this.test("}")) {
|
|
break;
|
|
}
|
|
const res = this.parseStructField();
|
|
if (!res.ok) {
|
|
return this.expr({ type: "error" }, res.pos!);
|
|
}
|
|
fields.push(res.value);
|
|
}
|
|
}
|
|
if (!this.test("}")) {
|
|
this.report("expected '}'");
|
|
return this.expr({ type: "error" }, pos);
|
|
}
|
|
this.step();
|
|
return this.expr({ type: "struct", fields }, pos);
|
|
}
|
|
|
|
private parseStructField(): Res<Field> {
|
|
const pos = this.pos();
|
|
if (!this.test("ident")) {
|
|
this.report("expected 'ident'");
|
|
return { ok: false, pos };
|
|
}
|
|
const ident = this.current().identValue!;
|
|
this.step();
|
|
if (!this.test(":")) {
|
|
this.report("expected ':'");
|
|
return { ok: false, pos };
|
|
}
|
|
this.step();
|
|
const expr = this.parseExpr();
|
|
return { ok: true, value: { ident, expr, pos } };
|
|
}
|
|
|
|
private 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);
|
|
}
|
|
const elsePos = this.pos();
|
|
this.step();
|
|
if (this.test("if")) {
|
|
const falsy = this.parseIf();
|
|
return this.expr({ type: "if", cond, truthy, falsy, elsePos }, 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, elsePos }, pos);
|
|
}
|
|
|
|
private parseBinary(): Expr {
|
|
return this.parseOr();
|
|
}
|
|
|
|
private parseOr(): Expr {
|
|
const pos = this.pos();
|
|
let left = this.parseAnd();
|
|
while (true) {
|
|
if (this.test("or")) {
|
|
left = this.parBinTail(left, pos, this.parseAnd, "or");
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
if (this.test("-")) {
|
|
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,
|
|
);
|
|
}
|
|
|
|
private parsePrefix(): Expr {
|
|
const pos = this.pos();
|
|
if (this.test("not") || this.test("-")) {
|
|
const unaryType = this.current().type as UnaryType;
|
|
this.step();
|
|
const subject = this.parsePrefix();
|
|
return this.expr({ type: "unary", unaryType, subject }, pos);
|
|
}
|
|
if (this.test("&")) {
|
|
this.step();
|
|
let type: "ref" | "ref_mut" = "ref";
|
|
if (this.test("mut")) {
|
|
this.step();
|
|
type = "ref_mut";
|
|
}
|
|
const subject = this.parsePrefix();
|
|
return this.expr({ type, subject }, pos);
|
|
}
|
|
if (this.test("*")) {
|
|
this.step();
|
|
const subject = this.parsePrefix();
|
|
return this.expr({ type: "deref", subject }, pos);
|
|
}
|
|
return this.parsePostfix();
|
|
}
|
|
|
|
private parsePostfix(): Expr {
|
|
let subject = this.parseOperand();
|
|
while (true) {
|
|
if (this.test(".")) {
|
|
subject = this.parseFieldTail(subject);
|
|
continue;
|
|
}
|
|
if (this.test("[")) {
|
|
subject = this.parseIndexTail(subject);
|
|
continue;
|
|
}
|
|
if (this.test("(")) {
|
|
subject = this.parseCallTail(subject);
|
|
continue;
|
|
}
|
|
if (this.test("::")) {
|
|
subject = this.parsePathTail(subject);
|
|
continue;
|
|
}
|
|
if (this.test("::<")) {
|
|
subject = this.parseETypeArgsTail(subject);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
return subject;
|
|
}
|
|
|
|
private parseETypeArgsTail(subject: Expr): Expr {
|
|
const pos = this.pos();
|
|
const etypeArgs = this.parseDelimitedList(
|
|
this.parseETypeArg,
|
|
">",
|
|
",",
|
|
);
|
|
return this.expr(
|
|
{ type: "etype_args", subject, etypeArgs },
|
|
pos,
|
|
);
|
|
}
|
|
|
|
private parseFieldTail(subject: Expr): Expr {
|
|
const pos = this.pos();
|
|
this.step();
|
|
if (!this.test("ident")) {
|
|
this.report("expected ident");
|
|
return this.expr({ type: "error" }, pos);
|
|
}
|
|
const ident = this.current().identValue!;
|
|
this.step();
|
|
return this.expr({ type: "field", subject, ident }, pos);
|
|
}
|
|
|
|
private parseIndexTail(subject: Expr): Expr {
|
|
const pos = this.pos();
|
|
this.step();
|
|
const value = this.parseExpr();
|
|
if (!this.test("]")) {
|
|
this.report("expected ']'");
|
|
return this.expr({ type: "error" }, pos);
|
|
}
|
|
this.step();
|
|
return this.expr({ type: "index", subject, value }, pos);
|
|
}
|
|
|
|
private parseCallTail(subject: Expr): Expr {
|
|
const pos = this.pos();
|
|
const args = this.parseDelimitedList(
|
|
this.parseExprArg,
|
|
")",
|
|
",",
|
|
);
|
|
return this.expr({ type: "call", subject, args }, pos);
|
|
}
|
|
|
|
private parsePathTail(subject: Expr): Expr {
|
|
const pos = this.pos();
|
|
this.step();
|
|
if (!this.test("ident")) {
|
|
this.report("expected ident");
|
|
return this.expr({ type: "error" }, pos);
|
|
}
|
|
const ident = this.current().identValue!;
|
|
this.step();
|
|
return this.expr({ type: "path", subject, ident }, pos);
|
|
}
|
|
|
|
private parseExprArg(): Res<Expr> {
|
|
return { ok: true, value: this.parseExpr() };
|
|
}
|
|
|
|
private parseETypeArg(): Res<EType> {
|
|
return { ok: true, value: this.parseEType() };
|
|
}
|
|
|
|
private parseOperand(): Expr {
|
|
const pos = this.pos();
|
|
if (this.test("ident")) {
|
|
const ident = this.current().identValue!;
|
|
this.step();
|
|
return this.expr({ type: "ident", ident }, 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.parseArray();
|
|
}
|
|
if (this.test("struct")) {
|
|
return this.parseStruct();
|
|
}
|
|
if (this.test("{")) {
|
|
return this.parseBlock();
|
|
}
|
|
if (this.test("if")) {
|
|
return this.parseIf();
|
|
}
|
|
if (this.test("loop")) {
|
|
return this.parseLoop();
|
|
}
|
|
|
|
this.report(`expected expr, got '${this.current().type}'`, pos);
|
|
this.step();
|
|
return this.expr({ type: "error" }, pos);
|
|
}
|
|
|
|
private parseEType(): EType {
|
|
const pos = this.pos();
|
|
if (["null", "int", "bool", "string"].includes(this.current().type)) {
|
|
const type = this.current().type as
|
|
| "null"
|
|
| "int"
|
|
| "bool"
|
|
| "string";
|
|
this.step();
|
|
return this.etype({ type }, pos);
|
|
}
|
|
if (this.test("ident")) {
|
|
const ident = this.current().identValue!;
|
|
this.step();
|
|
return this.etype({ type: "ident", ident: ident }, pos);
|
|
}
|
|
if (this.test("[")) {
|
|
this.step();
|
|
const subject = this.parseEType();
|
|
if (!this.test("]")) {
|
|
this.report("expected ']'", pos);
|
|
return this.etype({ type: "error" }, pos);
|
|
}
|
|
this.step();
|
|
return this.etype({ type: "array", subject }, 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);
|
|
}
|
|
if (this.test("&")) {
|
|
this.step();
|
|
let type: "ref" | "ref_mut" = "ref";
|
|
if (this.test("mut")) {
|
|
this.step();
|
|
type = "ref_mut";
|
|
}
|
|
const subject = this.parseEType();
|
|
return this.etype({ type, subject }, pos);
|
|
}
|
|
if (this.test("*")) {
|
|
this.step();
|
|
let type: "ptr" | "ptr_mut" = "ptr";
|
|
if (this.test("mut")) {
|
|
this.step();
|
|
type = "ptr_mut";
|
|
}
|
|
const subject = this.parseEType();
|
|
return this.etype({ type, subject }, pos);
|
|
}
|
|
this.report("expected type");
|
|
return this.etype({ type: "error" }, pos);
|
|
}
|
|
|
|
private 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;
|
|
}
|
|
|
|
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()) {
|
|
this.reporter.reportError({
|
|
msg,
|
|
pos,
|
|
reporter: "Parser",
|
|
});
|
|
printStackTrace();
|
|
}
|
|
|
|
private stmt(kind: StmtKind, pos: Pos, details?: StmtDetails): Stmt {
|
|
return this.astCreator.stmt(kind, pos, details);
|
|
}
|
|
|
|
private expr(kind: ExprKind, pos: Pos): Expr {
|
|
return this.astCreator.expr(kind, pos);
|
|
}
|
|
|
|
private etype(kind: ETypeKind, pos: Pos): EType {
|
|
return this.astCreator.etype(kind, pos);
|
|
}
|
|
}
|