1.01.2000

Программа, которая выводит сама себя.


Статья написана для людей которые программируют изредка. Для души так сказать. Одна из относительно старых моих наработок в программирование. Программа выводящая свой исходник. Некоторые считают, что такую написать нельзя. Я знаю по крайней мере два способа. Первый - мой, который приведён здесь. Есть ещё один способ - он более подробно описан в moon_bug в 6 или 7 номере.

Тема: Написание программы выводящей свой исходный текст.
Собственно статья ниже.
Автор: p0st[p0st@sunlimited.ru]

1. Введение.
2. Алгоритм работы программы.
3. Собственно написание.
4. Напоследок.
5. Прикрёплен мой листинг.


Введение:
Иногда нужно написать программу, выводящую свой листинг. Часто такие (или похожие) задачи бывают на различных конкурсах, соревнованиях и т.д. Часто встречая такие задачи в различных задачниках по программированию (в учебниках по нему же они встречаются, что-то заметно реже :)). Бывает не сразу понятно как подступиться к такой задаче. Я расскажу алгоритм, по которому можно самому написать программу выводящую свой листинг. Пример для Turbo Pascal 7.0 (delphi должен открыть как консольное приложение и всё будет работать). Вывод осуществляется в стандартный поток output.

Алгоритм работы программы:
Будем использовать следующий метод: в программе объявлена константа как массив типа string. В нём будет храниться код программы. Тогда нам для вывода исходника потребуется вывести константу и потом сделать тоже самое ещё раз. При выводе константы будут небольшие трудности, но с ними легко справиться по ходу написания.

Собственно написание:
Сначала примерный текст того, что должно быть(потом доредактируем по написанию, исправим ошибки какие будут. Сейчас главное накидать основу):
--------------------------------Start---------------------------------
program outsource;
const
N=15;{Примерное количество строчек. потом исправим на какое надо}
mas:array[-3..N]of string=(
'program outsource;',{Массив объёвлен с отрицательными индексирование для
более удобного использования по тексту}
'const',
'N=15;',
'mas:array[-1..N]of string=(',
{Здесь будут ещё строки всего пока N+4 строк, можно пока встывить пустышки.
Мне лень.}
);

var
i:integer;

begin
for i:=-3 to 0 do {Выводим то что перед константой}
writeln(mas[i]);

for i:=-3 to N do
writeln(#39,mas[i],#39,#44);
{Выводим саму константу, кавычки при выводе использовать не рекоменуеться
так как будут проблемы для вывода формирования того же в константе}

writeln(#39,mas[N],#39);{Вывод последней строки константы без запятой}
writeln(#41,#59);{Конец константы}

for i:=1 to N-1 do {Выводим сам текст программы}
writeln(mas[i]);
writeln(mas[N],#46); {И end с точкой в конце}
end.
--------------------------------End-----------------------------------
Далее требуется перенести весь получившийся исходник в константу. Получившаяся константа должна иметь вид примерно следующий:
--------------------------------Start---------------------------------
mas:array[-3..N]of string=(
'program outsource;',
'const',
'N=13;',
'mas:array[-3..N]of string=(',
'var',
'i:integer;',
'begin',
'for i:=-3 to 0 do',
'writeln(mas[i]);',
'for i:=-3 to N-1 do',
'writeln(#39,mas[i],#39,#44);',
'writeln(#39,mas[N],#39);',
'writeln(#41,#59);',
'for i:=1 to N-1 do',
'writeln(mas[i]);',
'writeln(mas[N],#46);',
'end'
);
--------------------------------End-----------------------------------
Теперь объединяем куски в одну программы. Проверяем ,чтобы в константе было нужное число строчек и всё в таком духе.
Проверяем(1.pas - наша программа, запускать из каталога, в котором tpc):
tpc.exe 1.pas
1.exe > 2.pas
tpc.exe 2.pas
2.exe > 3.pas
fc 2.pas 3.pas

Напоследок:
Если получаем что ошибок не найдено то всё хорошо, иначе смотрим получившийся исходник, находим разницу и правим по месту. Идея решения была объяснена в самом начале статьи. Сравнивать вывод программы надо не с первоначальным исходником (в котором могут быть комментарии и другие вещи для понимания решения) , а с тем что получиться в конечном итоге.
p.s. Для заинтересовавшихся - можно попробовать вывод в файл. Создаёт небольшие проблемы, но помогает лучше разобраться в принципе работы программы.
p.p.s. Используя этот же алгоритм мы можем написать программу выводящую свой листинг на любом (ну почти на любом) языке программирования. У меня есть реализация на ассемблере :).
Приложение 1.pas(с моими коментариями):
(*--------------------------Start-----------------------------------*)
program outsource;
const
N=13;{Количество строчек в константе.}
mas:array[-3..N]of string=(
'program outsource;',{Массив объёвлен с отрицательными индексирование
для более удобного использования по тексту}
'const',
'N=13;',
'mas:array[-3..N]of string=(',
'var',
'i:integer;',
'begin',
'for i:=-3 to 0 do',
'writeln(mas[i]);',
'for i:=-3 to N-1 do',
'writeln(#39,mas[i],#39,#44);',
'writeln(#39,mas[N],#39);',
'writeln(#41,#59);',
'for i:=1 to N-1 do',
'writeln(mas[i]);',
'writeln(mas[N],#46);',
'end'
);

var
i:integer;

begin
for i:=-3 to 0 do {Выводим то что перед константой}
writeln(mas[i]);

for i:=-3 to N-1 do
writeln(#39,mas[i],#39,#44);
{Выводим саму константу, кавычки при выводе использовать не рекоменуеться
так как будут проблемы для вывода формирования того же в константе
#39 - кавычка "'" #44 - запятая ","}

writeln(#39,mas[N],#39);{Вывод последней строки константы без запятой}
writeln(#41,#59); {Конец константы #41 - скобка ")", #59 - ";"}

for i:=1 to N-1 do {Выводим сам текст программы}
writeln(mas[i]);
writeln(mas[N],#46); {И "end" с точкой в конце }
end.
(*--------------------------End-------------------------------------*)