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

       

РАБОТА С ТОЧКАМИ С ЗАПЯТОЙ


Есть два различных способа работы с точками с запятой используемые в популярных языках. В Паскале точка с запятой расценивается как разделитель операторов. Точка с запятой не требуется после последнего утверждения в блоке. Синтаксис:

<block> ::= <statement> ( ';' <statement>)*

     <statement> ::= <assignment> | <if> | <while> ... | null

(пустое утверждение важно!)

Паскаль также определяет некоторые точки с запятой в других местах, таких как после утверждения PROGRAM.

В C и Ada, с другой стороны, точка с запятой рассматривается как терминатор операторов и следует после всех утверждений (с некоторыми смущающими и путающими исключениями). Синтаксис для них простой:

     <block> ::= ( <statement> ';')*

Из двух синтаксисов, синтаксис Паскаля внешне выглядит более рациональным, но опыт показал, что он ведет к некоторым странным трудностям. Люди так привыкают ставить точку с запятой после каждого утверждения, что они также предпочитают ставить ее и после последнего утверждения в блоке. Это обычно не приносит какого-либо вреда... она просто обрабатывается как пустое утверждение. Многие программисты на Паскале, включая вашего покорного слугу,  делают точно также. Но есть одно место, в котором вы абсолютно не можете поставить точку с запятой - прямо перед ELSE. Это маленький подводный камень стоил мне множества дополнительных компиляций, особенно когда ELSE добавляется к существующему коду. Так что выбор C/Ada оказывается лучше. Очевидно, Никлаус Вирт думает также: в Modula-2 он отказался от Паскалевского подхода.

Имея эти два синтаксиса, легко (теперь, когда мы реорганизовали синтаксический анализатор!) добавить эти возможности в наш анализатор. Давайте сначала возьмем последний случай, так как он проще.

Для начала я упростил программу представив новую подпрограмму распознавания:

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

{ Match a Semicolon }




procedure Semi;

begin

   MatchString(';');

end;

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

Эта процедура очень похожа на наш старый Match. Она требует чтобы следующим токеном была точка с запятой. Найдя его, она переходит к следующему.

Так как точка с запятой следует за утверждением, процедура Block почти единственная, которую мы должны изменить:

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

{ 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;

       'R': DoRead;

       'W': DoWrite;

       'x': Assignment;

      end;

      Semi;

      Scan;

   end;

end;

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

Внимательно взгляните на тонкие изменения в операторе case. Вызов Assigment теперь ограничивается проверкой Token. Это позволит избежать вызова Assigment когда токен является точкой с запятой (что случается когда утверждение пустое).

Так как объявления - тоже утверждения, мы также должны добавить вызов Semi в процедуру TopDecl:

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

{ Parse and Translate Global Declarations }

procedure TopDecls;

begin

   Scan;

   while Token = 'v' do begin

      Alloc;

      while Token = ',' do

         Alloc;

      Semi;

   end;

end;

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

Наконец нам нужен вызов для утверждения PROGRAM:

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



{ Main Program }

begin

   Init;

   MatchString('PROGRAM');

   Semi;

   Header;

   TopDecls;

   MatchString('BEGIN');

   Prolog;

   Block;

   MatchString('END');

   Epilog;

end.

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

Проще некуда. Испробуйте это с копией TINY и скажите как вам это нравится.

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

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

{ Parse and Translate a Single Statement }

procedure Statement;

begin

   Scan;

   case Token of

    'i': DoIf;

    'w': DoWhile;

    'R': DoRead;

    'W': DoWrite;

    'x': Assignment;

   end;

end;

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

Используя эту процедуру мы можем переписать Block так:

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

{ Parse and Translate a Block of Statements }

procedure Block;

begin

   Statement;

   while Token = ';' do begin

      Next;

      Statement;

   end;

end;

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

Это, уверен, не повредило, не так ли? Теперь мы можем анализировать точки с запятой в Паскаль подобном стиле.


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