2020-05-21 12:56:36 +00:00
|
|
|
/*
|
|
|
|
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:{}}}
|
|
|
|
|
2020-05-21 14:19:31 +00:00
|
|
|
[{type:"int", val:1},{type:"int", val:1},{type:"builtin", op:"+"}]
|
|
|
|
|
2020-05-21 15:31:45 +00:00
|
|
|
[{type:"int", val:1},{type:"int", val:1},{type:"func", args:["a"], body:[{type:"ident", val:"a"},{type:"int", val:1},{type:"builtin", op:"+"}]}]
|
|
|
|
|
2020-05-21 12:56:36 +00:00
|
|
|
exported functions:
|
2020-05-21 14:19:31 +00:00
|
|
|
addDefn - adds a builtin function
|
2020-05-21 12:56:36 +00:00
|
|
|
doStep - consumes instruction stream, stack, outputs stack
|
|
|
|
execRPN - consumes scope, instruction stream, outputs stack
|
|
|
|
*/
|
|
|
|
|
2020-05-21 14:19:31 +00:00
|
|
|
const makeEval = (sign, defnarg) => {
|
2020-05-21 12:56:36 +00:00
|
|
|
if (typeof sign === "number") {
|
2020-05-21 14:19:31 +00:00
|
|
|
return {nargs:sign, defn:defnarg};
|
2020-05-21 12:56:36 +00:00
|
|
|
} else {
|
|
|
|
return {nargs:sign.length, defn:(scope, args) => {
|
|
|
|
let stripped = [];
|
|
|
|
for (let i = 0; i < sign.length; i++) {
|
2020-05-21 14:19:31 +00:00
|
|
|
if (args[i].type === sign[i]) {
|
2020-05-21 12:56:36 +00:00
|
|
|
stripped.push(args[i].val);
|
|
|
|
} else {
|
|
|
|
throw 'typeerror'
|
|
|
|
}
|
|
|
|
}
|
2020-05-21 14:19:31 +00:00
|
|
|
return defnarg(scope, stripped);
|
2020-05-21 12:56:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-30 21:39:45 +00:00
|
|
|
export const makeFn = (sign, defnarg) => {
|
2020-05-30 20:20:04 +00:00
|
|
|
return {type:"closure", args:[], func:makeEval(sign, defnarg)};
|
2020-05-21 12:56:36 +00:00
|
|
|
}
|
|
|
|
|
2020-05-30 20:20:04 +00:00
|
|
|
let builtinDefn = {};
|
2020-05-21 12:56:36 +00:00
|
|
|
|
2020-05-30 19:05:05 +00:00
|
|
|
export const addDefn = (name, sign, func) => {
|
2020-05-30 21:39:45 +00:00
|
|
|
builtinDefn[name] = makeFn(sign, func);
|
2020-05-21 14:19:31 +00:00
|
|
|
}
|
|
|
|
|
2020-05-30 19:51:47 +00:00
|
|
|
export const addRPNASTDefn = (name, ast) => {
|
2020-05-30 20:20:04 +00:00
|
|
|
builtinDefn[name] = makeObj(ast);
|
2020-05-30 19:51:47 +00:00
|
|
|
}
|
|
|
|
|
2020-05-21 15:31:45 +00:00
|
|
|
const makeLambda = (lambda) => {
|
|
|
|
return {nargs:lambda.args.length, defn:(scope, args) => {
|
2020-05-30 21:39:45 +00:00
|
|
|
let newscope = Object.create(scope);
|
2020-05-21 15:31:45 +00:00
|
|
|
for (let i = 0; i < lambda.args.length; i++) {
|
2020-05-30 21:39:45 +00:00
|
|
|
newscope[lambda.args[i]] = args[args.length-1-i];
|
2020-05-21 15:31:45 +00:00
|
|
|
}
|
|
|
|
return execRPN(newscope, lambda.body).stack;
|
|
|
|
}};
|
2020-05-21 12:56:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const makeObj = (elem) => {
|
2020-05-30 19:05:05 +00:00
|
|
|
if (elem.type === "func") {
|
2020-05-21 12:56:36 +00:00
|
|
|
return {type:"closure", args:[], func:makeLambda(elem)};
|
|
|
|
} else {
|
|
|
|
return elem;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-30 19:05:05 +00:00
|
|
|
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) {
|
2020-05-30 20:20:04 +00:00
|
|
|
return cloneElem(n);
|
2020-05-30 19:05:05 +00:00
|
|
|
} else {
|
2020-05-30 22:49:46 +00:00
|
|
|
throw 'var "' + name + '" not in scope'
|
2020-05-30 19:05:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-21 12:56:36 +00:00
|
|
|
const giveArg = (closure, arg, scope) => {
|
|
|
|
closure.args.push(arg);
|
|
|
|
if (closure.args.length === closure.func.nargs) {
|
2020-05-21 15:31:45 +00:00
|
|
|
return closure.func.defn(scope, closure.args);
|
2020-05-21 12:56:36 +00:00
|
|
|
} else {
|
|
|
|
return [closure];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const apply = (elem, stack) => {
|
|
|
|
if (elem.type === "closure") {
|
2020-05-30 19:51:47 +00:00
|
|
|
if (elem.func.nargs === 0) {
|
|
|
|
applyMany(elem.func.defn(stack.scope, []), stack);
|
|
|
|
} else if (stack.stack.length > 0) {
|
2020-05-30 19:05:05 +00:00
|
|
|
let out = giveArg(elem, stack.stack.pop(), stack.scope);
|
|
|
|
applyMany(out, stack);
|
|
|
|
} else {
|
|
|
|
stack.stack.push(elem);
|
|
|
|
}
|
2020-05-21 14:19:31 +00:00
|
|
|
} else if (elem.type === "ident") {
|
2020-05-30 19:05:05 +00:00
|
|
|
let id = lookupScope(elem.val, stack.scope);
|
2020-05-21 14:19:31 +00:00
|
|
|
apply(id, stack);
|
2020-05-21 12:56:36 +00:00
|
|
|
} else {
|
2020-05-21 14:19:31 +00:00
|
|
|
stack.stack.push(elem);
|
2020-05-21 12:56:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const applyMany = (outstack, stack) => {
|
|
|
|
for (let i = 0; i < outstack.length; i++) {
|
|
|
|
apply(outstack[i], stack);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-21 14:19:31 +00:00
|
|
|
const pushS = (elem, stack) => {
|
|
|
|
if (elem.type === "ident") {
|
2020-05-30 19:05:05 +00:00
|
|
|
let id = lookupScope(elem.val, stack.scope);
|
2020-05-21 14:19:31 +00:00
|
|
|
stack.stack.push(id);
|
|
|
|
} else {
|
|
|
|
stack.stack.push(elem);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-21 15:31:45 +00:00
|
|
|
const defn = (elem, name, stack) => {
|
|
|
|
stack.scope[name] = makeObj(elem);
|
|
|
|
}
|
|
|
|
|
2020-05-30 20:20:04 +00:00
|
|
|
const doStep = (ins, stack) => {
|
2020-05-21 14:19:31 +00:00
|
|
|
let instruction = ins.shift();
|
|
|
|
if (instruction.type === "push") {
|
|
|
|
pushS(makeObj(instruction.elem), stack);
|
2020-05-21 15:31:45 +00:00
|
|
|
} else if (instruction.type === "defn") {
|
2020-05-21 20:36:04 +00:00
|
|
|
defn(instruction.defn, instruction.ident, stack);
|
2020-05-21 14:19:31 +00:00
|
|
|
} else {
|
|
|
|
apply(makeObj(instruction), stack);
|
|
|
|
}
|
2020-05-21 12:56:36 +00:00
|
|
|
}
|
|
|
|
|
2020-05-30 22:49:46 +00:00
|
|
|
const showIns = (curr) => {
|
|
|
|
if (curr.type === "ident") {
|
|
|
|
return curr.val;
|
|
|
|
} else if (curr.type === "push") {
|
|
|
|
return "'" + showIns(curr.elem)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-30 19:05:05 +00:00
|
|
|
export const execRPN = (scope, i) => {
|
|
|
|
let ins = JSON.parse(JSON.stringify(i));
|
2020-05-21 12:56:36 +00:00
|
|
|
let stack = {scope:scope, stack:[]};
|
|
|
|
while (ins.length > 0) {
|
2020-05-30 22:49:46 +00:00
|
|
|
let curr = ins[0];
|
|
|
|
try {
|
|
|
|
doStep(ins, stack);
|
|
|
|
} catch (error) {
|
|
|
|
throw error + ' while executing "' + showIns(curr) + '"'
|
|
|
|
}
|
2020-05-21 12:56:36 +00:00
|
|
|
}
|
2020-05-21 14:19:31 +00:00
|
|
|
return stack;
|
2020-05-21 12:56:36 +00:00
|
|
|
}
|