ZX Time
#10
22 ноября 2002 |
|
Кодерам - рекурсия на спектруме.
О том - 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- нием! - - -
Другие статьи номера:
Похожие статьи:
В этот день... 21 ноября