parsing pattern matching
This commit is contained in:
parent
d81318333e
commit
7f94cfe8cd
4 changed files with 176 additions and 11 deletions
45
src/ast.ts
45
src/ast.ts
|
|
@ -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}}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
16
src/lexer.ts
16
src/lexer.ts
|
|
@ -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');
|
||||||
|
|
|
||||||
117
src/parser.ts
117
src/parser.ts
|
|
@ -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…
Add table
Add a link
Reference in a new issue