Some of this is pretty ugly--I'd imagine it could be golfed further.
Sr.r-1(
)s.!+S1+.@@.@\<!@@*Ys.!+*S.!!4*.(sn1(
Each line of code in Ouroboros represents a snake eating its tail.
Snake 1
S
switches to the shared stack. r.r
reads one number, duplicates it, and reads another. (Nonnumeric characters like P
are skipped.) -
subtracts the two. If the input was 7P2
, we now have 7
, 5
on the shared stack. Finally, 1(
eats the final character of the snake. Since this is the character that the instruction pointer is on, the snake dies.
Snake 2
)s
does nothing the first time through. .!+
duplicates the top of snake 2's stack, checks if it's zero, and if so adds 1. On the first iteration, the stack is empty and treated as if it contained infinite zeros, so this pushes 1
; on later iterations, the stack contains a nonzero value and this has no effect.
Next, S
switches to the shared stack, where we have the number n
and a counter for calculating the product. 1+
increments the counter. .@@.@\<!
duplicates both numbers and pushes 1 if n
is still greater than or equal to the counter, 0 otherwise. @@*Y
then multiplies the counter by this quantity and yanks a copy onto snake 2's stack.
s.!+
switches back to snake 2's stack and uses the same code as earlier to convert the top number to 1 if it was 0 and keep it the same otherwise. Then *
multiplies the result by the partial product that was sitting on this stack.
We now go back to the shared stack (S
), duplicate the counter-or-zero (.
), and negate it twice (!!
) to turn a nonzero counter into a 1. 4*.(
multiplies this by 4, duplicates, and eats that many characters from the end of the snake.
- If we haven't reached the halting condition, we've got a 4 on the stack. The four characters after the
(
are eaten, and control loops around to the beginning of the code. Here )
regurgitates four characters, s
switches back to snake 2's stack, and execution continues.
- If the counter has passed
n
, we've got a 0 on the stack, and nothing is eaten. sn
switches to snake 2's stack and outputs the top value as a number; then 1(
eats the last character and dies.
The result is that the product (r+1)*(r+2)*...*n
is calculated and output.
Try it out
// Define Stack class
function Stack() {
this.stack = [];
this.length = 0;
}
Stack.prototype.push = function(item) {
this.stack.push(item);
this.length++;
}
Stack.prototype.pop = function() {
var result = 0;
if (this.length > 0) {
result = this.stack.pop();
this.length--;
}
return result;
}
Stack.prototype.top = function() {
var result = 0;
if (this.length > 0) {
result = this.stack[this.length - 1];
}
return result;
}
Stack.prototype.toString = function() {
return "" + this.stack;
}
// Define Snake class
function Snake(code) {
this.code = code;
this.length = this.code.length;
this.ip = 0;
this.ownStack = new Stack();
this.currStack = this.ownStack;
this.alive = true;
this.wait = 0;
this.partialString = this.partialNumber = null;
}
Snake.prototype.step = function() {
if (!this.alive) {
return null;
}
if (this.wait > 0) {
this.wait--;
return null;
}
var instruction = this.code.charAt(this.ip);
var output = null;
console.log("Executing instruction " + instruction);
if (this.partialString !== null) {
// We're in the middle of a double-quoted string
if (instruction == '"') {
// Close the string and push its character codes in reverse order
for (var i = this.partialString.length - 1; i >= 0; i--) {
this.currStack.push(this.partialString.charCodeAt(i));
}
this.partialString = null;
} else {
this.partialString += instruction;
}
} else if (instruction == '"') {
this.partialString = "";
} else if ("0" <= instruction && instruction <= "9") {
if (this.partialNumber !== null) {
this.partialNumber = this.partialNumber + instruction; // NB: concatenation!
} else {
this.partialNumber = instruction;
}
next = this.code.charAt((this.ip + 1) % this.length);
if (next < "0" || "9" < next) {
// Next instruction is non-numeric, so end number and push it
this.currStack.push(+this.partialNumber);
this.partialNumber = null;
}
} else if ("a" <= instruction && instruction <= "f") {
// a-f push numbers 10 through 15
var value = instruction.charCodeAt(0) - 87;
this.currStack.push(value);
} else if (instruction == "$") {
// Toggle the current stack
if (this.currStack === this.ownStack) {
this.currStack = this.program.sharedStack;
} else {
this.currStack = this.ownStack;
}
} else if (instruction == "s") {
this.currStack = this.ownStack;
} else if (instruction == "S") {
this.currStack = this.program.sharedStack;
} else if (instruction == "l") {
this.currStack.push(this.ownStack.length);
} else if (instruction == "L") {
this.currStack.push(this.program.sharedStack.length);
} else if (instruction == ".") {
var item = this.currStack.pop();
this.currStack.push(item);
this.currStack.push(item);
} else if (instruction == "m") {
var item = this.ownStack.pop();
this.program.sharedStack.push(item);
} else if (instruction == "M") {
var item = this.program.sharedStack.pop();
this.ownStack.push(item);
} else if (instruction == "y") {
var item = this.ownStack.top();
this.program.sharedStack.push(item);
} else if (instruction == "Y") {
var item = this.program.sharedStack.top();
this.ownStack.push(item);
} else if (instruction == "\\") {
var top = this.currStack.pop();
var next = this.currStack.pop()
this.currStack.push(top);
this.currStack.push(next);
} else if (instruction == "@") {
var c = this.currStack.pop();
var b = this.currStack.pop();
var a = this.currStack.pop();
this.currStack.push(c);
this.currStack.push(a);
this.currStack.push(b);
} else if (instruction == ";") {
this.currStack.pop();
} else if (instruction == "+") {
var b = this.currStack.pop();
var a = this.currStack.pop();
this.currStack.push(a + b);
} else if (instruction == "-") {
var b = this.currStack.pop();
var a = this.currStack.pop();
this.currStack.push(a - b);
} else if (instruction == "*") {
var b = this.currStack.pop();
var a = this.currStack.pop();
this.currStack.push(a * b);
} else if (instruction == "/") {
var b = this.currStack.pop();
var a = this.currStack.pop();
this.currStack.push(a / b);
} else if (instruction == "%") {
var b = this.currStack.pop();
var a = this.currStack.pop();
this.currStack.push(a % b);
} else if (instruction == "_") {
this.currStack.push(-this.currStack.pop());
} else if (instruction == "I") {
var value = this.currStack.pop();
if (value < 0) {
this.currStack.push(Math.ceil(value));
} else {
this.currStack.push(Math.floor(value));
}
} else if (instruction == ">") {
var b = this.currStack.pop();
var a = this.currStack.pop();
this.currStack.push(+(a > b));
} else if (instruction == "<") {
var b = this.currStack.pop();
var a = this.currStack.pop();
this.currStack.push(+(a < b));
} else if (instruction == "=") {
var b = this.currStack.pop();
var a = this.currStack.pop();
this.currStack.push(+(a == b));
} else if (instruction == "!") {
this.currStack.push(+ !this.currStack.pop());
} else if (instruction == "?") {
this.currStack.push(Math.random());
} else if (instruction == "n") {
output = "" + this.currStack.pop();
} else if (instruction == "o") {
output = String.fromCharCode(this.currStack.pop());
} else if (instruction == "r") {
var input = this.program.io.getNumber();
this.currStack.push(input);
} else if (instruction == "i") {
var input = this.program.io.getChar();
this.currStack.push(input);
} else if (instruction == "(") {
this.length -= Math.floor(this.currStack.pop());
this.length = Math.max(this.length, 0);
} else if (instruction == ")") {
this.length += Math.floor(this.currStack.pop());
this.length = Math.min(this.length, this.code.length);
} else if (instruction == "w") {
this.wait = this.currStack.pop();
}
// Any unrecognized character is a no-op
if (this.ip >= this.length) {
// We've swallowed the IP, so this snake dies
this.alive = false;
this.program.snakesLiving--;
} else {
// Increment IP and loop if appropriate
this.ip = (this.ip + 1) % this.length;
}
return output;
}
Snake.prototype.getHighlightedCode = function() {
var result = "";
for (var i = 0; i < this.code.length; i++) {
if (i == this.length) {
result += '<span class="swallowedCode">';
}
if (i == this.ip) {
if (this.wait > 0) {
result += '<span class="nextActiveToken">';
} else {
result += '<span class="activeToken">';
}
result += escapeEntities(this.code.charAt(i)) + '</span>';
} else {
result += escapeEntities(this.code.charAt(i));
}
}
if (this.length < this.code.length) {
result += '</span>';
}
return result;
}
// Define Program class
function Program(source, speed, io) {
this.sharedStack = new Stack();
this.snakes = source.split(/\r?\n/).map(function(snakeCode) {
var snake = new Snake(snakeCode);
snake.program = this;
snake.sharedStack = this.sharedStack;
return snake;
}.bind(this));
this.snakesLiving = this.snakes.length;
this.io = io;
this.speed = speed || 10;
this.halting = false;
}
Program.prototype.run = function() {
this.step();
if (this.snakesLiving) {
this.timeout = window.setTimeout(this.run.bind(this), 1000 / this.speed);
}
}
Program.prototype.step = function() {
for (var s = 0; s < this.snakes.length; s++) {
var output = this.snakes[s].step();
if (output) {
this.io.print(output);
}
}
this.io.displaySource(this.snakes.map(function (snake) {
return snake.getHighlightedCode();
}).join("<br>"));
}
Program.prototype.halt = function() {
window.clearTimeout(this.timeout);
}
var ioFunctions = {
print: function (item) {
var stdout = document.getElementById('stdout');
stdout.value += "" + item;
},
getChar: function () {
if (inputData) {
var inputChar = inputData[0];
inputData = inputData.slice(1);
result = inputChar.charCodeAt(0);
} else {
result = -1;
}
var stdinDisplay = document.getElementById('stdin-display');
stdinDisplay.innerHTML = escapeEntities(inputData);
return result;
},
getNumber: function () {
while (inputData && (inputData[0] < "0" || "9" < inputData[0])) {
inputData = inputData.slice(1);
}
if (inputData) {
var inputNumber = inputData.match(/\d+/)[0];
inputData = inputData.slice(inputNumber.length);
result = +inputNumber;
} else {
result = -1;
}
var stdinDisplay = document.getElementById('stdin-display');
stdinDisplay.innerHTML = escapeEntities(inputData);
return result;
},
displaySource: function (formattedCode) {
var sourceDisplay = document.getElementById('source-display');
sourceDisplay.innerHTML = formattedCode;
}
};
var program = null;
var inputData = null;
function showEditor() {
var source = document.getElementById('source'),
sourceDisplayWrapper = document.getElementById('source-display-wrapper'),
stdin = document.getElementById('stdin'),
stdinDisplayWrapper = document.getElementById('stdin-display-wrapper');
source.style.display = "block";
stdin.style.display = "block";
sourceDisplayWrapper.style.display = "none";
stdinDisplayWrapper.style.display = "none";
source.focus();
}
function hideEditor() {
var source = document.getElementById('source'),
sourceDisplay = document.getElementById('source-display'),
sourceDisplayWrapper = document.getElementById('source-display-wrapper'),
stdin = document.getElementById('stdin'),
stdinDisplay = document.getElementById('stdin-display'),
stdinDisplayWrapper = document.getElementById('stdin-display-wrapper');
source.style.display = "none";
stdin.style.display = "none";
sourceDisplayWrapper.style.display = "block";
stdinDisplayWrapper.style.display = "block";
var sourceHeight = getComputedStyle(source).height,
stdinHeight = getComputedStyle(stdin).height;
sourceDisplayWrapper.style.minHeight = sourceHeight;
sourceDisplayWrapper.style.maxHeight = sourceHeight;
stdinDisplayWrapper.style.minHeight = stdinHeight;
stdinDisplayWrapper.style.maxHeight = stdinHeight;
sourceDisplay.textContent = source.value;
stdinDisplay.textContent = stdin.value;
}
function escapeEntities(input) {
return input.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
}
function resetProgram() {
var stdout = document.getElementById('stdout');
stdout.value = null;
if (program !== null) {
program.halt();
}
program = null;
inputData = null;
showEditor();
}
function initProgram() {
var source = document.getElementById('source'),
stepsPerSecond = document.getElementById('steps-per-second'),
stdin = document.getElementById('stdin');
program = new Program(source.value, +stepsPerSecond.innerHTML, ioFunctions);
hideEditor();
inputData = stdin.value;
}
function runBtnClick() {
if (program === null || program.snakesLiving == 0) {
resetProgram();
initProgram();
} else {
program.halt();
var stepsPerSecond = document.getElementById('steps-per-second');
program.speed = +stepsPerSecond.innerHTML;
}
program.run();
}
function stepBtnClick() {
if (program === null) {
initProgram();
} else {
program.halt();
}
program.step();
}
function sourceDisplayClick() {
resetProgram();
}
function stdinDisplayClick() {
resetProgram();
}
.container {
width: 100%;
}
.so-box {
font-family:'Helvetica Neue', Arial, sans-serif;
font-weight: bold;
color: #fff;
text-align: center;
padding: .3em .7em;
font-size: 1em;
line-height: 1.1;
border: 1px solid #c47b07;
-webkit-box-shadow: 0 2px 2px rgba(0, 0, 0, 0.3), 0 2px 0 rgba(255, 255, 255, 0.15) inset;
text-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
background: #f88912;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.3), 0 2px 0 rgba(255, 255, 255, 0.15) inset;
}
.control {
display: inline-block;
border-radius: 6px;
float: left;
margin-right: 25px;
cursor: pointer;
}
.option {
padding: 10px 20px;
margin-right: 25px;
float: left;
}
h1 {
text-align: center;
font-family: Georgia, 'Times New Roman', serif;
}
a {
text-decoration: none;
}
input, textarea {
box-sizing: border-box;
}
textarea {
display: block;
white-space: pre;
overflow: auto;
height: 50px;
width: 100%;
max-width: 100%;
min-height: 25px;
}
span[contenteditable] {
padding: 2px 6px;
background: #cc7801;
color: #fff;
}
#stdout-container, #stdin-container {
height: auto;
padding: 6px 0;
}
#reset {
float: right;
}
#source-display-wrapper , #stdin-display-wrapper{
display: none;
width: 100%;
height: 100%;
overflow: auto;
border: 1px solid black;
box-sizing: border-box;
}
#source-display , #stdin-display{
font-family: monospace;
white-space: pre;
padding: 2px;
}
.activeToken {
background: #f93;
}
.nextActiveToken {
background: #bbb;
}
.swallowedCode{
color: #999;
}
.clearfix:after {
content:".";
display: block;
height: 0;
clear: both;
visibility: hidden;
}
.clearfix {
display: inline-block;
}
* html .clearfix {
height: 1%;
}
.clearfix {
display: block;
}
<!--
Designed and written 2015 by D. Loscutoff
Much of the HTML and CSS was taken from this Befunge interpreter by Ingo Bürk: http://codegolf.stackexchange.com/a/40331/16766
-->
<div class="container">
<textarea id="source" placeholder="Enter your program here" wrap="off">Sr.r-s1(
)s.!+S1+.@@.@\<!@@*Ys.!+*S.!!4*.(sn1(</textarea>
<div id="source-display-wrapper" onclick="sourceDisplayClick()"><div id="source-display"></div></div></div><div id="stdin-container" class="container">
<textarea id="stdin" placeholder="Input" wrap="off">3P2</textarea>
<div id="stdin-display-wrapper" onclick="stdinDisplayClick()"><div id="stdin-display"></div></div></div><div id="controls-container" class="container clearfix"><input type="button" id="run" class="control so-box" value="Run" onclick="runBtnClick()" /><input type="button" id="pause" class="control so-box" value="Pause" onclick="program.halt()" /><input type="button" id="step" class="control so-box" value="Step" onclick="stepBtnClick()" /><input type="button" id="reset" class="control so-box" value="Reset" onclick="resetProgram()" /></div><div id="stdout-container" class="container"><textarea id="stdout" placeholder="Output" wrap="off" readonly></textarea></div><div id="options-container" class="container"><div class="option so-box">Steps per Second:
<span id="steps-per-second" contenteditable>20</span></div></div>
split
to split the input at theP
? What about a function that converts a string to a number? \$\endgroup\$0 <= r <= n
? \$\endgroup\$