Source: stack.js

function MemoryError(message) {
    this.name = MemoryError.exceptionName();
    this.message = message;
}
MemoryError.exceptionName = function() {
    return 'Stack Error';
}
MemoryError.prototype.toString = function() {
    return '{0}: {1}'.format(MemoryError.exceptionName(), this.message);
}

/**
 * @class MemoryBase
 * @param {Object} options constructor options
 * - options.onChange A function to be called when a stack address changes.
 * - options.baseAddress The default base address.
 * 
 * Subclasses must implement
 * - getMinValidAddress()
 * - getMaxValidAddress()
 */
function MemoryBase(options) {
    var BITS_PER_REGISTER = 32; // TODO: get this from somewhere else?
    var MIN_ADDRESS = MIPS.minUnsignedValue(BITS_PER_REGISTER);
    var MAX_ADDRESS = MIPS.maxUnsignedValue(BITS_PER_REGISTER - 1); // TODO: shouldn't need minus 1

    // Returns a random integer between min and max
    function getRandomInt(min, max) {
      return Math.floor(Math.random() * (max - min + 1) + min);
    }

    options = options || {};
    _.defaults(options, {
        /**
         * Function to call when memory contents change. (e.g. onChange(indexNumber))
         * @member MemoryBase
         * @type {Function}
         */
        onChange: null,
        /**
         * Function to call when the memory gets extended. (e.g. onAdd(indexNumber))
         * @member MemoryBase
         * @type {Function}
         */
        onAdd: null,
        baseAddress: getRandomInt(12500, MAX_ADDRESS/4)*4
    });
    assert(MIN_ADDRESS <= options.baseAddress && options.baseAddress <= MAX_ADDRESS, "Memory addresses must be able to be stored in a register, thus they cannot exceed the register bounds.");

    // Private memebers

    var that = this; // see http://javascript.crockford.com/private.html

    // The stack will be implemented as a single array where each
    // element will store a single byte (e.g. a number between 0 and 255).
    var data = [];

    function extendToIndex(index) {
        // ensure this index is accessible
        var numElementsToAdd = index - data.length + 1;
        for (var i = 0; i < numElementsToAdd; i++) {
            var randomNumFrom0to255 = Math.floor((Math.random()*256));
            data.push(randomNumFrom0to255);
            if (options.onAdd) options.onAdd(data.length - 1);
        }
    }
    function isValidAddress(address) {
        return (that.getMinValidAddress() <= address && address <= that.getMaxValidAddress());
    }
    this.getBaseAddress = function() {
        return options.baseAddress;
    }
    this.getByteAtAddress = function(address) {
        assert(typeof address == "number");
        if (!isValidAddress(address)) {
            throw new MemoryError('Invalid memory address ({0}). Valid memory addresses are between {1} and {2}.'.format(address, this.getMinValidAddress(), this.getMaxValidAddress()));
        }
        let index = this.indexForAddress(address);
        extendToIndex(index);
        return data[index];
    }
    this.setByteAtAddress = function(address, byte) {
        assert(typeof address == "number");
        if (!isValidAddress(address)) {
            throw new MemoryError('Invalid memory address ({0}). Valid memory addresses are between {1} and {2}.'.format(address, this.getMinValidAddress(), this.getMaxValidAddress()));
        }
        assert(typeof byte === "number");
        assert(0 <= byte && byte <= 255);
        let index = this.indexForAddress(address);
        extendToIndex(index);
        data[index] = byte;
        if (options.onChange) options.onChange(address, byte);
    }
    /**
     * Clear the data contained in the memory object
     * @return {null}
     */
    this.reset = function() {
        data = [];
    };
}

function Stack(options) {
    MemoryBase.call(this, options);
}
Object.setPrototypeOf(Stack.prototype, MemoryBase.prototype);
/**
 * Get a pointer to the address at the bottom of the stack
 * @return {Number} Address to the bottom of the stack
 */
Stack.prototype.pointerToBottomOfStack = function() {
    // the initial value of the stack pointer. before you read or write to it, you must decrement the stack pointer.
    return this.getBaseAddress();
}
Stack.prototype.indexForAddress = function (address) {
    return this.getMaxValidAddress() - address;
}
Stack.prototype.getMinValidAddress = function() {
    return 0;
}
Stack.prototype.getMaxValidAddress = function() {
    return this.getBaseAddress()-1;
}

function Heap(options) {
    options = options || {}
    _.defaults(options, {
        initialSize: 0,
        onAdjustSize: null
    })
    MemoryBase.call(this, options);
    this.size = options.initialSize;

    this.adjustSize = function(adjustAmount) {
        var oldEnd = this.getMaxValidAddress();
        assert (this.size + adjustAmount >= 0);
        this.size = this.size + adjustAmount;
        if (options.onAdjustSize) options.onAdjustSize(this.size);
        return oldEnd;
    }
}
Object.setPrototypeOf(Heap.prototype, MemoryBase.prototype);
Heap.prototype.indexForAddress = function (address) {
    return address - this.getBaseAddress();
}
Heap.prototype.getMinValidAddress = function() {
    return this.getBaseAddress();
}
Heap.prototype.getMaxValidAddress = function() {
    return this.getBaseAddress() + this.size-1;
}

/**
 * @class CombinedMemory
 * 
 * A memory entity that is constructed from the combination of a set of memories.
 * Each access is routed to the first memory that contains the address between its
 * min and maximum addresses.
 * 
 * @param {Array} memories 
 */
function CombinedMemory(memories) {
    let minAddress = memories.map(m => m.getMinValidAddress()).reduce((m1, m2) => Math.min(m1, m2));
    let maxAddress = memories.map(m => m.getMaxValidAddress()).reduce((m1, m2) => Math.max(m1, m2));

    this.getMinValidAddress = function() {
        return minAddress;
    }
    this.getMaxValidAddress = function() {
        return maxAddress;
    }
    function getAccessedMemory(address) {
        let accessedMemory =  memories.find(mem => (mem.getMinValidAddress() <= address && address <= mem.getMaxValidAddress()));
        if (typeof accessedMemory == 'undefined') {
            throw new MemoryError("Invalid Memory Address {0}: No memory associated with the given address".format(address));
        }
        return accessedMemory;
    }
    this.getByteAtAddress = function(address) {
        return getAccessedMemory(address).getByteAtAddress(address);
    }
    this.setByteAtAddress = function(address, value) {
        return getAccessedMemory(address).setByteAtAddress(address, value);
    }
    this.reset = function() {
        for (memory of memories) {
            memory.reset();
        }
    }
};

/**
 * @class BigEndianAccess
 * Provides word and half-word access in big-endian-order to a byte-based memory delegate
 * 
 * @param {Object} delegate   The delegate to access
 */
function BigEndianAccess(delegate) {
    // Public variables

    this.MIN_BYTE_VALUE = 0;
    this.MAX_BYTE_VALUE = 255;
    this.BITS_PER_BYTE = 8;
    this.BYTES_PER_BYTE = 1;
    this.BYTES_PER_HALFWORD = 2;
    this.BYTES_PER_WORD = 4;

    this.reset = delegate.reset;
    this.pointerToBottomOfStack = delegate.pointerToBottomOfStack;
    this.getMinValidAddress = delegate.getMinValidAddress;
    this.getMaxValidAddress = delegate.getMaxValidAddress;
    this.getBaseAddress = delegate.getBaseAddress;
    this.getByteAtAddress = delegate.getByteAtAddress;
    this.setByteAtAddress = delegate.setByteAtAddress;
    this.indexForAddress = delegate.indexForAddress;
}

// Public functions
/**
 * Get a given number of bytes at an address
 * @param  {Number} address    The starting address for your data
 * @param  {Number} byteCount  How many bytes of data you want returned
 * @param  {Boolean} asUnsigned True if you want the number returned without a sign extension.
 * @return {Number}
 */
BigEndianAccess.prototype.getDataAtAddress = function (address, byteCount, asUnsigned) {
    asUnsigned = asUnsigned || false;
    var result = 0;
    for (var i = 0; i < byteCount; i++) {
        var value = this.getByteAtAddress(address + i);
        assert(this.MIN_BYTE_VALUE <= value && value <= this.MAX_BYTE_VALUE);
        result = result << this.BITS_PER_BYTE;
        result += value;
    };
    if (asUnsigned) {
        return result;
    } else {
        return MIPS.unsignedNumberToSignedNumber(result, byteCount * this.BITS_PER_BYTE);
    }
};
/**
 * Set data at an address
 * @param {Number} address   The address you want to write
 * @param {Number} byteCount How many bytes you want to overwrite
 * @param {Number} data      Number you want to set there.
 */
BigEndianAccess.prototype.setDataAtAddress = function(address, byteCount, data) {
    assert(typeof data === "number", "Only numbers supported.");
    var bitCount = byteCount * this.BITS_PER_BYTE;
    assert(0 <= bitCount && bitCount <= 32, "The & operator will only work for up to 32 bits.");

    /* We check the range as allowed by the given number of bits in either
        * unsigned or (twos complement) signed representation.
        */
    var minValidValue = MIPS.minSignedValue(bitCount);
    var maxValidValue = MIPS.maxUnsignedValue(bitCount);
    if (data < minValidValue || maxValidValue < data) {
        throw new MemoryError('Unable to store out-of-range value ({0}). Valid values are {1} through {2}.'.format(data, minValidValue, maxValidValue));
    };

    /* Ensure that the actual data is unsigned so we can split it up into its bytes.
        * It will then be in the valid range of bitCount bits unsigned integer due to the
        * check above. */
    if (data < 0) {
        // convert negative value to positive one
        try {
            data = MIPS.signedNumberToUnsignedNumber(data, bitCount);
        } catch (e) {
            if (e instanceof MipsError) {
                throw new MemoryError(e.message);
            } else {
                throw e;
            }
        }
    }

    /* Split it up into the respective bytes */
    for (var i = byteCount - 1; i >= 0; i--) {
        var rightMostByte = data & this.MAX_BYTE_VALUE;
        this.setByteAtAddress(address + i, rightMostByte);
        data = data >>> this.BITS_PER_BYTE;
    };
};

/**
 * Get a byte at a specified address
 * @param  {Number} pointer The address the wanted data lives
 * @return {Number} Requested data
 */
BigEndianAccess.prototype.getByte = function (pointer) {
    return this.getDataAtAddress(pointer, this.BYTES_PER_BYTE);
};
/**
 * Get an unsigned byte at the specified address
 * @param  {Number} pointer Address where you want data from.
 * @return {Number} Data at address.
 */
BigEndianAccess.prototype.getUnsignedByte = function (pointer) {
    return this.getDataAtAddress(pointer, this.BYTES_PER_BYTE, true);
};
/**
 * Get one half word (16 bits if word is 32) at a given address
 * @param  {Number} pointer Address where data lives
 * @return {Number} Retrieved data
 */
BigEndianAccess.prototype.getHalfword = function (pointer) {
    return this.getDataAtAddress(pointer, this.BYTES_PER_HALFWORD);
};
/**
 * Get unsigned half word (16 bits if word is 32) at a given address
 * @param  {Number} pointer Address where data lives
 * @return {Number} Retrieved data
 */
BigEndianAccess.prototype.getUnsignedHalfword = function (pointer) {
    return this.getDataAtAddress(pointer, this.BYTES_PER_HALFWORD, true);
};
/**
 * Get word at a given address
 * @param  {Number} pointer Address where data lives
 * @return {Number} Retrieved data
 */
BigEndianAccess.prototype.getWord = function (pointer) {
    return this.getDataAtAddress(pointer, this.BYTES_PER_WORD);
};
/**
 * Get unsigned word at a given address
 * @param  {Number} pointer Address where data lives
 * @return {Number} Retrieved data
 */
BigEndianAccess.prototype.getUnsignedWord = function (pointer) {
    return this.getDataAtAddress(pointer, this.BYTES_PER_WORD, true);
};
/**
 * Set a byte at given address
 * @param {Number} pointer The address with data you want to set
 * @param {Number} data The data you want to write to the address
 */
BigEndianAccess.prototype.setByte = function (pointer, data) {
    this.setDataAtAddress(pointer, this.BYTES_PER_BYTE, data);
};

/**
 * Set half word at given address
 * @param {Number} pointer Address to overwrite with half word of data
 * @param {Number} data Data to write to address
 */
BigEndianAccess.prototype.setHalfword = function (pointer, data) {
    this.setDataAtAddress(pointer, this.BYTES_PER_HALFWORD, data);
};

/**
 * Set word at given address
 * @param {Number} pointer where data will be written
 * @param {Number} data Data to be written to the address
 */
BigEndianAccess.prototype.setWord = function (pointer, data) {
    this.setDataAtAddress(pointer, this.BYTES_PER_WORD, data);
};