Куайн, квайн (англ. 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, уже описанный выше.А вот и сам квайн:
#include <stdio.h>
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);}