Передовица » Макулатура » ИиО » Препроцессор интерпретатора языка Бейсик ПЭВМ Агат

Препроцессор интерпретатора языка Бейсик ПЭВМ Агат (N3/1992)

Препроцессор (написанный на языке РАПИРА), обрабатывает текст basic-программы, набранный в обычном текстовом редакторе и даёт на выходе вновь текстовый файл, который затем можно загрузить командой EXEC в среде интерпретатора Бейсик. На этапе препроцессинга текстовые метки заменяются на строки, поддерживаются конструкции #ifdef, #define, собственные комментарии препроцессора (т.е. те, которые не будут в дальнейшем частью оператора REM). Осталось только расширить синтаксический анализатор препроцессора с тем, чтобы на выходе он сразу генерировал A-файл (смешанное двоично-текстовое представление Бейсик-программы, использующееся для хранения программы в файлах и оперативной памяти ЭВМ).

В.Литвинов. Группа сайта просит вас связаться с нами! (ЗАЧЕМ ЭТО?)

Написать серьёзную программу на Бейсике "Агата" - задача не из лёгких, если речь не идёт о сотне - другой операторов. Решить её поможет технология программирования, основанная на препроцессировании, модульном программировании и абстрактных типах данных, предлагаемая ниже.

Введение

В дисковой операционной системе ПЭВМ "Агат" имеется команда исполнения из текстового файла EXEC <имя файла>. Она позволяет готовить и корректировать программу на Бейсике с помощью любого текстового редактора, например, с помощью текстового редактора системы "Школьница", а представление программ в виде текстовых файлов в свою очередь позволяет применить к ним препроцессирование (макрообработку).

Знакомство с препроцессором в языке Си [1, 2] привело меня к мысли написать примерно такой же препроцессор и для интерпретатора языка Бейсик ПЭВМ "Агат". Кроме того, работа с [3] и модульное программирование на Бейсике показали существование ещё одного вида программирования в абсолютных адресах - программирования, так сказать, в "абсолютных номерах строк", а также своеобразной фрагментации поля номеров строк программы. Поэтому было решено, что к функциям препроцессора, подобным имеющимся в языке Си, необходимо добавить возможность передачи управления на метки, а также конструкцию ELSE в операторе IF. Разумеется, лучше всего написать препроцессор на ассемблере, однако по причине недостатка времени я остановил свой выбор на языке Рапира. В результате препроцессор был написан за полтора месяца; правда, он работает не очень быстро, но разве плохо в течение тех 2-5 минут, пока идёт препроцессирование, оторваться от экрана, размяться? Кроме того, вряд ли ту же работу вручную можно выполнить быстрее. Чем меньше исходная программа, тем быстрее будет получен результат, поэтому необходимо стремиться к модульному программированию [3].

Описание препроцессора

Препроцессором называется программа для внесения различного рода изменений в исходный текст на одном из языков программирования в автоматическом режиме. В отсутствие препроцессора эта работа обычно производится вручную с помощью текстового редактора. Следовательно, препроцессор повышает производительность труда программиста, сокращает число описок при составлении больших программ. Применять же препроцессор для маленьких программ нерационально. Это будет похоже на исполнение чижика-пыжика симфоническим оркестром.

    Описываемый препроцессор имеет следующие возможности:
  • включение в программу текстов из внешних файлов, замену имён текстами, удаление имён (простые макроопределения);
  • условное препроцессирование;
  • обработка передач управления на метки и автоматическая нумерация строк;
  • расширение оператора IF конструкцией ELSE;
  • собственные комментарии.

Включение из файлов и простые макроопределения. Для каждого массива в программе может потребоваться примерно такая подпрограмма распечатки его элементов:

8000 FOR I=1 TO NEL
8010 PRINT I;":";MAS(I)
8020 NEXT
...

Если массивов много, то удобно, используя препроцессор, написать простое (для данного случая - даже очень) макроопределение и записать его как файл на диск:

...
.&PTARR FOR I=1 TO &NARR
        PRINT I;":";&ARR
        NEXT

В нужном месте препроцессор согласно указаниям программиста включит это макроопределение из файла в текст составляемой программы и заменит имена, начинающиеся символом &, на конкретные, т.е. произведёт настройку имён.

Условное препроцессирование. В зависимости от значения некоторого признака можно включать в составляемую программу тот или иной фрагмент текста, например, ту или иную подпрограмму, описание данных и т.д. Например, для небольшого числа элементов массива лучше применять сортировку методом пузырька, для большого - быструю сортировку. Написав в программе

...
#IF МАЛЭЛ
#INC ПУЗЫРЬКОВАЯ
#ELSE
#INC БЫСТРАЯ
#END
...

мы заставим препроцессор включить либо ту, либо другую подпрограмму - в зависимости от признака МАЛЭЛ.

Обработка передач управления на метки и автоматическая нумерация строк. Вот фрагмент программы.

...
10220 GOSUB 980
10230 GOSUB 990
10240 GOSUB 1000
10250 GOTO 400
...

С расчётом на использование препроцессора этот фрагмент запишется так:

...
GOSUB ВЫЧИСЛЕНИЕ_ЧИСЛИТЕЛЯ
GOSUB ВЫЧИСЛЕНИЕ_ЗНАМЕНАТЕЛЯ
GOSUB ВЫЧИСЛЕНИЕ_ВЫРАЖЕНИЯ
GOSUB ПЕЧАТЬ
...

Ненамного длиннее и намного понятней. А вообще-то самая краткая запись программы - машинный код.

Расширение оператора IF конструкцией ELSE позволяет увеличить наглядность программы и соблюсти одно из требований структурного программирования к оператору ветвления. Вот примеры.

...
00780 IF NUM<HG THEN A=NUM:GOTO 3890
00790 B=NUM:GOTO 5200
...
...
IF NUM<HG THEN A=NUM:GOTO НАЛЕВО
          ELSE B=NUM:GOTO НАПРАВО
...

Собственные комментарии. Препроцессор не передаёт собственные комментарии в готовую программу, тем самым экономя оперативную память. Комментарии существуют только в файлах на диске. В условиях нарастающей важности специфики программных модулей это просто необходимо.

Используя препроцессор, можно специфицировать модуль достаточно подробно; например:

***********************************
* Модуль MCOMP вычисляет модуль   *
* комплексного числа.             *
* вход: X - действительная часть, *
* Y - мнимая часть.               *
* выход:Z - модуль числа.         *
* локальные переменные: нет.      *
***********************************
.MCOMP Z=SQR(X*X+Y*Y)
       RETURN
***********************************
* конец модуля MCOMP              *
***********************************

В загрузочный модуль будет вынесено

...
1010 Z=SQRT(X*X+Y*Y)
1020 RETURN
...

Работа препроцессора

На вход препроцессора поступает текстовый файл (исходный модуль), каждая строка которого обрабатывается определённым образом.

Если первый символ строки *, то это комментарий, и строка игнорируется.

Если первый символ -#, то это команда препроцессора, и она соответствующим образом выполняется.

Если первый символ - точка, то это означает, что идентификатор, следующий без пробела сразу за ней, - метка. Она учитывается, как обычно, в таблице меток вместе с соответствующим номером строки. Остаток строки обрабатывается как строка операторов Бейсика.

Любой другой символ означает, что это строка операторов Бейсика. Строке присваивается текущий номер, разрешаются ссылки на метки (т.е. вместо имён меток проставляются соответствующие им номера строк), конструкция IF заменяется эквивалентным представлением и готовая строка (в случае ELSE - две) на Бейсике выводится в другой текстовый файл. Текущий номер строки увеличивается на текущий шаг. Процесс повторяется до исчерпания исходного модуля. В результате получается текстовый файл с готовой программой (загрузочный модуль) на Бейсике, которую можно загружать и интерпретировать.

Команды препроцессора

Команды начинаются символом # в первой позиции строки. Сразу за этим символом без пробела следует имя команды, а затем через пробелы один или два операнда. При составлении исходных модулей можно указывать только три первых символа имени команды.

Команда INCLUDE.

Формат:

#INCLUDE <имя файла>

По этой команде обработка строк текущего файла приостанавливается, и начинается обработка строк файла, имя которого указано в операнде. Обработка продолжается до исчерпания этого файла. В этом файле также могут встречаться команды препроцессора. Уровень вложенности INCLUDE не должен превосходить 6.

Если имя файла начинается подчерком (например, _TEST), то это означает, что на входе препроцессора - загрузочный модуль, и он целиком без изменений переносится в выходной файл. Это очень важный момент: появляется возможность использовать ранее созданные загрузочные модули. Для нумерации новых присоединяемых модулей необходимо прибегать к команде препроцессора NUMBER во избежание пересечения номеров строк.

Команда DEFINE.

Формат:

#DEFINE <имя> <фрагмент текста>

После этой команды каждое вхождение имени, начинающегося с символа &, из первого операнда, будет заменено фрагментом текста из второго операнда. Фрагмент текста начинается с любого символа, кроме пробела, и продолжается до конца строки. Замена будет производиться во всех последующих строках, содержащих метки и операторы на Бейсике. Замена производится до разрешения ссылок. Вложенные замены недопустимы. В строках комментариев и команд препроцессора замена не производится. Если второй операнд отсутствует, то имя просто удаляется из строки.

Имя определено, если оно встретилось в команде DEFINE и при этом присутствовал второй операнд.

Команда IF.

Формат:

#IF <имя>

Это команда условного препроцессирования.

Если имя определено, то все последующие строки обрабатываются как обычно до соответствующей команды ELSE или ENDIF. Если имя не определено (а имя можно сделать неопределённым, указав его в команде DEFINE без второго операнда), то все последующие строки до ELSE или ENDIF игнорируются. Возможна вложенность команд IF практически неограниченной глубины.

Команда ELSE.

Формат:

#ELSE

Необязательна. Может соответствовать некоторой команде IF и является альтернативой ей. Если в искомой IF имя не определено, то обрабатываются строки между ELSE и соответствующей ENDIF. Если же имя в IF определено, то строки между ELSE и ENDIF игнорируются.

Команда ENDIF.

Формат:

#ENDIF

Обязательно должна соответствовать каждой команде IF. Прекращает действие последней.

Команда NUMBER.

Формат:

#NUMBER <номер> <шаг>

Служит для установки текущего номера строки и шага нумерации. Оба операнда - числа. Нельзя использовать шаг, равный 1, если в модуле имеются конструкции ELSE. В начале работы препроцессора текущий номер и шаг устанавливаются равными 10.

Пояснения к примеру

Для демонстрации рассмотрим пример программирования на Бейсике с применением средств препроцессора. Представлена программа POSTFIX, преобразующая инфиксное арифметическое выражение в постфиксное [3]. В ней использовался абстрактный тип данных - стек, оформленный в виде отдельного настраиваемого модуля [4].

Модуль настраивается путём изменения фактических имён и констант в файле POSTFIX_STACK_DEF, например, добавлением в начале каждого фактического имени буквы W. Далее этот файл может быть записан на диск с новым именем, допустим: POSTFIX_STACK_WDEF. Чтобы создать ещё один стек, в программу необходимо ввести строки

...
#INC POSTFIX_STACK WDEF
#INC STACK_TYP
GOSUB WCREATE
...
.AO 1

В дальнейшем при работе с новым стеком необходимо учитывать, что имена его операций начинаются с буквы W.

Отладка программы

Как видим, каждая операция в абстракции стека реализована в виде отдельного модуля и имеет свои входные, выходные и локальные переменные.

Распечатка во время выполнения программы имени модуля, содержимого входных переменных в начале работы модуля и выходных в конце даёт достаточно полную информацию о ходе выполнения (хотя ничто не мешает таким же образом печатать и локальные переменные). Разумеется, сказанное касается и всех других модулей, а не только реализующих стек, например, модуля POSTFIX_PRCD.

Препроцессор позволяет легко вставлять и удалять отладочные операторы.

Рассмотрим модуль STACK_PUSH, реализующий операцию размещения в стеке. Расположим после метки .&PUSH следующие строки:

#IF TRACE
  PRINT "***&PUSH***"
  PRINT "&TP=";&TP;" MAXSTACK=";
                   &MAXSTACK;" &X=";&X
#ENDIF

а перед строкой с RETURN

#IF TRACE
  GOSUB &PTSTK
  PRINT "&TP=";&TP;" OVER=";OVER
#ENDIF

Тогда, разместив в начале программы POSTFIX строку

#DEFINE TRACE YES              (1)

мы тем самым определим TRACE и заставим препроцессор сгенерировать операторы отладочной печати в нужном месте. Чтобы отключить печать, нужно или удалить строку (1), или убрать из неё YES.

Можно ввести признак отладки для каждого модуля в отдельности и свести эти признаки в файл отладки, который будет иметь примерно такой вид:

* ФАЙЛ POSTFIX_TRACE_DEF
#DEF TRACE_EMPTY
#DEF TRACE_PUSH YES
      ...
#DEF TRACE_PRCD YES

В основной же программе можно включать и отключать отладку с помощью команды

#INC POSTFIX_TRACE_DEF

Манипулируя операндами в командах DEFINE файла POSTFIX_TRACE_DEF, можно выбирать модули, подлежащие трассировке.

Заключение

Весьма важный совет: если вы используете в работе препроцессор, будьте последовательны. Не торопитесь. Не вносите исправлений в загрузочный модуль. Это ещё может себя оправдать, если модулей несколько штук. А если их многие десятки? Тогда мелкий выигрыш во времени отладки одного модуля может обернуться крупным проигрышем при их взаимоувязке.

Для тех, кто заинтересовался описанной темой, у кого появились вопросы, дополнения, - мой адрес: 307239, Курская обл., г. Курчатов, 6-й микрорайон, ул. Садовая, 4, кв. 67. Литвинову Виктору Ивановичу.

    Литература
  1. Хендрикс Д. Компилятор языка Си для микроЭВМ. М.: Радио и связь, 1989.
  2. Дансмур М., Дейвис Г. Операционная система UNIX и программирование на языке Си. М.: Радио и связь, 1989.
  3. Лэнгсам Й., Огенстайн М., Тененбаум А. Структуры данных для персональных ЭВМ. М.: Мир. 1989.
  4. Лисков Б., Гатэг Дж. Использование абстракций и спецификаций при разработке программ. М.: Мир. 1989.

* * *

Использование материалов проекта agatcomp без получения предварительного письменного разрешения agatcomp запрещено.


Почта для обратной связи: mail@agatcomp.ru


Живое общение по теме Агата: Telegram группа Agatcomp.


Накопленные знания и проекты: тематический ФОРУМ.


© 2004-2024 agatcomp.su / agatcomp.ru

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *