Source: Fish.js

/**
 * Namespace for stuff related to the ><> (Fish) programming language
 *
 * @namespace
 */
Fish = {
	/**
	 * Turns the code into a codebox. All newline characters will be removed and turned into actual new lines.
	 * To get actual newline characters into the codebox, manipulate it using {@link Fish.Codebox#setCodeAt}
	 *
	 * @param {String} code	Code to parse
	 *
	 * @constructor
	 *
	 * @memberof Fish
	 */
	Codebox: function(code) {
		// Always working reference to this
		var self = this;

		// Split the code into lines
		var grid = code.split("\n");

		// Important variables
		var width = 0;
		var height = grid.length;
		var codebox = {};

		// Split the code into individual characters
		(function() {
			for (var y = 0; y < height; y++) {
				grid[y] = grid[y].split("");

				// Check if this line is the longest this far
				if (grid[y].length > width) {
					width = grid[y].length;
				}

				for (var x = 0; x < grid[y].length; x++) {
						codebox[x + " " + y] = grid[y][x];
				}
			}
		})();

		// Delete the grid
		delete grid;

		// Make properties on this object
		Object.defineProperties(this, {
			/**
			 * Width of the code box
			 *
			 * @instance
	 		 * @memberof Fish.Codebox
			 */
			width: {get: function() {return width;}},
			/**
			 * Height of the code box
			 *
			 * @instance
	 		 * @memberof Fish.Codebox
			 */
			height: {get: function() {return height;}}
		});

		/**
		 * Function to get code at a point in the code box
		 *
		 * @param {Object} ip	Instruction pointer location
		 * @param {Number} ip.char	Character index
		 * @param {Number} ip.line	Line index
		 *
		 * @return {Number}	Charcode of the code at the given position
		 */
		this.getCodeAt = function(ip) {
			if (typeof codebox[ip.char + " " + ip.line] == "undefined") {
				return 0;
			} else {
				return codebox[ip.char + " " + ip.line];
			}
		};

		/**
		 * Function to set code at a point in the code box
		 *
		 * @param {Object} ip	Instruction pointer location
		 * @param {Number} ip.char	Character index
		 * @param {Number} ip.line	Line index
		 * @param {Number} code	Charcode of the code to insert
		 */
		this.setCodeAt = function(ip, code) {
			codebox[ip.char + " " + ip.line] = code;
			if (ip.char > width) {width = ip.char;}
			if (ip.line > height) {height = ip.line;}
		};

		/**
		 * Function to get ALL code points in the code box
		 *
		 * @return {Array}	An array with objects on the form {char: number, line: numer}
		 */
		this.getAllCodePoints = function() {
			var codePoints = [];
			for (var a in codebox) {
				var point = a.split(" ");
				codePoints.push({char: point[0], line: point[1]});
			}
			return codePoints;
		};
	},
	/**
	 * Parseis a codebox. Objects of this type are Event Emitters ({@link SoupEvents})
	 *
	 * @param {String|object} codebox	Either a string of code, or a {@link Fish#Codebox}
	 *
	 * @return {Fish.Parse}	An object which can control code execution and register event listeners (@see {@link SoupEvents}).
	 *
	 * @constructor
	 */
	Parse: function(code) {
		// Always working reference to this
		var self = this;

		// Make this an event emitter
		SoupEvents.makeEventEmitter(this);

		// Check if the code is a string, and turn it into a codebox if it is
		var codebox;
		if (typeof code == "string") {
			codebox = new Fish.Codebox(code);
		} else if (code instanceof Fish.Codebox) {
			codebox = code;
		} else {
			throw new Error("Invalid argument");
		}

		Object.defineProperties(this, {
			/**
			 * The {@link Fish.Codebox} of the program
			 *
			 * @instance
	 		 * @memberof Fish.Parse
			 */
			codebox: {get: function() {
				var copy = {width: codebox.width, height: codebox.height};
				var codePoints = codebox.getAllCodePoints();
				while (codePoints.length > 0) {
					var point = codePoints.shift();
					copy[point.char + " " + point.line] = codebox.getCodeAt(point);
				}
				return copy;
			}}
		});

		// Instruction pointer
		var ip = {char: -1, line: 0};

		// Current direction
		var dir = "E";

		Object.defineProperties(this, {
			/**
			 * Current location and direction of the instruction pointer
			 *
			 * @instance
	 		 * @memberof Fish.Parse
			 */
			ip: {get: function() {
				char = started ? ip.char : 0;
				return {location: {char: char, line: ip.line}, direction: dir};
			}}
		});

		// Program is running
		var running = false;
		Object.defineProperties(this, {
			/**
			 * True if the program is currently running, false otherwise
			 *
			 * @instance
	 		 * @memberof Fish.Parse
			 */
			running: {get: function() {return running;}}
		});

		// Program has not started
		var started = false;
		Object.defineProperties(this, {
			/**
			 * True if the program has been started, false otherwise
			 *
			 * @instance
	 		 * @memberof Fish.Parse
			 */
			started: {get: function() {return started;}}
		});

		// Emit an event on error
		function error() {
			self.dispatchEvent(new CustomEvent("error", {detail: "something smells fishy..."}));
			stop();
		};

		// Stops the execution
		function stop() {
			running = false;
			self.dispatchEvent(new CustomEvent("ended"));
		};

		// Speed of the execution, Ticks Per Second
		var speed = 1;
		Object.defineProperties(this, {
			/**
			 * Speed of the execution, in Ticks per Second. Can be manipulated
			 *
			 * @instance
	 		 * @memberof Fish.Parse
			 */
			speed: {
				get: function() {return speed;},
				set: function(newSpeed) {
					speed = parseFloat(newSpeed);
				}
			}
		});

		// The all-important stack
		var stack = new Fish.StackHandler();
		Object.defineProperties(this, {
			/**
			 * A copy of the current stack
			 *
			 * @instance
	 		 * @memberof Fish.Parse
			 */
			stack: {get: function() {
				var copy = [];
				for (var i = 0; i < stack.current.length; i++) {
					copy.push(stack.current.get(i));
				}
				return copy;
			}}
		});
		function stackchanged() {
			self.dispatchEvent(new CustomEvent("stackchanged"));
		};

		// I/O streams
		// In
		var stdin = new function() {
			// Characters in the stream
			var stream = [];

			// Reads a single number from the stream
			this.read = function() {
				if (stream.length > 0) {
					stack.current.push(stream.shift());
				} else {
					stack.current.push(-1);
				}
				self.dispatchEvent(new CustomEvent("inputconsumed"));
			};

			// Writes a string into the stream
			this.write = function(str) {
				str = ("" + str).split("");
				for (var i = 0; i < str.length; i++) {
					stream.push(str.shift());
				}
			};

			// Returns the data in the stream as a string
			this.stream = function() {
				return stream.join("");
			};
		};
		/**
		 * Writes a string to stdin. It will be parsed after everything which has previously been written to stdin
		 *
		 * @param {String} str	The string to write
		 */
		this.stdin = stdin.write;

		// Out
		var stdout = new function() {
			// Data in the stream
			var stream = [];

			// Reads a single number from the stream
			this.read = function() {
				if (stream.length > 0) {
					return stream.shift();
				} else {
					throw new Error("No data available to read");
				}
			};

			// Check if data is available
			this.dataAvailable = function() {
				return stream.length > 0;
			};

			// Writes a number
			this.writeNumber = function(num) {
				stream.push(num);
				self.dispatchEvent(new CustomEvent("output", {detail: {data: num}}));
			};

			// Writes a character
			this.writeCharacter = function(num) {
				var char = String.fromCharCode(num);
				stream.push(char);
				self.dispatchEvent(new CustomEvent("output", {detail: {data: char}}));
			};

			// Returns the data in the stream as a string
			this.stream = function() {
				return stream.join();
			};
		};
		/**
		 * Perform operations on the output stream
		 */
		this.stdout = {
			/**
			 * Reads a token from stdout
			 *
			 * @return {String|Number}	A token of data
			 * @instance
			 */
			read: stdout.read,
			/**
			 * Checks if there are tokens to be read from the stream
			 *
			 * @return {boolean}	True if data can be read, false otherwise
			 * @instance
			 */
			dataAvailable: stdout.dataAvailable
		};

		// Currently string-parsing?
		var stringparsing = false;

		// Other properties
		Object.defineProperties(this, {
			/**
			 * True if the program has finished running, false otherwise
			 *
			 * @instance
	 		 * @memberof Fish.Parse
			 */
			finished: {get: function() {return started && !running;}}
		});

		// Function to advance the IP
		function move(x, y) {
			var from = {char: ip.char, line: ip.line};
			if (typeof x != "undefined" && typeof y != "undefined") {
				if (x < 0 || y < 0) {
					error();
					return;
				}
				ip = {char: x, line: y};
			} else {
				switch (dir) {
					case "N":
						ip.line--;
						break;
					case "E":
						ip.char++;
						break;
					case "S":
						ip.line++;
						break;
					case "W":
						ip.char--;
						break;
				}
				ip.line = ip.line % codebox.height;
				ip.char = ip.char % codebox.width;
				if (ip.line < 0) {ip.line += codebox.height;}
				if (ip.char < 0) {ip.char += codebox.width;}

				if (typeof x != "undefined" && x === true && typeof y == "undefined") {
					self.dispatchEvent(new CustomEvent("moved", {detail: {from: from, to: ip}}));
				}
			}
		};

		// Function to execute the instruction in the current cell
		function execute() {
			var instruction = codebox.getCodeAt(ip);
			if (instruction === 0) {instruction = " ";}
			if (stringparsing != false && instruction != stringparsing) {
				stack.current.push(instruction.charCodeAt(0));
				stackchanged();
			} else if (stringparsing != false && instruction == stringparsing) {
				stringparsing = false;
			} else {
				switch (instruction) {
					// Movement changers
					case "^":	// OK
						dir = "N";
						break;
					case ">":	// OK
						dir = "E";
						break;
					case "v":	// OK
						dir = "S";
						break;
					case "<":	// OK
						dir = "W";
						break;
					case "x":	// OK
						dir = ["N", "E", "S", "W"][Math.floor(Math.random()*4)];
						break;
					// Mirrors
					case "/":	// OK	
						if (dir == "N") {dir = "E";}
						else if (dir == "E") {dir = "N";}
						else if (dir == "S") {dir = "W";}
						else if (dir == "W") {dir = "S";}
						break;
					case "\\":	// OK
						if (dir == "N") {dir = "W";}
						else if (dir == "E") {dir = "S";}
						else if (dir == "S") {dir = "E";}
						else if (dir == "W") {dir = "N";}
						break;
					case "|":	// OK
						if (dir == "E") {dir = "W";}
						else if (dir == "W") {dir = "E";}
						break;
					case "_":	// OK
						if (dir == "N") {dir = "S";}
						else if (dir == "S") {dir = "N";}
						break;
					case "#":	// OK
						if (dir == "N") {dir = "S";}
						else if (dir == "E") {dir = "W";}
						else if (dir == "S") {dir = "N";}
						else if (dir == "W") {dir = "E";}
						break;
					// Trampolines
					case "!":	// OK
						move();
						break;
					case "?":	// OK
						if (stack.current.pop() == 0) {
							move();
						}
						stackchanged();
						break;
					case ".":	// OK
						var y = stack.current.pop();
						var x = stack.current.pop();
						move(x, y);
						stackchanged();
						break;
					// Literals
					case "0":	// OK
					case "1":	// OK
					case "2":	// OK
					case "3":	// OK
					case "4":	// OK
					case "5":	// OK
					case "6":	// OK
					case "7":	// OK
					case "8":	// OK
					case "9":	// OK
					case "a":	// OK
					case "b":	// OK
					case "c":	// OK
					case "d":	// OK
					case "e":	// OK
					case "f":	// OK
						stack.current.push(parseInt(instruction, 16));
						stackchanged();
						break;
					// Operators
					case "+":	// OK
						var x = stack.current.pop();
						var y = stack.current.pop();
						stack.current.push(y + x);
						stackchanged();
						break;
					case "-":	// OK
						var x = stack.current.pop();
						var y = stack.current.pop();
						stack.current.push(y - x);
						stackchanged();
						break;
					case "*":	// OK
						var x = stack.current.pop();
						var y = stack.current.pop();
						stack.current.push(y * x);
						stackchanged();
						break;
					case ",":	// OK
						var x = stack.current.pop();
						var y = stack.current.pop();
						if (x == 0) {
							error();
						}
						stack.current.push(y / x);
						stackchanged();
						break;
					case "%":	// OK
						var x = stack.current.pop();
						var y = stack.current.pop();
						stack.current.push(y % x);
						stackchanged();
						break;
					case "=":	// OK
						var x = stack.current.pop();
						var y = stack.current.pop();
						stack.current.push(y == x ? 1 : 0);
						stackchanged();
						break;
					case ")":	// OK
						var x = stack.current.pop();
						var y = stack.current.pop();
						stack.current.push(y > x ? 1 : 0);
						stackchanged();
						break;
					case "(":	// OK
						var x = stack.current.pop();
						var y = stack.current.pop();
						stack.current.push(y < x ? 1 : 0);
						stackchanged();
						break;
					case "\"":	// OK
						stringparsing = "\"";
						break;
					case "'":	// OK
						stringparsing = "'";
						break;
					// Stack manipulation
					case ":":	// OK
						stack.current.duplicateTop();
						stackchanged();
						break;
					case "~":	// OK
						stack.current.removeTop();
						stackchanged();
						break;
					case "$":	// OK
						stack.current.swapTwo();
						stackchanged();
						break;
					case "@":	// OK
						stack.current.swapThree();
						stackchanged();
						break;
					case "}":	// OK
						stack.current.shiftRight();
						stackchanged();
						break;
					case "{":	// OK
						stack.current.shiftLeft();
						stackchanged();
						break;
					case "r":	// OK
						stack.current.reverse();
						stackchanged();
						break;
					case "l":	// OK
						stack.current.pushLength();
						stackchanged();
						break;
					case "[":	// OK
						stack.newStack();
						stackchanged();
						break;
					case "]":	// OK
						stack.removeStack();
						stackchanged();
						break;
					// I/O
					case "o":	// OK
						stdout.writeCharacter(stack.current.pop());
						stackchanged();
						break;
					case "n":	// OK
						stdout.writeNumber(stack.current.pop());
						stackchanged();
						break;
					case "i":
						stdin.read();
						stackchanged();
						break;
					// Reflection/Misc
					case "&":	// OK
						stack.current.register();
						stackchanged();
						break;
					case "g":	// OK
						var y = stack.current.pop();
						var x = stack.current.pop();
						var code = codebox.getCodeAt({char: x, line: y});
						if (typeof code == "string") {
							stack.current.push(code.charCodeAt(0));
						} else {
							stack.current.push(code);
						}
						stackchanged();
						break;
					case "p":	
						var y = stack.current.pop();
						var x = stack.current.pop();
						var v = String.fromCharCode(stack.current.pop());
						codebox.setCodeAt({char: x, line: y}, v);
						self.dispatchEvent(new CustomEvent("codechanged", {detail: {location: {char: x, line: y}, newCode: v}}));
						stackchanged();
						break;
					case ";":	// OK
						stop();
						break;
					// NOP
					case " ":
						break;
					// Unknown char. Do nothing
					default:
						error();
						break;
				}
			}
		};

		/**
		 * Starts the execution of the program
		 *
		 * @param {Array} [initialStack]	Stack to start the program with. Defaults to []
		 * @param {boolean} [animated]	True to make a delay between each instruction, false to have it execute immediately. Default: true
		 */
		this.start = function(initialStack, animated) {
			if (!started) {
				// Put on the initial stack
				if (!(initialStack instanceof Array)) {
					initialStack = [];
				}
				while (initialStack.length > 0) {
					stack.current.push(initialStack.shift());
				}
				

				if (typeof animated == "undefined") {
					animated = true;
				}

				running = true;
				started = true;
				if (animated) {
					setTimeout(runAnimated, 1000/speed);
				} else {
					runImmediate();
				}
			} else {
				throw new Error("Program already started");
			}
		};

		// The "loop" itself, animated version
		function runAnimated() {
			move(true);
			try {
				execute();
			} catch (e) {
				error();
			}
			if (running) {
				setTimeout(runAnimated, 1000/speed);
			}
		};

		// The "loop" itself, immediate version
		function runImmediate() {
			move(true);
			try {
				execute();
			} catch (e) {
				error();
			}
			if (!running) {
				return;
			}
			// Tail recursive
			runImmediate();
		};

		return this;
	},
	/**
	 * Constructor for a stack handler
	 *
	 * @constructor
	 */
	StackHandler: function() {
		// Constructor for a stack
		function Stack() {
			// The actual stack
			var stack = [];

			// The register
			var register = null;

			// Manipulate the register
			this.register = function() {
				if (register === null) {
					register = this.pop();
				} else {
					this.push(register);
					register = null;
				}
			};

			// Push a value onto the stack
			this.push = function(val) {
				stack.push(val);
			};

			// Pop a value from the stack
			this.pop = function() {
				if (stack.length == 0) {
					throw new Error("something smells fishy...");
				} else {
					return stack.pop();
				}
			};

			// Duplicate the top value
			this.duplicateTop = function() {
				var a = this.pop();
				this.push(a);
				this.push(a);
			};

			// Remove the top value from the stack
			this.removeTop = function() {
				this.pop();
			};

			// Swaps the top two elements on the stack
			this.swapTwo = function() {
				var a = this.pop();
				var b = this.pop();
				this.push(a);
				this.push(b);
			};

			// Swap the top three elements on the stack
			this.swapThree = function() {
				var a = this.pop();
				var b = this.pop();
				var c = this.pop();
				this.push(a);
				this.push(c);
				this.push(b);
			};

			// Shifts the stack right
			this.shiftRight = function() {
				if (stack.length > 0) {
					stack.unshift(this.pop());
				}
			};

			// Shifts the stack left
			this.shiftLeft = function() {
				if (stack.length > 0) {
					this.push(stack.shift());
				}
			};

			// Reverses the stack
			this.reverse = function() {
				stack.reverse();
			};

			// Pushes the length of the stack onto the stack
			this.pushLength = function() {
				this.push(stack.length);
			};

			// Gets the value on an index in the stack
			this.get = function(i) {
				if (typeof stack[i] == "undefined") {
					return null;
				}
				return stack[i];
			};

			Object.defineProperties(this, {
				/**
				 * Size of the stack
				 *
				 * @instance
				 * @memberof Fish.StackHandler.Stack
				 */
				length: {get: function() {return stack.length;}}
			});

		};

		// Stack of stacks
		var stackStack = [new Stack()];

		// Reference to the current stack
		var currentStack = stackStack[0];
		Object.defineProperties(this, {
			/**
			 * The current stack
			 *
			 * @instance
			 * @memberof Fish.StackHandler#
			 */
			current: {get: function() {return currentStack;}}
		});

		/**
		 * Creates a new stack on top of the current stack
		 */
		this.newStack = function() {
			var n = currentStack.pop();
			if (n < 0) {	// XXX Reference implementation dows not appear to have thought about negative numbers here
				throw new Error("something smells fishy...");
			}

			var newStack = new Stack();
			for (var i = 0; i < n; i++) {
				newStack.push(currentStack.pop());
			}

			currentStack = newStack;
			stackStack.push(newStack);
		};

		/**
		 * Removes the topmost stack from the stackstack, pushing it's values onto the stack below
		 */
		this.removeStack = function() {
			if (stackStack.length == 0) {
				stackStack = [new Stack()];
				currentStack = stackStack[0];
			} else {
				var oldStack = stackStack.pop();
				currentStack = stackStack[stackStack.length-1];
				while (oldStack.length > 0) {
					currentStack.push(oldStack.pop());
				}
			}
		};
	}
};