/* EVAL.js ------- reads exprs from AST and executes them types of elem: all have type all have val TYPE VAL "closure" {scope:{}, args:['x', 'y'], defn:} : {type:"ins", ins:[]} {type:"builtin", fn:(scope) => {stack}} exported functionality: defnOp(name, function) - define a built-in operator defn(name, ins, scope) - extend scope with AST makeOp(name, args, fn) - lift a function to an operator evalRPN(scope, ast) */ let builtins = {}; export const defnOp = (name, data) => { builtins[name] = data; } export const defn = (name, ins, scope) => { let defscope = Object.create(scope); let fnForm = {type:"closure", val:{scope:defscope, args:[], defn:{type:"ins", ins:ins}}}; defscope[name] = [fnForm]; let out = execRPN(defscope, ins); defscope[name] = out.stacks[0]; return defscope; } export const makeOp = (args, fn) => { return [{type:"closure", val:{scope:{}, args:args, defn:{type:"builtin", fn:fn}}}]; } const lookup = (name, scope) => { let n = scope[name]; if (n) { return n; } n = builtins[name]; if (n) { return n; } console.log(scope); throw '"' + name + '" not in scope' } const extend = (scope, name, elems) => { let o = Object.create(scope); o[name] = elems; return o; } const runFn = (defn, state) => { if (defn.type === "ins") { state.calls.push(defn.ins); state.stacks.push([]); } else if (defn.type === "builtin") { let scope = state.scopes[state.scopes.length-1]; let out = defn.fn(scope); state.calls.push([]); state.stacks.push(out); } } const giveArg = (arg, elem) => { let argN = elem.val.args[elem.val.args.length-1]; let newscope = extend(elem.val.scope, argN, [arg]); return {type:elem.type, val:{scope:newscope, args:elem.val.args.slice(0,-1), defn:elem.val.defn}}; } const apply = (elem, state) => { if (elem.type === "closure") { if (elem.val.args.length === 0) { state.scopes.push(elem.val.scope); runFn(elem.val.defn, state); } else if (state.stacks[state.stacks.length-1].length > 0) { apply(giveArg(state.stacks[state.stacks.length-1].pop(), elem), state); } else { state.stacks[state.stacks.length-1].push(elem); } } else { state.stacks[state.stacks.length-1].push(elem); } } const applyMany = (elems, state) => { for (let i = 0; i < elems.length; i++) { apply(elems[i], state); } } const makeStackElems = (ins, state, handler) => { if (ins.type === "push") { throw 'nested push error' } else if (ins.type === "ident") { return lookup(ins.val, state.scopes[state.scopes.length-1]); } else if (ins.type === "func") { return [{type:"closure", val:{scope:state.scopes[state.scopes.length-1], args:ins.args, defn:{type:"ins", ins:ins.body}}}]; } else { return handler(ins); } } const doIns = (ins, state, handler) => { if (ins.type === "push") { state.stacks[state.stacks.length-1] = state.stacks[state.stacks.length-1].concat(makeStackElems(ins.elem, state, handler)); } else if (ins.type === "defn") { state.scopes[state.scopes.length-1] = defn(ins.ident, ins.defn, state.scopes[state.scopes.length-1]); } else { applyMany(makeStackElems(ins, state, handler), state); } } let hasDoneAchievement = false export const step = (state, handler, showIns, maxdepth) => { if (!hasDoneAchievement && state.stacks.length > 100) { if ("points" in window) { points.then(points => points.unlockAchievement("rpnv4recursion")) } hasDoneAchievement = true } if (state.stacks.length > maxdepth) { throw 'max recursion depth exceeded' } if (state.calls[state.calls.length-1].length === 0) { if (state.calls.length === 1) { throw 'finished execution' } if (state.stacks.length < 2) { throw 'nothing to return' } state.calls.pop(); state.scopes.pop(); let out = state.stacks.pop(); applyMany(out, state); return {start:0, end:0}; } else { let ins = state.calls[state.calls.length-1][0]; state.calls[state.calls.length-1] = state.calls[state.calls.length-1].slice(1); try { doIns(ins, state, handler); } catch (error) { throw error + ' while executing "' + showIns(ins) + '"' } return ins.pos; } } export const execRPN = (scope, ins, handler=(x)=>[x], showIns=(x)=>x.name, maxdepth=16384) => { let state = {scopes:[scope], stacks:[[]], calls:[ins]}; while (state.calls[0].length > 0 || state.calls.length > 1) { step(state, handler, showIns, maxdepth); } return state; }