Ouroboros - це езоланг, який я розробив цього тижня. Час взяти це за спину!
i.1+!57*(\m1(M\1).96>.@32*-.80=\.78=3*\.66=3*\.82=5*\81=9*++++\2*1\-*+
)L!4*(4Sn1(
Кожен рядок однозначних команд 1 являє собою вуроборову змію, в якій виконання триває від голови (початку) до хвоста (кінця) і петлі назад до голови. Команди (
і )
дозволяють з'їсти частину хвоста або відкотити його, тим самим змінюючи, які команди виконуються. Якщо вказівник інструкції колись проковтнеться, змія гине (припиняє виконання). Програма Ouroboros складається з однієї або декількох змій, які виконуються паралельно. У кожної змії є своя стопка, а також є спільний стек.
1 Один виняток, який відрізняє Уробороса від багатьох двовимірних мов: багатоцифрові числа можна писати прямо, без того, щоб робити математику чи натискати 0 спочатку.
Змія 1
Перша змія зчитує символ ( i
) і перевіряє, чи -1 / EOF ( .1+!
). Якщо так, він з'їдає більшу частину хвоста, аж до M
( 57*(
).
Потім змія поміняє код символу на підрахунок, який знаходиться над ним на стеці ( \
), перемістить таблицю на спільний стек ( m
) і проковтує інший символ ( 1(
). Якщо він уже проковтнув купу, це означає, що він проковтне той (
факт, що IP зараз увімкнено, і гине. В іншому випадку виконання відбувається шляхом переміщення талію назад до стека змії 1, замінивши його char-кодом та повторно регулюючи характер, який раніше був проковтнутий ( M\1)
).
Потім ми використовуємо математичні та стекові операції, щоб генерувати відповідний бал для персонажа. .96>
тестує, чи це малі чи ні; наступні 32*-
перетворюються на великі регістри. Потім довга тяганина .80=
до 81=9*++++
карт P
-> 1
, N
-> 3
і т. Д. Нарешті, \2*1\-*
заперечує рахунок, якщо літера була малою літерою, і +
додає її до запущеної таблиці. Потім змія замикається і читає іншого символу.
Змія 2
Друга змія починається з відрижкової операції ( )
), яка нічого не робить з першого разу (оскільки нічого ще не проковтнулося, а також з моменту появи порожнього стека 0
). Далі він висуває довжину спільного стека до власної стеки та логічно заперечує ( L!
). Це дає, 1
якщо стек порожній, 0
інакше. Змія множиться на 4 і з'їдає багато символів ( 4*(
).
Якщо загальний стек був порожнім, це означає, що змія зараз закінчується перед S
. Він штовхається 4
і петлі повертається назад до місця )
, де він відриває символів, які він щойно проковтнув, і починається заново.
Якщо в спільному стеку було значення, проте жодні символи не проковтуються і виконання продовжується. Змія перемикається на загальний стек і видає там номер ( Sn
); потім він ковтає свого останнього персонажа і вмирає ( 1(
).
Синхронізація
Дві змії повинні бути ретельно синхронізовані, щоб ніколи не було значення на спільному стеку, коли змія 2 робить свою перевірку, до кінця введення. Snake 1 кладе значення на спільний стек на короткий час кожного проходу через його цикл. Таким чином, багатожильний 2 в L
команді ніколи не повинна виконуватися між m
і M
командами в змії 1. До щастя, змій вибудовується дуже добре. Найважливіше, що довжина циклу змії 1 (70 інструкцій) є кратним циклом змії 2 (7 інструкцій), тому вони ніколи не вийдуть із синхронізації:
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
Якби цифри не спрацювали так ідеально, я б проклав одну або обидві змії пробілами, щоб вони вирівнялися за потребою.
Все це дуже добре, але я хочу бачити це в дії!
Ось наведена вище програма через Stack Snippet. Навіть на 1000 операцій в секунду потрібно 10 хвилин, щоб виплюнути відповідь на зразок введення - але він дійсно туди потрапляє!
// 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>