js-forth
Version:
An implementation of [Forth](https://en.wikipedia.org/wiki/Forth_(programming_language)) in Javascript
377 lines (317 loc) • 11.4 kB
JavaScript
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;