Лекции по построению компилятора на Pascal

       

ТАБЛИЦА ИДЕНТИФИКАТОРОВ


Существует одна проблема с компилятором в его текущем состоянии: он ничего не делает для сохранения переменной когда мы ее объявляем. Так что компилятор совершенно спокойно распределит память для нескольких переменных с тем же самым именем. Вы можете легко убедиться в этом набрав строку типа

pvavavabe.

Здесь мы объявили переменную A три раза. Как вы можете видеть, компилятор бодро принимает это и генерирует три идентичных метки. Не хорошо.

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

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

     var ST: array['A'..'Z'] of char;

и вставьте следующую функцию:

{--------------------------------------------------------------}

{ Look for Symbol in Table }

function InTable(n: char): Boolean;

begin

   InTable := ST[n] <> ' ';

end;

{--------------------------------------------------------------}

Нам также необходимо инициализировать таблицу пробелами. Следующие строки в Init сделают эту работу:

var i: char;

begin

   for i := 'A' to 'Z' do

      ST[i] := ' ';

   ...

Наконец, вставьте следующие две строки в начало Alloc:

if InTable(N) then Abort('Duplicate Variable Name ' + N);

ST[N] := 'v';

Это должно все решить. Теперь компилятор будет отлавливать двойные объявления. Позднее мы также сможем использовать InTable при генерации ссылок на переменные.


Если вы осторожный тип (как я), вам возможно захотелось бы начать с тестовой программы, которая ничего не делает а просто инициализирует таблицу и затем создает ее дамп. Только для того, чтобы быть уверенным, что все мы находимся на одной волне, ниже я воспроизвожу всю программу, дополненную новыми процедурами. Заметьте, что эта версия включает поддержку пробелов:
{--------------------------------------------------------------}
program Types;
{--------------------------------------------------------------}

{ Constant Declarations }
const TAB = ^I;

      CR  = ^M;

      LF  = ^J;
{--------------------------------------------------------------}

{ Variable Declarations }
var Look: char;              { Lookahead Character }
    ST: Array['A'..'Z'] of char;

{--------------------------------------------------------------}

{ Read New Character From Input Stream }
procedure GetChar;

begin

   Read(Look);

end;
{--------------------------------------------------------------}

{ Report an Error }
procedure Error(s: string);

begin

   WriteLn;

   WriteLn(^G, 'Error: ', s, '.');

end;
{--------------------------------------------------------------}

{ Report Error and Halt }
procedure Abort(s: string);

begin

   Error(s);

   Halt;

end;
{--------------------------------------------------------------}

{ Report What Was Expected }
procedure Expected(s: string);

begin

   Abort(s + ' Expected');

end;
{--------------------------------------------------------------}

{ Dump the Symbol Table }
procedure DumpTable;

var i: char;

begin

   for i := 'A' to 'Z' do

        WriteLn(i, ' ', ST[i]);

end;
{--------------------------------------------------------------}

{ Recognize an Alpha Character }
function IsAlpha(c: char): boolean;


begin

   IsAlpha := UpCase(c) in ['A'..'Z'];

end;
{--------------------------------------------------------------}

{ Recognize a Decimal Digit }
function IsDigit(c: char): boolean;

begin

   IsDigit := c in ['0'..'9'];

end;
{--------------------------------------------------------------}

{ Recognize an AlphaNumeric Character }
function IsAlNum(c: char): boolean;

begin

   IsAlNum := IsAlpha(c) or IsDigit(c);

end;
{--------------------------------------------------------------}

{ Recognize an Addop }
function IsAddop(c: char): boolean;

begin

   IsAddop := c in ['+', '-'];

end;
{--------------------------------------------------------------}

{ Recognize a Mulop }
function IsMulop(c: char): boolean;

begin

   IsMulop := c in ['*', '/'];

end;
{--------------------------------------------------------------}

{ Recognize a Boolean Orop }
function IsOrop(c: char): boolean;

begin

   IsOrop := c in ['|', '~'];

end;
{--------------------------------------------------------------}

{ Recognize a Relop }
function IsRelop(c: char): boolean;

begin

   IsRelop := c in ['=', '#', '<', '>'];

end;
{--------------------------------------------------------------}

{ Recognize White Space }
function IsWhite(c: char): boolean;

begin

   IsWhite := c in [' ', TAB];

end;
{--------------------------------------------------------------}

{ Skip Over Leading White Space }
procedure SkipWhite;

begin

   while IsWhite(Look) do

      GetChar;

end;
{--------------------------------------------------------------}

{ Skip Over an End-of-Line }
procedure Fin;

begin

   if Look = CR then begin

      GetChar;

      if Look = LF then

         GetChar;

   end;

end;
{--------------------------------------------------------------}


{ Match a Specific Input Character }
procedure Match(x: char);

begin

   if Look = x then GetChar

   else Expected('''' + x + '''');

   SkipWhite;

end;
{--------------------------------------------------------------}

{ Get an Identifier }
function GetName: char;

begin

   if not IsAlpha(Look) then Expected('Name');

   GetName := UpCase(Look);

   GetChar;

   SkipWhite;

end;
{--------------------------------------------------------------}

{ Get a Number }
function GetNum: char;

begin

   if not IsDigit(Look) then Expected('Integer');

   GetNum := Look;

   GetChar;

   SkipWhite;

end;
{--------------------------------------------------------------}

{ Output a String with Tab }
procedure Emit(s: string);

begin

   Write(TAB, s);

end;
{--------------------------------------------------------------}

{ Output a String with Tab and CRLF }
procedure EmitLn(s: string);

begin

   Emit(s);

   WriteLn;

end;
{--------------------------------------------------------------}

{ Initialize }
procedure Init;

var i: char;

begin

   for i := 'A' to 'Z' do

      ST[i] := '?';

   GetChar;

   SkipWhite;

end;
{--------------------------------------------------------------}

{ Main Program }
begin

   Init;

   DumpTable;

end.
{--------------------------------------------------------------}
ОК, запустите эту программу. Вы должны получить (очень быстро) распечатку всех букв алфавита (потенциальных идентификаторов) сопровождаемых вопросительным знаком. Не очень захватывающе, но это только начало.
Конечно, вообще-то мы хотим видеть типы только тех переменных, которые были определены. Мы можем устранить другие добавив в DumpTable условие IF. Измените цикл следующим образом:
for i := 'A' to 'Z' do
   if ST[i] <> '?' then
       WriteLn(i, ' ', ST[i]);
Теперь запустите программу снова. Что вы получили?
Хорошо, это даже более скучно чем раньше! Сейчас вообще ничего не выводится, так как в данный момент ни одно из имен не было объявлено. Мы можем немного приправить результат вставив в основную программу несколько операторов, объявляющих несколько записей.  Попробуйте такие:
ST['A'] := 'a';
ST['P'] := 'b';
ST['X'] := 'c';
На этот раз, когда вы запустите программу, вы должны получить распечатку, показывающую, что таблица идентификаторов работает правильно.

Содержание раздела