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; } }