|
|
|
|
@ -1,4 +1,4 @@
|
|
|
|
|
import type { AST } from './ast';
|
|
|
|
|
import type { AST, Pattern } from './ast';
|
|
|
|
|
import type { Env } from './env';
|
|
|
|
|
import type { Value } from './types';
|
|
|
|
|
|
|
|
|
|
@ -136,6 +136,24 @@ export function evaluate(ast: AST, env: Env): Value {
|
|
|
|
|
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:
|
|
|
|
|
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}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|