Обратите внимание, что у нас есть таблица идентификаторов и есть логика для проверки допустимости имени переменной. Также стоит обратить внимание на то, что я включил код, который вы видели ранее для поддержки пробелов и переносов строк. Наконец заметьте, что основная программа ограничена как обычно операторными скобками BEGIN-END.
Если вы скопировали программу в Turbo, первым делом нужно откомпилировать ее и удостовериться что она работает. Сделайте несколько объявлений а затем блок begin. Попробуйте что-нибудь вроде:
va (для VAR A)
vb (для VAR B)
vc (для VAR C)
b (для BEGIN)
a=b
b=c
e. (для END.)
Как обычно, вы должны сделать некоторые преднамеренные ошибки и проверить, что программа правильно их отлавливает.
Если вы удовлетворены, как работает наша маленькая программа, тогда пришло время поработать с процедурами. Так как мы еще не говорили о параметрах мы начнем с рассмотрения только таких процедур которые не имеют списка параметров.
Для начала, давайте рассмотрим простую программу с процедурой и подумаем о коде который мы хотели бы увидеть для нее сгенерированным:
PROGRAM FOO;
.
.
PROCEDURE BAR; BAR:
BEGIN .
. .
. .
END; RTS
BEGIN { MAIN PROGRAM } MAIN:
. .
. .
FOO; BSR BAR
. .
. .
END. END MAIN
Здесь я показал конструкции высокоуровневого языка слева и желаемый ассемблерный код справа. Прежде всего заметьте, что здесь несомненно нам не нужно генерировать много кода! Для большей части процедуры и основной программы наши существующие конструкции позаботятся о генерируемом коде.
Ключ к работе с телом процедуры – понимание того, что хотя процедура может быть очень длинной, ее объявление в действительности не отличается от объявления переменной. Это просто еще один вид объявлений. Мы можем записать БНФ:
Это означает, что можно легко изменить TopDecl для работы с процедурами. Как насчет синтаксиса процедуры? Хорошо, вот предлагаемый синтаксис, который по существу такой же как и в Pascal:
Здесь практически не требуется никакой генерации кода., кроме генерации внутри блока begin. Мы должны только выдать метку в начале процедуры и RTS в конце.
Вот требуемый код:
{–}
{ Parse and Translate a Procedure Declaration }
procedure DoProc;
var N: char;
begin
Match('p');
N := GetName;
Fin;
if InTable(N) then Duplicate(N);
ST[N] := 'p';
PostLabel(N);
BeginBlock;
Return;
end;
{–}
Обратите внимание, что я добавил новую подпрограмму генерации кода Return, которая просто выдает инструкцию RTS. Создание этой подпрограммы «оставлено как упражнение студенту».
Для завершения этой версии добавьте следующую строку в оператор Case в DoBlock.
'p': DoProc;
Я должен упомянуть, что эта структура для объявлений и БНФ, которая управляет ей, отличается от стандартного Паскаля. В определении Паскаля от Дженсена и Вирта объявления переменных и, фактически, все виды объявлений, должны следовать в определенном порядке, т.е. метки, константы, типы, переменные, процедуры и основная программа. Чтобы следовать такой схеме, мы должны разделить два объявления и написать в основной программе что-нибудь вроде:
DoVars;
DoProcs;
DoMain;
Однако, большинство реализаций Паскаля, включая Turbo, не требуют такого порядка и позволяют вам свободно перемешивать различные объявления до тех пор пока вы не попробуете обратиться к чему-то прежде, чем это будет объявлено. Хотя это может быть больше эстетическим удовлетворением объявлять все глобальные переменные на вершине программы, конечно не будет никакого вреда от того, чтобы разрешить расставлять их в любом месте. Фактически, это может быть даже немного полезно, в том смысле, что это даст нам возможность выполнять небольшое элементарное скрытие информации. Переменные, к которым нужно обращатся только из основной программы, к примеру, могут быть объявлены непосредственно перед ней и будут таким образом недоступны для процедур.
Испытайте эту новую версию. Заметьте, что мы можем объявить так много процодур, как захотим (до тех пор, пока не исчерпаем односимвольные имена!) и все метки и RTS появятся в правильных местах.