/* Function
s 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 Function
s.
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 String
s 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 String
s 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]);