parsing pattern matching

master
Dustin Swan 5 days ago
parent d81318333e
commit 7f94cfe8cd
Signed by: dustinswan
GPG Key ID: 30D46587E2100467

@ -127,7 +127,7 @@ export function prettyPrint(ast: AST, indent = 0): string {
switch (ast.kind) { switch (ast.kind) {
case 'literal': case 'literal':
return `${i}${ast.value.value}`; return `${i}${ast.value}`;
case 'variable': case 'variable':
return `${i}${ast.name}`; return `${i}${ast.name}`;
@ -154,17 +154,56 @@ export function prettyPrint(ast: AST, indent = 0): string {
return `${i}{${fields}}`; return `${i}{${fields}}`;
case 'lambda': case 'lambda':
const params = ast.params.join(', ') const params = ast.params.join(', ');
return `${i}(${params}) => ${prettyPrint(ast.body)}` return `${i}(${params}) => ${prettyPrint(ast.body)}`
case 'record-access': case 'record-access':
return `${i}${prettyPrint(ast.record)}.${ast.field}` return `${i}${prettyPrint(ast.record)}.${ast.field}`;
case 'record-update': case 'record-update':
const updates = Object.entries(ast.updates).map(([k, v]) => `${k} = ${prettyPrint(v, 0)}`).join(', '); const updates = Object.entries(ast.updates).map(([k, v]) => `${k} = ${prettyPrint(v, 0)}`).join(', ');
return `${i}${prettyPrint(ast.record)} { ${updates} }` return `${i}${prettyPrint(ast.record)} { ${updates} }`
case 'match':
const expr = prettyPrint(ast.expr, 0);
const cases = ast.cases
.map(c => ` | ${prettyPrintPattern(c.pattern)} -> ${prettyPrint(c.result, 0)}`)
.join('\n');
return `${i}match ${expr}\n${cases}`;
default: default:
return `${i}${ast.kind}` return `${i}${ast.kind}`
} }
} }
function prettyPrintPattern(pattern: Pattern): string {
switch (pattern.kind) {
case 'wildcard':
return '_';
case 'var':
return pattern.name;
case 'literal':
return JSON.stringify(pattern.value);
case 'constructor':
if (pattern.args.length === 0) {
return pattern.name;
}
const args = pattern.args.map(prettyPrintPattern).join(' ');
return `(${pattern.name} ${args})`;
case 'list':
const elems = pattern.elements.map(prettyPrintPattern).join(', ');
return `[${elems}]`;
case 'record':
const fields = Object.entries(pattern.fields)
.map(([k, p]) => `${k} = ${prettyPrintPattern(p)}`)
.join(', ');
return `{${fields}}`;
}
}

@ -48,6 +48,7 @@ export function tokenize(source: string): Token[] {
continue; continue;
} }
// Comments
if (char === '#') { if (char === '#') {
while (i < source.length && source[i] !== '\n') { while (i < source.length && source[i] !== '\n') {
i++; i++;
@ -76,7 +77,7 @@ export function tokenize(source: string): Token[] {
continue; continue;
} }
// Idents // Idents & Wildcard
if (/[A-Za-z_]/.test(char)) { if (/[A-Za-z_]/.test(char)) {
let str = ''; let str = '';
while (i < source.length && /[A-Za-z0-9_!-]/.test(source[i])) { while (i < source.length && /[A-Za-z0-9_!-]/.test(source[i])) {
@ -84,11 +85,16 @@ export function tokenize(source: string): Token[] {
i++; i++;
} }
const isType = /[A-Z]/.test(str[0]); if (str === '_') {
// Wildcards
tokens.push({ kind: 'underscore' });
} else {
const isType = /[A-Z]/.test(str[0]);
tokens.push(isType tokens.push(isType
? { kind: 'type-ident', value: str } ? { kind: 'type-ident', value: str }
: { kind: 'ident', value: str }); : { kind: 'ident', value: str });
}
continue; continue;
} }

@ -5,11 +5,15 @@ import { Parser } from './parser'
import { prettyPrint } from './ast'; import { prettyPrint } from './ast';
function e(str: string) { function e(str: string) {
console.log(str);
const tokens = tokenize(str); const tokens = tokenize(str);
console.log(tokens);
const p = new Parser(tokens); const p = new Parser(tokens);
const ast = p.parse(); const ast = p.parse();
console.log(ast);
console.log(prettyPrint(ast));
const env: Env = new Map(); const env: Env = new Map();
console.log(str, tokens, prettyPrint(ast), evaluate(ast, env)); // console.log(evaluate(ast, env));
} }
e('add1 = (x \\ x + 1); add1 3'); e('add1 = (x \\ x + 1); add1 3');
@ -21,3 +25,6 @@ e('rec = { a = 3, b = 5 }; rec{ a = 10 }');
e('add1 = (x \\ x + 1); 3 > add1'); e('add1 = (x \\ x + 1); 3 > add1');
e('[1, 2] & [3, 4]'); e('[1, 2] & [3, 4]');
e('"abc" & "def"'); e('"abc" & "def"');
// e('n | 0 \\ 1 | _ \\ 99');
e('m | Some x \\ 1 | None \\ 0');
e('head = list \\ list | [x, _] \\ Some x | [] \\ None; head');

@ -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 {

Loading…
Cancel
Save