Что такое куайн или квайн программирование?
Куайн, квайн (англ. quine) — компьютерная программа (частный случай метапрограммирования), которая выдаёт на выходе точную копию своего исходного текста.
На днях столкнулся с подобной задачей: реализовать на С++ куайн или квайн, но с одним но - вывести текст не на стандартное устройство вывода (например, консоль), а во внешний текстовый файлик (например, output.txt).
Пришлось немного поломать голову. С одной стороны - просто, а с другой начинаешь себя спрашивать - с какого угла подойти? Немного помогла статья с "хабры" -
"Как писать квайны":
Введение
Многие программисты считают написание квайнов
(программ, выводящих свой исходный код) непосильной задачей. И
действительно — все эти цепные квайны и квайны различного порядка, при
взгляде на которые можно потеряться в казалось бы бессмысленном наборе
символов…
Однако на самом деле написать квайн на каком-либо языке не так сложно,
как кажется. Сейчас я расскажу, как сделать это на различных языках
программирования. Более того, мы не будем использовать «хаки»
интерпретеруемых языков вроде операции вывода исходного кода и функций
типа eval, а также напишем квайны на интерпретируемых и компилируемых
языках.
Теория
Попробуем написать квайн. Для этого возьмём инструкцию языка для вывода и
передадим ей как параметр код программы. Однако в коде мы снова
используем этот же код и так далее — возникает бесконечная рекурсия. Но
что можно сделать для того, чтобы не передавать строковую константу?
Решение — поместить строку (копию части кода) в переменную. Для удобства
назовём такую строку s-строкой, а переменную с этой строкой — s-переменной.
Чтобы и в s-переменной не было рекурсии, мы просто исключим из неё
фрагмент со значением этой самой переменной. То есть, выглядеть это
будет примерно так:
C:
char s[]="char s[]=;";
Примечание. Как в этом фрагменте, так и в квайнах, которые
получатся в итоге, для простоты мы не будем соблюдать правила
форматирования кода. Тем не менее, их соблюдение вы сможете без труда
добавить самостоятельно после прочтения статьи.
Далее, при выводе, мы подставим значение s-строки в её же определение в
коде (в примере выше — перед тремя последними символами). Здесь же
возникает ещё несколько проблем. Первая проблема — при подставлении в
s-строке нельзя использовать символы, которые поведут себя в коде не
так, как надо. Например, мы не можем так просто вставить кавычку — ведь
вместо того, чтобы стать частью s-строки, она завершит её определение и
выводимый код не будет совпадать с исходным, являясь некорректным
вообще.
Экранирование применить здесь достаточно сложно — символ экранирования
надо тоже экранировать и т.д.. Гораздо проще, например, использовать
другой вариант кавычек — так, во многих интерпретируемых языках
разрешено использование как одинарных, так и двойных кавычек для задания
строки, а отличие состоит в том, что можно без проблем использовать
одну кавычку в константе, если она ограничивается другими. То есть, код
'"'
создаст односимвольную строку с двойной кавычкой, а код "'"
— с одиночной. Если использовать этот вариант, удобно задать в начале
переменную с какой-либо кавычкой, а затем использовать её при выводе.
Но и этот вариант не универсален: в Си, к примеру, есть лишь один
вариант кавычек. Тогда можно использовать другой способ — задавать
кавычку кодом символа, печатая символ с таким кодом при выводе.
Следующая проблема — вставка другой строки (или символа с каким-либо
кодом) в вывод s-строки. Решение здесь очевидно — брать подстроку
s-строки специальной функцией, выводить её, далее выводить то, что надо
вставить, затем выводить другую подстроку s-строки. Может показаться,
что в Си взятие подстроки для вывода потребует немало кода. Тут нам на
помощь придёт мощь функции printf. Так, например, вот варианты кода для
различных языков, печатающего часть строки s со второго символа (считая с
единицы) по четвёртый включительно:
Python:
print(s[1:4])
Ruby:
print s[1..3]
Perl:
print substr(s,1,2)
C:
printf("%.2s",s+1);
Обычно методы взятия подстроки могут также брать её остаток до конца.
Например, напечатаем строку s со второго символа до конца строки (то
есть, всю строку кроме первого символа):
Python:
print(s[1:])
Ruby:
print s[1..-1]
Perl:
print substr(s,1)
C:
printf("%s",s+1);
Если такой возможности нет, придётся на место параметра с длиной
подстроки поставить заглушку типа «XX», а затем в конце посчитать
символы до конца и подставить их вместо «XX» в коде и в s-строке, не
изменяя длины различных частей кода. Например, если в длине окажется
одна цифра, целесообразно подставить вместо первого икса пробел, ведь
если его удалить, длины частей s-строки изменятся и их придётся
пересчитывать.
Интерпретируемые языки
Итак, начнём писать квайны, собрав все суждения выше. На Python я написал такой квайн (работает и на 3.x):
q="'";s='q="";s=;print(s[:3]+q+s[3:7]+q+s+q+s[7:])';print(s[:3]+q+s[3:7]+q+s+q+s[7:])
Здесь переменная q используется как переменная, где хранится одинарная
кавычка, далее идёт определение s-переменной со всем кодом, кроме самой
s-строки. После этого идёт вывод s-переменной со следующими вставками:
1). Одинарная кавычка как значение переменной q;
2). Одинарная кавычка как начало определения s-строки;
3). Сама s-строка (да-да, s-строка вставляется внутри s-строки);
4). Одинарная кавычка как конец определения s-строки.Примечание. При написании квайнов по данному методу не забывайте копировать все изменения в коде в копию кода в s-строке.
С минимальными изменениями можно получить квайн только для
Python 2.x:
q="'";s='q="";s=;print s[:3]+q+s[3:7]+q+s+q+s[7:]';print s[:3]+q+s[3:7]+q+s+q+s[7:]
Абсолютно аналогичны и квайны на других языках, где мы изменяем лишь некоторые синтаксические особенности:
Ruby:
q="'";s='q="";s=;print s[0..2]+q+s[3..6]+q+s+q+s[7..-1]';print s[0..2]+q+s[3..6]+q+s+q+s[7..-1]
Perl:
$q="'";$s='$q="";$s=;print substr($s,0,4).$q.substr($s,4,5).$q.$s.$q.substr($s,9)';
print substr($s,0,4).$q.substr($s,4,5).$q.$s.$q.substr($s,9)
PHP:
<?$q="'";$s='<?$q="";$s=;print substr($s,0,6).$q.substr($s,6,5).$q.$s.$q.substr($s,11);'
;print substr($s,0,6).$q.substr($s,6,5).$q.$s.$q.substr($s,11);
Компилируемые языки.
Написание квайна на C оказалось чуть более трудной задачей. Здесь
я активно использовал коды символов: двойной кавычки — 34, и перевода
строки — 13 (он понадобился, чтобы отделить директиву компилятора для
включения stdio.h), а также интересный способ взятия подстроки с помощью
printf, уже описанный выше.
А вот и сам квайн:
int main(){const char *s="#include <stdio.h>int main(){const char *s=;
printf(%.18s%c%.25s%c%s%c%.8s%c%.33s%c%s,s,10,s+18,34,s,34,s+43,34,s+51,34,s+84);
return 0;}";printf("%.18s%c%.25s%c%s%c%.8s%c%.33s%c%s",s,10,s+18,34,s,34,s+43,34,s+51,34,s+84);
return 0;}
Заключение.
Вот и всё. Я написал квайны на большинстве языков, интерпретаторы и
компиляторы которых обнаружил на своём компьютере. Думаю, теперь вы и
сами напишете подобную программу на своём любимом языке
программирования, если я не упомянул его здесь. В качестве упражнения вы
также можете написать квайн на таких языках, как Java, C#, Haskell или
Pascal. Не бойтесь трудностей — достаточно попробовать, и всё получится!
На мой взгляд, главное в решении такого рода задачи проявить внимание к деталям и усидчивость, так как легко допустить просчет.
Ниже приведен листинг решения поставленной задачи. Скачать можно по ссылке - Download.
#include <stdio.h>
void main(){FILE *f=fopen("output.txt","w");const char *s="#include <stdio.h>void main(){FILE *f=fopen(output.txt,w);const char *s=;fprintf(f,%.18s%c%.26s%c%.10s%c%.1s%c%.1s%c%.16s%c%s%c%.11s%c%.60s%c%s,s,10,s+18,34,s+44,34,s+54,34,s+55,34,s+56,34,s,34,s+72,34,s+83,34,s+143);fclose(f);}";fprintf(f,"%.18s%c%.26s%c%.10s%c%.1s%c%.1s%c%.16s%c%s%c%.11s%c%.60s%c%s",s,10,s+18,34,s+44,34,s+54,34,s+55,34,s+56,34,s,34,s+72,34,s+83,34,s+143);fclose(f);}