evaluating pattern matching

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

@ -126,8 +126,18 @@ export function prettyPrint(ast: AST, indent = 0): string {
const i = ' '.repeat(indent); const i = ' '.repeat(indent);
switch (ast.kind) { switch (ast.kind) {
case 'literal': case 'literal': {
return `${i}${ast.value}`; const val = ast.value;
switch (val.kind) {
case 'int':
case 'float':
return `${i}${val.value}`;
case 'string':
return `${i}"${val.value}"`;
default:
return `${i}${val.kind}`;
}
}
case 'variable': case 'variable':
return `${i}${ast.name}`; return `${i}${ast.name}`;
@ -171,7 +181,6 @@ export function prettyPrint(ast: AST, indent = 0): string {
.join('\n'); .join('\n');
return `${i}match ${expr}\n${cases}`; return `${i}match ${expr}\n${cases}`;
default: default:
return `${i}${ast.kind}` return `${i}${ast.kind}`
} }

@ -1,4 +1,4 @@
import type { AST } from './ast'; import type { AST, Pattern } from './ast';
import type { Env } from './env'; import type { Env } from './env';
import type { Value } from './types'; import type { Value } from './types';
@ -136,6 +136,24 @@ export function evaluate(ast: AST, env: Env): Value {
return evaluate(func.body, callEnv); return evaluate(func.body, callEnv);
} }
case 'match': {
const value = evaluate(ast.expr, env);
for (const matchCase of ast.cases) {
const bindings = matchPattern(value, matchCase.pattern);
if (bindings !== null) {
const newEnv = new Map(env);
for (const [name, val] of Object.entries(bindings)) {
newEnv.set(name, val);
}
return evaluate(matchCase.result, newEnv);
}
}
throw new Error('Non-exhaustive pattern match');
}
default: default:
throw new Error('Syntax Error'); throw new Error('Syntax Error');
} }
@ -206,3 +224,66 @@ function evaluateBuiltIn(op: string, left: Value, right: Value): Value {
throw new Error(`Unknown built-in: ${op}`); throw new Error(`Unknown built-in: ${op}`);
} }
type Bindings = { [key: string]: Value };
function matchPattern(value: Value, pattern: Pattern): Bindings | null {
switch (pattern.kind) {
case 'wildcard':
return {};
case 'var':
return { [pattern.name]: value };
case 'literal':
if (value.kind === 'int' || value.kind === 'float' || value.kind === 'string') {
if (value.value === pattern.value) {
return {};
}
}
return null;
case 'constructor': {
if (value.kind !== 'constructor') return null;
if (value.name !== pattern.name) return null;
if (value.args.length !== pattern.args.length) return null;
const bindings: Bindings = {};
for (let i = 0; i < pattern.args.length; i++) {
const argBindings = matchPattern(value.args[i], pattern.args[i]);
if (argBindings === null) return null;
Object.assign(bindings, argBindings);
}
return bindings;
}
case 'list': {
if (value.kind !== 'list') return null;
if (value.elements.length !== pattern.elements.length) return null;
const bindings: Bindings = {};
for (let i = 0; i < pattern.elements.length; i++) {
const elemBindings = matchPattern(value.elements[i], pattern.elements[i]);
if (elemBindings === null) return null;
Object.assign(bindings, elemBindings);
}
return bindings;
}
case 'record': {
if (value.kind !== 'record') return null;
const bindings: Bindings = {};
for (const [fieldName, fieldPattern] of Object.entries(pattern.fields)) {
const fieldValue = value.fields[fieldName];
if (fieldValue === undefined) return null;
const fieldBindings = matchPattern(fieldValue, fieldPattern);
if (fieldBindings === null) return null;
Object.assign(bindings, fieldBindings);
}
return bindings;
}
}
}

@ -13,7 +13,7 @@ function e(str: string) {
console.log(ast); console.log(ast);
console.log(prettyPrint(ast)); console.log(prettyPrint(ast));
const env: Env = new Map(); const env: Env = new Map();
// console.log(evaluate(ast, env)); console.log(evaluate(ast, env));
} }
e('add1 = (x \\ x + 1); add1 3'); e('add1 = (x \\ x + 1); add1 3');
@ -26,5 +26,8 @@ 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('n | 0 \\ 1 | _ \\ 99');
e('m | Some x \\ 1 | None \\ 0'); // e('m | Some x \\ 1 | None \\ 0');
e('head = list \\ list | [x, _] \\ Some x | [] \\ None; head'); // e('head = list \\ list | [x, _] \\ Some x | [] \\ None; head');
e('n = 5; n | 5 \\ "five" | _ \\ "other"');
e('list = [1, 2, 3]; list | [x, y, z] \\ x + y + z');
e('point = {x = 5, y = 10}; point | {x = px, y = py} \\ px + py');

@ -179,7 +179,7 @@ export class Parser {
// Record // Record
if (token.kind === 'open-brace') { if (token.kind === 'open-brace') {
this.advance(); this.advance();
const fields: { [key: string]: Pattern }= []; const fields: { [key: string]: Pattern } = {};
let first = true; let first = true;
while (this.current().kind !== 'close-brace') { while (this.current().kind !== 'close-brace') {
@ -193,7 +193,7 @@ export class Parser {
} }
this.expect('close-brace'); this.expect('close-brace');
return { kind: 'list', fields }; return { kind: 'record', fields };
} }
// Parens // Parens

Loading…
Cancel
Save