You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

294 lines
8.7 KiB
TypeScript

import type { AST, Pattern } from './ast';
import type { Env } from './env';
import type { Value } from './types';
export function evaluate(ast: AST, env: Env): Value {
switch (ast.kind) {
case 'literal':
return ast.value;
case 'variable': {
const val = env.get(ast.name);
if (val === undefined)
throw new Error(`Unknown variable: ${ast.name}`);
return val;
}
case 'list':
return {
kind: 'list',
elements: ast.elements.map(el => evaluate(el, env))
};
case 'record':
const fields: { [key: string]: Value } = {};
Object.entries(ast.fields).forEach(([k, v]) => {
fields[k] = evaluate(v, env);
});
return { kind: 'record', fields };
case 'record-access': {
const record = evaluate(ast.record, env);
if (record.kind !== 'record')
throw new Error('Not a record');
const value = record.fields[ast.field];
if (value === undefined) {
throw new Error(`Field ${ast.field} not found`);
}
return value;
}
case 'record-update': {
const record = evaluate(ast.record, env);
if (record.kind !== 'record')
throw new Error('Not a record');
const newFields: { [key: string]: Value } = { ...record.fields };
for (const [field, expr] of Object.entries(ast.updates)) {
newFields[field] = evaluate(expr, env);
}
return { kind: 'record', fields: newFields };
}
case 'constructor':
return {
kind: 'constructor',
name: ast.name,
args: [] // TODO: constructors args
};
case 'let': {
const newEnv = new Map(env);
const val = evaluate(ast.value, newEnv);
newEnv.set(ast.name, val);
return evaluate(ast.body, newEnv);
}
case 'lambda':
return {
kind: 'closure',
params: ast.params,
body: ast.body,
env
}
case 'apply': {
// Operators
if (ast.func.kind === 'variable') {
const name = ast.func.name;
const builtIns = ['+', '-', '*', '/', '>', '&'];
if (builtIns.includes(name)) {
const argValues = ast.args.map(arg => evaluate(arg, env));
if (argValues.length !== 2) {
throw new Error(`${name} expects 2 args`);
}
return evaluateBuiltIn(name, argValues[0], argValues[1]);
}
}
const func = evaluate(ast.func, env);
if (func.kind !== 'closure')
throw new Error('Not a function');
const argValues = ast.args.map(arg => evaluate(arg, env));
// Too few args (Currying)
if (argValues.length < func.params.length) {
// Bind the params we have
const newEnv = new Map(func.env);
for (let i = 0; i < argValues.length; i++) {
newEnv.set(func.params[i], argValues[i]);
}
return {
kind: 'closure',
params: func.params.slice(argValues.length),
body: func.body,
env: newEnv
};
}
// Too many args
if (argValues.length > func.params.length)
throw new Error('Too many arguments');
// Exact number of args
const callEnv = new Map(func.env);
for (let i = 0; i < argValues.length; i++) {
callEnv.set(func.params[i], argValues[i]);
}
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');
}
}
function evaluateBinaryOp(op: string, left: Value, right: Value): Value {
const leftKind = left.kind;
const rightKind = right.kind;
const bothNumbers = ((leftKind === 'int' || leftKind === 'float')) && ((rightKind === 'int' || rightKind === 'float'));
if (!bothNumbers)
throw new Error(`Not numbers: ${left}, ${right}`);
const leftValue = left.value;
const rightValue = right.value;
switch (op) {
case '+':
return { value: leftValue + rightValue, kind: 'int' };
case '-':
return { value: leftValue - rightValue, kind: 'int' };
case '*':
return { value: leftValue * rightValue, kind: 'int' }
case '/':
return { value: leftValue / rightValue, kind: 'int' }
default:
throw new Error(`Unknown operation: ${op}`);
}
}
function evaluateBuiltIn(op: string, left: Value, right: Value): Value {
if (op === '+' || op === '-' || op === '*' || op === '/') {
return evaluateBinaryOp(op, left, right);
}
if (op === '>') {
// x > f means f(x)
if (right.kind !== 'closure')
throw new Error('Right side of > must be a function');
if (right.params.length !== 1)
throw new Error('Pipe only works with 1-arg functions for now..');
const callEnv = new Map(right.env);
callEnv.set(right.params[0], left);
return evaluate(right.body, callEnv);
}
if (op === '&') {
if (left.kind == 'list' && right.kind === 'list') {
return {
kind: 'list',
elements: [...left.elements, ...right.elements]
};
}
if (left.kind == 'string' && right.kind === 'string') {
return {
kind: 'string',
value: left.value + right.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;
}
}
}