71 lines
1.8 KiB
TypeScript
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;
|
|
}
|
|
}
|