mirror of
https://github.com/osmarks/website
synced 2024-11-13 22:24:48 +00:00
158 lines
4.5 KiB
JavaScript
158 lines
4.5 KiB
JavaScript
|
/*
|
||
|
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:<function-like>}
|
||
|
|
||
|
<function-like>:
|
||
|
{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);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export const step = (state, handler, showIns, maxdepth) => {
|
||
|
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;
|
||
|
}
|