|
|
|
@ -13,28 +13,11 @@ export function evaluate(ast: AST, env: Env): Value {
|
|
|
|
throw new Error(`Unknown variable: ${ast.name}`);
|
|
|
|
throw new Error(`Unknown variable: ${ast.name}`);
|
|
|
|
return val;
|
|
|
|
return val;
|
|
|
|
|
|
|
|
|
|
|
|
case 'binaryop':
|
|
|
|
|
|
|
|
const left = evaluate(ast.left, env);
|
|
|
|
|
|
|
|
const right = evaluate(ast.right, env);
|
|
|
|
|
|
|
|
return evaluateBinaryOp(ast.operator, left, right);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
case 'let':
|
|
|
|
case 'let':
|
|
|
|
const val2 = evaluate(ast.value, env);
|
|
|
|
const val2 = evaluate(ast.value, env);
|
|
|
|
const newEnv = new Map(env).set(ast.name, val2);
|
|
|
|
const newEnv = new Map(env).set(ast.name, val2);
|
|
|
|
return evaluate(ast.body, newEnv);
|
|
|
|
return evaluate(ast.body, newEnv);
|
|
|
|
|
|
|
|
|
|
|
|
case 'if':
|
|
|
|
|
|
|
|
const res = evaluate(ast.condition, env);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (res.kind !== 'bool')
|
|
|
|
|
|
|
|
throw new Error('Condition must be bool');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (res.value) {
|
|
|
|
|
|
|
|
return evaluate(ast.then, env);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
return evaluate(ast.else, env);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
case 'lambda':
|
|
|
|
case 'lambda':
|
|
|
|
return {
|
|
|
|
return {
|
|
|
|
kind: 'closure',
|
|
|
|
kind: 'closure',
|
|
|
|
@ -44,21 +27,54 @@ export function evaluate(ast: AST, env: Env): Value {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
case 'apply':
|
|
|
|
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);
|
|
|
|
const func = evaluate(ast.func, env);
|
|
|
|
|
|
|
|
|
|
|
|
if (func.kind !== 'closure')
|
|
|
|
if (func.kind !== 'closure')
|
|
|
|
throw new Error('Not a closure');
|
|
|
|
throw new Error('Not a function');
|
|
|
|
|
|
|
|
|
|
|
|
const params = func.params;
|
|
|
|
const argValues = ast.args.map(arg => evaluate(arg, env));
|
|
|
|
const callEnv = new Map(func.env);
|
|
|
|
|
|
|
|
const args = ast.args;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (args.length !== params.length)
|
|
|
|
// Too few args (Currying)
|
|
|
|
throw new Error('Parameter argument mismatch');
|
|
|
|
if (argValues.length < func.params.length) {
|
|
|
|
|
|
|
|
// Bind the params we have
|
|
|
|
|
|
|
|
const newEnv = new Map(func.env);
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < args.length; i++) {
|
|
|
|
for (let i = 0; i < argValues.length; i++) {
|
|
|
|
const arg = evaluate(args[i], env);
|
|
|
|
newEnv.set(func.params[i], argValues[i]);
|
|
|
|
const param = params[i];
|
|
|
|
}
|
|
|
|
callEnv.set(param, arg);
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
return evaluate(func.body, callEnv);
|
|
|
|
@ -97,3 +113,17 @@ function evaluateBinaryOp(op: string, left: Value, right: Value): Value {
|
|
|
|
throw new Error(`Unknown operation: ${op}`);
|
|
|
|
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 === '>')
|
|
|
|
|
|
|
|
throw new Error('TODO Not implemented yet');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (op === '&')
|
|
|
|
|
|
|
|
throw new Error('TODO Not implemented yet');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
throw new Error(`Unknown built-in: ${op}`);
|
|
|
|
|
|
|
|
}
|
|
|
|
|