import type { Token } from './lexer' import type { AST, MatchCase, Pattern } from './ast' export class Parser { private tokens: Token[] private pos: number = 0 constructor(tokens: Token[]) { this.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++]; } private peek(offset = 1): Token { const pos = this.pos + offset; if (pos >= this.tokens.length) { return { kind: 'eof' } as Token; } return this.tokens[pos]; } 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(); } private isInfixOp(): boolean { const kind = this.current().kind; return kind === 'plus' || kind === 'minus' || kind === 'star' || kind === 'slash' || kind === 'greater-than' || kind === 'ampersand'; } private tokenToOpName(token: Token): string { switch (token.kind) { case 'plus': return '+'; case 'minus': return '-'; case 'star': return '*'; case 'slash': return '/'; case 'greater-than': return '>'; case 'ampersand': return '&'; default: throw new Error(`Not an operator: ${token.kind}`); } } private canStartPrimary(): boolean { const kind = this.current().kind; return kind === 'ident' || kind === 'type-ident' || kind === 'int' || kind === 'float' || kind === 'string' || kind === 'open-paren' || kind === 'open-bracket' || kind === 'open-brace'; } private isLambdaStart(): boolean { const kind = this.current().kind; if (kind === 'backslash') { return true; } if (kind !== 'ident') { return false; } let offset = 1; while (true) { const token = this.peek(offset); if (token.kind === 'backslash') return true; if (token.kind !== 'ident') return false; offset++; } } parse(): AST { return this.parseExpression(); } private parseExpression(): AST { // Lambda if (this.isLambdaStart()) { return this.parseLambda(); } // Let if (this.current().kind === 'ident' && this.peek().kind === 'equals') { return this.parseLet(); } let expr = this.parseInfix(); // Match if (this.current().kind === 'pipe') { return this.parseMatch(expr); } return expr; } private parseMatch(expr: AST): AST { const cases: MatchCase[] = []; while(this.current().kind === 'pipe') { this.advance(); const pattern = this.parsePattern(); this.expect('backslash'); const result = this.parseInfix(); cases.push({ pattern, result }) } return { kind: 'match', expr, cases }; } private parsePattern(): Pattern { const token = this.current(); // Wildcard if (token.kind === 'underscore') { this.advance(); return { kind: 'wildcard' }; } // Literal if (token.kind === 'int' || token.kind === 'float' || token.kind === 'string') { this.advance(); return { kind: 'literal', value: token.value }; } // Variable if (token.kind === 'ident') { this.advance(); return { kind: 'var', name: token.value }; } // Constructor if (token.kind === 'type-ident') { this.advance(); const name = token.value; const args: Pattern[] = []; while (this.canStartPattern()) { args.push(this.parsePattern()); } return { kind: 'constructor', name, args }; } // List if (token.kind === 'open-bracket') { this.advance(); const elements: Pattern[] = []; let first = true; while (this.current().kind !== 'close-bracket') { if (!first) this.expect('comma'); first = false; elements.push(this.parsePattern()); } this.expect('close-bracket'); return { kind: 'list', elements }; } // Record if (token.kind === 'open-brace') { this.advance(); const fields: { [key: string]: Pattern }= []; let first = true; while (this.current().kind !== 'close-brace') { if (!first) this.expect('comma'); first = false; const keyToken = this.expect('ident'); const key = (keyToken as { value: string }).value; this.expect('equals'); fields[key] = this.parsePattern(); } this.expect('close-brace'); return { kind: 'list', fields }; } // Parens if (token.kind === 'open-paren') { this.advance(); const pattern = this.parsePattern(); this.expect('close-paren'); return pattern; } throw new Error(`Unexpected token in pattern: ${token.kind}`) } private canStartPattern(): boolean { const kind = this.current().kind; return kind === 'underscore' || kind === 'ident' || kind === 'type-ident' || kind === 'int' || kind === 'float' || kind === 'string' || kind === 'open-paren'; } private parseLambda(): AST { const params: string[] = []; while (this.current().kind === 'ident') { const param = this.advance(); params.push((param as { value: string }).value); } this.expect('backslash'); const body = this.parseExpression(); return { kind: 'lambda', params, body }; } private parseLet(): AST { const nameToken = this.expect('ident'); const name = (nameToken as { value: string }).value; this.expect('equals'); const value = this.parseExpression(); this.expect('semicolon'); const body = this.parseExpression(); return { kind: 'let', name, value, body }; } private parseInfix(): AST { let left = this.parseApplication(); while (this.isInfixOp()) { const opToken = this.advance(); const opName = this.tokenToOpName(opToken); const right = this.parseApplication(); left = { kind: 'apply', func: { kind: 'variable', name: opName }, args: [left, right] } } return left; } private parseApplication(): AST { let func = this.parsePostfix(); while (this.canStartPrimary()) { const arg = this.parsePostfix(); func = { kind: 'apply', func, args: [arg] }; } return func; } private parsePostfix(): AST { let expr = this.parsePrimary(); while (true) { if (this.current().kind === 'dot') { // Record access this.advance(); const fieldToken = this.expect('ident'); const field = (fieldToken as { value: string }).value; expr = { kind: 'record-access', record: expr, field }; } else if (this.current().kind === 'open-brace') { // Record update this.advance(); const updates: { [key: string]: AST } = {}; let first = true; while (this.current().kind !== 'close-brace') { if (!first) { this.expect('comma'); } first = false; const keyToken = this.expect('ident'); const key = (keyToken as { value: string }).value; this.expect('equals'); updates[key] = this.parseExpression(); } this.expect('close-brace'); expr = { kind: 'record-update', record: expr, updates } } else { break; } } return expr; } private parsePrimary(): AST { const token = this.current(); if (token.kind === 'open-paren') { this.advance(); const expr = this.parseExpression(); this.expect('close-paren'); return expr; } if (token.kind === 'open-bracket') { this.advance(); const items: AST[] = []; let first = true; while (this.current().kind !== 'close-bracket') { if (!first) { this.expect('comma'); } first = false; items.push(this.parseExpression()); } this.expect('close-bracket'); return { kind: 'list', elements: items }; } if (token.kind === 'open-brace') { this.advance(); const fields: { [key: string]: AST } = {}; let first = true; while (this.current().kind !== 'close-brace') { if (!first) { this.expect('comma'); } first = false; const keyToken = this.expect('ident'); const key = (keyToken as { value: string }).value; this.expect('equals'); fields[key] = this.parseExpression(); } this.expect('close-brace'); return { kind: 'record', fields }; } if (token.kind === 'int') { this.advance(); return { kind: 'literal', value: { kind: 'int', value: token.value } }; } if (token.kind === 'float') { this.advance(); return { kind: 'literal', value: { kind: 'float', value: token.value } }; } if (token.kind === 'string') { this.advance(); return { kind: 'literal', value: { kind: 'string', value: token.value } }; } if (token.kind === 'ident') { this.advance(); return { kind: 'variable', name: token.value }; } if (token.kind === 'type-ident') { this.advance(); return { kind: 'constructor', name: token.value }; } throw new Error(`Unexpected token: ${token.kind}`); } }