evaluating pattern matching
This commit is contained in:
parent
7f94cfe8cd
commit
a85203bc94
4 changed files with 102 additions and 9 deletions
15
src/ast.ts
15
src/ast.ts
|
|
@ -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…
Add table
Add a link
Reference in a new issue