nandgame-compiler/parser.ts

759 lines
22 KiB
TypeScript
Raw Normal View History

2023-04-27 01:32:06 +01:00
import {
BinaryType,
ParsedExpr,
ParsedParameter,
ParsedType,
} from "./parsed.ts";
import {
CompileError,
Position,
Token,
TokenIter,
TokenType,
} from "./token.ts";
export class Parser {
private current: Token;
public errors: CompileError[] = [];
public constructor(private lexer: TokenIter) {
this.current = lexer.next();
}
public parse(): ParsedExpr[] {
while (this.currentIs("semicolon")) this.step();
const statements: ParsedExpr[] = [];
while (!this.done()) {
while (this.currentIs("semicolon")) this.step();
}
return statements;
}
private checkAndSkipStatementLinebreak() {
if (!this.currentIs("semicolon")) {
this.errors.push({
pos: this.position(),
message:
`expected ';' after statement, got '${this.current.tokenType}'`,
});
} else {
this.step();
}
}
public parseStatement(): ParsedExpr {
if (this.currentIs("fn")) {
return this.parseFn();
} else if (this.currentIs("if")) {
return this.parseIf();
} else if (this.currentIs("loop")) {
return this.parseLoop();
} else if (this.currentIs("while")) {
return this.parseWhile();
} else if (this.currentIs("return")) {
const statement = this.parseReturn();
this.checkAndSkipStatementLinebreak();
return statement;
} else if (this.currentIs("break")) {
const statement = this.parseBreak();
this.checkAndSkipStatementLinebreak();
return statement;
} else if (this.currentIs("continue")) {
const statement = this.parseContinue();
this.checkAndSkipStatementLinebreak();
return statement;
} else if (this.currentIs("let")) {
const statement = this.parseLet();
this.checkAndSkipStatementLinebreak();
return statement;
} else {
const statement = this.parseAssign();
this.checkAndSkipStatementLinebreak();
return statement;
}
}
private parseFn(): ParsedExpr {
const pos = this.position();
this.step();
if (this.done() || this.current.tokenType != "id") {
return this.errorExpr(
`expected 'Id' after 'fn' as function name, got '${this.current.tokenType}'`,
);
}
const subject = this.current.value;
this.step();
if (!this.currentIs("lparen")) {
return this.errorExpr(
`expected '(' before function-parameters, got '${this.current.tokenType}'`,
);
}
this.step();
const params: ParsedParameter[] = [];
params.push(this.parseParam());
while (this.currentIs("comma")) {
this.step();
if (this.done() || this.currentIs("rparen")) break;
params.push(this.parseParam());
}
if (!this.currentIs("rparen")) {
return this.errorExpr(
`expected ')' after function-parameters, got '${this.current.tokenType}'`,
);
}
this.step();
let returnType: ParsedType | null = null;
if (this.currentIs("minusgt")) {
this.step();
returnType = this.parseType();
}
if (!this.currentIs("lbrace")) {
return this.errorExpr(
`expected '{' after 'loop', got '${this.current.tokenType}'`,
);
}
const body = this.parseBlock();
return {
pos,
exprType: "fn",
subject,
params,
returnType,
body,
};
}
private parseLoop(): ParsedExpr {
const pos = this.position();
this.step();
if (!this.currentIs("lbrace")) {
return this.errorExpr(
`expected '{' after 'loop', got '${this.current.tokenType}'`,
);
}
const body = this.parseBlock();
return {
pos,
exprType: "loop",
body,
};
}
private parseWhile(): ParsedExpr {
const pos = this.position();
this.step();
const condition = this.parseExpr();
if (!this.currentIs("lbrace")) {
return this.errorExpr(
`expected '{' after while-condition, got '${this.current.tokenType}'`,
);
}
const body = this.parseBlock();
return {
pos,
exprType: "while",
condition,
body,
};
}
private parseReturn(): ParsedExpr {
const pos = this.position();
this.step();
let value: ParsedExpr | null = null;
if (!this.done() && this.current.tokenType != "semicolon") {
value = this.parseExpr();
}
return { pos, exprType: "return", value };
}
private parseBreak(): ParsedExpr {
const pos = this.position();
this.step();
return { pos, exprType: "break" };
}
private parseContinue(): ParsedExpr {
const pos = this.position();
this.step();
return { pos, exprType: "continue" };
}
private parseLet(): ParsedExpr {
const pos = this.position();
this.step();
const param = this.parseParam();
if (!this.currentIs("equal")) {
return this.errorExpr(
`expected '=' after let-parameter, got '${this.current.tokenType}'`,
);
}
this.step();
const value = this.parseExpr();
return {
pos,
exprType: "let",
param,
value,
};
}
private parseParam(): ParsedParameter {
const pos = this.position();
const mutable = this.currentIs("mut");
if (mutable) this.step();
if (this.done() || this.current.tokenType != "id") {
return this.errorParam(
`expected 'id' in parameter, got '${this.current.tokenType}'`,
);
}
const subject = this.current.value;
this.step();
let valueType: ParsedType | null = null;
if (this.currentIs("colon")) {
this.step();
valueType = this.parseType();
}
return {
pos,
paramType: "value",
subject,
valueType,
mutable,
};
}
private parseAssign(): ParsedExpr {
const pos = this.position();
const subject = this.parseExpr();
if (this.currentIs("equal")) {
this.step();
const value = this.parseExpr();
return {
pos,
exprType: "assign",
assignType: "equal",
subject,
value,
};
} else if (this.currentIs("plusequal")) {
this.step();
const value = this.parseExpr();
return {
pos,
exprType: "assign",
assignType: "add",
subject,
value,
};
} else if (this.currentIs("minusequal")) {
this.step();
const value = this.parseExpr();
return {
pos,
exprType: "assign",
assignType: "subtract",
subject,
value,
};
} else if (this.currentIs("ampersandequal")) {
this.step();
const value = this.parseExpr();
return {
pos,
exprType: "assign",
assignType: "and",
subject,
value,
};
} else if (this.currentIs("pipeequal")) {
this.step();
const value = this.parseExpr();
return {
pos,
exprType: "assign",
assignType: "or",
subject,
value,
};
} else if (this.currentIs("hatequal")) {
this.step();
const value = this.parseExpr();
return {
pos,
exprType: "assign",
assignType: "xor",
subject,
value,
};
} else {
return subject;
}
}
public parseExpr(): ParsedExpr {
return this.parseLogOr();
}
private binaryExpr(
binaryType: BinaryType,
left: ParsedExpr,
right: ParsedExpr,
pos: Position,
): ParsedExpr {
return {
pos,
exprType: "binary",
binaryType,
left,
right,
};
}
private parseLogOr(): ParsedExpr {
const pos = this.position();
let left = this.parseLogAnd();
while (!this.done()) {
if (this.currentIs("or")) {
this.step();
const right = this.parseLogAnd();
left = this.binaryExpr("log_or", left, right, pos);
} else {
break;
}
}
return left;
}
private parseLogAnd(): ParsedExpr {
const pos = this.position();
let left = this.parseBitOr();
while (!this.done()) {
if (this.currentIs("and")) {
this.step();
const right = this.parseBitOr();
left = this.binaryExpr("log_and", left, right, pos);
} else {
break;
}
}
return left;
}
private parseBitOr(): ParsedExpr {
const pos = this.position();
let left = this.parseBitXor();
while (!this.done()) {
if (this.currentIs("pipe")) {
this.step();
const right = this.parseBitXor();
left = this.binaryExpr("bit_or", left, right, pos);
} else {
break;
}
}
return left;
}
private parseBitXor(): ParsedExpr {
const pos = this.position();
let left = this.parseBitAnd();
while (!this.done()) {
if (this.currentIs("hat")) {
this.step();
const right = this.parseBitAnd();
left = this.binaryExpr("bit_xor", left, right, pos);
} else {
break;
}
}
return left;
}
private parseBitAnd(): ParsedExpr {
const pos = this.position();
let left = this.parseEquality();
while (!this.done()) {
if (this.currentIs("ampersand")) {
this.step();
const right = this.parseEquality();
left = this.binaryExpr("bit_and", left, right, pos);
} else {
break;
}
}
return left;
}
private parseEquality(): ParsedExpr {
const pos = this.position();
let left = this.parseComparison();
while (!this.done()) {
if (this.currentIs("equalequal")) {
this.step();
const right = this.parseComparison();
left = this.binaryExpr("equal", left, right, pos);
} else if (this.currentIs("exclamationequal")) {
this.step();
const right = this.parseComparison();
left = this.binaryExpr("inequal", left, right, pos);
} else {
break;
}
}
return left;
}
private parseComparison(): ParsedExpr {
const pos = this.position();
const left = this.parseAddSubtract();
if (this.currentIs("lt")) {
this.step();
const right = this.parseAddSubtract();
return this.binaryExpr("lt", left, right, pos);
} else if (this.currentIs("gt")) {
this.step();
const right = this.parseAddSubtract();
return this.binaryExpr("gt", left, right, pos);
} else if (this.currentIs("ltequal")) {
this.step();
const right = this.parseAddSubtract();
return this.binaryExpr("ltequal", left, right, pos);
} else if (this.currentIs("gtequal")) {
this.step();
const right = this.parseAddSubtract();
return this.binaryExpr("gtequal", left, right, pos);
} else {
return left;
}
}
private parseAddSubtract(): ParsedExpr {
const pos = this.position();
let left = this.parseUnary();
while (!this.done()) {
if (this.currentIs("plus")) {
this.step();
const right = this.parseUnary();
left = this.binaryExpr("add", left, right, pos);
} else if (this.currentIs("minus")) {
this.step();
const right = this.parseUnary();
left = this.binaryExpr("subtract", left, right, pos);
} else {
break;
}
}
return left;
}
private parseUnary(): ParsedExpr {
if (this.currentIs("minus")) {
const pos = this.position();
this.step();
return {
pos,
exprType: "unary",
unaryType: "negate",
subject: this.parseUnary(),
};
} else if (this.currentIs("not")) {
const pos = this.position();
this.step();
return {
pos,
exprType: "unary",
unaryType: "log_not",
subject: this.parseUnary(),
};
} else if (this.currentIs("tilde")) {
const pos = this.position();
this.step();
return {
pos,
exprType: "unary",
unaryType: "bit_not",
subject: this.parseUnary(),
};
} else if (this.currentIs("ampersand")) {
const pos = this.position();
this.step();
return {
pos,
exprType: "unary",
unaryType: "addressof",
subject: this.parseUnary(),
};
} else if (this.currentIs("asterisk")) {
const pos = this.position();
this.step();
return {
pos,
exprType: "unary",
unaryType: "dereference",
subject: this.parseUnary(),
};
} else {
return this.parseCallIndex();
}
}
private parseCallIndex(): ParsedExpr {
const pos = this.position();
let subject = this.parseOperand();
while (!this.done()) {
if (this.currentIs("lparen")) {
this.step();
const args: ParsedExpr[] = [];
if (!this.done() && this.current.tokenType != "rparen") {
args.push(this.parseExpr());
while (this.currentIs("comma")) {
this.step();
if (this.done() || this.currentIs("rparen")) {
break;
}
args.push(this.parseExpr());
}
}
if (!this.currentIs("rparen")) {
return this.errorExpr(
`expected ')', got '${this.current.tokenType}'`,
);
}
this.step();
subject = {
pos,
exprType: "call",
subject,
args,
};
} else if (this.currentIs("lbracket")) {
this.step();
const value = this.parseExpr();
if (!this.currentIs("rbracket")) {
return this.errorExpr(
`expected ']', got '${this.current.tokenType}'`,
);
}
this.step();
subject = {
pos,
exprType: "index",
subject,
value,
};
} else {
break;
}
}
return subject;
}
private parseOperand(): ParsedExpr {
if (this.current.tokenType == "id") {
const pos = this.position();
const value = this.current.value;
this.step();
return {
pos,
exprType: "id",
value,
};
} else if (this.current.tokenType == "int") {
const pos = this.position();
const value = this.current.value;
this.step();
return {
pos,
exprType: "int",
value,
};
} else if (this.currentIs("lparen")) {
const pos = this.position();
this.step();
if (this.currentIs("rparen")) {
this.step();
return { pos, exprType: "unit" };
}
const subject = this.parseExpr();
if (!this.currentIs("rparen")) {
return this.errorExpr(
`expected ')', got '${this.current.tokenType}'`,
);
}
this.step();
subject.pos = pos;
return subject;
} else if (this.currentIs("if")) {
return this.parseIf();
} else if (this.currentIs("lbrace")) {
return this.parseBlock();
} else {
return this.errorExpr(
`expected operand, got '${this.current.tokenType}'`,
);
}
}
private parseIf(): ParsedExpr {
const pos = this.position();
this.step();
const condition = this.parseExpr();
if (!this.currentIs("lbrace")) {
return this.errorExpr(
`expected '{' after if-condition, got '${this.current.tokenType}'`,
);
}
const truthy = this.parseBlock();
let falsy: ParsedExpr | null = null;
if (this.currentIs("else")) {
this.step();
if (!this.currentIs("lbrace")) {
return this.errorExpr(
`expected '{' after 'else', got '${this.current.tokenType}'`,
);
}
falsy = this.parseBlock();
}
return {
pos,
exprType: "if",
condition,
truthy,
falsy,
};
}
private parseBlock(): ParsedExpr {
const pos = this.position();
this.step();
while (this.currentIs("semicolon")) this.step();
const body: ParsedExpr[] = [];
while (!this.done() && this.current.tokenType != "rbrace") {
body.push(this.parseStatement());
while (this.currentIs("semicolon")) this.step();
}
if (!this.currentIs("rbrace")) {
return this.errorExpr(
`expected '}' after block, got '${this.current.tokenType}'`,
);
}
this.step();
return {
pos,
exprType: "block",
body,
};
}
private parseType(): ParsedType {
return this.parsePointerType();
}
private parsePointerType(): ParsedType {
if (this.currentIs("asterisk")) {
const pos = this.position();
this.step();
let mutable = false;
if (this.currentIs("mut")) {
mutable = true;
this.step();
}
const subject = this.parseTypeOperand();
return {
pos,
typeType: "pointer",
subject,
mutable,
};
} else {
return this.parseTypeOperand();
}
}
private parseTypeOperand(): ParsedType {
if (this.current.tokenType == "id") {
const pos = this.position();
const value = this.current.value;
this.step();
return {
pos,
typeType: "id",
value,
};
} else if (this.currentIs("lparen")) {
const pos = this.position();
this.step();
if (this.currentIs("rparen")) {
this.step();
return { pos, typeType: "unit" };
}
const subject = this.parseType();
if (!this.currentIs("rparen")) {
return this.errorType(
`expected ')', got '${this.current.tokenType}'`,
);
}
this.step();
subject.pos = pos;
return subject;
} else {
return this.errorType(
`expected type, got '${this.current.tokenType}'`,
);
}
}
private errorExpr(
message: string,
pos: Position = this.position(),
): ParsedExpr {
this.errors.push({ pos, message });
return {
pos,
exprType: "error",
message,
};
}
private errorParam(
message: string,
pos: Position = this.position(),
): ParsedParameter {
this.errors.push({ pos, message });
return {
pos,
paramType: "error",
message,
};
}
private errorType(
message: string,
pos: Position = this.position(),
): ParsedType {
this.errors.push({ pos, message });
return {
pos,
typeType: "error",
message,
};
}
private position() {
return this.current.pos;
}
private step() {
this.current = this.lexer.next();
}
private currentIs(tokenType: TokenType): boolean {
return this.current.tokenType == tokenType;
}
private done(): boolean {
return this.current.tokenType == "eof";
}
}