Во второй главе были перечислены лишь самые необходимые команды редактора GENS. До сих пор этого было вполне достаточно для ввода, редактирования и трансляции процедур и фрагментов программ, предложенных в книге. Но когда вы начнете писать свои собственные игры, они наверняка окажутся значительно больших размеров. Возможно даже, что исходные тексты в несколько раз превзойдут по объему всю имеющуюся в наличии память и их придется разбивать на части. И вот тогда вы почувствуете, что описанных возможностей GENS явно маловато. Поэтому в данной главе мы приводим описание некоторых других весьма полезных команд редактора, а также способы сокращения исходного текста и придания ему большей наглядности.
F[номер начальной строки],[номер конечной строки], [текст для поиска][,текст для замены]Как и раньше, квадратные скобки указывают на необязательность параметров. Если какой-то из них не задан, то он берется из последней введенной команды. Вообще же лучше на всякий случай перечислять все элементы команды, особенно при замене текста.
Предположим, вам нужно в тексте программы найти все места, где встречается метка LABEL. Введите в редакторе команду
F1,20000,LABELКак только функция найдет последовательность символов, совпадающую с заданной (LABEL), на экране появится строка текста, содержащая эту последовательность, и GENS перейдет в режим редактирования. При этом курсор для удобства автоматически устанавливается в самое начало найденного текста. После этого у вас есть два варианта дальнейших действий: либо закончить поиск, нажав клавишу Enter, либо продолжить его, для чего нет надобности набирать команду заново, а достаточно нажать клавишу F. Естественно, вам ничто не мешает сразу же внести в текст какие-то изменения, но если они должны быть везде однотипными, проще задать в команде F также и последний параметр. Например, чтобы заменить все имена LABEL на METKA введите в редакторе строку
F1,20000,LABEL,METKAВнешне поведение функции при этом не изменится: при нахождении первого имени строка точно так же будет вызываться на редактирование, а курсор указывать на первый символ слова LABEL. Вы так же можете продолжать вносить изменения вручную и по желанию продолжать поиск без изменений текста или прервать выполнение команды. Но теперь у вас появилась и иная возможность. Если нажать клавишу S, то слово LABEL мгновенно заменится словом METKA, а на экране появится следующая найденная строка.
бит 1 (2) - произвести проверку синтаксиса строк программы, не создавая при этом машинного кода;
бит 2 (4) - вывести на втором проходе листинг ассемблирования программы;
бит 3 (8) - на время трансляции назначить вывод вместо экрана на принтер;
бит 4 (16) - реально размещать машинный код сразу за таблицей меток (адрес, указанный в директиве ORG влияет только на создание ссылок в программе);
бит 5 (32) - не делать проверки расположения кода в памяти (обычно же максимальная верхняя граница задается системной переменной UDG и может быть изменена командой редактора U).
Ключи 2 и 4 бывают особенно полезны на этапе отладки, а вот без ключа 16 не обойтись при трансляции больших программ или если машинный код должен располагаться в недоступных иными средствами областях памяти. Как уже говорилось, при использовании этого ключа исполняемый код располагается сразу за таблицей символов, создаваемой ассемблером на первом проходе трансляции (которая, в свою очередь, помещается следом за исходным текстом). Это позволяет отвести для исполняемого модуля всю свободную память. Однако после сохранения полученной программы на ленте или диске адрес загрузки ее кодов в заголовке файла окажется равным тому, который вы указали в директиве ORG. Все адреса переходов, естественно, также будут правильными.
Применять последний ключ 32 можно лишь в тех случаях, когда вы абсолютно уверены, что исполняемый код полностью уместится в оперативной памяти, иначе «хвост» программы залезет в начальные адреса ПЗУ, где толку от него не будет никакого. Этот ключ может принести пользу, пожалуй, лишь при создании операционных систем или перемещаемых модулей, но эта тема уже выходит за рамки нашей книги.
No Symbol table space!говорящее о том, что ассемблеру не хватило памяти для размещения таблицы меток. Это может показаться странным, особенно если ваша программа имеет небольшие размеры и свободной памяти на самом деле остается еще килобайт 15-20. А все дело в том, что GENS перед началом трансляции исходного текста выделяет под таблицу не все пространство, а определенных размеров буфер, величина которого рассчитывается исходя из размера текста программы, находящейся в памяти. Чем больше строк вы ввели, тем больше окажется и таблица. В большинстве случаев рассчитанного объема оказывается вполне достаточно, но если при небольших размерах текста он будет насыщен метками и прочими именами, то тогда, скорее всего, и появится указанное выше сообщение.
Бороться с подобной ситуацией поможет второй параметр, задаваемый в команде A. Он соответствует размеру создаваемой таблицы в байтах. Например, если вы считаете, что таблицы в две с половиной тысячи байт будет достаточно, введите для начала ассемблирования команду
A,2500При необходимости, конечно, можете указать также и ключи трансляции.
К этой возможности можно прибегать и в тех случаях, когда реальный размер таблицы меток получается намного меньше рассчитанного ассемблером. Таким образом, например, можно высвободить дополнительное пространство для размещения исполняемого кода.
Конечно, можно транслировать разные модули программы раздельно и определять глобальные адреса, запрашивая вывод таблицы меток (см. ключи ассемблирования). Однако подобный метод вряд ли можно назвать хоть сколько-нибудь удобным и прибегать к нему имеет смысл лишь в исключительных случаях, когда все остальное не помогает. Мы же советуем воспользоваться другой возможностью, имеющейся в ассемблере GENS4.
Идея заключается в том, чтобы не загружать в память исходный текст программы, а транслировать его непосредственно с внешнего носителя. Это позволит высвободить максимальное пространство, так как именно текстовый файл имеет наибольшие размеры по сравнению с таблицей меток и исполняемым файлом. Если ваш компьютер не снабжен дисководом и вы вынуждены работать с лентой, то прежде всего потребуется создать текстовый файл в специальном формате, пригодном для трансляции таким способом. При этом GENS разбивает исходный текст на небольшие фрагменты, а затем считывает и транслирует файл по частям. Считывание происходит в специально предназначенный для этих целей буфер, размер которого необходимо указать в самом начале работы. Введите в редакторе команду C (Change buffers - изменить буферы). На экране появится запрос:
Include buffer?на который нужно ввести размер входного буфера для включаемых файлов в байтах. Введите в ответ на него, например, значение 1000. Затем вы увидите еще один запрос - Macro buffer? - который нас пока не интересует, поэтому его можно пропустить, просто нажав Enter. После этого загрузите текст программы в память и сохраните его снова, но уже не обычным образом, а с помощью команды T. Ее формат похож на формат команды P для сохранения текста:
T[нач.строка],[конечн.строка],[имя_файла]Например, чтобы перевести весь исходный текст во включаемый формат и записать его в файле с именем INCL, нужно ввести команду
T1,20000,INCLТеперь очистите память командой Z и напишите одну-единственную строку:
10 *F INCLОбратите внимание, что команда ассемблера *F (не путайте с командами редактора) должна находиться в поле меток, а имя файла записывается следом за ней после единичного пробела. Для начала трансляции введите команду редактора A, например:
A16,5400Конечно, размер таблицы меток в вашем случае может потребоваться совершенно иной, но важно то, что он обязательно должен быть указан, так как при единственной строке в памяти GENS для этих нужд зарезервирует примерно 100 байт, которых хватит от силы на пару десятков меток. Размер таблицы желательно задать максимально близким к тому, который будет использован, и чтобы узнать его, может понадобиться сначала оттранслировать программу «вхолостую», использовав ключ 2. Для окончательной трансляции желательно применить ключ 16, который отведет максимум памяти для исполняемого модуля.
Таким образом можно обрабатывать исходные тексты программ, превосходящие размеры всей свободной памяти компьютера. Ведь текст может располагаться не в одном, а в нескольких файлах еще до обработки их командой T. Например, программа MOON занимает 3 файла. Создайте для каждого из них три включаемых файла с именами MOON1, MOON2 и MOON3, а затем введите три строки
10 *F MOON1 20 *F MOON2 30 *F MOON3которые будут транслироваться уже описанным способом.
При использовании такого метода трансляции нужно помнить еще об одном. Если вы создали включаемые файлы, но не стали их транслировать сразу же, а решили отложить эту работу до следующего раза, то перед началом ассемблирования необходимо будет заново указать размер буфера для включения, причем он должен в точности совпадать с указанным при сохранении программы командой T.
Обладателям дисковой системы TR-DOS повезло несравненно больше. При работе с диском не нужно переводить файлы в специальный формат, достаточно воспользоваться командой *F с указанием имени файла, которое должно начинаться, как и в прочих командах работы с внешней памятью, с указания номера дисковода, например, строка
10 *F 1:BATTYоттранслирует непосредственно с диска, находящегося на дисководе A, текстовый файл с именем BATTY. Правда, и в этом случае потребуется задать размер таблицы меток достаточных размеров, но и не слишком большой, чтобы осталось больше места для исполняемого файла. С этой же целью также желательно указать ключ 16.
Еще один существенный плюс использования для трансляции дисковода (помимо скорости считывания) заключается в том, что если исполняемый модуль перекрывает всю свободную память, уже полученный машинный код «сбрасывается» на диск порциями по мере заполнения памяти. Однако в этом случае необходимо указать и третий параметр в команде редактора A - имя выходного файла, то есть того файла, в который будет записана оттранслированная программа. К примеру, это может выглядеть так:
A16,8000,2:BATT.EXEПосле ассемблирования текста исполняемый модуль будет сохранен на дискете в дисководе B под именем BATT.EXE.
Для определения макроса в поле меток записывается его имя, а в поле мнемоник - директива ассемблера MAC. Затем пишется тело макроопределения, состоящее из любых команд, и завершается запись директивой ENDM. В качестве примера можно предложить такой макрос:
PRAT MAC LD A,22 RST 16 LD A,B RST 16 LD A,C RST 16 ENDMВсякий раз, когда в программу потребуется включить записанную между директивами MAC и ENDM последовательность инструкций, достаточно в поле мнемоник записать макрокоманду PRAT. Это позволит сократить исходный текст программы и сделать его несколько более наглядным, приблизив запись к языкам высокого уровня. Действительно, ведь макрокоманды очень похожи на операторы Бейсика. Например, вместо того чтобы писать
CALL 3435 LD A,2 CALL 5633можно оформить этот фрагмент в виде макроса и присвоить ему имя CLS. Тогда для очистки экрана вы сможете на время забыть адреса соответствующих процедур ПЗУ и записывать в поле мнемоник этот старый знакомый оператор Бейсика.
При задании макросов имеется ряд ограничений, которые необходимо всегда учитывать. Во-первых, имена макроопределений в отличие от имен меток могут состоять не более чем из пяти символов. Причем лишние символы в этом случае не игнорируются, а приводят к появлению ошибки (вообще же лучше обходиться четырьмя символами, тогда вы избавите себя от лишних вопросов). Во-вторых, внутри макроопределения не должно быть строк с метками, так как многократное использование одного и того же макроса приведет к дублированию имен и в результате также появится сообщение об ошибке трансляции. Не допускаются и вложения макросов, то есть внутри макроопределения не могут встречаться ссылки на другие макросы.
Мы уже говорили, что в макросах можно определять не только строго совпадающие фрагменты исходного текста, но и слегка отличающиеся друг от друга. Это становится реальным благодаря возможности использования так называемых формальных параметров. Для каждого макроса допускается задавать до 16 таких параметров. Например, при рисовании точек на экране нужно указывать две координаты. Можно написать макрос, в котором регистры B и C будут загружаться требуемыми значениями и который вызывается командой
PLOT X,Yгде X и Y - любые допустимые в одноименном операторе Бейсика значения координат. Формальные параметры в макроопределении задаются знаком равенства (=) и символом, код которого соответствует порядковому номеру фактического параметра в макрокоманде. Для первого параметра этот символ может иметь коды 0, 16, 32, 48 и так далее, второй параметр будут описывать любые символы с кодами 1, 17, 33, 49... Чтобы не запутаться, рекомендуем использовать цифровые символы от 0 до 9 для определения первых десяти параметров, а остальные 6 можно задавать, например, буквами K, L, M, N, O и P. Тогда макрос PLOT будет записан следующим образом:
PLOT MAC LD C,=0 LD B,=1 CALL 8933 ENDMПосле трансляции вышеприведенной макрокоманды PLOT с параметрами 100 для координаты X и 80 для Y получится следующая последовательность команд микропроцессора:
LD C,100 LD B,80 CALL 8933то есть формальные параметры =0 и =1 заменятся фактическими 100 и 80 соответственно.
При написании макрокоманд нужно помнить, что если имя макроса состоит из пяти символов (напомним еще раз, что это максимальная длина имен макросов), то фактические параметры обязательно нужно заключать в круглые скобки, например:
PRINT (TEXT)Прежде чем привести пример использования макросов в реальной программе, добавим, что в качестве параметров могут выступать только непосредственные числовые значения. Использование символьных строк (за исключением имен меток и констант) не разрешается.
Во время трансляции текст макроопределения не переводится сразу в машинные коды, а помещается в специальный буфер, из которого затем извлекается по мере необходимости. Поэтому перед вводом команды A необходимо указать размер этого буфера с помощью команды C. Помните, при вводе этой команды сначала запрашивается размер входного буфера Include buffer?, а затем появляется еще один запрос - Macro buffer? На него нужно ввести количество байт, достаточное для размещения текста всех макроопределений, заданных в программе. Если задать слишком маленькое число, то во время первого прохода ассемблирования появится сообщение No Macro Space. В этом случае нужно повторить ввод с большим числом. В приведенном ниже примере для размещения макросов достаточно 300 байт.
ORG 60000 ENT $ ; Печать ASCIIZ-строки в позиции экрана, задаваемой первыми двумя параметрами PRN MAC LD B,=0 LD C,=1 LD HL,=2 CALL PRNZ ENDM ; Позиционирование печати PRAT MAC LD A,22 RST 16 LD A,B RST 16 LD A,C RST 16 ENDM ; Установка цветов INK и PAPER, а также цвета бордюра COLOR MAC LD A,=1*8+=0 LD (23693),A LD A,=1 CALL 8859 ENDM ; Очистка экрана и назначение вывода на основной экран CLS MAC CALL 3435 LD A,2 CALL 5633 ENDM ; Установка PLOT-позиции без рисования точки PSET MAC LD L,=0 LD H,=1 LD (23677),HL ENDM ; Черчение линии из текущей PLOT-позиции DRAW MAC EXX PUSH HL LD DE,=0 LD C,=1 LD B,=2 CALL 9402 POP HL EXX ENDM ; Направления рисования линий UP_RT EQU #0101 ;вверх и вправо DN_RT EQU #FF01 ;вниз и вправо DN_LF EQU #FFFF ;вниз и влево UP_LF EQU #01FF ;вверх и влево ; ------ BEGIN COLOR (5,0) CLS PRN 5,8,TEXT1 PRN 7,7,TEXT2 PSET 48,144 DRAW UP_RT,131,0 DRAW DN_RT,0,39 DRAW UP_LF,131,0 DRAW UP_RT,0,39 PSET 50,142 DRAW UP_RT,127,0 DRAW DN_RT,0,35 DRAW UP_LF,127,0 DRAW UP_RT,0,35 RET ; Подпрограмма печати ASCIIZ-строки, вызываемая макросом PRN PRNZ PUSH HL PRAT PRNZ1 LD A,(HL) INC HL AND A JR Z,PRNZ2 RST 16 JR PRNZ1 PRNZ2 POP HL RET ; ------ TEXT1 DEFB 16,2,19,1 DEFM "*** DEMO ***" DEFB 0 TEXT2 DEFB 16,6,19,1 DEFM "### MACROS ###" DEFB 16,5,0Имея возможность работать с дисководом, очень удобно собрать все макросы в одном или нескольких файлах (например, по родству выполняемых функций) и затем при необходимости включать их в исходный текст с помощью команды ассемблера *F. Так как макросы сразу не транслируются, то это никак не повлияет на размер исполняемого кода, даже если среди включаемых макросов есть такие, которые ни разу не используются в программе. Они, конечно, займут некоторый объем памяти, но так и останутся в буфере невостребованными.
В заключение хочется предостеречь вас от чрезмерного увлечения макроопределениями. Во всем нужно знать меру. Учтите, что макросы могут запросто свести все преимущества ассемблера на нет, снизив эффективность программы, в лучшем случае, до уровня компиляторов.
......... IF выражение команды_1 [ELSE команды_2] END .........Команда ELSE и следующий за ней блок инструкций «команды_2» являются необязательной частью условной конструкции, поэтому в данном примере они заключены в квадратные скобки. Если значение выражения после команды IF истинно (то есть не равно нулю), то транслируется блок команд «команды_1» до ELSE или, если его нет, до END. В противном случае (если значение выражения равно нулю) ассемблируются «команды_2» после ELSE, конечно, если эта команда указана. После END трансляция текста протекает как обычно.
Часто эти команды используются для получения различных версий одной и той же программы, одна из которых, например, предназначена для работы на «обычном» Speccy, другая на ZX Spectrum 128 и т. п. Но, на наш взгляд, наиболее полезными они оказываются при написании макроопределений. В этом случае макрос можно составить таким образом, чтобы в зависимости от задаваемых в макрокоманде параметров получался максимально компактный код. Рассмотрим такой пример:
CHAN MAC IF =0 LD A,=0 ;если первый параметр не 0 ELSE XOR A ;если параметр равен 0 END CALL 5633 ENDMВстретив в тексте макрокоманду CHAN, ассемблер обратится к одноименному макросу и в первую очередь проверит значение первого параметра =0. Если его величина отлична от 0 (условие истинно), то транслируется команда LD A,N, затем ассемблирование продолжается после команды END. В противном же случае, то есть если заданный параметр равен 0 (условие ложно), то обрабатываются команды после ELSE, в данном случае - XOR A и далее текст транслируется, как и в предыдущем варианте. Поэтому после трансляции макрокоманды
CHAN 2получится последовательность инструкций
LD A,2 CALL 5633а если задать
CHAN 0то такая макрокоманда оттранслируется иначе:
XOR A CALL 5633Приведем другой, более серьезный пример применения команд условной трансляции в макросах:
ORG 60000 UP EQU 1 DN EQU %10 RT EQU %100 LF EQU %1000 SCRL MAC PUSH BC LD HL,=1*256+=0 LD (COL),HL LD HL,=3*256+=2 LD (LEN),HL IF =4 & UP ;если 5-й параметр = UP CALL SCR_UP END IF =4 & DN ;если 5-й параметр = DN CALL SCR_DN END IF =4 & RT ;если 5-й параметр = RT CALL SCR_RT END IF =4 & LF ;если 5-й параметр = LF CALL SCR_LF END POP BC ENDM ; ------ LD B,16 SCRL1 SCRL 10,4,5,7,UP DJNZ SCRL1 LD B,16 SCRL2 SCRL 10,4,5,7,RT DJNZ SCRL2 LD B,16 SCRL3 SCRL 10,4,5,7,DN DJNZ SCRL3 LD B,16 SCRL4 SCRL 10,4,5,7,LF DJNZ SCRL4 RET SCR_UP ......... SCR_DN ......... SCR_RT ......... SCR_LF ......... COL DEFB 0 ROW DEFB 0 LEN DEFB 0 HGT DEFB 0В выражениях ассемблера GENS отсутствует знак равенства, но из этого затруднения можно выйти, если употребить поразрядную операцию «И» - AND, обозначаемую символом «амперсанд» (&), а в соответствующем параметре использовать отдельные биты, указывающие на различные действия. В приведенном макросе после определения графических переменных COL, ROW, LEN и HGT в зависимости от последнего параметра вызывается одна из четырех процедур скроллингов (напомним, что сами процедуры были описаны в 6-й главе). Как видите, благодаря командам условной трансляции стало возможно объединить их в одном макросе. В результате и текст программы заметно сократился и стал значительно более удобочитаемым. Правда, при этом несколько возрос размер исполняемого модуля, но этот недостаток также можно устранить, слегка доработав макрос. Например, можно добавить еще один условный блок в самом начале, в котором проверяется значение самого первого параметра и только если он не равен 0, транслируются команды определения переменных, а в противном случае они будут пропускаться.
Глава 10 | Приложение I