Instantly share code, notes, and snippets.

Embed
What would you like to do?
const R = require("ramda");
//state operations
const updStack = R.curry((prop, updater, state) => R.over(R.lensProp(prop), updater, state));
const updCall = (stackOp) => updStack("callStack", stackOp);
const updData = (stackOp) => updStack("dataStack", stackOp);
//stack operations
const pop = R.curry((num, stack) => {
if (stack.length < num) throw new Error("Stack underflow!");
return R.dropLast(num, stack);
});
const peek = R.curry((num, stack) => {
if (stack.length < num) throw new Error("Stack underflow!");
return R.takeLast(num, stack);
});
const push = R.curry((args, stack) => R.concat(stack, args));
const popPush = R.curry((numArgs, reducer, stack) => push(
reducer(peek(numArgs, stack)),
pop(numArgs, stack)
));
const advance = updCall(popPush(1, ([a]) => [a + 1]));
const popPushAdv = (numArgs, reducer) => R.pipe(
updData(popPush(numArgs, reducer)),
advance
);
const pushAdv = (vals) => R.pipe(
updData(push(vals)),
advance
);
const call = (ptr) => R.pipe(
advance,
updCall(push([ptr]))
);
// opcode: (opArgs) -> (state) -> state'
const operations = {
noop: () => advance,
add: () => popPushAdv(2, ([x, y]) => [x + y]),
sub: () => popPushAdv(2, ([x, y]) => [x - y]),
mul: () => popPushAdv(2, ([x, y]) => [x * y]),
div: () => popPushAdv(2, ([x, y]) => [x / y]),
or: () => popPushAdv(2, ([x, y]) => [x || y ? 1 : 0]),
and: () => popPushAdv(2, ([x, y]) => [x && y ? 1 : 0]),
xor: () => popPushAdv(2, ([x, y]) => [!!x ^ !!y]),
not: () => popPushAdv(1, ([x]) => [x ? 0 : 1]),
// TODO: comparisons, conditions
// max: () => popPushAdv(2, ([x, y]) => [Math.max(x, y)]),
// min: () => popPushAdv(2, ([x, y]) => [Math.min(x, y)]),
dup: () => popPushAdv(1, ([x]) => [x, x]),
swap: () => popPushAdv(2, ([x, y]) => [y, x]),
push: (args) => pushAdv(args),
call: ([ptr]) => call(ptr),
ret: () => updCall(pop(1))
};
const isIgnoredLine = (line) => R.startsWith("#", line) || R.isEmpty(line);
const isLabel = (line) => line.includes(":");
const parseLabel = (line, currentPc) => {
const [label, givenVal] = line.split(":");
return {label, val: givenVal == "" ? currentPc : Number.parseFloat(givenVal)};
};
const parseOpArgs = (opArgs, labels) => opArgs.map((opArg) =>
R.startsWith("$", opArg) ? labels[opArg.substring(1)] : Number.parseFloat(opArg)
);
const parseProgram = (programSrc) => {
const instructions = [], labels = {};
for (let line of programSrc.split("\n").map(R.trim)) {
if (isIgnoredLine(line)) {
continue;
} else if (isLabel(line)) {
const {label, val} = parseLabel(line, instructions.length);
labels[label] = val;
} else {
const [opcode, ...opArgs] = line.split(" ");
instructions.push({opcode, opArgs: parseOpArgs(opArgs, labels)});
}
}
return {instructions, mainAddr: labels["main"]};
};
const runProgram = (initialStack, programSrc) => {
const {instructions, mainAddr} = parseProgram(programSrc);
let state = {dataStack: initialStack, callStack: [mainAddr]};
while (true) {
let pc = R.takeLast(1, state.callStack)[0];
if (pc >= instructions.length) break;
const {opcode, opArgs} = instructions[pc];
if (!operations[opcode]) throw new Error(`Unknown opcode: ${opcode}`);
console.log(`${opcode} ${opArgs} ${JSON.stringify(state)}`);
state = operations[opcode](opArgs)(state);
}
return state.dataStack;
};
const resultStack = runProgram([], `
initial:3
increment:
push 1
add
ret
main:
push $initial
call $increment
`);
console.log(resultStack);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment