// ======================================================== // L3 normal eval import { Sexp } from "s-expression"; import { map } from "ramda"; import { CExp, Exp, IfExp, Program, parseL3Exp } from "./L3-ast"; import { isAppExp, isBoolExp, isCExp, isDefineExp, isIfExp, isLitExp, isNumExp, isPrimOp, isProcExp, isStrExp, isVarRef } from "./L3-ast"; import { applyEnv, makeEmptyEnv, makeEnv, Env } from './L3-env'; import { isTrueValue } from "./L3-eval"; import { applyPrimitive } from "./evalPrimitive"; import { renameExps, substitute } from "./substitute"; import { isClosure, makeClosure, SExpValue, Value } from "./L3-value"; import { first, rest, isEmpty } from '../shared/list'; import { Result, makeOk, makeFailure, bind, mapResult } from "../shared/result"; import { parse as p } from "../shared/parser"; /* Purpose: Evaluate an L3 expression with normal-eval algorithm Signature: L3-normal-eval(exp,env) Type: CExp * Env => Value */ export const L3normalEval = (exp: CExp, env: Env): Result<Value> => isBoolExp(exp) ? makeOk(exp.val) : isNumExp(exp) ? makeOk(exp.val) : isStrExp(exp) ? makeOk(exp.val) : isPrimOp(exp) ? makeOk(exp) : isLitExp(exp) ? makeOk(exp.val) : isVarRef(exp) ? applyEnv(env, exp.var) : isIfExp(exp) ? evalIf(exp, env) : isProcExp(exp) ? makeOk(makeClosure(exp.args, exp.body)) : // This is the difference between applicative-eval and normal-eval // Substitute the arguments into the body without evaluating them first. isAppExp(exp) ? bind(L3normalEval(exp.rator, env), proc => L3normalApplyProc(proc, exp.rands, env)) : makeFailure(`Bad ast: ${JSON.stringify(exp, null, 2)}`); const evalIf = (exp: IfExp, env: Env): Result<Value> => bind(L3normalEval(exp.test, env), (test: SExpValue) => isTrueValue(test) ? L3normalEval(exp.then, env) : L3normalEval(exp.alt, env)); /* =========================================================== Normal Order Application handling Purpose: Apply a procedure to NON evaluated arguments. Signature: L3-normalApplyProcedure(proc, args) Pre-conditions: proc must be a prim-op or a closure value */ const L3normalApplyProc = (proc: Value, args: CExp[], env: Env): Result<Value> => { if (isPrimOp(proc)) { const argVals: Result<Value[]> = mapResult((arg) => L3normalEval(arg, env), args); return bind(argVals, (args: Value[]) => applyPrimitive(proc, args)); } else if (isClosure(proc)) { // Substitute non-evaluated args into the body of the closure const vars = map((p) => p.var, proc.params); const body = renameExps(proc.body); return L3normalEvalSeq(substitute(body, vars, args), env); } else { return makeFailure(`Bad proc applied ${JSON.stringify(proc, null, 2)}`); } }; /* Purpose: Evaluate a sequence of expressions Signature: L3-normal-eval-sequence(exps, env) Type: [List(CExp) * Env -> Value] Pre-conditions: exps is not empty */ const L3normalEvalSeq = (exps: CExp[], env: Env): Result<Value> => { if (isEmpty(rest(exps))) return L3normalEval(first(exps), env); else { L3normalEval(first(exps), env); return L3normalEvalSeq(rest(exps), env); } }; /* Purpose: evaluate a program made up of a sequence of expressions. (Same as in L1) When def-exp expressions are executed, thread an updated env to the continuation. For other expressions (that have no side-effect), execute the expressions sequentially. Signature: evalNormalProgram(program) */ export const evalNormalProgram = (program: Program): Result<Value> => evalExps(program.exps, makeEmptyEnv()); // Evaluate a sequence of expressions (in a program) export const evalExps = (exps: Exp[], env: Env): Result<Value> => isEmpty(exps) ? makeFailure("Empty program") : isDefineExp(first(exps)) ? evalDefineExps(first(exps), rest(exps), env) : evalCExps(first(exps), rest(exps), env); const evalCExps = (exp1: Exp, exps: Exp[], env: Env): Result<Value> => isCExp(exp1) && isEmpty(exps) ? L3normalEval(exp1, env) : isCExp(exp1) ? bind(L3normalEval(exp1, env), _ => evalExps(exps, env)) : makeFailure("Never"); // Eval a sequence of expressions when the first exp is a Define. // Compute the rhs of the define, extend the env with the new binding // then compute the rest of the exps in the new env. const evalDefineExps = (def: Exp, exps: Exp[], env: Env): Result<Value> => isDefineExp(def) ? bind(L3normalEval(def.val, env), (rhs: Value) => evalExps(exps, makeEnv(def.var.var, rhs, env))) : makeFailure(`Unexpected ${JSON.stringify(def, null, 2)}`); export const evalNormalParse = (s: string): Result<Value> => bind(p(s), (parsed: Sexp) => bind(parseL3Exp(parsed), (exp: Exp) => evalExps([exp], makeEmptyEnv())));