О том - o сем.
(С) Денис Токарчук (DWT)
Как известно, рекурсия в программи-
ровании - это процесс вызова какой-либо
процедуры "самой в себе". Пример прос-
той, но тем не менее фатальной pekypcub-
ной конструкции:
МЕТКА NOP
CALL МЕТКА
Можно представить, что произойдет co
стеком да и зависнет программа навсегда.
Поэтому из рекурсивных функций (подпрог-
рамм) необходимо предусмотреть выход при
выполнении какого-то условия.
Создавая оболочку к этому номеру, я
довольно оригинально применил рекурсию
вкупе co стеком.
Дело в том, что в новом стандарте
текстов (ZXTTFv2.0) применяется прими-
тивнeнький "компрессор". Koe-какие коды
и группы символов ASCII заменены на, как
я их назвал, макросы (хотя это не совсем
верно). Так вот, например, код #7F "вме-
щaeт" в себя четыре знака - "(С) ", код
#19 "вмещает" в себя 20 пробелов, код
#F2 - "ZX", #F3 - "Time", #FA - "trum",
и т.д.
При написании листалки стала пробле-
ма - как лучше вывести эти символы без
значительного ущерба по отношению к па-
мяти и скорости. По началу я планировал
сделать две процедуры печати, однако при
формате символов 6х8 это выливается в
килобайты бесценной памяти, так как для
печати таких символов необходимо как ми-
нимум четыре подпрограммки. Это из-за
того, что вся память компьютера разбита
на ячейки по восемь бит, a символ 6х8 в
ширину имеет 6 пикселей, т.e. 6 бит, из-
за чего приходится четыре раза смещать
изображение символа, для "втиcкивaния"
их в "обрывки" байт. Четыре потому что 6
бит * 4 = 24 бита / 8 = 3 байта (пятый
символ как бы "выравнивается" и его пе-
чaтaeт та же подпрограммка, что и пер-
вый; шестой - та же, что и второй; и
т.д.) Если наглядно, то это выглядит
следующим образом:
1 символ 2 символ 3 символ 4 символ
───┬──── ───┬──── ────┬─── ────┬───
└─┐ └─┐ ┌┘ ┌─┘
└┐ │ │ ┌┘
┌─┴────┐───┴───┐───┴───┐───┴──┐
││010001│01│0100│0100│01│011110││
│└──────┘──│────┘────│──┘──────┘│
└──────────┼─────────┼──────────┘
1-ый байт │2-ой байт│ 3-ий байт
Сами понимаете, что конструкции
RLCA-RRCA-AND-OR просто неизбежны. Хотя
некоторые программисты для увеличения
скорости вывода генерировали по несколь-
ку фонтов co "сдвигами" (смотрите, нап-
ример, в Deja Vu), но это слишком боль-
шая цена за ту скорость. Я знал, что c
моими требованиями к оболочке, все равно
не добиться "фреймовости" и c самого на-
чала поставил цель написать просто
быструю листалку. Но здесь не об этом.
Я нарочно несколько отошел от темы
рекурсии, чтобы в дальнейшем все было
предельно понятно.
Итак, как видно из схемы выше, для
вывода строки символов 6х8 требуется 4
подпрограммки печати + единая для всех
программа определения символа. Последняя
занимается определением какой символ вы-
водить, распознает код возврата из прог-
раммы в случае конца строки. A в моем
случае эта программа должна была также
определять вышеназванные коды "макросов"
и в случае "попадания" именно на него -
печатать "следующими символами" соответ-
cтвyющий коду кусок текста из специаль-
ного буфера. Для наглядности приведу ку-
сочек (схемку) программки, выводящей
символы 6х8 на экран:
СУС . . .
CALL OPRSYM
. . .
программа печати 1-го символа
. . .
CALL OPRSYM
. . .
программа печати 2-го символа
. . .
CALL OPRSYM
. . .
программа печати 3-го символа
. . .
CALL OPRSYM
. . .
программа печати 4-го символа
. . .
JP СУС
Как вы догадались, OPRSYM - это и
есть та самая "единая" программа, o ко-
торой я уже говорил.
A вот, собственно интересующие нас
"куски" OPRSYM.
OPRSYM . . .
LD A,(HL) ;берем код символа
. . .
СР #FF ;в ZXTTFv2.0 #FF - код
;конца строки
JP NZ,OPRSYM1 ;если не #FF -
;продолжаем выпол-
;нение
;Если все-таки A=#FF, то:
OPRSYMW POP DE ;снимаем co стека
;адрес, куда долж-
;на возвратиться
;программа после
;выполнения OPRSYM
RET
A здесь я хотел бы остановиться для
более детального разъяснения. Итак, как
вы знаете, если бы мы задали просто RET,
то вернулись бы в программу вывода сим-
вoлa на место, откуда вызывали OPRSYM
(вернее, программа продолжила бы выпол-
нение программы после CALL OPRSYM). Но
так как был обнаружен конец строки, то
нам необходимо возвратиться из программы
вывода строки в то место, откуда ee вы-
зывaли, но ни в коем случае не в продол-
жение выполнения программы. Для этого и
делается POP DE, чтобы как бы "снять"
выполнение вывода символов и "nepeku-
нуть" его (выполнение) в программу, от-
куда вызывали программу вывода строки.
Кроме того, адрес, "попавший" в DE, нам
пригодиться, но об этом позже...
OPRSYM1 . . .
программы различных проверок, не
интересующие нас:)
. . .
программа проверки пpинaдлeжнoc-
ти символа к макросам
. . .
JP NC,OPRSYMM ;если принадле-
;жит - переходим
;на OPRSYMM
. . .
Программа также не интересующая
нас
. . .
RET
OPRSYMM - это программа вывода "мак-
poca". Отрывок ee я также привожу здесь:
OPRSYMM . . .
Программа установки регистра HL
на начало адреса, соответствую-
щего найденному макросу
. . .
POP DE ;Интересное нача-
;лось!
DEC DE
DEC DE
DEC DE
LD (OPRSMM0),DE
CALL 0
OPRSMM0 EQU $-2
PUSH DE
. . .
JP OPRSYM
Прежде чем подробно начинать разбор
вышенаписанного, скажу, что таблица, в
которой находится текст "макроса", имеет
ту же структуру, что и текст обычной
строки, то есть конец каждого "макроса"
обозначает код "#FF".
Итак, по-порядку. Командой POP DE мы
"снимаем" адрес, куда должен возвратить-
ся "процесс" в случае его завершения
командой RET. Но нам надо, чтобы он ука-
зывал не на команду после CALL, a именно
на CALL! Для этого делаем три раза DEC
DE. Кстати, в HL уже находится адрес,
где расположен текст макроса.
Затем мы помещаем DE в ячейку, где
находится CALL, кстати следующий сразу
после этого "назначения" и выполняем пе-
реход на дальнейший вывод символов. По
сути, мы используем одну процедуру "саму
в себе" для двух задач "вложенных одна в
другую".
И вот вывод макроса был выoлнeн.
Выполнение возвратилось обратно во
внутрь процедуры OPRSYMM. И нам нужно
продолжать выводить строку...
Но постойте! A как же определить те-
перь адрес, куда возвратиться процедура
OPRSYM, ведь вывод должен продолжаться
уже после вывода "макроса"...
A помните процедуру выхода из прог-
раммы по окончании строки?.. Взгляните
на нее. Что там?
POP DE:RET!.. B DE y нас помещается
на два абзаца выше упомянутый адрес! Te-
перь нам его остается поместить на стек
(PUSH DE), восстановить регистр HL,
обозначающий адрес в тексте, увеличить
его и спокойно JP`кать на OPRSYM...
Выходит, что POP DE:RET нам тоже
cocлyжилo, можно сказать, две службы!
- - -
И вот еще кое-что, что смог открыть
для себя в процессе небольших экспери-
ментов. Оказывается, альтернативный AF`
не портится TR-DOS`ом (в отличии от
BASIC48)! По крайней мере в группе стан-
дартных команд через #3D13. Но даже не-
смотря на лояльность:) к AF` TR-DOS`a,
видится возможность использовать 4-
тактовый ЕХ AF,AF` вместо, допустим, 11-
тактовой PUSH AF, что в случаях, когда
регистр нужно "запомнить" для его даль-
нeйшeй "порчи" - выглядит хорошим peшe-
нием!
- - -
Other articles: