{–}
{ Parse and Translate a Program }
procedure Prog;
begin
Match('p');
Header;
TopDecls;
Main;
Match('.');
end;
{–}
Теперь добавьте две новые процедуры:
{–}
{ Process a Data Declaration }
procedure Decl;
begin
Match('v');
GetChar;
end;
{–}
{ Parse and Translate Global Declarations }
procedure TopDecls;
begin
while Look <> 'b' do
case Look of
'v': Decl;
else Abort('Unrecognized Keyword ''' + Look + '''');
end;
end;
{–}
Заметьте, что на данный момент Decl – просто заглушка. Она не генерирует никакого кода и не обрабатывает список... каждая переменная должна быть в отдельном утверждении VAR.
ОК, теперь у нас может быть любое число объявлений данных, каждое начинается с "v" вместо VAR, перед блоком BEGIN. Попробуйте несколько вариантов и посмотрите, что происходит.
Объявления и идентификаторы
Это выглядит довольно хорошо, но мы все еще генерируем только пустую программу. Настоящий ассемблер должен выдавать директивы ассемблера для распределения памяти под переменные. Пришло время действительно получить какой-нибудь код.
С небольшим дополнительным кодом это легко сделать в процедуре Decl. Измените ее следующим образом:
{–}
{ Parse and Translate a Data Declaration }
procedure Decl;
var Name: char;
begin
Match('v');
Alloc(GetName);
end;
{–}
Процедура Alloc просто выдает команду ассемблеру для распределения памяти:
{–}
{ Allocate Storage for a Variable }
procedure Alloc(N: char);
begin
WriteLn(N, ':', TAB, 'DC 0');
end;
{–}
Погоняйте программу. Попробуйте входную последовательность, которая объявляет какие-нибудь переменные, например:
pvxvyvzbe.
Видите, как распределяется память? Просто, да? Заметьте также, что точка входа «MAIN» появляется в правильном месте.
Кстати, «настоящий» компилятор имел бы также таблицу идентификаторов для записи используемых переменных. Обычно, таблица идентификаторов необходима для записи типа каждой переменной. Но так как в нашем случае все переменные имеют один и тот же тип, нам не нужна таблица идентификаторов. Оказывается, мы смогли бы находить идентификатор даже без различия типов, но давайте отложим это пока не возникнет такая необходимость.
Конечно, в действительности мы не анализировали правильный синтаксис для объявления данных, так как он включает список переменных. Наша версия разрешает только одну переменную. Это также легко исправить.
БНФ для
Добавление этого синтаксиса в Decl дает новую версию:
{–}
{ Parse and Translate a Data Declaration }
procedure Decl;
var Name: char;
begin
Match('v');
Alloc(GetName);
while Look = ',' do begin
GetChar;
Alloc(GetName);
end;
end;
{–}
ОК, теперь откомпилируйте этот код и испытайте его. Попробуйте ряд строк с объявлениями VAR, попробуйте список из нескольких переменных в одной строке и комбинации этих двух. Работает?
Инициализаторы
Пока мы работали с объявлениями данных, меня беспокоила одна вещь – то, что Pascal не позволяет инициализировать данные в объявлении. Эта возможность по общему признанию является своего рода излишеством, и ее может не быть в языке, который считается минимальным языком. Но ее также настолько просто добавить, что было бы позором не сделать этого. БНФ становится:
::=
Измените Alloc как показано ниже:
{–}
{ Allocate Storage for a Variable }
procedure Alloc(N: char);
begin
Write(N, ':', TAB, 'DC ');
if Look = '=' then begin
Match('=');
WriteLn(GetNum);
end
else
WriteLn('0');
end;
{–}
Вот оно: инициализатор в шесть дополнительных строк Pascal.
Испытайте эту версию TINY и проверьте, что вы действительно можете задавать начальное значение перменных.
Ей богу, он начинает походить на настоящий компилятор! Конечно, он все еще ничего не делает, но выглядит хорошо, не так ли?