parsing pattern matching

This commit is contained in:
Dustin Swan 2026-02-01 18:42:50 -07:00
parent d81318333e
commit 7f94cfe8cd
No known key found for this signature in database
GPG key ID: 30D46587E2100467
4 changed files with 176 additions and 11 deletions

View file

@ -1,5 +1,5 @@
import type { Token } from './lexer'
import type { AST } from './ast'
import type { AST, MatchCase, Pattern } from './ast'
export class Parser {
private tokens: Token[]
@ -92,15 +92,128 @@ export class Parser {
}
private parseExpression(): AST {
// Lambda
if (this.isLambdaStart()) {
return this.parseLambda();
}
// Let
if (this.current().kind === 'ident' && this.peek().kind === 'equals') {
return this.parseLet();
}
return this.parseInfix();
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 {