Ouroboros adalah esolang yang saya rancang minggu ini. Saatnya untuk berputar!
i.1+!57*(\m1(M\1).96>.@32*-.80=\.78=3*\.66=3*\.82=5*\81=9*++++\2*1\-*+
)L!4*(4Sn1(
Setiap baris perintah single-char 1 mewakili ular ouroboros, di mana eksekusi berlangsung dari kepala (mulai) hingga ekor (ujung) dan loop kembali ke kepala. The (
dan )
perintah membiarkan Anda makan bagian dari ekor atau memuntahkan itu, sehingga mengubah apa perintah dijalankan. Jika penunjuk instruksi pernah ditelan, ular mati (berhenti mengeksekusi). Program Ouroboros terdiri dari satu atau lebih ular yang dieksekusi secara paralel. Setiap ular memiliki tumpukan sendiri, dan ada juga tumpukan bersama.
1 Satu pengecualian, yang membedakan Ouroboros dari banyak bahasa 2D: angka multi-digit dapat ditulis dengan mudah, tanpa harus melakukan matematika atau menekan angka 0 terlebih dahulu.
Ular 1
Ular pertama membaca karakter ( i
) dan memeriksa apakah itu -1 / EOF ( .1+!
). Jika demikian, ia memakan sebagian besar ekornya, hingga dan termasuk M
( 57*(
).
Ular kemudian menukar kode karakter dengan penghitungan yang di atasnya pada stack ( \
), memindahkan penghitungan ke stack bersama ( m
), dan menelan karakter lain ( 1(
). Jika sudah menelan banyak, ini berarti menelan (
bahwa IP saat ini hidup dan mati. Jika tidak, eksekusi akan dilanjutkan dengan menggerakkan penghitungan kembali ke tumpukan ular 1, menukar dengan kode char, dan memuntahkan karakter yang sebelumnya ditelan ( M\1)
).
Kami kemudian menggunakan operasi matematika dan tumpukan untuk menghasilkan skor yang sesuai untuk karakter. .96>
menguji apakah huruf kecil atau tidak; 32*-
konversi selanjutnya ke huruf besar. Kemudian bentangan panjang dari .80=
ke 81=9*++++
peta P
-> 1
, N
-> 3
, dll. Akhirnya, \2*1\-*
meniadakan skor jika huruf itu huruf kecil, dan +
menambahkannya ke penghitungan berjalan. Ular itu kemudian loop dan membaca karakter lain.
Ular 2
Ular kedua dimulai dengan operasi memuntahkan ( )
), yang tidak melakukan apa pun pertama kali melalui (karena belum ada yang ditelan, dan juga sejak muncul tumpukan memberi kosong 0
). Selanjutnya, ia mendorong panjang tumpukan bersama ke tumpukannya sendiri dan secara logis meniadakan ( L!
). Ini memberi 1
jika tumpukan kosong, 0
jika tidak. Ular itu bertambah banyak dengan 4 dan memakan banyak karakter ( 4*(
).
Jika tumpukan bersama kosong, ini berarti ular sekarang berakhir sebelum S
. Ini mendorong 4
dan loop kembali ke )
, di mana ia memuntahkan karakter yang baru saja ditelan dan mulai lagi.
Namun, jika ada nilai pada tumpukan bersama, tidak ada karakter yang ditelan dan eksekusi berlanjut. Ular beralih ke tumpukan bersama dan menampilkan nomor di sana ( Sn
); kemudian ia menelan karakter terakhirnya dan mati ( 1(
).
Sinkronisasi
Kedua ular harus disinkronkan dengan hati-hati sehingga tidak pernah ada nilai pada tumpukan bersama ketika ular 2 melakukan pemeriksaannya, sampai akhir input tercapai. Snake 1 memberi nilai pada stack yang dibagikan secara singkat pada setiap pass melalui loop-nya. Dengan demikian, L
perintah snake 2 tidak boleh dieksekusi antara perintah m
dan M
di snake 1. Untungnya, ular berbaris dengan sangat baik. Yang terpenting, panjang lingkaran ular 1 (70 instruksi) adalah kelipatan lingkaran ular 2 (7 instruksi), sehingga keduanya tidak akan pernah keluar dari sinkronisasi:
i.1+!57*(\m1(M\1).96>.@32*-.80=\.78=3*\.66=3*\.82=5*\81=9*++++\2*1\-*+
)L!5*(5)L!5*(5)L!5*(5)L!5*(5)L!5*(5)L!5*(5)L!5*(5)L!5*(5)L!5*(5)L!5*(5
|__|
Danger zone
Jika angkanya tidak berjalan dengan sempurna, saya akan memberi satu atau dua ular dengan spasi untuk membuatnya sejajar yang diperlukan.
Semua ini sangat baik, tetapi saya ingin melihatnya beraksi!
Berikut adalah program di atas via Stack Snippet. Bahkan pada 1000 operasi per detik, dibutuhkan sekitar 10 detik untuk mengeluarkan jawaban untuk input sampel - tetapi ia sampai di sana!
// 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;
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 instruction not covered by the above cases is ignored
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;
}
// 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() {
if (this.snakesLiving) {
this.step();
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);
}
}
}
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);
return inputChar.charCodeAt(0);
} else {
return -1;
}
},
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);
return +inputNumber;
} else {
return -1;
}
}
};
var program = null;
var inputData = null;
function resetProgram() {
var stdout = document.getElementById('stdout');
stdout.value = null;
if (program !== null) {
program.halt();
}
program = null;
inputData = null;
}
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);
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();
}
.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: 40px;
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 {
display: none;
width: 100%;
height: 100%;
overflow: auto;
border: 1px solid black;
box-sizing: border-box;
}
#source-display {
font-family: monospace;
white-space: pre;
padding: 2px;
}
.activeToken {
background: #f88912;
}
.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">i.1+!57*(\m1(M\1).96>.@32*-.80=\.78=3*\.66=3*\.82=5*\81=9*++++\2*1\-*+
)L!4*(4Sn1(</textarea>
<div id="source-display-wrapper">
<div id="source-display"></div>
</div>
</div>
<div id="stdin-container" class="container">
<textarea id="stdin" placeholder="Input" wrap="off">5k2/ppp5/4P3/3R3p/6P1/1K2Nr2/PP3P2/8</textarea>
</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>1000</span>
</div>
</div>