Работая над утилитой для собственного использования столкнулся с одной «волшебной» ошибкой. Изначально я намеревался сделать программу с возможностями квайна, но этого не получилось, так как даже от строки (из Википедии):
мой мозг стремительно уходил в рекурсию. Что говорить про более сложные программы? Молчу про квайны на несколько языков. Поэтому я решил сделать квайн не напрямую, т.е. который выводит свой код, а через обходные пути, т.е. есть базовая программа, которую нужно вывести и программа, которая выводит. Получился псевдо-квайн. А дальше пошло самое настоящее волшебство, которое у программистов бывает часто.
В какой-то момент я перепутал код выводимой программы и код программы, которую нужно вывести (ошибся в названии файла). И получил настоящий квайн. Немного допилил до более лучшего вида и пришел сюда поделится своей историей.
Мои инструменты:
Free Pascal Compiler 2.6.4
Notepad++
Соответственно, язык программирования FreePascal (режим objFPC)
Обо всем по порядку. И я расскажу как все было.
Тут немного. Нам нужна программа, чей код мы будем выводить на первых порах. Сойдет любая, даже такая:
Я же в качестве «базовой программы» писал немного больше кода.
Преобразуем нашу программу в массив HEX-кода. Для этого используем стандартный data2inc (поставляется вместе с Free Pascal Compiler). Т.к. нам надо будет часто использовать эту утилиту, лучше написать такой bat-скрипт:
И положим в папку нашей программы. После запуска у меня вышел такой код моей программы:
Сразу говорю — я переправил диапазон с [0..406] на [1..407], чтобы в будущем длину файла не уменьшать на единицу каждый раз. Верхнюю границу вынес отдельно в константу.
Пишем программу для вывода HEX-кода программы из первой части.
Пару условий — нужно вывести код различными способами:
1) Преобразовать массив в текст. Получим первоначальный листинг программы.
2) Вывести HEX-код как HEX-код. Т.е. с полным оформлением. В паскале для этого нужно проставить запятые и обозначения HEX-чисел. Можно для красоты и переносы.
Если у нас длина массива от 1 до N, тогда нам нужно такое число K, которое будет удовлетворять условию 1=<K=<N.
Выводим первые K чисел массив как текст. Потом выводим от 1 до N последовательность HEX-кодов. И после этого завершаем вывод от K+1 до N.
Числа K и N оформить в виде констант дабы не бегать редактировать по всему листингу. Их придется править иной раз очень часто.
Компилируем, запускаем, программа отображает текст «базовой» программы, а потом ее HEX-последовательность. Псевдо-квайн готов!
Нас интересуют эти 2 константы:
N = 00407;
K = 00407;
Сейчас нам нужно будет изменить эти 2 числа. Должно быть минимум 5 знаков. Если не хватает — допишите нули. Они нужны, если у нас будет меняться размер файла, тогда изменится разрядность и нам придется заново переправлять файл. Дабы не уходить в рекурсию, лучше сделать сразу так, чтобы изменение разрядности не влияло на основную программу.
Удаляем все, что между скобок в массиве, оставив только это:
MyArray: array[1..N] of byte = ();
В свойствах файла можно посмотреть количество байт. Записываем его в N. Сохраняем файл.
Всё, что от начала и до открывающей скобки — копируем в новый файл и смотрим сколько байт он весит (этот новый файл нам больше не пригодится).
Записываем длину усеченного файла в K.
Теперь с помощью data2inc (в начале статьи я рассказывал, как с ним работать) преобразуем файл в HEX-массив. Новый HEX-массив ставим, где когда-то давно был другой.
Компилируем и получаем настоящий квайн.
Для того, чтобы написать квайн, не обязательно писать квайн. Можно написать программу, которая выводит какой-то документ, я специально зашифровал его в виде HEX-последовательности. Можно заменить на другие способы кодирования. Это избавляет от необходимости экранировать служебные символы.
P.S. Лично у меня вышло не с первого раза, да и этой инструкции не было. Поэтому пришлось несколько раз перекодировать и редактировать вручную HEX-код. В этой инструкции я от редактирования HEX-кода избавился.
var s:string='var s:string=;begin insert(#39+s+#39,s,14);write(s)end.';begin insert(#39+s+#39,s,14);write(s)end.
мой мозг стремительно уходил в рекурсию. Что говорить про более сложные программы? Молчу про квайны на несколько языков. Поэтому я решил сделать квайн не напрямую, т.е. который выводит свой код, а через обходные пути, т.е. есть базовая программа, которую нужно вывести и программа, которая выводит. Получился псевдо-квайн. А дальше пошло самое настоящее волшебство, которое у программистов бывает часто.
В какой-то момент я перепутал код выводимой программы и код программы, которую нужно вывести (ошибся в названии файла). И получил настоящий квайн. Немного допилил до более лучшего вида и пришел сюда поделится своей историей.
Мои инструменты:
Free Pascal Compiler 2.6.4
Notepad++
Соответственно, язык программирования FreePascal (режим objFPC)
Примечание про консольный вывод русских букв
если хотите чтобы в Windows консоли можно было увидеть русские буквы — набирайте код в кодировке OEM 866 (в Notepad++ меню: Кодировки->Кодировки->Кириллица->OEM 866) или в самой среде FreePascal. Работает на русскоязычной Windows.
Обо всем по порядку. И я расскажу как все было.
Часть первая. «Базовая программа»
Тут немного. Нам нужна программа, чей код мы будем выводить на первых порах. Сойдет любая, даже такая:
Hello, World!
program MyProgram; begin write('Hello, world'); end.
Я же в качестве «базовой программы» писал немного больше кода.
Преобразуем нашу программу в массив HEX-кода. Для этого используем стандартный data2inc (поставляется вместе с Free Pascal Compiler). Т.к. нам надо будет часто использовать эту утилиту, лучше написать такой bat-скрипт:
D:\FPC\2.6.4\bin\i386-win32\data2inc.exe -B -A myProg.pp myProg.inc MyArray
bat-скрипт. Описание аргументов
1) полный путь data2inc.exe (Я отказался от засорения PATH)
2) -B для восприятия как массив байтов преобразуемого файла
3) -A для вывода как массив HEX-констант
4) myProg.pp название нашего файла
5) myProg.inc куда генерировать массив
6) MyArray как обозвать получающийся массив
2) -B для восприятия как массив байтов преобразуемого файла
3) -A для вывода как массив HEX-констант
4) myProg.pp название нашего файла
5) myProg.inc куда генерировать массив
6) MyArray как обозвать получающийся массив
И положим в папку нашей программы. После запуска у меня вышел такой код моей программы:
Код
const MyArray : array[1..407] of byte=( $2F,$2F,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D, $2D,$2D,$2D,$2D,$2D,$2D,$2D,$0D,$0A,$2F,$2F,$20,$80,$A2,$E2, $AE,$E0,$3A,$20,$49,$76,$61,$6E,$54,$61,$6D,$65,$72,$6C,$61, $6E,$0D,$0A,$2F,$2F,$20,$76,$6B,$2E,$63,$6F,$6D,$2F,$49,$76, $61,$6E,$54,$61,$6D,$65,$72,$6C,$61,$6E,$0D,$0A,$2F,$2F,$2D, $2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D, $2D,$2D,$2D,$2D,$0D,$0A,$70,$72,$6F,$67,$72,$61,$6D,$20,$4D, $79,$51,$75,$69,$6E,$65,$3B,$0D,$0A,$7B,$24,$6D,$6F,$64,$65, $20,$6F,$62,$6A,$66,$70,$63,$7D,$0D,$0A,$76,$61,$72,$20,$69, $3A,$20,$69,$6E,$74,$65,$67,$65,$72,$3B,$0D,$0A,$20,$20,$20, $20,$73,$31,$2C,$20,$73,$32,$3A,$20,$50,$43,$68,$61,$72,$3B, $0D,$0A,$0D,$0A,$66,$75,$6E,$63,$74,$69,$6F,$6E,$20,$4D,$79, $46,$75,$6E,$63,$53,$75,$6D,$28,$61,$2C,$62,$3A,$69,$6E,$74, $65,$67,$65,$72,$29,$3A,$69,$6E,$74,$65,$67,$65,$72,$3B,$0D, $0A,$62,$65,$67,$69,$6E,$0D,$0A,$20,$72,$65,$73,$75,$6C,$74, $3A,$3D,$61,$2B,$62,$3B,$0D,$0A,$65,$6E,$64,$3B,$0D,$0A,$0D, $0A,$62,$65,$67,$69,$6E,$0D,$0A,$20,$69,$3A,$3D,$20,$4D,$79, $46,$75,$6E,$63,$53,$75,$6D,$28,$31,$31,$32,$2C,$31,$30,$30, $35,$30,$30,$29,$3B,$20,$2F,$2F,$20,$AA,$A0,$AA,$A0,$EF,$2D, $E2,$AE,$20,$E4,$E3,$AD,$AA,$E6,$A8,$EF,$2E,$0D,$0A,$20,$73, $31,$3A,$3D,$27,$8A,$A0,$AF,$E0,$A0,$AB,$2C,$20,$AC,$EB,$20, $AF,$E0,$AE,$A8,$A3,$E0,$A0,$AB,$A8,$21,$27,$3B,$0D,$0A,$20, $73,$32,$3A,$3D,$27,$20,$AD,$A0,$AC,$20,$AD,$A5,$20,$E3,$A4, $A0,$AB,$AE,$E1,$EC,$20,$AE,$E2,$AF,$E0,$A0,$A2,$A8,$E2,$EC, $20,$E8,$A8,$E4,$E0,$AE,$A2,$AA,$E3,$20,$AD,$A0,$20,$E5,$A0, $A1,$E0,$A5,$20,$3D,$28,$27,$3B,$0D,$0A,$20,$77,$72,$69,$74, $65,$6C,$6E,$28,$73,$31,$2C,$73,$32,$29,$3B,$0D,$0A,$65,$6E, $64,$2E);
Сразу говорю — я переправил диапазон с [0..406] на [1..407], чтобы в будущем длину файла не уменьшать на единицу каждый раз. Верхнюю границу вынес отдельно в константу.
Часть вторая. Вывод HEX-кода
Пишем программу для вывода HEX-кода программы из первой части.
Пару условий — нужно вывести код различными способами:
1) Преобразовать массив в текст. Получим первоначальный листинг программы.
2) Вывести HEX-код как HEX-код. Т.е. с полным оформлением. В паскале для этого нужно проставить запятые и обозначения HEX-чисел. Можно для красоты и переносы.
Если у нас длина массива от 1 до N, тогда нам нужно такое число K, которое будет удовлетворять условию 1=<K=<N.
Выводим первые K чисел массив как текст. Потом выводим от 1 до N последовательность HEX-кодов. И после этого завершаем вывод от K+1 до N.
Числа K и N оформить в виде констант дабы не бегать редактировать по всему листингу. Их придется править иной раз очень часто.
Листинг моей программы
//-------------------- // Автор: IvanTamerlan //-------------------- program myQuine; {$mode objfpc} var i:integer; const N = 00407; K = 00407; MyArray : array[1..N] of byte = ( $2F,$2F,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D, $2D,$2D,$2D,$2D,$2D,$2D,$2D,$0D,$0A,$2F,$2F,$20,$80,$A2,$E2, $AE,$E0,$3A,$20,$49,$76,$61,$6E,$54,$61,$6D,$65,$72,$6C,$61, $6E,$0D,$0A,$2F,$2F,$20,$76,$6B,$2E,$63,$6F,$6D,$2F,$49,$76, $61,$6E,$54,$61,$6D,$65,$72,$6C,$61,$6E,$0D,$0A,$2F,$2F,$2D, $2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D,$2D, $2D,$2D,$2D,$2D,$0D,$0A,$70,$72,$6F,$67,$72,$61,$6D,$20,$4D, $79,$51,$75,$69,$6E,$65,$3B,$0D,$0A,$7B,$24,$6D,$6F,$64,$65, $20,$6F,$62,$6A,$66,$70,$63,$7D,$0D,$0A,$76,$61,$72,$20,$69, $3A,$20,$69,$6E,$74,$65,$67,$65,$72,$3B,$0D,$0A,$20,$20,$20, $20,$73,$31,$2C,$20,$73,$32,$3A,$20,$50,$43,$68,$61,$72,$3B, $0D,$0A,$0D,$0A,$66,$75,$6E,$63,$74,$69,$6F,$6E,$20,$4D,$79, $46,$75,$6E,$63,$53,$75,$6D,$28,$61,$2C,$62,$3A,$69,$6E,$74, $65,$67,$65,$72,$29,$3A,$69,$6E,$74,$65,$67,$65,$72,$3B,$0D, $0A,$62,$65,$67,$69,$6E,$0D,$0A,$20,$72,$65,$73,$75,$6C,$74, $3A,$3D,$61,$2B,$62,$3B,$0D,$0A,$65,$6E,$64,$3B,$0D,$0A,$0D, $0A,$62,$65,$67,$69,$6E,$0D,$0A,$20,$69,$3A,$3D,$20,$4D,$79, $46,$75,$6E,$63,$53,$75,$6D,$28,$31,$31,$32,$2C,$31,$30,$30, $35,$30,$30,$29,$3B,$20,$2F,$2F,$20,$AA,$A0,$AA,$A0,$EF,$2D, $E2,$AE,$20,$E4,$E3,$AD,$AA,$E6,$A8,$EF,$2E,$0D,$0A,$20,$73, $31,$3A,$3D,$27,$8A,$A0,$AF,$E0,$A0,$AB,$2C,$20,$AC,$EB,$20, $AF,$E0,$AE,$A8,$A3,$E0,$A0,$AB,$A8,$21,$27,$3B,$0D,$0A,$20, $73,$32,$3A,$3D,$27,$20,$AD,$A0,$AC,$20,$AD,$A5,$20,$E3,$A4, $A0,$AB,$AE,$E1,$EC,$20,$AE,$E2,$AF,$E0,$A0,$A2,$A8,$E2,$EC, $20,$E8,$A8,$E4,$E0,$AE,$A2,$AA,$E3,$20,$AD,$A0,$20,$E5,$A0, $A1,$E0,$A5,$20,$3D,$28,$27,$3B,$0D,$0A,$20,$77,$72,$69,$74, $65,$6C,$6E,$28,$73,$31,$2C,$73,$32,$29,$3B,$0D,$0A,$65,$6E, $64,$2E); MyHEX : array[0..15] of byte=( $30,$31,$32,$33,$34,$35,$36,$37, $38,$39,$41,$42,$43,$44,$45,$46); function ByteToHex (const b:byte):string; begin result := #$24 + char(MyHEX[b div 16]) + char(MyHEX[b mod 16]); end; begin for i:=1 to K do write(char(MyArray[i])); write(#$0D#$0A#$20#$20); for i:=1 to N do begin if ((i mod 10) = 0) then write(#$0D#$0A#$20#$20); write(ByteToHex(MyArray[i])); if i<N then write(', '); end; for i:=K + 1 to N do write(char(MyArray[i])); end.
Компилируем, запускаем, программа отображает текст «базовой» программы, а потом ее HEX-последовательность. Псевдо-квайн готов!
Часть третья. Волшебство!
Нас интересуют эти 2 константы:
N = 00407;
K = 00407;
Сейчас нам нужно будет изменить эти 2 числа. Должно быть минимум 5 знаков. Если не хватает — допишите нули. Они нужны, если у нас будет меняться размер файла, тогда изменится разрядность и нам придется заново переправлять файл. Дабы не уходить в рекурсию, лучше сделать сразу так, чтобы изменение разрядности не влияло на основную программу.
Удаляем все, что между скобок в массиве, оставив только это:
MyArray: array[1..N] of byte = ();
В свойствах файла можно посмотреть количество байт. Записываем его в N. Сохраняем файл.
Всё, что от начала и до открывающей скобки — копируем в новый файл и смотрим сколько байт он весит (этот новый файл нам больше не пригодится).
Записываем длину усеченного файла в K.
Теперь с помощью data2inc (в начале статьи я рассказывал, как с ним работать) преобразуем файл в HEX-массив. Новый HEX-массив ставим, где когда-то давно был другой.
Компилируем и получаем настоящий квайн.
Вывод
Для того, чтобы написать квайн, не обязательно писать квайн. Можно написать программу, которая выводит какой-то документ, я специально зашифровал его в виде HEX-последовательности. Можно заменить на другие способы кодирования. Это избавляет от необходимости экранировать служебные символы.
P.S. Лично у меня вышло не с первого раза, да и этой инструкции не было. Поэтому пришлось несколько раз перекодировать и редактировать вручную HEX-код. В этой инструкции я от редактирования HEX-кода избавился.