rpncalc-v4/eval.mjs

205 lines
5.2 KiB
JavaScript

/*
EVAL.js
-------
function definition object:
{nargs:0, defn:() => {}}
types of object on list:
{type:"int", val:0}
{type:"char", val:"0"}
{type:"closure", args:[], func:{}}
{type:"pair", val:{fst:{}, snd:{}}}
[{type:"int", val:1},{type:"int", val:1},{type:"builtin", op:"+"}]
[{type:"int", val:1},{type:"int", val:1},{type:"func", args:["a"], body:[{type:"ident", val:"a"},{type:"int", val:1},{type:"builtin", op:"+"}]}]
exported functions:
addDefn - adds a builtin function
doStep - consumes instruction stream, stack, outputs stack
execRPN - consumes scope, instruction stream, outputs stack
*/
const makeEval = (sign, defnarg) => {
if (typeof sign === "number") {
return {nargs:sign, defn:defnarg};
} else {
return {nargs:sign.length, defn:(scope, args) => {
let stripped = [];
for (let i = 0; i < sign.length; i++) {
if (args[i].type === sign[i]) {
stripped.push(args[i].val);
} else {
throw 'typeerror'
}
}
return defnarg(scope, stripped);
}
}
}
}
const add = (_, args) => {
return [{type:"int", val:args[0] + args[1]}];
}
const sub = (_, args) => {
return [{type:"int", val:args[1] - args[0]}];
}
const div = (_, args) => {
return [{type:"int", val:args[1] / args[0]}];
}
const mult = (_, args) => {
return [{type:"int", val:args[0] * args[1]}];
}
const type = (_, args) => {
return [{type:"string", val:args[0].type}];
}
const pair = (_, args) => {
return [{type:"pair", val:{fst:args[0], snd:args[1]}}];
}
const fst = (_, args) => [args[0].fst];
const snd = (_, args) => [args[0].snd];
const tru = (_, args) => [args[0]];
const fls = (_, args) => [args[1]];
const eq = (_, args) => {
if (args[0].type === args[1].type && args[0].val === args[1].val) {
return [{type:"ident", val:"true"}];
} else {
return [{type:"ident", val:"false"}];
}
}
let builtinDefn = {
"+": makeEval(["int", "int"], add),
"-": makeEval(["int", "int"], sub),
"/": makeEval(["int", "int"], div),
"*": makeEval(["int", "int"], mult),
"true": makeEval(2, tru),
"false": makeEval(2, fls),
"==": makeEval(2, eq),
"typeof": makeEval(1, type),
"pair": makeEval(2, pair),
"fst": makeEval(["pair"], fst),
"snd": makeEval(["pair"], snd)
}
export const addDefn = (name, sign, func) => {
builtinDefn[name] = makeEval(sign, func);
}
const makeLambda = (lambda) => {
return {nargs:lambda.args.length, defn:(scope, args) => {
let newscope = Object.create(scope); // I am so sorry...
for (let i = 0; i < lambda.args.length; i++) {
newscope[lambda.args[i]] = args[i];
}
return execRPN(newscope, lambda.body).stack;
}};
}
const makeObj = (elem) => {
if (elem.type === "func") {
return {type:"closure", args:[], func:makeLambda(elem)};
} else {
return elem;
}
}
const cloneElem = (elem) => {
if (elem.type === "closure") {
let argsClone = [];
for (let i = 0; i < elem.args.length; i++) {
argsClone.push(cloneElem(elem.args[i]));
}
return {type:"closure", args:argsClone, func:elem.func};
} else {
return elem;
}
}
const lookupScope = (name, scope) => {
let n = scope[name];
if (n) {
return cloneElem(n);
}
n = builtinDefn[name];
if (n) {
return {type:"closure", args:[], func:n};
} else {
throw "var " + n + " not in scope"
}
}
const giveArg = (closure, arg, scope) => {
closure.args.push(arg);
if (closure.args.length === closure.func.nargs) {
return closure.func.defn(scope, closure.args);
} else {
return [closure];
}
}
const apply = (elem, stack) => {
if (elem.type === "closure") {
if (stack.stack.length > 0) {
let out = giveArg(elem, stack.stack.pop(), stack.scope);
applyMany(out, stack);
} else {
stack.stack.push(elem);
}
} else if (elem.type === "ident") {
let id = lookupScope(elem.val, stack.scope);
apply(id, stack);
} else {
stack.stack.push(elem);
}
}
const applyMany = (outstack, stack) => {
for (let i = 0; i < outstack.length; i++) {
apply(outstack[i], stack);
}
}
const pushS = (elem, stack) => {
if (elem.type === "ident") {
let id = lookupScope(elem.val, stack.scope);
stack.stack.push(id);
} else {
stack.stack.push(elem);
}
}
const defn = (elem, name, stack) => {
stack.scope[name] = makeObj(elem);
}
export const doStep = (ins, stack) => {
let instruction = ins.shift();
if (instruction.type === "push") {
pushS(makeObj(instruction.elem), stack);
} else if (instruction.type === "defn") {
defn(instruction.defn, instruction.ident, stack);
} else {
apply(makeObj(instruction), stack);
}
}
export const execRPN = (scope, i) => {
let ins = JSON.parse(JSON.stringify(i));
let stack = {scope:scope, stack:[]};
while (ins.length > 0) {
doStep(ins, stack);
}
return stack;
}