Прочитал статью «Эстафета из 50-ти квайнов». Действительно, человек написал потрясающую штуку, колоссальный труд, настоящее произведение искусства. Но по комментам судя, многие не понимают, как подобные вещи делаются и полагают их чем-то на грани, если не за гранью, человеческих возможностей, особенно много эмоций было по поводу эзотерических языков (Brainfuck, Unlambda, Whitespace) в списке.
В этой статье я попытаюсь объяснить, как подобные квайны пишутся.
Вот код на питоне, который генерирует код на брейнфаке, который генерирует код на анлямбде, который генерирует первоначальный код на питоне:
Я попытался сделать код максимально понятным, тем не менее, на всякий случай, пройдусь по нему построчно.
В первой строчке закодированы все следующие строки в питоновском представлении. эта строчка пишется последней, после того как весь програмный код написан, он кодируется.
Соответственно, текст программы целиком можно представить следующей строкой:
Функция unlambda получает текст и возвращает программу на unlambda его печатающую.
Функция brainfuck — то же самое для brainfuck.
Ну и соответственно
это строка, представляющая из себя программу на Brainfuck, выводящую программу на Unlambda, выводящую исходную программу на
Питоне.
В цепочку легко добавлять новые языки, для каждого языка пишется функция, которая получает строчку и возвращает программу на этом языке, которая эту строчку печатает, затем эта функция добавляется к строчке вывода, ну и соответственно data вычисляется заново.
Апдейт.
Посмотрел на дискуссию в комментах и понял, что нужно объяснить получше.
Квайноподобную конструкцию (здесь и далее речь только о языках, где можно разделить данные и код) можно поделить на две части:
1) сегмент данных (важный момент, эта часть остаётся незаполненной до того момента, когда код будет завершён, после этого он кодируется).
2) сегмент кода
Сегмент данных — это некий шифр, который можно расшифровать двумя способами, в результате расшифровки первым способом получится строковое представление собственно сегмента данных, при расшифровке вторым способом получится строковое представление сегмента кода. Объединив результаты этих расшифровок, мы получим строку, в которой содержится весь исходный код программы.
В вышеприведённом примере, такая строка это - 'data = %s'%`data`+chr(10)+data.
'data = %s'%`data` — расшифровка дающая сегмент данных, data — расшифровка дающая сегмент кода, ну и chr(10) — переход строки между ними.
А дальше, после того, как у нас в руках есть эта волшебная строчка, мы можем сделать с ней всё что захотим. Если мы её просто распечатаем, получим обычный квайн:
Если перевернём, получим программу, которая печатает свой текст задом-наперёд:
Можем написать программу, которая печатает своё шестнадцатеричное представление:
В общем можем написать программу, которая печатает свой код после любой трансформации (ну или цепочки трансформаций, как в нашем случае). «Шифр» не обязан быть именно внутренним питоновским представлением строк, можно зашифровать например в виде ascii-кодов или ещё как-нибудь и соответствующем образом изменить процедуру расшифровки:
В этой статье я попытаюсь объяснить, как подобные квайны пишутся.
Вот код на питоне, который генерирует код на брейнфаке, который генерирует код на анлямбде, который генерирует первоначальный код на питоне:
data = 'def brainfuck(st): return "".join(["+"*ord(c)+".>" for c in st])\ndef unlambda(st): return "`r"+"`"*len(st)+"".join(["."+e for e in st])+"i"\nprint brainfuck(unlambda(\'data = %s\'%`data`+chr(10)+data))'
def brainfuck(st): return "".join(["+"*ord(c)+".>" for c in st])
def unlambda(st): return "`r"+"`"*len(st)+"".join(["."+e for e in st])+"i"
print brainfuck(unlambda('data = %s'%`data`+chr(10)+data))
Я попытался сделать код максимально понятным, тем не менее, на всякий случай, пройдусь по нему построчно.
В первой строчке закодированы все следующие строки в питоновском представлении. эта строчка пишется последней, после того как весь програмный код написан, он кодируется.
>>> data = 'def brainfuck(st): return "".join(["+"*ord(c)+".>" for c in st])\ndef unlambda(st): return "`r"+"`"*len(st)+"".join(["."+e for e in st])+"i"\nprint brainfuck(unlambda(\'data = %s\'%`data`+chr(10)+data))'
>>> print data
def brainfuck(st): return "".join(["+"*ord(c)+".>" for c in st])
def unlambda(st): return "`r"+"`"*len(st)+"".join(["."+e for e in st])+"i"
print brainfuck(unlambda('data = %s'%`data`+chr(10)+data))
Соответственно, текст программы целиком можно представить следующей строкой:
'data = %s'%`data`+chr(10)+data
Функция unlambda получает текст и возвращает программу на unlambda его печатающую.
Функция brainfuck — то же самое для brainfuck.
Ну и соответственно
brainfuck(unlambda('data = %s'%`data`+chr(10)+data))
это строка, представляющая из себя программу на Brainfuck, выводящую программу на Unlambda, выводящую исходную программу на
Питоне.
В цепочку легко добавлять новые языки, для каждого языка пишется функция, которая получает строчку и возвращает программу на этом языке, которая эту строчку печатает, затем эта функция добавляется к строчке вывода, ну и соответственно data вычисляется заново.
Апдейт.
Посмотрел на дискуссию в комментах и понял, что нужно объяснить получше.
Квайноподобную конструкцию (здесь и далее речь только о языках, где можно разделить данные и код) можно поделить на две части:
1) сегмент данных (важный момент, эта часть остаётся незаполненной до того момента, когда код будет завершён, после этого он кодируется).
2) сегмент кода
Сегмент данных — это некий шифр, который можно расшифровать двумя способами, в результате расшифровки первым способом получится строковое представление собственно сегмента данных, при расшифровке вторым способом получится строковое представление сегмента кода. Объединив результаты этих расшифровок, мы получим строку, в которой содержится весь исходный код программы.
В вышеприведённом примере, такая строка это - 'data = %s'%`data`+chr(10)+data.
'data = %s'%`data` — расшифровка дающая сегмент данных, data — расшифровка дающая сегмент кода, ну и chr(10) — переход строки между ними.
А дальше, после того, как у нас в руках есть эта волшебная строчка, мы можем сделать с ней всё что захотим. Если мы её просто распечатаем, получим обычный квайн:
data = "l = 'data = %s'%`data`+chr(10)+data\nprint l"
l = 'data = %s'%`data`+chr(10)+data
print l
Если перевернём, получим программу, которая печатает свой текст задом-наперёд:
data = "l = 'data = %s'%`data`+chr(10)+data\nprint l[-1::-1]"
l = 'data = %s'%`data`+chr(10)+data
print l[-1::-1]
Можем написать программу, которая печатает своё шестнадцатеричное представление:
data = "l = 'data = %s'%`data`+chr(10)+data\nprint ' '.join([hex(ord(e)) for e in l])"
l = 'data = %s'%`data`+chr(10)+data
print ' '.join([hex(ord(e)) for e in l])
В общем можем написать программу, которая печатает свой код после любой трансформации (ну или цепочки трансформаций, как в нашем случае). «Шифр» не обязан быть именно внутренним питоновским представлением строк, можно зашифровать например в виде ascii-кодов или ещё как-нибудь и соответствующем образом изменить процедуру расшифровки:
data = [108, 32, 61, 32, 39, 100, 97, 116, 97, 32, 61, 32, 37, 115, 39, 37, 100, 97, 116, 97, 43, 99, 104, 114, 40, 49, 48, 41, 43, 39, 39, 46, 106, 111, 105, 110, 40, 91, 99, 104, 114, 40, 101, 41, 32, 102, 111, 114, 32, 101, 32, 105, 110, 32, 100, 97, 116, 97, 93, 41, 10, 112, 114, 105, 110, 116, 32, 108]
l = 'data = %s'%data+chr(10)+''.join([chr(e) for e in data])
print l