/* Functions in JavaScript have a length property, measuring the number of named formal parameters. This means that a stack-based language can tell how many items to pop off the stack to pass to the function.

Here is a very simple stack language, implemented in quasi-literate JavaScript.

(That is, you can copy this post out of your browser into a .js file and run it in Node.) */

var interpreter = {

/* The “Standard Library” is implemented as a dictionary of plain JavaScript functions. */

    '+': function(a, b) { return [a + b]; },
    '-': function(a, b) { return [a - b]; },
    'dup': function(a) { return [a, a]; },
    'swap': function(a, b) { return [b, a]; },
    /* Must be wrapped. `console.log.length` === 0 */
    '.': function(i) { console.log(i); },

/* run is the entry point to the interpreter. It resets the inputBuffer and stack fields. */

    'run': function(program) {
        this.inputBuffer = program;
        this.stack = [];

        while (this.inputBuffer) {
            // read one word from inputBuffer & interpret it
            this.interpret(this.word());
        }
    },

/* word removes one word from the inputBuffer and returns it. */

    'word': function(/* operates on inputBuffer, not stack */) {
        var word = '';
        while (this.inputBuffer) {
            var ch = this.inputBuffer.charAt(0);
            this.inputBuffer = this.inputBuffer.substr(1);
            if (/^\s?$/.test(ch)) {
                if (word) {
                    break;
                }
            } else {
                word += ch;
            }
        }
        return word;
    },

/* interpret decides how to handle each word.

  • If the word is in the dictionary, execute it.
  • If it is a number, push the number onto the stack.
  • Otherwise throw an exception.

*/

    'interpret': function(word) {
        if (!word) return;
        if (word in this) {
            // it's defined, execute it.
            this.execute(this[word]);
        } else if (isFinite(word)) {
            // it's a number
            this.stack.push(parseFloat(word));
        } else throw new Error('unknown word `' + word + '`');
    },

/* execute provides the interoperability with JavaScript Functions. It provides the top fn.length items from the stack as arguments to the function.

If fn returns a value, the value is concatenated back on top of the stack.

Multiple values can be returned in an array; Array.prototype.concat will correctly concatenate all of the values to this.stack in the intended order. */

    'execute': function(fn) {
        var len = fn.length;
        // pop the top `len` items off the stack, in order
        var applies = this.stack.splice(-len, len);
        // apply the items to the Function
        var results = fn.apply(this, applies);
        if (results) {
            // put any results back onto the stack
            this.stack = this.stack.concat(results);
        }
    },
};

/* Unit tests to demonstrate usage */

var assert = require('assert');

/* An empty program leaves an empty stack. */

interpreter.run('');
assert.deepEqual(interpreter.stack, []);

/* The standard library: dup, -, +, swap */

interpreter.run('3 dup');
assert.deepEqual(interpreter.stack, [3, 3]);
interpreter.run("2 6 -");
assert.deepEqual(interpreter.stack, [-4]);
interpreter.run('5 8 +');
assert.deepEqual(interpreter.stack, [13]);
interpreter.run('10 20');
assert.deepEqual(interpreter.stack, [10, 20]);
interpreter.run('10 20 swap');
assert.deepEqual(interpreter.stack, [20, 10]);

/* We can reflect on just about any part of the execution of the interpreter. */

interpreter.run('12 23');
assert.deepEqual(interpreter.stack, [12, 23]);
// continues...

/* For example, calling interpret without run or word can be used to single-step: */

// ...continued
interpreter.interpret('swap');
assert.deepEqual(interpreter.stack, [23, 12]);
// continues...

/* We can execute a function that isn’t even in the dictionary: */

// ...continued
interpreter.execute(function(a, b) {
    assert.deepEqual([a, b], [23, 12]);
    return ['whoa', 'nelly'];
});
assert.deepEqual(interpreter.stack, ['whoa', 'nelly']);

/* Interpreting the word 'execute' pops a function off the stack */

interpreter.stack = [function() { return ['WOW'] }];
interpreter.interpret('execute');
assert.deepEqual(interpreter.stack, ['WOW']);

/* Use the word tokenizer function to put single-word Strings on the stack */

interpreter.run('word foo');
assert.deepEqual(interpreter.stack, ['foo']);

/* The preexisting + operator can work with this new String data type */

interpreter.run('word Hello, word World! +');
assert.deepEqual(interpreter.stack, ['Hello,World!']);

/* And interpret can be called on Strings on the stack */

interpreter.run('5 5 word + interpret');
assert.deepEqual(interpreter.stack, [10]);

/* Even if the String is a result of some other computation! */

interpreter.run('8 9 word sw word ap + interpret');
assert.deepEqual(interpreter.stack, [9, 8]);