git @ Cat's Eye Technologies yoob.js / master src / yoob / tape.js
master

Tree @master (Download .tar.gz)

tape.js @masterraw · history · blame

/*
 * This file is part of yoob.js version 0.11
 * Available from https://github.com/catseye/yoob.js/
 * This file is in the public domain.  See http://unlicense.org/ for details.
 */
if (window.yoob === undefined) yoob = {};

/*
 * A (theoretically) unbounded tape, like you'd find on a Turing machine.
 *
 * It can also be used as a stack -- in this case, give it a single cursor
 * starting at x=0.  Note that the result of trying to use it both as a stack
 * and as a tape is currently undefined.
 *
 * TODO: recalculate bounds?
 */
yoob.Tape = function() {
    this.init = function(cfg) {
        cfg = cfg || {};
        this._default = cfg.defaultValue;
        this.cursors = cfg.cursors || [];
        this.clear();
        return this;
    };

    /*
     * Removes all values that have been written to the tape.
     */
    this.clear = function() {
        this._store = {};
        this.min = undefined;
        this.max = undefined;
        return this;
    };

    /*
     * Obtain the value at the given position.
     * Returns the tape's default value, if the position was never written to.
     */
    this.get = function(pos) {
        var val = this._store[pos];
        return val === undefined ? this._default : val;
    };

    /*
     * Write a new value into the given position.
     */
    this.put = function(pos, value) {
        if (value === this._default) {
            delete this._store[pos];
            // NOTE: this does not recalculate the bounds.
            return;
        }
        if (this.min === undefined || pos < this.min) this.min = pos;
        if (this.max === undefined || pos > this.max) this.max = pos;
        this._store[pos] = value;
    };

    this.size = function() {
        return this._top;
    };

    /*
     * Iterate over every cell on the Tape.
     * fun is a callback which takes two parameters:
     * position and value.  If this callback returns a value,
     * it is written into the Tape at that position.
     * This function iterates in a defined order: ascending.
     * The callback is not called for cells containing the
     * default value unless `dense: true` is given in the
     * configuration object.
     */
    this.foreach = function(fun, cfg) {
        cfg = cfg || {};
        var dense = !!cfg.dense;
        for (var pos = this.min; pos <= this.max; pos++) {
            var value = this._store[pos];
            if (value === undefined) {
                if (dense) {
                    value = this._default;
                } else {
                    continue;
                }
            }
            var result = fun(pos, value);
            if (result !== undefined) {
                this.put(pos, result);
            }
        }
    };

    this.getExtent = function() {
        return this.max - this.min + 1;
    };

    this.getCursoredExtent = function() {
        var max_ = this.max;
        var min_ = this.min;
        var i;
        for (i = 0; i < this.cursors.length; i++) {
            var x = this.cursors[i].getX();
            if (x > max_) max_ = x;
            if (x < min_) min_ = x;
        }
        return max_ - min_ + 1;
    };

    /*
     * Cursored read/write interface
     */
    this.read = function(index) {
        var cursor = this.cursors[index || 0];
        return this.get(cursor.getX());
    };

    this.write = function(value, index) {
        var cursor = this.cursors[index || 0];
        this.put(cursor.getX(), value);
        return this;
    };

    /*
     * Cursored stack interface.
     */
    this.push = function(value) {
        var cursor = this.cursors[0];
        this.put(cursor.getX(), value);  // updates bounds
        cursor.moveRight();
        return this;
    };

    this.pop = function() {
        var cursor = this.cursors[0];
        var x = cursor.getX();
        if (x === 0) {
            return undefined;  // stack underflow
        }
        x--;
        cursor.setX(x);
        value = this.get(x);
        this.put(x, this._default);
        if (x === this.max) {  // which it really should be.  recalculate bounds
            this.max--;
            if (this.max < this.min) {
                this.max = undefined;
                this.min = undefined;
            }
        }
        return value;
    };

    this.peek = function() {
        var cursor = this.cursors[0];
        var x = cursor.getX();
        if (x === 0) {
            return undefined;  // empty stack
        }
        return this.get(x - 1);
    };

    this.getSize = function() {
        return this.max === undefined ? 0 : this.max + 1;
    };

};