Содержание материала

ЛЕКСИЧЕСКИЙ АНАЛИЗ

Конечно, вы знаете, что будет дальше: Мы должны преобразовать программу так, чтобы она могла работать с много символьными ключевыми словами, переводами строк и пробелами. Мы только что прошли все это в седьмой главе. Мы будем использовать метод распределенного сканера, который я показал вам в этой главе.      Фактическая реализация немного отличается, потому что различается способ, которым я обрабатываю переводы строк.

Для начала, давайте просто разрешим пробелы. Для этого необходимо только добавить вызовы SkipWhite в конец трех подпрограмм GetName, GetNum и Match. Вызов SkipWhite в Init запускает помпу в случае если есть ведущие пробелы.

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

Вставьте новую процедуру:

Code:

{ Skip Over an End-of-Line }

procedure NewLine;

begin

   while Look = CR do begin

      GetChar;

      if Look = LF then GetChar;

      SkipWhite;

   end;

end;

 
 

 

Заметьте, что мы видели эту процедуру раньше в виде процедуры Fin. Я изменил имя, так как новое кажется более соответствующим фактическому назначению. Я также изменил код чтобы учесть множественные переносы и строки только с пробелами.

Следующим шагом будет вставка вызовов NewLine везде, где мы посчитаем перенос допустимым. Как я подчеркивал ранее, этот момент может очень различаться для разных языков. В TINY я решил разрешить их практически в любом месте. Это означает, что нам нужно вызывать NewLine в начале (не в конце как с SkipWhite) процедур GetName, GetNum и Match.

Для процедур, которые имеют циклы While, таких как TopDecl, нам нужен вызов NewLine в начале процедуры и в конце каждого цикла. Таким способом мы можем быть уверены, что NewLine вызывается в начале каждого прохода через цикл.

Если вы все это сделали, испытайте программу и проверьте, что она действительно обрабатывает пробелы и переносы.

Если это так, тогда мы готовы работать с много символьными токенами и ключевыми словами. Для начала, добавьте дополнительные объявления (скопированные почти дословно из главы 7):

Code:

{ Type Declarations }

type Symbol = string[8];

     SymTab = array[1..1000] of Symbol;

     TabPtr = ^SymTab;

 

 

{ Variable Declarations }

var Look : char;             { Lookahead Character }

    Token: char;             { Encoded Token       }

    Value: string[16];       { Unencoded Token     }

    ST: Array['A'..'Z'] of char;

 

{ Definition of Keywords and Token Types }

const NKW =   9;

      NKW1 = 10;

const KWlist: array[1..NKW] of Symbol =

              ('IF', 'ELSE', 'ENDIF', 'WHILE', 'ENDWHILE',

               'VAR', 'BEGIN', 'END', 'PROGRAM');

const KWcode: string[NKW1] = 'xilewevbep';

 

Затем добавьте три процедуры, также из седьмой главы:

Code:

{ Table Lookup }

function Lookup(T: TabPtr; s: string; n: integer): integer;

var i: integer;

    found: Boolean;

begin

   found := false;

   i := n;

   while (i > 0) and not found do

      if s = T^[i] then

         found := true

      else

         dec(i);

   Lookup := i;

end;

 

.

.

 

{ Get an Identifier and Scan it for Keywords }

procedure Scan;

begin

   GetName;

   Token := KWcode[Lookup(Addr(KWlist), Value, NKW) + 1];

end;

 

.

.

 

{ Match a Specific Input String }

procedure MatchString(x: string);

begin

   if Value <> x then Expected('''' + x + '''');

end;

 

 

 

Теперь мы должны сделать довольно много тонких изменений в оставшихся процедурах. Сначала мы должны изменить функцию GetName на процедуру, снова как в главе 7:

Code:

{ Get an Identifier }

procedure GetName;

begin

   NewLine;

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

   Value := '';

   while IsAlNum(Look) do begin

      Value := Value + UpCase(Look);

      GetChar;

   end;

   SkipWhite;

end;

 
 

 

Обратите внимание, что эта процедура оставляет свой результат в глобальной строковой переменной Value.

Затем, мы должны изменить каждую обращение к GetName чтобы отразить ее новую форму. Они происходят в Factor, Assignment и Decl:

Code:

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

{ Parse and Translate a Math Factor }

procedure BoolExpression; Forward;

procedure Factor;

begin

   if Look = '(' then begin

      Match('(');

      BoolExpression;

      Match(')');

      end

   else if IsAlpha(Look) then begin

      GetName;

      LoadVar(Value[1]);

      end

   else

      LoadConst(GetNum);

end;

 

.

.

 

{ Parse and Translate an Assignment Statement }

procedure Assignment;

var Name: char;

begin

   Name := Value[1];

   Match('=');

   BoolExpression;

   Store(Name);

end;

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

.

.

 

{ Parse and Translate a Data Declaration }

procedure Decl;

begin

   GetName;

   Alloc(Value[1]);

   while Look = ',' do begin

      Match(',');

      GetName;

      Alloc(Value[1]);

   end;

end;

 

 

(Заметьте, что мы все еще разрешаем только одно-символьные имена переменных поэтому мы используем здесь простое решение и просто используем первый символ строки.)

Наконец, мы должны внести изменения, позволяющие использовать Token вместо Look как символа для проверки и вызывать Scan в подходящих местах. По большей части это включает удаление вызовов Match, редкие замены вызовов Match  на вызовы MatchString, и замену вызовов NewLine  на вызовы Scan.  Вот затронутые подпрограммы:

Code:

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

{ Recognize and Translate an IF Construct }

procedure Block; Forward;

procedure DoIf;

var L1, L2: string;

begin

   BoolExpression;

   L1 := NewLabel;

   L2 := L1;

   BranchFalse(L1);

   Block;

   if Token = 'l' then begin

      L2 := NewLabel;

      Branch(L2);

      PostLabel(L1);

      Block;

   end;

   PostLabel(L2);

   MatchString('ENDIF');

end;

 

{ Parse and Translate a WHILE Statement }

procedure DoWhile;

var L1, L2: string;

begin

   L1 := NewLabel;

   L2 := NewLabel;

   PostLabel(L1);

   BoolExpression;

   BranchFalse(L2);

   Block;

   MatchString('ENDWHILE');

   Branch(L1);

   PostLabel(L2);

end;

 

{ Parse and Translate a Block of Statements }

procedure Block;

begin

   Scan;

   while not(Token in ['e', 'l']) do begin

      case Token of

       'i': DoIf;

       'w': DoWhile;

      else Assignment;

      end;

      Scan;

   end;

end;

 

{ Parse and Translate Global Declarations }

procedure TopDecls;

begin

   Scan;

   while Token <> 'b' do begin

      case Token of

        'v': Decl;

      else Abort('Unrecognized Keyword ' + Value);

      end;

      Scan;

   end;

end;

 

{ Parse and Translate a Main Program }

procedure Main;

begin

   MatchString('BEGIN');

   Prolog;

   Block;

   MatchString('END');

   Epilog;

end;

 

{  Parse and Translate a Program }

procedure Prog;

begin

   MatchString('PROGRAM');

   Header;

   TopDecls;

   Main;

   Match('.');

end;

 

{ Initialize }

procedure Init;

var i: char;

begin

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

      ST[i] := ' ';

   GetChar;

   Scan;

end;

 

 

Это должно работать. Если все изменения сделаны правильно, вы должны теперь анализировать программы, которые выглядят как программы. (Если вы не сделали всех изменений, не отчаивайтесь.  Полный листинг конечной формы дан ниже.)

Работает? Если да, то мы почти дома. Фактически, с несколькими небольшими исключениями, мы уже получили компилятор, пригодный для использования. Имеются еще несколько областей, требующих усовершенствования.

Добавить комментарий

Не использовать не нормативную лексику.

Просьба писать ваши замечания, наблюдения и все остальное,
что поможет улучшить предоставляемую информацию на этом сайте.

ВСЕ КОММЕНТАРИИ МОДЕРИРУЮТСЯ ВРУЧНУЮ, ТАК ЧТО СПАМИТЬ БЕСПОЛЕЗНО!


Защитный код
Обновить