Квайны. Создание косвенным методом

в 16:03, , рубрики: Free Pascal, Pascal, quine, Программирование

Работая над утилитой для собственного использования столкнулся с одной «волшебной» ошибкой. Изначально я намеревался сделать программу с возможностями квайна, но этого не получилось, так как даже от строки (из Википедии):

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:FPC2.6.4bini386-win32data2inc.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 как обозвать получающийся массив

И положим в папку нашей программы. После запуска у меня вышел такой код моей программы:

Код

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-кода избавился.

Автор: IvanTamerlan

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js