jeene/src/compiler/parser.ts
2025-12-25 13:16:15 +00:00

71 lines
1.8 KiB
TypeScript

import type { ASTNode } from './ast';
import type { Grammar } from './grammar';
import type { Token, Tokenizer } from './tokenizer';
export class Parser {
lookahead: Token | null = null;
constructor(
private tokenizer: Tokenizer,
private grammar: Grammar,
) {}
parse(source: string) {
this.tokenizer.tokenize(source);
this.nextToken();
try {
return this.parseExpr();
} catch (error) {
if (error instanceof Error) {
const token = this.lookahead;
if (token) {
throw new Error(
`${error.message} at line ${token.line}, column ${token.col}`,
);
}
throw error;
}
throw error;
}
}
nextToken() {
this.lookahead = this.tokenizer.nextToken();
}
parseExpr(minBp = 0): ASTNode {
const token = this.lookahead!;
this.nextToken();
const prefixRule =
this.grammar.prefixOperatorsByText.get(token.text) ??
this.grammar.prefixOperatorsByKind.get(token.kind);
if (!prefixRule) throw new Error(`Unexpected token ${token.text}`);
let left = prefixRule.parse(this, token);
while (true) {
const next = this.lookahead;
if (!next) break;
// Check for postfix operators first
const postfixRule =
this.grammar.postfixOperatorsByText.get(next.text) ??
this.grammar.postfixOperatorsByKind.get(next.kind);
if (postfixRule) {
this.nextToken();
left = postfixRule.parse(this, next, left);
continue;
}
// Then check for infix operators
const infixRule =
this.grammar.operatorsByText.get(next.text) ??
this.grammar.operatorsByKind.get(next.kind);
if (!infixRule || (infixRule.bp ?? 0) <= minBp) break;
this.nextToken();
left = infixRule.parse(this, next, left);
}
return left;
}
}