js-forth
Version:
An implementation of [Forth](https://en.wikipedia.org/wiki/Forth_(programming_language)) in Javascript
1,646 lines (1,387 loc) • 2.03 MB
JavaScript
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
(function (global){
var Forth = require("../kernel/forth.js");
function Repl() {
var forth = Forth();
function loadForth(file) {
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
forth.run(xmlhttp.responseText, onForthOutput);
}
};
xmlhttp.open("GET", file, true);
xmlhttp.send();
}
loadForth("forth/forth.fth");
var inputHistory = [""];
var historyCount = 0;
var historySelection = 0;
function useHistory(selection) {
var inputNode = document.getElementById("input");
if (inputNode.value !== inputHistory[historySelection]) {
historySelection = historyCount - 1;
inputHistory[historyCount] = inputNode.value;
} else {
historySelection = Math.min(Math.max(selection, 0), inputHistory.length - 1);
}
inputNode.value = inputHistory[historySelection];
inputNode.selectionStart = inputNode.value.length;
}
function updateHistory(input) {
// Remove duplicates
for (var i = inputHistory.length - 1; i >= 0; i--) {
if (inputHistory[i] === input) {
inputHistory.splice(i, 1);
historyCount--;
}
}
inputHistory[historyCount] = input;
historyCount = inputHistory.length;
historySelection = inputHistory.length;
inputHistory.push("");
}
function createReplNode(icon, text, className) {
if (!text) return;
var textNode = document.createElement("textarea");
textNode.className = className;
textNode.readOnly = true;
textNode.cols = 80;
textNode.value = icon + " " + text;
var replNode = document.createElement("div");
replNode.appendChild(textNode);
var outputNode = document.getElementById("output");
outputNode.appendChild(replNode);
setTimeout(function() {
textNode.style.height = textNode.scrollHeight + "px";
outputNode.scrollTop = outputNode.scrollHeight - outputNode.clientHeight;
}, 0);
}
function onForthOutput(error, output) {
createReplNode("\u2190", output, "forth-output");
if (error) {
createReplNode("X", error, "error");
}
showStack();
}
function runforth() {
var inputNode = document.getElementById("input");
var input = inputNode.value.trim();
if (input) {
updateHistory(input);
createReplNode("\u2192", input, "user-output");
inputNode.value = "";
forth.run(input, onForthOutput);
}
}
function showStack() {
var stack = forth.stack;
var stackNode = document.getElementById("stack");
// Clear stack
while (stackNode.firstChild) stackNode.removeChild(stackNode.firstChild);
for (var i = 1; i <= stack.length(); i++) {
var element = document.createElement("span");
element.className = "stack-element";
element.textContent = String(stack.peek(i));
stackNode.appendChild(element);
}
}
return {
interpret: function(event) {
if (event.keyCode == 13 && !event.shiftKey)
runforth();
else if (event.keyCode == 80 && event.ctrlKey)
useHistory(historySelection - 1);
else if (event.keyCode == 78 && event.ctrlKey)
useHistory(historySelection + 1);
}
};
}
global.repl = Repl();
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"../kernel/forth.js":6}],2:[function(require,module,exports){
function ComparisonOperations(f) {
f.defjs("true", function _true() {
f.stack.push(-1);
});
f.defjs("false", function _false() {
f.stack.push(0);
});
f.defjs("and", function and() {
var first = f.stack.pop();
f.stack.push(f.stack.pop() & first);
});
f.defjs("or", function or() {
var first = f.stack.pop();
f.stack.push(f.stack.pop() | first);
});
f.defjs("xor", function xor() {
var first = f.stack.pop();
f.stack.push(f.stack.pop() ^ first);
});
f.defjs("invert", function invert() {
f.stack.push(~f.stack.pop());
});
f.defjs("=", function equal() {
var first = f.stack.pop();
f.stack.push((f.stack.pop() == first) ? -1 : 0);
});
f.defjs("<>", function notEqual() {
var first = f.stack.pop();
f.stack.push((f.stack.pop() != first) ? -1 : 0);
});
f.defjs("<", function lessThan() {
var first = f.stack.pop();
f.stack.push((f.stack.pop() < first) ? -1 : 0);
});
f.defjs(">", function greaterThan() {
var first = f.stack.pop();
f.stack.push((f.stack.pop() > first) ? -1 : 0);
});
f.defjs("<=", function lessThanEqual() {
var first = f.stack.pop();
f.stack.push((f.stack.pop() <= first) ? -1 : 0);
});
f.defjs(">=", function greaterThanEqual() {
var first = f.stack.pop();
f.stack.push((f.stack.pop() >= first) ? -1 : 0);
});
f.defjs("within", function within() {
var upperLimit = f.stack.pop();
var lowerLimit = f.stack.pop();
var value = f.stack.pop();
var result = (lowerLimit < upperLimit && lowerLimit <= value && value < upperLimit ||
lowerLimit > upperLimit && (lowerLimit <= value || value < upperLimit));
f.stack.push(result ? -1 : 0);
});
return f;
}
module.exports = ComparisonOperations;
},{}],3:[function(require,module,exports){
function ControlStructures(f) {
// if, else, then
f.defjs("jump", function jump() {
f.instructionPointer += f.dataSpace[f.instructionPointer];
});
f.defjs("jumpIfFalse", function jumpIfFalse() {
if (!f.stack.pop()) {
f.instructionPointer += f.dataSpace[f.instructionPointer];
} else {
f.instructionPointer++; // Skip the offset
}
});
// do, loop, +loop, unloop, leave, i, j
function _do() {
f.returnStack.push(f.dataSpace[f.instructionPointer++]);
var top = f.stack.pop();
f.returnStack.push(f.stack.pop());
f.returnStack.push(top);
}
f.defjs("do", function compileDo() {
f.dataSpace.push(_do);
f.dataSpace.push(0); // Dummy endLoop
f.stack.push(f.dataSpace.length - 1);
}, true); // Immediate
function questionDo() {
if (f.stack.peek(1) !== f.stack.peek(2)) {
_do();
} else {
f.stack.pop();
f.stack.pop();
f.instructionPointer = f.dataSpace[f.instructionPointer];
}
}
f.defjs("?do", function compileQuestionDo() {
f.dataSpace.push(questionDo);
f.dataSpace.push(0); // Dummy endLoop
f.stack.push(f.dataSpace.length - 1);
}, true); // Immediate
function plusLoop() {
var step = f.stack.pop();
var index = f.returnStack.pop() | 0;
var limit = f.returnStack.pop() | 0;
var exitLoop;
if (step > 0) {
if (index > limit) { // Overflow, so do unsigned
limit = limit >>> 0;
index = index >>> 0;
}
exitLoop = index < limit && index + step >= limit;
} else if (step < 0) {
if (index < limit) {
index = index >>> 0;
limit = limit >>> 0;
}
exitLoop = index >= limit && index + step < limit;
} else {
exitLoop = false;
}
if (exitLoop) {
f.returnStack.pop();
f.instructionPointer++;
} else {
f.returnStack.push(limit | 0);
f.returnStack.push(index + step | 0);
f.instructionPointer += f.dataSpace[f.instructionPointer];
}
}
var compilePlusLoop = f.defjs("+loop", function compilePlusLoop() {
f.dataSpace.push(plusLoop);
var doPosition = f.stack.pop();
f.dataSpace.push(doPosition - f.dataSpace.length + 1);
f.dataSpace[doPosition] = f.dataSpace.length;
}, true); // Immediate
f.defjs("loop", function loop() {
f.dataSpace.push(f._lit);
f.dataSpace.push(1);
compilePlusLoop();
}, true); // Immediate
f.defjs("unloop", function unloop() {
f.returnStack.pop();
f.returnStack.pop();
f.returnStack.pop();
});
f.defjs("leave", function leave() {
f.returnStack.pop();
f.returnStack.pop();
f.instructionPointer = f.returnStack.pop();
});
f.defjs("i", function i() {
f.stack.push(f.returnStack.peek());
});
f.defjs("j", function j() {
f.stack.push(f.returnStack.peek(4));
});
// recurse
f.defjs("recurse", function recurse() {
f.dataSpace.push(f.dataSpace[f._latest() + 1]);
}, true); // Immediate
// does
function _does() {
var wordPosition = f._latest();
var doDoesPosition = f.instructionPointer;
f.dataSpace[wordPosition + 1] = function doDoes() {
f.stack.push(wordPosition + 2);
f.returnStack.push(f.instructionPointer);
f.instructionPointer = doDoesPosition;
};
f.instructionPointer = f.returnStack.pop();
}
f.defjs("does>", function compileDoes() {
f.dataSpace.push(_does);
}, true); // Immediate
return f;
}
module.exports = ControlStructures;
},{}],4:[function(require,module,exports){
var Stack = require("./stack.js");
function Data(f) {
f.instructionPointer = 0;
f.dataSpace = [];
f.returnStack = new Stack("Return Stack");
f.stack = new Stack("Stack");
return f;
}
module.exports = Data;
},{"./stack.js":16}],5:[function(require,module,exports){
function Header(link, name, immediate, hidden, executionToken) {
this.link = link;
this.name = name;
this.immediate = immediate || false;
this.hidden = hidden || false;
this.executionToken = executionToken;
}
function Definitions(f) {
// Temporary definition until latest is defined as a variable
var latest = function latest() {
return null;
};
function defheader(name, immediate, hidden) {
f.dataSpace.push(new Header(latest(), name, immediate, hidden, f.dataSpace.length + 1));
latest(f.dataSpace.length - 1);
}
f.defjs = function defjs(name, fn, immediate, displayName) {
defheader(displayName || name, immediate);
f.dataSpace.push(fn);
return fn;
};
f.defvar = function defvar(name, initial) {
defheader(name);
var varAddress = f.dataSpace.length + 1;
f.dataSpace.push(function variable() {
f.stack.push(varAddress);
});
f.dataSpace.push(initial);
return function(value) {
if (value !== undefined)
f.dataSpace[varAddress] = value;
else
return f.dataSpace[varAddress];
};
};
latest = f.defvar("latest", f.dataSpace.length); // Replace existing function definition
f.compiling = f.defvar("state", 0);
f.compileEnter = function compileEnter(name) {
var instruction = f.dataSpace.length + 1;
var enter;
try {
enter = eval(`(
function ${name}() {
f.returnStack.push(f.instructionPointer);
f.instructionPointer = instruction;
})
`);
} catch (e) {
// Failback for names that are invalid identifiers
enter = function enter() {
f.returnStack.push(f.instructionPointer);
f.instructionPointer = instruction;
};
}
f.dataSpace.push(enter);
return enter;
};
f.findDefinition = function findDefinition(word) {
var current = latest();
while (current !== null) {
var wordDefinition = f.dataSpace[current];
// Case insensitive
if (wordDefinition.name && wordDefinition.name.toLowerCase() == word.toLowerCase() && !wordDefinition.hidden)
return wordDefinition;
current = wordDefinition.link;
}
return current;
};
f.defjs(":", function colon() {
var name = f._readWord();
defheader(name, false, true);
f.compileEnter(name);
f.compiling(true);
});
f.defjs(":noname", function noname() {
defheader(null, false, true);
f.stack.push(f.dataSpace.length);
f.compileEnter("_noname_");
f.compiling(true);
});
var exit = f.defjs("exit", function exit() {
f.instructionPointer = f.returnStack.pop();
});
f.defjs(";", function semicolon() {
f.dataSpace.push(exit);
f.dataSpace[latest()].hidden = false;
f.compiling(false);
}, true); // Immediate
f.defjs("find", function find() {
var input = f.stack.pop();
var word = input;
if (typeof input === "number") {
var startPosition = input;
var length = f._getAddress(startPosition);
word = "";
for (var i = 1; i <= length; i++) {
word += String.fromCharCode(f._getAddress(startPosition + i));
}
}
var definition = f.findDefinition(word);
if (definition) {
f.stack.push(definition.executionToken);
f.stack.push(definition.immediate ? 1 : -1);
} else {
f.stack.push(input);
f.stack.push(0);
}
});
// Converts an execution token into the data field address
f.defjs(">body", function dataFieldAddress() {
f.stack.push(f.stack.pop() + 1);
});
f.defjs("create", function create() {
defheader(f._readWord());
var dataFieldAddress = f.dataSpace.length + 1;
f.dataSpace.push(function pushDataFieldAddress() {
f.stack.push(dataFieldAddress);
});
});
f.defjs("allot", function allot() {
f.dataSpace.length += f.stack.pop();
});
f.defjs(",", function comma() {
f.dataSpace.push(f.stack.pop());
});
f.defjs("compile,", function compileComma() {
f.dataSpace.push(f.dataSpace[f.stack.pop()]);
});
f.defjs("[", function lbrac() {
f.compiling(false); // Immediate
}, true); // Immediate
f.defjs("]", function rbrac() {
f.compiling(true); // Compile
});
f.defjs("immediate", function immediate() {
var wordDefinition = f.dataSpace[latest()];
wordDefinition.immediate = true;
});
f.defjs("hidden", function hidden() {
var wordDefinition = f.dataSpace[f.stack.pop()];
wordDefinition.hidden = !wordDefinition.hidden;
});
f.defjs("'", function tick() {
f.stack.push(f.findDefinition(f._readWord()).executionToken);
});
var _lit = f.defjs("lit", function lit() {
f.stack.push(f.dataSpace[f.instructionPointer]);
f.instructionPointer++;
});
f.defjs("[']", function bracketTick() {
f.dataSpace.push(f._lit);
f.dataSpace.push(f.findDefinition(f._readWord()).executionToken);
}, true);
f.defjs("marker", function marker() {
var savedLatest = latest();
var savedLength = f.dataSpace.length;
defheader(f._readWord());
f.dataSpace.push(function marker() {
latest(savedLatest);
f.dataSpace.length = savedLength;
});
});
f._latest = latest;
f._lit = _lit;
return f;
}
module.exports = Definitions;
},{}],6:[function(require,module,exports){
var Data = require("./data.js");
var Definitions = require("./definitions.js");
var NumericOperations = require("./numeric-operations.js");
var BooleanOperations = require("./boolean-operations.js");
var StackOperations = require("./stack-operations.js");
var MemoryOperations = require("./memory-operations.js");
var ControlStructures = require("./control-structures.js");
var JsInterop = require("./js-interop.js");
var Input = require("./input.js");
var Output = require("./output.js");
var Include = require("./include.js");
var Interpreter = require("./interpreter.js");
function Forth() {
var forth = {};
Data(forth);
Definitions(forth);
Input(forth);
NumericOperations(forth);
BooleanOperations(forth);
StackOperations(forth);
MemoryOperations(forth);
ControlStructures(forth);
Output(forth);
JsInterop(forth);
Include(forth);
Interpreter(forth);
return forth;
}
module.exports = Forth;
},{"./boolean-operations.js":2,"./control-structures.js":3,"./data.js":4,"./definitions.js":5,"./include.js":7,"./input.js":9,"./interpreter.js":10,"./js-interop.js":11,"./memory-operations.js":12,"./numeric-operations.js":13,"./output.js":14,"./stack-operations.js":15}],7:[function(require,module,exports){
(function (process){
var request = require("request");
var url = require("url");
var fs = require("fs");
var InputExceptions = require("./input-exceptions.js");
function Include(f) {
f.defjs("include", function() {
var outputCallback = f.outputCallback;
var file = f._readWord();
if (process.browser || file.match(/^http/)) {
if (process.browser) file = url.resolve(location.href, file);
request.get(file, function(error, response, body) {
if (!error && response.statusCode == 200) {
f.run(body, outputCallback, file.toString());
} else {
console.error("Failed to load http file " + file + ". " + error);
}
});
} else {
fs.readFile(file, "utf8", function(error, body) {
if (!error) {
f.run(body, outputCallback, file);
} else {
console.error("Failed to load file " + file + ". " + error);
}
});
}
throw InputExceptions.WaitingOnInput;
});
return f;
}
module.exports = Include;
}).call(this,require('_process'))
},{"./input-exceptions.js":8,"_process":211,"fs":74,"request":237,"url":307}],8:[function(require,module,exports){
module.exports = {
EndOfInput: {},
WaitingOnInput: {}
};
},{}],9:[function(require,module,exports){
var InputExceptions = require("./input-exceptions.js");
function InputWindow(input, startPosition, endPosition, toIn, sourceId) {
var inputBufferPosition = startPosition;
var inputBufferLength = -1;
function refill() {
inputBufferPosition += inputBufferLength + 1;
inputBufferLength = input.substring(inputBufferPosition).search(/\n/);
if (inputBufferLength == -1 || inputBufferPosition + inputBufferLength > endPosition)
inputBufferLength = endPosition - inputBufferPosition;
toIn(0);
return inputBufferPosition < endPosition;
}
function readKey() {
var keyPosition = inputBufferPosition + toIn();
if (keyPosition < endPosition) {
toIn(toIn() + 1);
return input.charAt(keyPosition);
} else {
return null;
}
}
function parse(delimiter, skipLeading) {
delimiter = delimiter || " ".charCodeAt(0);
var inputBuf = inputBuffer();
var startPosition = toIn();
if (skipLeading) {
while (inputBuf.charCodeAt(startPosition) === delimiter && startPosition < inputBuf.length) {
startPosition++;
}
}
var endPosition = startPosition;
while (inputBuf.charCodeAt(endPosition) !== delimiter && endPosition < inputBuf.length) {
endPosition++;
}
toIn(endPosition + 1);
var result = inputBuf.substring(startPosition, endPosition);
return [inputBufferPosition + startPosition, result.length, result];
}
function readWord(delimiter) {
return parse(delimiter, true)[2];
}
function sBackslashQuote() {
var string = "";
while (true) {
var char = readKey();
if (char === "\"") {
break;
} else if (char === "\\") {
var nextChar = readKey();
switch (nextChar) {
case "a":
string += String.fromCharCode(7);
break;
case "b":
string += String.fromCharCode(8);
break;
case "e":
string += String.fromCharCode(27);
break;
case "f":
string += String.fromCharCode(12);
break;
case "l":
string += String.fromCharCode(10);
break;
case "m":
string += String.fromCharCode(13) + String.fromCharCode(10);
break;
case "n":
string += String.fromCharCode(10);
break;
case "q":
string += String.fromCharCode(34);
break;
case "r":
string += String.fromCharCode(13);
break;
case "t":
string += String.fromCharCode(9);
break;
case "v":
string += String.fromCharCode(11);
break;
case "z":
string += String.fromCharCode(0);
break;
case "\"":
string += String.fromCharCode(34);
break;
case "x":
string += String.fromCharCode(parseInt(readKey() + readKey(), 16));
break;
case "\\":
string += String.fromCharCode(92);
break;
default:
// Be lenient
string += nextChar;
}
} else {
string += char;
}
}
return string;
}
function source() {
return [inputBufferPosition, inputBufferLength];
}
function inputBuffer() {
if (inputBufferLength > 0)
return input.substring(inputBufferPosition, inputBufferPosition + inputBufferLength);
else
return "";
}
function subInput(position, length) {
return InputWindow(input, position, position + length, toIn, -1);
}
function charCodeAt(index) {
return input.charCodeAt(index);
}
return {
readWord: readWord,
readKey: readKey,
parse: parse,
refill: refill,
inputBuffer: inputBuffer,
source: source,
charCodeAt: charCodeAt,
subInput: subInput,
sBackslashQuote: sBackslashQuote,
sourceId: sourceId
};
}
function Input(f) {
f._base = f.defvar("base", 10);
// Input buffer pointer
var toIn = f.defvar(">in", 0);
// Address offset to indicate input addresses
var INPUT_SOURCE = 1 << 31;
f.defjs("source", function source() {
var positionLength = f._currentInput.source();
f.stack.push(INPUT_SOURCE + positionLength[0]);
f.stack.push(positionLength[1]);
});
f.defjs("source-id", function sourceId() {
f.stack.push(f._currentInput.sourceId);
});
f.defjs("refill", function refill() {
f.stack.push(f._currentInput.refill());
});
f.defjs("key", function key() {
f.stack.push(f._currentInput.readKey().charCodeAt(0));
});
f.defjs("parse", function parse() {
var addressLength = f._currentInput.parse(f.stack.pop(), false);
f.stack.push(INPUT_SOURCE + addressLength[0]);
f.stack.push(addressLength[1]);
});
f.defjs("parse-name", function parse() {
var addressLength = f._currentInput.parse(" ".charCodeAt(0), true);
f.stack.push(INPUT_SOURCE + addressLength[0]);
f.stack.push(addressLength[1]);
});
function readWord(delimiter) {
return f._currentInput.readWord(delimiter);
}
var wordBufferStart = f.dataSpace.length;
f.dataSpace.length += 128;
f.defjs("word", function word() {
var delimiter = f.stack.pop();
var word = readWord(delimiter);
var length = Math.min(word.length, 127);
f.dataSpace[wordBufferStart] = length;
for (var i = 0; i < length; i++) {
f.dataSpace[wordBufferStart + i + 1] = word.charCodeAt(i);
}
f.stack.push(wordBufferStart);
});
f.defjs("s\\\"", function sBackslashQuote() {
var string = f._currentInput.sBackslashQuote();
var stringAddress = f.dataSpace.length + 1;
f.dataSpace.push(function() {
f.stack.push(stringAddress);
f.stack.push(string.length);
// Jump over compiled string
f.instructionPointer += string.length;
});
for (var i = 0; i < string.length; i++) {
f.dataSpace.push(string[i]);
}
}, true); // Immediate
f.defjs("char", function char() {
f.stack.push(readWord().charCodeAt(0));
});
f.defjs("accept", function accept() {
var maxLength = f.stack.pop();
var address = f.stack.pop();
f.currentInstruction = function acceptCallback() {
f._currentInput.refill();
var received = f._currentInput.inputBuffer().substring(0, maxLength).split("\n")[0];
f.stack.push(received.length);
for (var i = 0; i < received.length; i++) {
f._setAddress(address + i, received[i]);
}
popInput();
};
throw InputExceptions.WaitingOnInput;
});
// returns NaN if any characters are invalid in base
function parseIntStrict(num, base) {
var int = 0;
if (num[0] !== "-") { // Positive
for (var i = 0; i < num.length; i++) {
int *= base;
int += parseInt(num[i], base);
}
return int;
} else {
for (var j = 1; j < num.length; j++) {
int *= base;
int -= parseInt(num[j], base);
}
return int;
}
}
// Parse a float in the current base
function _parseFloatInBase(string) {
var base;
if (string[0] === "'" && string.length === 3 && string[2] == "'") { // 'a'
return string.charCodeAt(1);
} else if (string[0] === "#") { // decimal - #1234567890
string = string.substring(1);
base = 10;
} else if (string[0] === "$") { // hex - $ff00ff
string = string.substring(1);
base = 16;
} else if (string[0] === "%") { // binary - %10110110
string = string.substring(1);
base = 2;
} else {
base = f._base();
}
var num = string.split(/\./);
var integerPart = 0;
if (num[0] !== '') {
integerPart = parseIntStrict(num[0], base);
}
var fractionalPart = 0;
if (num.length > 1 && num[1] !== '') {
fractionalPart = parseIntStrict(num[1], base) * Math.pow(base, -num[1].length);
}
if (integerPart >= 0) {
return integerPart + fractionalPart;
} else {
return integerPart - fractionalPart;
}
}
var inputString = "";
function newInput(input, sourceId) {
saveCurrentInput();
var startPosition = inputString.length;
inputString += input;
f._currentInput = InputWindow(inputString, startPosition, inputString.length, toIn, sourceId);
}
var inputStack = [];
function subInput(position, length) {
saveCurrentInput();
f._currentInput = f._currentInput.subInput(position, length);
}
function saveCurrentInput() {
if (f._currentInput) {
inputStack.push({
input: f._currentInput,
toIn: toIn(),
instructionPointer: f.instructionPointer
});
}
}
function popInput() {
var savedInput = inputStack.pop();
if (savedInput) {
f._currentInput = savedInput.input;
toIn(savedInput.toIn);
f.instructionPointer = savedInput.instructionPointer;
f.currentInstruction = f.dataSpace[f.instructionPointer++];
} else {
f._currentInput = null;
}
}
f.defjs("save-input", function saveInput() {
saveCurrentInput();
for (var i = 0; i < inputStack.length; i++) {
f.stack.push(inputStack[i]);
}
f.stack.push(inputStack.length);
inputStack.pop();
});
f.defjs("restore-input", function restoreInput() {
inputStack.length = 0;
var length = f.stack.pop();
for (var i = length - 1; i >= 0; i--) {
inputStack[i] = f.stack.pop();
}
var savedInput = inputStack.pop();
f._currentInput = savedInput.input;
toIn(savedInput.toIn);
f.stack.push(0);
});
f._readWord = readWord;
f._newInput = newInput;
f._subInput = subInput;
f._popInput = popInput;
f._parseFloatInBase = _parseFloatInBase;
f._INPUT_SOURCE = INPUT_SOURCE;
return f;
}
module.exports = Input;
},{"./input-exceptions.js":8}],10:[function(require,module,exports){
var InputExceptions = require("./input-exceptions.js");
function Interpreter(f) {
function run(input, outputCallback, sourceId) {
f.outputCallback = outputCallback;
f._newInput(input, sourceId || 0);
f._output = "";
try {
runInterpreter();
} catch (err) {
if (err !== InputExceptions.WaitingOnInput) {
console.error("Exception " + err + " at:\n" + printStackTrace());
console.error(f._currentInput.inputBuffer());
console.error(f._output);
f.currentInstruction = quit;
f.stack.clear();
outputCallback(err, f._output);
throw err;
}
}
outputCallback(null, f._output);
}
function runInterpreter() {
// Run while there is still input to consume
while (f._currentInput) {
try {
// As js doesn't support tail call optimisation the
// run function uses a trampoline to execute forth code
while (true) {
f.currentInstruction();
f.currentInstruction = f.dataSpace[f.instructionPointer++];
}
} catch (err) {
if (err === InputExceptions.EndOfInput) {
f._popInput();
} else {
throw err;
}
}
}
}
function printStackTrace() {
var stackTrace = " " + f.currentInstruction.name + " @ " + (f.instructionPointer - 1);
for (var i = f.returnStack.length - 1; i >= 0; i--) {
var instruction = f.returnStack[i];
stackTrace += "\n " + f.dataSpace[instruction - 1].name + " @ " + (instruction - 1);
}
return stackTrace;
}
f._evaluate = f.defjs("evaluate", function evaluate() {
var length = f.stack.pop();
var address = f.stack.pop();
if (address < 0) {
var position = address - f._INPUT_SOURCE;
f._subInput(position, length);
} else {
var string = "";
for (var i = 0; i < length; i++) {
string += String.fromCharCode(f._getAddress(address + i));
}
f._newInput(string, -1);
}
f.instructionPointer = interpretInstruction;
});
function interpretWord() {
var word = f._readWord();
while (!word) {
if (!f._currentInput.refill()) throw InputExceptions.EndOfInput;
word = f._readWord();
}
var definition = f.findDefinition(word);
if (definition) {
if (!f.compiling() || definition.immediate) {
f.dataSpace[definition.executionToken]();
return;
} else {
f.dataSpace.push(f.dataSpace[definition.executionToken]);
}
} else {
var num = f._parseFloatInBase(word);
if (isNaN(num)) throw "Word not defined: " + word;
if (f.compiling()) {
f.dataSpace.push(f._lit);
f.dataSpace.push(num);
} else {
f.stack.push(num);
}
}
}
var interpretInstruction = f.dataSpace.length + 1;
f.defjs("interpret", function interpret() {
f.instructionPointer = interpretInstruction; // Loop after interpret word is called
interpretWord();
});
var quit = f.defjs("quit", function quit() {
f.compiling(false); // Enter interpretation state
f.returnStack.clear(); // Clear return stack
f.instructionPointer = interpretInstruction; // Run the interpreter
});
var abort = f.defjs("abort", function abort(error) {
f.stack.clear();
throw error || "";
});
f.defjs('abort"', function abortQuote() {
var error = f._currentInput.parse('"'.charCodeAt(0))[2];
f.dataSpace.push(function abortQuote() {
if (f.stack.pop())
abort(error);
});
}, true); // Immediate
f.defjs("execute", function execute() {
f.dataSpace[f.stack.pop()]();
});
// Set initial instruction
f.currentInstruction = quit;
f.run = run;
return f;
}
module.exports = Interpreter;
},{"./input-exceptions.js":8}],11:[function(require,module,exports){
(function (global){
function JsInterop(f) {
// Interop
// - new with params js .new{1}
// - global variable access js /document
// - array access js .0.2
// - property access/setting js .name js .name!
// - function calling js .sin{1} .{2} >> obj = pop, f = obj[name], f.call(obj, pop(), pop())
// - method calling js /document.getElementById{1}
//
// When compiling it should resolve global names immediately.
function jsNewCall(path) {
var constructor = f.stack.pop();
var argsCount = parseInt(path.match(/\{(\d*)\}/)[1] || 0);
var args = [null]; // new replaces the first argument with this
for (var j = 0; j < argsCount; j++) {
args.push(f.stack.pop());
}
// Use new operator with any number of arguments
return new(Function.prototype.bind.apply(constructor, args))();
}
function jsFunctionCall(path) {
var argsCount = parseInt(path.match(/\{(\d*)\}/)[1] || 0);
var obj = f.stack.pop();
path = path.match(/[^\{]*/)[0];
var func = path ? obj[path] : obj;
var args = [];
for (var j = 0; j < argsCount; j++) {
args.push(f.stack.pop());
}
return func.apply(obj, args);
}
var jsAssignmentRegex = /(^[A-Za-z$_][\w$_]*!$)|(^\d+!$)/; // name!
var jsNewCallRegex = /new\{\d*\}$/; // new{2}
var jsFunctionCallRegex = /((^[A-Za-z$_][\w$_]*)|(^\d+))?\{\d*\}$/; // getElementById{1}
var globl = (typeof window !== 'undefined' && typeof navigator !== 'undefined' && window.document) ? window : global;
function jsInterop(js) {
if (js.startsWith("/")) { // Add global to f.stack
f.stack.push(globl);
} else if (!js.startsWith(".")) {
throw "js interop call must start with '/' or '.'";
}
var paths = js.length > 1 ? js.substring(1).split(".") : [];
for (var i = 0; i < paths.length; i++) {
var path = paths[i];
if (path.match(jsAssignmentRegex)) {
f.stack.pop()[path.substring(0, path.length - 1)] = f.stack.pop();
} else if (path.match(jsNewCallRegex)) {
f.stack.push(jsNewCall(path));
} else if (path.match(jsFunctionCallRegex)) {
f.stack.push(jsFunctionCall(path));
} else { // Property access
f.stack.push(f.stack.pop()[path]);
}
}
}
var JS = f.defjs("js", function js() {
jsInterop(f.stack.pop());
});
f.defjs("js", function js() {
if (f.compiling()) {
f.dataSpace.push(f._lit);
f.dataSpace.push(f._readWord());
f.dataSpace.push(JS);
} else {
jsInterop(f._readWord());
}
}, true);
f.defjs(">js-string", function toJsString() {
var length = f.stack.pop();
var address = f.stack.pop();
var string = "";
for (var i = 0; i < length; i++) {
string += String.fromCharCode(f._getAddress(address + i));
}
f.stack.push(string);
})
return f;
}
module.exports = JsInterop;
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{}],12:[function(require,module,exports){
function MemoryOperations(f) {
function getAddress(address) {
if (address < 0) {
return f._currentInput.charCodeAt(address - f._INPUT_SOURCE);
} else {
var value = f.dataSpace[address];
if (typeof value == "string")
return value.charCodeAt(0);
else
return value;
}
}
function setAddress(address, value) {
if (address < 0) {
throw "Illegal attempt to change input";
} else {
f.dataSpace[address] = value;
}
}
f.defjs("!", function store() {
var address = f.stack.pop();
var data = f.stack.pop();
setAddress(address, data);
});
f.defjs("@", function fetch() {
var address = f.stack.pop();
f.stack.push(getAddress(address));
});
f.defjs("+!", function addStore() {
var address = f.stack.pop();
var data = f.stack.pop();
f.dataSpace[address] = f.dataSpace[address] + data;
});
f.defjs("-!", function subtractStore() {
var address = f.stack.pop();
var data = f.stack.pop();
f.dataSpace[address] = f.dataSpace[address] - data;
});
f.defjs("here", function here() {
f.stack.push(f.dataSpace.length);
});
f._getAddress = getAddress;
f._setAddress = setAddress;
return f;
}
module.exports = MemoryOperations;
},{}],13:[function(require,module,exports){
var Long = require("long");
function NumericOperations(f) {
f.defjs("+", function plus() {
var first = f.stack.pop();
f.stack.push(f.stack.pop() + first | 0);
});
f.defjs("-", function minus() {
var first = f.stack.pop();
f.stack.push(f.stack.pop() - first | 0);
});
f.defjs("*", function multiply() {
var first = f.stack.pop();
f.stack.push(f.stack.pop() * first | 0);
});
f.defjs("/", function divide() {
var first = f.stack.pop();
var second = f.stack.pop();
f.stack.push(Math.trunc(second / first));
});
f.defjs("2*", function inc() {
f.stack.push(f.stack.pop() << 1);
});
f.defjs("2/", function inc() {
f.stack.push(f.stack.pop() >> 1);
});
f.defjs("mod", function mod() {
var first = f.stack.pop();
f.stack.push(f.stack.pop() % first);
});
f.defjs("s>d", function singleToDouble() {
var value = Long.fromInt(f.stack.pop());
f.stack.push(value.low);
f.stack.push(value.high);
});
f.defjs("*/", function multiplyDivide() {
var divisor = Long.fromInt(f.stack.pop());
var first = Long.fromInt(f.stack.pop());
var second = Long.fromInt(f.stack.pop());
var quotient = first.mul(second).div(divisor).toInt();
f.stack.push(quotient);
});
f.defjs("m*", function() {
var first = Long.fromInt(f.stack.pop());
var second = Long.fromInt(f.stack.pop());
var result = first.mul(second);
f.stack.push(result.low);
f.stack.push(result.high);
});
f.defjs("*/mod", function multiplyDivideMod() {
var divisor = Long.fromInt(f.stack.pop());
var first = Long.fromInt(f.stack.pop());
var second = Long.fromInt(f.stack.pop());
var mult = first.mul(second);
var quotient = mult.div(divisor).toInt();
var mod = mult.mod(divisor).toInt();
f.stack.push(mod);
f.stack.push(quotient);
});
f.defjs("um*", function() {
var first = Long.fromInt(f.stack.pop(), true);
var second = Long.fromInt(f.stack.pop(), true);
var result = first.mul(second);
f.stack.push(result.low);
f.stack.push(result.high);
});
f.defjs("um/mod", function unsignedDivideMod() {
var divisor = Long.fromInt(f.stack.pop());
var bigPart = f.stack.pop();
var smallPart = f.stack.pop();
var long = new Long(smallPart, bigPart, true);
var quotient = long.div(divisor).toInt();
var mod = long.mod(divisor).toInt();
f.stack.push(mod);
f.stack.push(quotient);
});
f.defjs("fm/mod", function flooredDivideMod() {
var divisor = Long.fromInt(f.stack.pop());
var bigPart = f.stack.pop();
var smallPart = f.stack.pop();
var long = new Long(smallPart, bigPart);
var quotient = long.div(divisor).toInt();
var mod = long.mod(divisor).toInt();
f.stack.push(mod);
f.stack.push(quotient);
});
f.defjs("sm/rem", function symmetricDivideRem() {
var divisor = Long.fromInt(f.stack.pop());
var bigPart = f.stack.pop();
var smallPart = f.stack.pop();
var long = new Long(smallPart, bigPart);
var quotient = long.div(divisor).toInt();
var mod = long.mod(divisor).toInt();
f.stack.push(mod);
f.stack.push(quotient);
});
f.defjs("abs", function abs() {
f.stack.push(Math.abs(f.stack.pop()) | 0);
});
f.defjs("lshift", function lshift() {
var shift = f.stack.pop();
var num = f.stack.pop();
f.stack.push(num << shift);
});
f.defjs("rshift", function rshift() {
var shift = f.stack.pop();
var num = f.stack.pop();
f.stack.push(num >>> shift);
});
f.defjs("max", function max() {
f.stack.push(Math.max(f.stack.pop(), f.stack.pop()));
});
f.defjs("min", function min() {
f.stack.push(Math.min(f.stack.pop(), f.stack.pop()));
});
f.defjs("negate", function negate() {
f.stack.push(-f.stack.pop());
});
return f;
}
module.exports = NumericOperations;
},{"long":182}],14:[function(require,module,exports){
var Long = require("long");
function Output(f) {
f._output = "";
f.defjs("cr", function cr() {
f._output += "\n";
});
f.defjs(".", function dot() {
var value;
var top = f.stack.pop();
if (typeof top === "undefined")
value = "undefined";
else if (top === null)
value = "null";
else
value = top.toString(f._base()); // Output numbers in current base
f._output += value + " ";
});
f.defjs(".r", function dotR() {
var value;
var width = f.stack.pop();
var top = f.stack.pop();
if (typeof top === "undefined")
value = "undefined";
else if (top === null)
value = "null";
else
value = top.toString(f._base()); // Output numbers in current base
while (value.length < width) {
value = " " + value;
}
f._output += value + " ";
});
f.defjs("emit", function emit() {
var value = f.stack.pop();
if (typeof value === "number")
f._output += String.fromCharCode(value);
else
f._output += value;
});
f.defjs("type", function type() {
var length = f.stack.pop();
var address = f.stack.pop();
for (var i = 0; i < length; i++) {
var value = f._getAddress(address + i);
if (typeof value === "number") {
f._output += String.fromCharCode(value);
} else
f._output += value;
}
});
// Numeric output
var numericOutputStart = f.dataSpace.length;
var numericOutput = "";
f.dataSpace.length += 128;
f.defjs("<#", function initialiseNumericOutput() {
numericOutput = "";
});
f.defjs("hold", function hold() {
var value = f.stack.pop();
if (typeof value === "number")
value = String.fromCharCode(value);
numericOutput += value;
});
f.defjs("#>", function finishNumericOutput() {
f.stack.pop();
f.stack.pop();
for (var i = 0; i < numericOutput.length; i++) {
f.dataSpace[numericOutputStart + i] = numericOutput[numericOutput.length - i - 1];
}
f.stack.push(numericOutputStart);
f.stack.push(numericOutput.length);
});
f.defjs("sign", function sign() {
if (f.stack.pop() < 0)
numericOutput += "-";
});
f.defjs("#", function writeNextNumericOutput() {
var bigPart = f.stack.pop();
var smallPart = f.stack.pop();
var value = new Long(smallPart, bigPart, true);
var base = Long.fromInt(f._base());
numericOutput += value.mod(base).toString(base).toUpperCase();
value = value.div(base);
f.stack.push(value.smallPart);
f.stack.push(value.bigPart);
});
f.defjs("#S", function writeAllNumericOutput() {
var bigPart = f.stack.pop();
var smallPart = f.stack.pop();
var value = new Long(smallPart, bigPart, true);
var base = Long.fromInt(f._base());
if (value.compare(Long.ZERO)) {
while (value.compare(Long.ZERO)) {
numericOutput += value.mod(base).toString(base).toUpperCase();
value = value.div(base);
}
} else {
numericOutput += "0";
}
f.stack.push(0);
f.stack.push(0);
});
f.defjs(">number", function toNumber() {
var base = Long.fromInt(f._base());
var length = f.stack.pop();
var address = f.stack.pop();
var bigPart = f.stack.pop();
var smallPart = f.stack.pop();
var value = new Long(smallPart, bigPart, true);
var unconverted = length;
for (var i = 0; i < length; i++) {
var next = parseInt(String.fromCharCode(f._getAddress(address)), base);
if (isNaN(next)) {
break;
} else {
address++;
unconverted--;
value = value.mul(base).add(Long.fromInt(next));
}
}
f.stack.push(value.low);
f.stack.push(value.high);
f.stack.push(address);
f.stack.push(unconverted);
});
return f;
}
module.exports = Output;
},{"long":182}],15:[function(require,module,exports){
function StackOperations(f) {
f.defjs("drop", function drop() {
f.stack.pop();
});
f.defjs("swap", function swap() {
var first = f.stack.pop();
var second = f.stack.pop();
f.stack.push(first);
f.stack.push(second);
});
f.defjs("dup", function dup() {
f.stack.push(f.stack.peek());
});
f.defjs("over", function over() {
f.stack.push(f.stack.peek(2));
});
f.defjs("pick", function pick() {
f.stack.push(f.stack.peek(f.stack.pop() + 1));
});
f.defjs("rot", function rot() {
var first = f.stack.pop();
var second = f.stack.pop();
var third = f.stack.pop();
f.stack.push(second);
f.stack.push(first);
f.stack.push(third);
});
f.defjs("-rot", function backRot() {
var first = f.stack.pop();
var second = f.stack.pop();
var third = f.stack.pop();
f.stack.push(first);
f.stack.push(third);
f.stack.push(second);
});
f.defjs("roll", function roll() {
var num = f.stack.pop();
f.stack.roll(num);
});
f.defjs("2drop", function twoDrop() {
f.stack.pop();
f.stack.pop();
});
f.defjs("2dup", function twoDup() {
f.stack.push(f.stack.peek(2));
f.stack.push(f.stack.peek(2));
});
f.defjs("2over", f