UNPKG

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
(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