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 === 'percent' || kind === 'caret' || kind === 'ampersand' || kind === 'equals-equals' || kind === 'not-equals' || kind === 'less-than' || kind === 'less-equals' || kind === 'greater-than' || kind === 'greater-equals' || kind === 'tilde'; } private tokenToOpName(token: Token): string { switch (token.kind) { case 'ampersand': return 'cat'; // Arithmetic case 'plus': return 'add'; case 'minus': return 'sub'; case 'star': return 'mul'; case 'slash': return 'div'; case 'percent': return 'mod'; case 'caret': return 'pow'; // Comparison case 'equals-equals': return 'eq'; case 'not-equals': return 'neq'; case 'greater-than': return 'gt'; case 'greater-equals': return 'gte'; case 'less-than': return 'lt'; case 'less-equals': return 'lte'; 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; let spreadName: string | null = null; while (this.current().kind !== 'close-bracket') { if (!first) this.expect('comma'); first = false; // Spread if (this.current().kind === 'dot-dot-dot') { this.advance(); const nameToken = this.expect('ident'); spreadName = (nameToken as { value: string }).value; break; } elements.push(this.parsePattern()); } this.expect('close-bracket'); if (spreadName !== null) { return { kind: 'list-spread', head: elements, spread: spreadName }; } 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: 'record', 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(); if (opToken.kind === 'tilde') { // function application operator const right = this.parseApplication(); left = { kind: 'apply', func: right, args: [left] }; } else { // operators desugar to function calls 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') { this.advance(); 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 { // Record access 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') { // if (expr.kind === 'constructor') { // Function / constructor application const record = this.parsePrimary(); expr = { kind: 'apply', func: expr, args: [record] }; // } else { // break; // } } 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 | { spread: AST })[] = []; let first = true; while (this.current().kind !== 'close-bracket') { if (!first) { this.expect('comma'); } first = false; // Spread if (this.current().kind === 'dot-dot-dot') { this.advance(); const expr = this.parseExpression(); items.push({ spread: expr }) } else { 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}`); } }