slash-a-node
Version:
Slash/A interpreter or Node.js
844 lines (647 loc) • 24.2 kB
JavaScript
/**
* Module for a basic set of instructions for the Slash/A
* programming language.
*/
/**
* SETI:
* Stores an integer in the I register.
*
* @param integer - the integer to be stored in the I register
* @param {Array} inStream - array reference where you shift input values from
* @param {Array} outStream - array reference where output values are pushed
* @param {Object} runEnv - object reference where runtime state is kept
*/
function seti(integer, inStream, outStream, runEnv) {
runEnv.I = integer;
}
/**
* The "seti" instruction's id: 0. The "seti" instructions is handled
* differently than the other instructions. If a bytecode of O is found
* in a bytecode program, it is interpreted as the integer value to set
* the I register to, not as the id of the "seti" instruction.
*/
Object.defineProperty(seti, "id", { get: function() { return 0; }});
/**
* ITOF:
* Stores the value in the I register in the F register, keeps I
* as is.
*
* @param value - not used here
* @param {Array} inStream - array reference where you shift input values from
* @param {Array} outStream - array reference where output values are pushed
* @param {Object} runEnv - object reference where runtime state is kept
*/
function itof(value, inStream, outStream, runEnv) {
runEnv.F = runEnv.I;
}
// The "itof" instruction's bytecode id: -1
Object.defineProperty(itof, "id", { get: function() { return -1; }});
/**
* FTOI:
* Stores the value in the F register in the I register i.e. I = round(abs(F)).
*
* @param value - not used here
* @param {Array} inStream - array reference where you shift input values from
* @param {Array} outStream - array reference where output values are pushed
* @param {Object} runEnv - object reference where runtime state is kept
*/
function ftoi(value, inStream, outStream, runEnv) {
runEnv.I = Math.round(Math.abs(runEnv.F));
}
// The "ftoi" instruction's bytecode id: -2
Object.defineProperty(ftoi, "id", { get: function() { return -2; }});
/**
* INC:
* Increases the value in the F register with 1.
*
* @param value - not used here
* @param {Array} inStream - array reference where you shift input values from
* @param {Array} outStream - array reference where output values are pushed
* @param {Object} runEnv - object reference where runtime state is kept
*/
function inc(value, inStream, outStream, runEnv) {
runEnv.F++;
}
// The "inc" instruction's bytecode id: -3
Object.defineProperty(inc, "id", { get: function() { return -3; }});
/**
* DEC:
* Decreases the value in the F registe with 1.
*
* @param value - not used here
* @param {Array} inStream - array reference where you shift input values from
* @param {Array} outStream - array reference where output values are pushed
* @param {Object} runEnv - object reference where runtime state is kept
*/
function dec(value, inStream, outStream, runEnv) {
runEnv.F--;
}
// The "dec" instruction's bytecode id: -4
Object.defineProperty(dec, "id", { get: function() { return -4; }});
/**
* LOAD:
* Loads a value from the D array into the F register, using the integer in the
* I register as index i.e. F = D[I].
*
* @param value - not used here
* @param {Array} inStream - array reference where you shift input values from
* @param {Array} outStream - array reference where output values are pushed
* @param {Object} runEnv - object reference where runtime state is kept
*/
function load(value, inStream, outStream, runEnv) {
var I = runEnv.I;
var D = runEnv.D;
if (D[I] !== undefined) runEnv.F = D[I];
}
// The "load" instruction's bytecode id: -5
Object.defineProperty(load, "id", { get: function() { return -5; }});
/**
* SAVE:
* Saves the value in the F register in data array D using the integer in the
* I register as index i.e. D[I] = F.
*
* @param value - not used here
* @param {Array} inStream - array reference where you shift input values from
* @param {Array} outStream - array reference where output values are pushed
* @param {Object} runEnv - object reference where runtime state is kept
*/
function save(value, inStream, outStream, runEnv) {
var I = runEnv.I;
var F = runEnv.F;
var D = runEnv.D;
if (I != undefined && F != undefined) D[I] = F;
}
// The "save" instruction's bytecode id: -6
Object.defineProperty(save, "id", { get: function() { return -6; }});
/**
* SWAP:
* Swap the value in F with the value in the data array D using the
* integer in the I register as index i.e. F <-> D[I].
*
* @param value - not used here
* @param {Array} inStream - array reference where you shift input values from
* @param {Array} outStream - array reference where output values are pushed
* @param {Object} runEnv - object reference where runtime state is kept
*/
function swap(value, inStream, outStream, runEnv) {
var I = runEnv.I;
var F = runEnv.F;
var DI = runEnv.D[I];
if (I != undefined && F != undefined && DI !== undefined) {
runEnv.F = DI;
runEnv.D[I] = F;
}
}
// The "swap" instruction's bytecode id: -7
Object.defineProperty(swap, "id", { get: function() { return -7; }});
/**
* CMP:
* If F === D[I] then F = 0 else F = -1
*
* @param value - not used here
* @param {Array} inStream - array reference where you shift input values from
* @param {Array} outStream - array reference where output values are pushed
* @param {Object} runEnv - object reference where runtime state is kept
*/
function cmp(value, inStream, outStream, runEnv) {
runEnv.F = runEnv.F === runEnv.D[runEnv.I] ? 0 : -1;
}
// The "cmp" instruction's bytecode id: -8
Object.defineProperty(cmp, "id", { get: function() { return -8; }});
/**
* LABEL:
* Creates a label in a Labels-array, for the current code posistion + 1,
* using the integer in the I register as index.
*
* @param value - not used here
* @param {Array} inStream - array reference where you shift input values from
* @param {Array} outStream - array reference where output values are pushed
* @param {Object} runEnv - object reference where runtime state is kept
*/
function label(value, inStream, outStream, runEnv) {
if (runEnv.I != undefined) runEnv.labels[runEnv.I] = runEnv.counter;
}
// The "label" instruction's bytecode id: -9
Object.defineProperty(label, "id", { get: function() { return -9; }});
/**
* GOTOIFP:
* If F >= 0 then set the counter (i.e. code posistion) to Labels[I].
*
* @param value - not used here
* @param {Array} inStream - array reference where you shift input values from
* @param {Array} outStream - array reference where output values are pushed
* @param {Object} runEnv - object reference where runtime state is kept
*/
function gotoifp(value, inStream, outStream, runEnv) {
if (runEnv.F >= 0 && runEnv.I != undefined &&
runEnv.labels[runEnv.I] !== undefined) {
runEnv.counter = runEnv.labels[runEnv.I];
}
}
// The "gotoifp" instruction's bytecode id: -10
Object.defineProperty(gotoifp, "id", { get: function() { return -10; }});
/**
* JUMPIFN:
* If F < 0 jump to the instruction after the corresponding jumphere
* instruction. If there is no corresponding jumphere instruction,
* the instruction is ignored. The first time the function is called,
* for a certain code, a "jumptable" is constructed that links jumpifn
* instructions to jumphere instructions.
*
* @param value - not used here
* @param {Array} inStream - array reference where you shift input values from
* @param {Array} outStream - array reference where output values are pushed
* @param {Object} runEnv - object reference where runtime state is kept
*/
function jumpifn(value, inStream, outStream, runEnv) {
if (runEnv.F < 0) {
if (!runEnv.jumpTable) createJumpTable(runEnv);
if (runEnv.jumpTable[runEnv.counter] !== null) {
// Jump to the corresponding jumphere instruction
runEnv.counter = runEnv.jumpTable[runEnv.counter];
}
}
}
// The "jumpifn" instruction's bytecode id: -11
Object.defineProperty(jumpifn, "id", { get: function() { return -11; }});
/**
* Creates an array that links jumpifn instruction with corresponding
* jumphere instructions.
*
* @param {Object} runEnv - runtime state object
*/
function createJumpTable(runEnv) {
var code = runEnv.code;
var jumpStack = [];
/* Create an array to hold the loop and endloop addresses
* (i.e. index in the code for the instructions)
*/
var jumpTable = runEnv.jumpTable = [];
for (var i = 0 ; i < code.length ; i++) {
switch (code[i]) {
case jumpifn.id:
jumpStack.push(i);
jumpTable[i] = null;
break;
case jumphere.id:
if (jumpStack.length) {
jumpTable[jumpStack.pop()] = i;
}
break;
}
}
}
/**
* JUMPHERE:
* A type of instruction who's single purpose is to mark a point in
* the code that the interpreter should jump to if there is a
* corresponding jumpifn instruction.
*/
function jumphere() {}
// The "jumphere" instruction's bytecode id: -12
Object.defineProperty(jumphere, "id", { get: function() { return -12; }});
/**
* LOOP:
* Instruction that marks the start of a loop. The code between a loop
* instruction and a corresponding endloop instruction shall be be iterated
* i number of times, where i is the value in the I register at the time
* the loop instruction is processed. If there is no corresponding
* endloop instruction, the instruction is ignored.
*
* The first time the function is called, for a certain code,
* a "loop table" is constructed that links loop instructions
* to endloop instructions.
*
* @param value - not used here
* @param {Array} inStream - array reference where you shift input values from
* @param {Array} outStream - array reference where output values are pushed
* @param {Object} runEnv - object reference where runtime state is kept
*/
function loop(value, inStream, outStream, runEnv) {
// Is there a loop table?
if (!runEnv.loopTable) {
// No, create one
createLoopTable(runEnv);
}
// Is this a valid loop i.e. is there a corresponding endloop?
if (runEnv.loopTable[runEnv.counter] !== null) {
// I holds the number of times to execute the loop code
if (runEnv.I === 0) {
// Jump straight to the endloop instruction
runEnv.counter = runEnv.loopTable[runEnv.counter];
} else {
// Set the number of times the loop shall be executed
runEnv.loopCounter[runEnv.counter] = runEnv.I;
}
}
}
// The "loop" instruction's bytecode id: -13
Object.defineProperty(loop, "id", { get: function() { return -13; }});
/**
* Creates an array that links loop instruction with corresponding
* endloop instructions.
*
* @param runEnv - runtime state object
*/
function createLoopTable(runEnv) {
var loopAdr;
var code = runEnv.code;
var loopStack = [];
/* Create an array to hold the loop and endloop addresses
* (i.e. index in the code for the instructions)
*/
var loopTable = runEnv.loopTable = [];
/*
* Create an array to hold the number of times to loop.
* The index in the array corresponds to the address of
* the loop instruction (i.e. index in the code for the
* loop instruction)
*/
runEnv.loopCounter = [];
for (var i = 0 ; i < code.length ; i++) {
switch (code[i]) {
case loop.id:
loopStack.push(i);
loopTable[i] = null;
break;
case endloop.id:
if (loopStack.length) {
loopTable[loopAdr = loopStack.pop()] = i;
loopTable[i] = loopAdr;
} else {
loopTable[i] = null;
}
break;
}
}
}
/**
* ENDLOOP:
* Instruction that marks the end a loop. If there are iterations
* left of the loop, it sets the code position to the start of the
* loop.
*
* If there is no corresponding loop instruction or if there are no
* iterations left, nothing is done.
*
* @param value - not used here
* @param {Array} inStream - array reference where you shift input values from
* @param {Array} outStream - array reference where output values are pushed
* @param {Object} runEnv - object reference where runtime state is kept
*/
function endloop(value, inStream, outStream, runEnv) {
var loopStartAdr;
if (runEnv.loopTable) { // FIXME: Create a loop table?
// Is there a matching loop start?
if (runEnv.loopTable[runEnv.counter] !== null) {
loopStartAdr = runEnv.loopTable[runEnv.counter];
// Are there any iterations left?
if (runEnv.loopCounter[loopStartAdr] > 1) {
// Go to the loop start
runEnv.counter = loopStartAdr;
// Count down the number of iterations left
runEnv.loopCounter[loopStartAdr]--;
}
}
}
}
// The "endloop" instruction's bytecode id: -14
Object.defineProperty(endloop, "id", { get: function() { return -14; }});
/**
* INPUT:
* Shifts the first value from the input array and stores it in the
* F register.
*
* @param value - not used here
* @param {Array} inStream - array reference where you shift input values from
* @param {Array} outStream - array reference where output values are pushed
* @param {Object} runEnv - object reference where runtime state is kept
*/
function input(value, inStream, outStream, runEnv) {
var inData = inStream.shift();
if (inData !== undefined) {
if (typeof inData === 'number') {
runEnv.F = inData;
} else {
throw new Error('Values in the indata array must be numbers');
}
}
}
// The "input" instruction's bytecode id: -15
Object.defineProperty(input, "id", { get: function() { return -15; }});
/**
* OUTPUT:
* Pushes the value in the F register, if it isn't undefined, onto the
* outStream array.
*
* @param value - not used here
* @param {Array} inStream - array reference where you shift input values from
* @param {Array} outStream - array reference where output values are pushed
* @param {Object} runEnv - object reference where runtime state is kept
*/
function output(value, inStream, outStream, runEnv) {
if (runEnv.F != undefined) outStream.push(runEnv.F);
}
// The "output" instruction's bytecode id: -16
Object.defineProperty(output, "id", { get: function() { return -16; }});
/**
* ADD:
* Adds data from the D array to the value in the F register, using
* the value in the I register as index (F = F + D[I]).
*
* @param value - not used here
* @param {Array} inStream - array reference where you shift input values from
* @param {Array} outStream - array reference where output values are pushed
* @param {Object} runEnv - object reference where runtime state is kept
*/
function add(value, inStream, outStream, runEnv) {
var I = runEnv.I;
var F = runEnv.F;
var DI = runEnv.D[I];
if (I != undefined && F != undefined && DI !== undefined) {
runEnv.F += DI;
}
}
// The "add" instruction's bytecode id: -17
Object.defineProperty(add, "id", { get: function() { return -17; }});
/**
* SUB:
* Substracts, from F, a value in the data array D, using
* the value in the I register as index (F = F - D[I]).
*
* @param value - not used here
* @param {Array} inStream - array reference where you shift input values from
* @param {Array} outStream - array reference where output values are pushed
* @param {Object} runEnv - object reference where runtime state is kept
*/
function sub(value, inStream, outStream, runEnv) {
var I = runEnv.I;
var F = runEnv.F;
var DI = runEnv.D[I];
if (I != undefined && F != undefined && DI !== undefined) {
runEnv.F -= DI;
}
}
// The "sub" instruction's bytecode id: -18
Object.defineProperty(sub, "id", { get: function() { return -18; }});
/**
* MUL:
* Multiplies F with a value from the data array D, using
* the value in the I register as index (F = F * D[I]).
*
* @param value - not used here
* @param {Array} inStream - array reference where you shift input values from
* @param {Array} outStream - array reference where output values are pushed
* @param {Object} runEnv - object reference where runtime state is kept
*/
function mul(value, inStream, outStream, runEnv) {
var I = runEnv.I;
var F = runEnv.F;
var DI = runEnv.D[I];
if (I != undefined && F != undefined && DI !== undefined) {
runEnv.F *= DI;
}
}
// The "mul" instruction's bytecode id: -19
Object.defineProperty(mul, "id", { get: function() { return -19; }});
/**
* DIV:
* Divides F with a value from the data array D, using
* the value in the I register as index (F = F / D[I]).
*
* @param value - not used here
* @param {Array} inStream - array reference where you shift input values from
* @param {Array} outStream - array reference where output values are pushed
* @param {Object} runEnv - object reference where runtime state is kept
*/
function div(value, inStream, outStream, runEnv) {
var I = runEnv.I;
var F = runEnv.F;
var DI = runEnv.D[I];
if (I != undefined && F != undefined && DI !== undefined) {
runEnv.F /= DI;
}
}
// The "div" instruction's bytecode id: -20
Object.defineProperty(div, "id", { get: function() { return -20; }});
/**
* ABS:
* Calculates the abs value of the value in the F register (F = |F|).
*
* @param value - not used here
* @param {Array} inStream - array reference where you shift input values from
* @param {Array} outStream - array reference where output values are pushed
* @param {Object} runEnv - object reference where runtime state is kept
*/
function abs(value, inStream, outStream, runEnv) {
var F = runEnv.F;
if (F != undefined) runEnv.F = Math.abs(F);
}
// The "abs" instruction's bytecode id: -21
Object.defineProperty(abs, "id", { get: function() { return -21; }});
/**
* SIGN:
* Changes the sign of the value in the F register (F = -F).
*
* @param value - not used here
* @param {Array} inStream - array reference where you shift input values from
* @param {Array} outStream - array reference where output values are pushed
* @param {Object} runEnv - object reference where runtime state is kept
*/
function sign(value, inStream, outStream, runEnv) {
var F = runEnv.F;
if (F != undefined) runEnv.F = -F;
}
// The "sign" instruction's bytecode id: -22
Object.defineProperty(sign, "id", { get: function() { return -22; }});
/**
* EXP:
* Calculates F = e^F.
*
* @param value - not used here
* @param {Array} inStream - array reference where you shift input values from
* @param {Array} outStream - array reference where output values are pushed
* @param {Object} runEnv - object reference where runtime state is kept
*/
function exp(value, inStream, outStream, runEnv) {
var F = runEnv.F;
if (F != undefined) runEnv.F = Math.exp(F);
}
// The "exp" instruction's bytecode id: -23
Object.defineProperty(exp, "id", { get: function() { return -23; }});
/**
* LOG:
* Calculates F = log(F).
*
* @param value - not used here
* @param {Array} inStream - array reference where you shift input values from
* @param {Array} outStream - array reference where output values are pushed
* @param {Object} runEnv - object reference where runtime state is kept
*/
function log(value, inStream, outStream, runEnv) {
var F = runEnv.F;
if (F != undefined) runEnv.F = Math.log(F);
}
// The "log" instruction's bytecode id: -24
Object.defineProperty(log, "id", { get: function() { return -24; }});
/**
* SIN:
* Calculates F = sin(F).
*
* @param value - not used here
* @param {Array} inStream - array reference where you shift input values from
* @param {Array} outStream - array reference where output values are pushed
* @param {Object} runEnv - object reference where runtime state is kept
*/
function sin(value, inStream, outStream, runEnv) {
var F = runEnv.F;
if (F != undefined) runEnv.F = Math.sin(F);
}
// The "sin" instruction's bytecode id: -25
Object.defineProperty(sin, "id", { get: function() { return -25; }});
/**
* POW:
* Calculates F = F^D[I], where D is the data array and I is the
* value in the I register.
*
* @param value - not used here
* @param {Array} inStream - array reference where you shift input values from
* @param {Array} outStream - array reference where output values are pushed
* @param {Object} runEnv - object reference where runtime state is kept
*/
function pow(value, inStream, outStream, runEnv) {
var I = runEnv.I;
var F = runEnv.F;
var DI = runEnv.D[I];
if (I != undefined && F != undefined && DI !== undefined) {
runEnv.F = Math.pow(F, DI);
}
}
// The "pow" instruction's bytecode id: -26
Object.defineProperty(pow, "id", { get: function() { return -26; }});
/**
* RAN:
* Creates a random number between 0 and 1 (exclusive) and stores it
* the F register.
*
* @param value - not used here
* @param {Array} inStream - array reference where you shift input values from
* @param {Array} outStream - array reference where output values are pushed
* @param {Object} runEnv - object reference where runtime state is kept
*/
function ran(value, inStream, outStream, runEnv) {
runEnv.F = Math.random();
}
// The "ran" instruction's bytecode id: -27
Object.defineProperty(ran, "id", { get: function() { return -27; }});
/**
* NOP:
* Does nothing?!
*/
function nop() {
}
// The "nop" instruction's bytecode id: -28
Object.defineProperty(nop, "id", { get: function() { return -28; }});
/**
* What this module exports
*/
/**
* An object where the implementing functions can be accessed by
* the instructions' names.
*/
var instructions = {
seti: seti,
itof: itof,
ftoi: ftoi,
inc: inc,
dec: dec,
load: load,
save: save,
swap: swap,
cmp: cmp,
label: label,
gotoifp: gotoifp,
jumpifn: jumpifn,
jumphere: jumphere,
loop: loop,
endloop: endloop,
input: input,
output: output,
add: add,
sub: sub,
mul: mul,
div: div,
abs: abs,
sign: sign,
exp: exp,
log: log,
sin: sin,
pow: pow,
ran: ran,
nop: nop
};
exports.instructions = instructions;
/**
* Creates an array where you can access the implementing functions using
* the instructions' bytecode ids.
*
* @param {Object} instructions - implementing functions as an object/associative array
* @returns {Array} array where indexes maps to instructions' bytecode ids as: i = -id
*/
function createInstrArray(instructions) {
var instrArray = [];
var func;
for (var instr in instructions) {
if (instructions.hasOwnProperty(instr)) {
func = instructions[instr];
instrArray[-func.id] = func;
}
}
return instrArray;
}
exports.idToImpl = createInstrArray(instructions);
// Utils, to be able to test them
exports.utils = {
createJumpTable: createJumpTable,
createLoopTable: createLoopTable
};