Got parser working. now i'm switching from toy language to CG syntax. lexer is... done??? maybe
parent
f74d374555
commit
71237d0307
@ -0,0 +1,177 @@
|
|||||||
|
import type { Token } from './lexer'
|
||||||
|
import type { AST } from './ast'
|
||||||
|
|
||||||
|
export class Parser {
|
||||||
|
private tokens: Token[]
|
||||||
|
private pos: number = 0
|
||||||
|
|
||||||
|
constructor(tokens: Token[]) {
|
||||||
|
this.tokens = tokens;
|
||||||
|
console.log("tokens", tokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
private current(): Token {
|
||||||
|
if (this.pos >= this.tokens.length) {
|
||||||
|
return { kind: 'eof' } as Token;
|
||||||
|
}
|
||||||
|
return this.tokens[this.pos];
|
||||||
|
}
|
||||||
|
|
||||||
|
private advance(): Token {
|
||||||
|
return this.tokens[this.pos++];
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(): AST {
|
||||||
|
return this.parseExpression();
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseExpression(): AST {
|
||||||
|
if (this.current().kind === 'let') {
|
||||||
|
return this.parseLet();
|
||||||
|
}
|
||||||
|
return this.parseAdditive();
|
||||||
|
}
|
||||||
|
|
||||||
|
private parsePrimary(): AST {
|
||||||
|
const token = this.current();
|
||||||
|
|
||||||
|
if (token.kind === 'open-paren') {
|
||||||
|
const savedPos = this.pos;
|
||||||
|
this.advance();
|
||||||
|
|
||||||
|
let isLambda = false;
|
||||||
|
if (this.current().kind === 'close-paren') {
|
||||||
|
isLambda = true;
|
||||||
|
} else if (this.current().kind === 'ident') {
|
||||||
|
let tempPos = this.pos;
|
||||||
|
this.advance();
|
||||||
|
if (this.current().kind === 'comma' || (this.current().kind === 'close-paren' && this.tokens[this.pos + 1]?.kind === 'arrow')) {
|
||||||
|
isLambda = true;
|
||||||
|
}
|
||||||
|
this.pos = tempPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pos = savedPos;
|
||||||
|
|
||||||
|
if (isLambda) {
|
||||||
|
return this.parseLambda();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.advance();
|
||||||
|
const expr = this.parseExpression();
|
||||||
|
this.expect('close-paren');
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token.kind === 'number') {
|
||||||
|
this.advance();
|
||||||
|
|
||||||
|
return { kind: 'literal', value: { kind: 'int', value: token.value } };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token.kind === 'ident') {
|
||||||
|
this.advance();
|
||||||
|
|
||||||
|
return { kind: 'variable', name: token.value };
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Unexpected token: ${token.kind}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseMultiplicative(): AST {
|
||||||
|
let left = this.parsePostfix();
|
||||||
|
|
||||||
|
while (this.current().kind === 'star' || this.current().kind === 'slash') {
|
||||||
|
const op = this.current().kind === 'star' ? '*' : '/';
|
||||||
|
this.advance();
|
||||||
|
const right = this.parsePostfix();
|
||||||
|
|
||||||
|
left = { kind: 'binaryop', operator: op, left, right };
|
||||||
|
}
|
||||||
|
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseAdditive(): AST {
|
||||||
|
let left = this.parseMultiplicative();
|
||||||
|
|
||||||
|
while (this.current().kind === 'plus' || this.current().kind === 'minus') {
|
||||||
|
const op = this.current().kind === 'plus' ? '+' : '-';
|
||||||
|
this.advance();
|
||||||
|
const right = this.parseMultiplicative();
|
||||||
|
|
||||||
|
left = { kind: 'binaryop', operator: op, left, right };
|
||||||
|
}
|
||||||
|
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseLet(): AST {
|
||||||
|
this.expect('let');
|
||||||
|
const nameToken = this.expect('ident');
|
||||||
|
const name = (nameToken as { value: string }).value;
|
||||||
|
this.expect('equals');
|
||||||
|
const value = this.parseExpression();
|
||||||
|
this.expect('in');
|
||||||
|
const body = this.parseExpression();
|
||||||
|
|
||||||
|
return { kind: 'let', name, value, body };
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseLambda(): AST {
|
||||||
|
this.expect('open-paren');
|
||||||
|
|
||||||
|
const params: string[] = [];
|
||||||
|
|
||||||
|
if (this.current().kind !== 'close-paren') {
|
||||||
|
const first = this.expect('ident');
|
||||||
|
params.push((first as { value: string }).value);
|
||||||
|
|
||||||
|
while (this.current().kind === 'comma') {
|
||||||
|
this.advance();
|
||||||
|
const param = this.expect('ident');
|
||||||
|
params.push((param as { value: string }).value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.expect('close-paren');
|
||||||
|
this.expect('arrow');
|
||||||
|
|
||||||
|
const body = this.parseExpression();
|
||||||
|
|
||||||
|
return { kind: 'lambda', params, body }
|
||||||
|
}
|
||||||
|
|
||||||
|
private parsePostfix(): AST {
|
||||||
|
let expr = this.parsePrimary();
|
||||||
|
|
||||||
|
while (this.current().kind === 'open-paren') {
|
||||||
|
this.advance();
|
||||||
|
|
||||||
|
const args: AST[] = [];
|
||||||
|
if (this.current().kind !== 'close-paren') {
|
||||||
|
args.push(this.parseExpression());
|
||||||
|
|
||||||
|
while (this.current().kind === 'comma') {
|
||||||
|
this.advance();
|
||||||
|
args.push(this.parseExpression());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.expect('close-paren');
|
||||||
|
|
||||||
|
expr = { kind: 'apply', func: expr, args };
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private expect(kind: Token['kind']): Token {
|
||||||
|
const token = this.current();
|
||||||
|
if (token.kind !== kind) {
|
||||||
|
throw new Error(`Expected ${kind}, got ${token.kind}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.advance();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue