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