Есть два различных способа работы с точками с запятой используемые в популярных языках. В Паскале точка с запятой расценивается как разделитель операторов. Точка с запятой не требуется после последнего утверждения в блоке. Синтаксис:
(пустое утверждение важно!)
Паскаль также определяет некоторые точки с запятой в других местах, таких как после утверждения PROGRAM.
В C и Ada, с другой стороны, точка с запятой рассматриватся как терминатор операторов и следует после всех утверждений (с некоторыми смущающими и путающими исключениями). Синтаксис для них простой:
Из двух синтаксисов, синтаксис Паскаля внешне выглядит более рациональным, но опыт показал, что он ведет к некоторым странным трудностям. Люди так привыкают ставить точку с запятой после каждого утверждения, что они также предпочитают ставить ее и после последнего утверждения в блоке. Это обычно не приносит какого-либо вреда... она просто обрабатывается как пустое утверждение. Многие программисты на Паскале, включая вашего покорного слугу, делают точно также. Но есть одно место, в котором вы абсолютно не можете поставить точку с запятой – прямо перед 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;
{–}
Это, уверен, не повредило, не так ли? Теперь мы можем анализировать точки с запятой в Паскаль-подобном стиле.
Теперь, когда мы знаем как работать с точками с запятой, означает ли это, что я собираюсь поместить их в KISS/TINY? И да и нет. Мне нравится дополнительный сахар и защита, которые приходят с уверенным знанием, где заканчиваются утверждения. Но я не изменил своей антипатии к ошибкам компиляции, связанным с точками с запятой.
Так что я придумал хороший компромис: сделаем их необязательными!
Рассмотрите следующую версию Semi: