Синтаксис и его перевод:
   DO
   <expr> { Emit(SUBQ #1,D0);
   L = NewLabel;
   PostLabel(L);
   Emit(MOVE D0,-(SP) }
   <block>
   ENDDO { Emit(MOVE (SP)+,D0;
   Emit(DBRA D0,L) } 
   Это гораздо проще! Цикл будет выполняться <expr> раз. Вот код:
   {–}
   { Parse and Translate a DO Statement }
   procedure Dodo;
   var L: string;
   begin
   Match('d');
   L := NewLabel;
   Expression;
   EmitLn('SUBQ #1,D0');
   PostLabel(L);
   EmitLn('MOVE D0,-(SP)');
   Block;
   EmitLn('MOVE (SP)+,D0');
   EmitLn('DBRA D0,' + L);
   end;
   {–}
   Я думаю вы согласитесь, что это гораздо проще, чем классический цикл FOR. Однако, каждая конструкция имеет свое назначение.

Оператор BREAK

   Ранее я обещал вам оператор BREAK для сопровождения цикла LOOP. Им я в некотором роде горд. На первый взгляд BREAK кажется действительно сложным. Моим первым подходом было просто использовать его как дополнительный ограничитель в Block и разделить все циклы на две части точно также как я сделал это для ELSE оператора IF. Но, оказывается, это не работает, потому что оператор BREAK редко находится на том же самом уровне, что и сам цикл. Наиболее вероятное место для BREAK – сразу после IF, что приводило бы к выходу из конструкции IF, а не из окружающего цикла. Неправильно. BREAK должен выходить из внутреннего LOOP даже если он вложен в несколько уровней IF.
   Моей следующей мыслью было просто сохранять в какой-то глобальной переменной, метку окончания самого вложенного цикла. Это также не работает, потому что может возникнуть прерывание из внутренного цикла с последующим прерыванием из внешнего. Сохранение метки для внутреннего цикла затерло бы метку для внешного. Так глобальная переменная превратилась в стек. Дело становилось грязным.
   Тогда я решил последовать своему собственному совету. Помните последний урок, когда я показал вам как хорошо служит нам неявный стек синтаксического анализатора с рекурсивным спуском. Я сказал, что если вы начинаете видеть потребность во внешнем стеке, возможно вы делаете что-то неправильно. Действительно возможно заставить рекурсию, встроенную в наш синтаксический анализатор, позаботиться обо всем и это решение настолько простое, что кажется удивительным.
   Секрет состоит в том, чтобы заметить, что каждый оператор BREAK должен выполняться внутри блока... и ни в каком другом месте. Так что все, что мы должны сделать это передать в Block адрес выхода из самого внутреннего цикла. Затем он может передать этот адрес подпрограмме, транслирующей инструкцию Break. Так как оператор IF не изменяет уровень цикла, процедура DoIf не должна делать что-либо за исключением передачи метки в ее блок (оба из них). Так как циклы изменяют уровень, каждый цикл просто игнорирует любую метку выше его и передает свою собственную метку выхода дальше.
   Все это проще показать вам чем описывать. Я продемонстрирую это с самым простым циклом, циклом LOOP:
   {–}
   { Parse and Translate a LOOP Statement }
   procedure DoLoop;
   var L1, L2: string;
   begin
   Match('p');
   L1 := NewLabel;
   L2 := NewLabel;
   PostLabel(L1);
   Block(L2);
   Match('e');
   EmitLn('BRA ' + L1);
   PostLabel(L2);
   end;
   {–}
   Заметьте, что теперь DoLoop имеет две метки а не одну. Вторая дает команде BREAK адрес перехода Если в цикле нет BREAK, то мы зря потратили метку и немного загромоздили код, но не нанесли никакого вреда.
   Заметьте также, что процедура Block теперь имеет параметр, который для циклов всегда будет адресом выхода. Новая версия Block:
   {–}
   { Recognize and Translate a Statement Block }
   procedure Block(L: string);
   begin
   while not(Look in ['e', 'l', 'u']) do begin
   case Look of
   'i': DoIf(L);
   'w': DoWhile;
   'p': DoLoop;
   'r': DoRepeat;
   'f': DoFor;
   'd': DoDo;
   'b': DoBreak(L);
   else Other;
   end;
   end;
   end;
   {–}
   Снова заметьте, что все что Block делает с меткой это передает ее в DoIf и DoBreak. Циклы не нуждаются в ней, потому что они в любом случае передают свою собственную метку.
   Новая версия DoIf:
   {–}
   { Recognize and Translate an IF Construct }
   procedure Block(L: string); Forward;
   procedure DoIf(L: string);
   var L1, L2: string;
   begin
   Match('i');
   Condition;
   L1 := NewLabel;
   L2 := L1;
   EmitLn('BEQ ' + L1);
   Block(L);
   if Look = 'l' then begin
   Match('l');
   L2 := NewLabel;
   EmitLn('BRA ' + L2);
   PostLabel(L1);
   Block(L);
   end;
   Match('e');
   PostLabel(L2);
   end;
   {–}
   Здесь единственное, что изменяется, это добавляется параметр у процедуры Block. Оператор IF не меняет уровень вложенности цикла, поэтому DoIf просто передает метку дальше. Независимо от того, сколько уровней вложенности IF мы имеем, будет использоваться та же самая метка.
   Теперь не забудьте, что DoProgram также вызывает Block и теперь необходимо передавать ей метку. Попытка выхода из внешнего блока является ошибкой, поэтому DoProgram передает пустую метку, которая перехватывается DoBreak:
   {–}
   { Recognize and Translate a BREAK }
   procedure DoBreak(L: string);
   begin
   Match('b');
   if L <> '' then
   EmitLn('BRA ' + L)
   else Abort('No loop to break from');
   end;
   {–}
   { Parse and Translate a Program }
   procedure DoProgram;
   begin
   Block('');
   if Look <> 'e' then Expected('End');
   EmitLn('END')
   end;
   {–}
   Этот код позаботится почти обо всем. Испытайте его, посмотрите, сможете ли вы «сломать» («break») его (каламбур). Аккуратней однако. К настоящему времени мы использовали так много букв, что трудно придумать символ, который не представляет сейчас какое либо зарезервированное слово. Не забудьте, перед тем, как вы протестируете программу, вы должны будете исправить каждый случай появления Block в других циклах для включения нового параметра. Сделайте это точно так же, как я сделал это для LOOP.
   Я сказал выше «почти». Есть одна небольшая проблема: если вы внимательно посмотрите на код, генерируемый для DO, вы увидите, что если вы прервете этот цикл, то значение счетчика все еще остается в стеке. Мы должны исправить это! Позор... это была одна из самых маленьких наших подпрограмм, но это не помогло. Вот новая версия, которая не имеет этой проблемы:
   {–}
   { Parse and Translate a DO Statement }
   procedure Dodo;
   var L1, L2: string;
   begin
   Match('d');
   L1 := NewLabel;
   L2 := NewLabel;
   Expression;
   EmitLn('SUBQ #1,D0');
   PostLabel(L1);
   EmitLn('MOVE D0,-(SP)');
   Block(L2);
   EmitLn('MOVE (SP)+,D0');
   EmitLn('DBRA D0,' + L1);
   EmitLn('SUBQ #2,SP');
   PostLabel(L2);
   EmitLn('ADDQ #2,SP');
   end;
   {–}
   Две дополнительные инструкции SUBQ и ADDQ заботятся о сохранении стека в правильной форме.

Заключение

   К этому моменту мы создали ряд управляющих конструкций... в действительности более богатый набор чем предоставляет почти любой другой язык программирования. И, за исключением цикла FOR, это было довольно легко сделать. Но даже этот цикл был сложен только потому, что сложность заключалась в ассемблере.
   Я завершаю на этом урок. Чтобы можно было обернуть наш продукт красной ленточкой, в действительности мы должны иметь настоящие ключевые слова вместо этих игрушечных односимвольных. Вы уже видели, что расширить компилятор для поддержки многосимвольных слов не трудно, но в этом случае возникнут большие различия в представлении нашего входного кода. Я оставлю этот небольшой кусочек для следующей главы. В этой главе мы также рассмотрим логические выражения, так что мы сможем избавиться от фиктивной версии Condition, которую мы здесь использовали. Увидимся.
   Для справочных целей привожу полный текст синтаксического анализатора для этого урока:
   {–}
   program Branch;
   {–}
   { Constant Declarations }
   const TAB = ^I;
   CR = ^M;
   {–}
   { Variable Declarations }
   var Look : char; { Lookahead Character }
   Lcount: integer; { Label Counter }
   {–}
   { 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;
   {–}
   { Match a Specific Input Character }
   procedure Match(x: char);
   begin
   if Look = x then GetChar
   else Expected('''' + x + '''');
   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 Addop }
   function IsAddop(c: char): boolean;
   begin
   IsAddop := 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;
   {–}
   { Get an Identifier }
   function GetName: char;
   begin
   if not IsAlpha(Look) then Expected('Name');
   GetName := UpCase(Look);
   GetChar;
   end;
   {–}
   { Get a Number }
   function GetNum: char;
   begin
   if not IsDigit(Look) then Expected('Integer');
   GetNum := Look;
   GetChar;
   end;
   {–}
   { Generate a Unique Label }
   function NewLabel: string;
   var S: string;
   begin
   Str(LCount, S);
   NewLabel := 'L' + S;
   Inc(LCount);
   end;
   {–}
   { Post a Label To Output }
   procedure PostLabel(L: string);
   begin
   WriteLn(L, ':');
   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;
   {–}
   { Parse and Translate a Boolean Condition }
   procedure Condition;
   begin
   EmitLn('<condition>');
   end;
   {–}
   { Parse and Translate a Math Expression }
   procedure Expression;
   begin
   EmitLn('<expr>');
   end;
   {–}
   { Recognize and Translate an IF Construct }
   procedure Block(L: string); Forward;
   procedure DoIf(L: string);
   var L1, L2: string;
   begin
   Match('i');
   Condition;
   L1 := NewLabel;
   L2 := L1;
   EmitLn('BEQ ' + L1);
   Block(L);
   if Look = 'l' then begin
   Match('l');
   L2 := NewLabel;
   EmitLn('BRA ' + L2);
   PostLabel(L1);
   Block(L);
   end;
   Match('e');
   PostLabel(L2);
   end;
   {–}
   { Parse and Translate a WHILE Statement }
   procedure DoWhile;
   var L1, L2: string;
   begin
   Match('w');
   L1 := NewLabel;
   L2 := NewLabel;
   PostLabel(L1);
   Condition;
   EmitLn('BEQ ' + L2);
   Block(L2);
   Match('e');
   EmitLn('BRA ' + L1);
   PostLabel(L2);
   end;
   {–}
   { Parse and Translate a LOOP Statement }
   procedure DoLoop;
   var L1, L2: string;
   begin
   Match('p');
   L1 := NewLabel;
   L2 := NewLabel;
   PostLabel(L1);
   Block(L2);
   Match('e');
   EmitLn('BRA ' + L1);
   PostLabel(L2);
   end;
   {–}
   { Parse and Translate a REPEAT Statement }
   procedure DoRepeat;
   var L1, L2: string;
   begin
   Match('r');
   L1 := NewLabel;
   L2 := NewLabel;
   PostLabel(L1);
   Block(L2);
   Match('u');
   Condition;
   EmitLn('BEQ ' + L1);
   PostLabel(L2);
   end;
   {–}
   { Parse and Translate a FOR Statement }
   procedure DoFor;
   var L1, L2: string;
   Name: char;
   begin
   Match('f');
   L1 := NewLabel;
   L2 := NewLabel;
   Name := GetName;
   Match('=');
   Expression;
   EmitLn('SUBQ #1,D0');
   EmitLn('LEA ' + Name + '(PC),A0');
   EmitLn('MOVE D0,(A0)');
   Expression;
   EmitLn('MOVE D0,-(SP)');
   PostLabel(L1);
   EmitLn('LEA ' + Name + '(PC),A0');
   EmitLn('MOVE (A0),D0');
   EmitLn('ADDQ #1,D0');
   EmitLn('MOVE D0,(A0)');
   EmitLn('CMP (SP),D0');
   EmitLn('BGT ' + L2);
   Block(L2);
   Match('e');
   EmitLn('BRA ' + L1);
   PostLabel(L2);
   EmitLn('ADDQ #2,SP');
   end;
   {–}
   { Parse and Translate a DO Statement }
   procedure Dodo;
   var L1, L2: string;
   begin
   Match('d');
   L1 := NewLabel;
   L2 := NewLabel;
   Expression;
   EmitLn('SUBQ #1,D0');
   PostLabel(L1);
   EmitLn('MOVE D0,-(SP)');
   Block(L2);
   EmitLn('MOVE (SP)+,D0');
   EmitLn('DBRA D0,' + L1);
   EmitLn('SUBQ #2,SP');
   PostLabel(L2);
   EmitLn('ADDQ #2,SP');
   end;
   {–}
   { Recognize and Translate a BREAK }
   procedure DoBreak(L: string);
   begin
   Match('b');
   EmitLn('BRA ' + L);
   end;
   {–}
   { Recognize and Translate an «Other» }
   procedure Other;
   begin
   EmitLn(GetName);
   end;
   {–}
   { Recognize and Translate a Statement Block }
   procedure Block(L: string);
   begin
   while not(Look in ['e', 'l', 'u']) do begin
   case Look of
   'i': DoIf(L);
   'w': DoWhile;
   'p': DoLoop;
   'r': DoRepeat;
   'f': DoFor;
   'd': DoDo;
   'b': DoBreak(L);
   else Other;
   end;
   end;
   end;
   {–}
   { Parse and Translate a Program }
   procedure DoProgram;
   begin
   Block('');
   if Look <> 'e' then Expected('End');
   EmitLn('END')
   end;
   {–}
   { Initialize }
   procedure Init;
   begin
   LCount := 0;
   GetChar;
   end;
   {–}
   { Main Program }
   begin
   Init;
   DoProgram;
   end.
   {–}

Булевы выражения

Введение

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

План

   Мы собираемся подойти к этой главе немного по-другому, чем к любой другой. В других главах мы начинали немедленно с экспериментов, используя компилятор Pascal, выстраивая синтаксические анализаторы от самых элементарных начал до их конечных форм, не тратя слишком много времени на предварительное планирование. Это называется кодированием без спецификации и обычно к нему относятся неодобрительно. Раньше мы могли избегать планирования, потому что правила арифметики довольно хорошо установлены... мы знаем, что означает знак "+" без необходимости подробно это обсуждать. То же самое относится к ветвлениям и циклам. Но способы, которыми языки программирования реализуют логику, немного отличаются от языка к языку. Поэтому прежде, чем мы начнем серьезное кодирование, лучше мы сперва примем решение что же мы хотим. И способ сделать это находится на уровне синтаксических правил БНФ (грамматики).

Грамматика

   Некоторое время назад мы реализовали синтаксические уравнения БНФ для арифметических выражений фактически даже не записав их все в одном месте. Пришло время сделать это. Вот они:
   <expression> ::= <unary op> <term> [<addop> <term>]*
   <term> ::= <factor> [<mulop> factor]*
   <factor> ::= <integer> | <variable> | ( <expression> )
   (Запомните, преимущества этой грамматики в том, что она осуществляет такую иерархию приоритетов операторов, которую мы обычно ожидаем для алгебры.)
   На самом деле, пока мы говорим об этом, я хотел бы прямо сейчас немного исправить эту грамматику. Способ, которым мы обрабатываем унарный минус, немного неудобный. Я нашел, что лучше записать грамматику таким образом:
   <expression> ::= <term> [<addop> <term>]*
   <term> ::= <signed factor> [<mulop> factor]*
   <signed factor> ::= [<addop>] <factor>
   <factor> ::= <integer> | <variable> | (<expression>)
   Это возлагает обработку унарного минуса на Factor, которому он в действительности и принадлежит.
   Это не означает, что вы должны возвратиться назад и переписать программы, которые вы уже написали, хотя вы свободны сделать так, если хотите. Но с этого момента я буду использовать новый синтаксис.
   Теперь, возможно, для вас не будет ударом узнать, что мы можем определить аналогичную грамматику для булевой алгебры. Типичный набор правил такой:
   <b-expression>::= <b-term> [<orop> <b-term>]*
   <b-term> ::= <not-factor> [AND <not-factor>]*
   <not-factor> ::= [NOT] <b-factor>
   <b-factor> ::= <b-literal> | <b-variable> | (<b-expression>)
   Заметьте, что в этой грамматике оператор AND аналогичен "*", а OR (и исключающее OR) – "+". Оператор NOT аналогичен унарному минусу. Эта иерархия не является абсолютным стандартом... некоторые языки, особенно Ada, обрабатывают все логические операторы как имеющие одинаковый уровень приоритета... но это кажется естественным.
   Обратите также внимание на небольшое различие способов, которыми обрабатываются NOT и унарный минус. В алгебре унарный минус считается идущим со всем термом и поэтому никогда не появляется более одного раза в данном терме. Поэтому выражение вида:
   a * -b
   или еще хуже:
   a – -b
   не разрешены. В булевой алгебре наоборот, выражение
   a AND NOT b
   имеет точный смысл и показанный синтаксис учитывает это.

Операторы отношений

   Итак, предполагая что вы захотите принять грамматику, которую я показал здесь, мы теперь имеем синтаксические правила и для арифметики и для булевой алгебры. Сложность возникает когда мы должны объединить их. Почему мы должны сделать это? Ну, эта тема возникла из-за необходимости обрабатывать «предикаты» (условия), связанные с управляющими операторами такими как IF. Предикат должен иметь логическое значение, то есть он должен быть оценен как TRUE или FALSE. Затем ветвление выполняется или не выполняется в зависимости от этого значения. Тогда то, что мы ожидаем увидеть происходящим в процедуре Condition, будет вычисление булевого выражения.
   Но имеется кое-что еще. Настоящее булево выражение может действительно быть предикатом управляющего оператора... подобно:
   IF a AND NOT b THEN ....
   Но более часто мы видим, что булева алгебра появляется в таком виде:
   IF (x >= 0) and (x <= 100) THEN...
   Здесь два условия в скобках являются булевыми выражениями, но индивидуальные сравниваемые термы: x, 0 и 100 являются числовыми по своей природе. Операторы отношений >= и <= являются катализаторами, с помощью которых булевские и арифметические компоненты объединяются вместе.
   Теперь, в примере выше сравниваемые термы являются просто термами. Однако, в общем случае, каждая сторона может быть математическим выражением. Поэтому мы можем определить отношение как:
   <relation> ::= <expression> <relop> <expression>,
   где выражения, о которых мы говорим здесь – старого числового типа, а операторы отношений это любой из обычных символов:
   =, <> (или !=), <, >, <= и >=
   Если вы подумаете об этом немного, то согласитесь, что так как этот вид предиката имеет логическое значение, TRUE или FALSE, это в действительности просто еще один вид показателя. Поэтому мы можем расширить определение булевого показателя следующим образом:
   <b-factor> ::= <b-literal>
   | <b-variable>
   | (<b-expression>)
   | <relation>
   Вот эта связь! Операторы отношений и отношения, которые они определяют, служат для объединения двух типов алгебры. Нужно заметить, что это подразумевает иерархию, в которой арифметическое выражение имеет более высокий приоритет, чем булевский показатель и, следовательно, чем все булевы операторы. Если вы выпишите уровни приоритета для всех операторов, вы прийдете к следующему списку:
   Уровень Синтаксический элемент Оператор
   0 factor literal, variable
   1 signed factor unary minus
   2 term *, /
   3 expression +, -
   4 b-factor literal, variable, relop
   5 not-factor NOT
   6 b-term AND
   7 b-expression OR, XOR
   Если мы захотим принять столько уровней приоритета, эта грамматика кажется приемлемой. К несчастью, она не будет работать! Грамматика может быть великолепной в теории, но она может совсем не иметь смысла в практике нисходящего синтаксического анализатора. Чтобы увидеть проблему рассмотрите следующий фрагмент кода:
   IF ((((((A + B + C) < 0 ) AND....
   Когда синтаксический анализатор анализирует этот код он знает, что после того, как он рассмотрит токен IF следующим должно быть булево выражение. Поэтому он может приступить к началу вычисления такого выражения. Но первое выражение в примере является арифметическим выражением A + B + C. Хуже того, в точке, в которой анализатор прочитал значительную часть входной строки:
   IF ((((((A ,
   он все еще не имеет способа узнать с каким видом выражения он имеет дело. Так не пойдет, потому что мы должны иметь две различных программы распознавания для этих двух случаев. Ситуация может быть обработана без изменения наших определений но только если мы захотим принять произвольное количество возвратов (backtracking) чтобы избавить наш путь от неверных предположений. Ни один из создателей компиляторов в здравом уме не согласился бы на это.
   Происходит то, что красота и элегантность грамматики БНФ столкнулась лицом к лицу с реальностью технологии компиляции.
   Чтобы работать с этой ситуацией создатели компиляторов должны идти на компромиссы, так чтобы один анализатор мог бы поддерживать грамматику без возвратов.

Исправление грамматики

   Проблема, с которой мы столкнулись, возникает потому, что наше определение и арифметических и булевых показателей позволяет использовать выражения в скобках. Так как определения рекурсивны, мы можем закончить с любым числом уровней скобок и синтаксический анализатор не может знать с каким видом выражения он имеет дело.
   Решение просто, хотя и приводит к глубоким изменениям нашей грамматики. Мы можем разрешить круглые скобки только в одном виде показателей. Способ сделать это значительно изменяется от языка к языку. Это то место, где не существует соглашения или договора способного нам помочь.
   Когда Никлаус Вирт разработал Паскаль, его желанием было ограничить количество уровней приоритета (меньше подпрограмм синтаксического анализа, в конце концов). Так операторы OR и исключающее OR рассматриваются просто как Addop и обрабатываются на уровне математического выражения. Аналогично AND рассматривается подобно Mulop и обрабатывается с Term. Уровни приоритета:
   Заметьте, что имеется только один набор синтаксических правил, применимый к обоим видам операторов. Тогда согласно этой грамматике выражения типа:
   x + (y AND NOT z) DIV 3
   являются совершенно допустимыми. И, фактически, они таковыми являются... настолько, насколько синтаксический анализатор в этом заинтересован. Паскаль не позволяет смешивать арифметические и логические переменные, и подобные вещи скорее перехватываются на семантическом уровне, когда придет время генерировать для них код, чем на синтаксическом уровне.
   Авторы C взяли диаметрально противоположный метод: они обрабатывают операторы как разные и C имеет что-то гораздо более похожее на наши семь уровней приоритета. Фактически, в C имеется не менее 17 уровней! Дело в том, что C имеет также операторы '=', '+=' и их родственников '<<', '>>', '++', '–' и т.д. Как ни странно, хотя в C арифметические и булевые операторы обрабатываются раздельно, то переменные нет... в C нет никаких булевых или логических переменных, так что логическая проверка может быть сделана на любом целочисленном значении.
   Мы сделаем нечто среднее. Я склонен обычно придерживаться Паскалевского подхода, так как он кажется самым простым с точки зрения реализации, но это приводит к некоторым странностям, которые я никогда очень сильно не любил, как например в выражении:
   IF (c >= 'A') and (c <= 'Z') then ...
   скобки обязательны. Я никогда не мог понять раньше почему, и ни мой компилятор, ни любой человек также не объясняли этого достаточно хорошо. Но сейчас мы все можем видеть, что оператор «and», имеющий приоритет как у оператора умножения, имеет более высокий приоритет, чем у операторов отношения, поэтому без скобок выражение эквивалентно:
   IF c >= ('A' and c) <= 'Z' then
   что не имеет смысла.
   В любом случае, я решил разделить операторы на различные уровни, хотя и не столько много как в C.
   <b-expression> ::= <b-term> [<orop> <b-term>]*
   <b-term> ::= <not-factor> [AND <not-factor>]*
   <not-factor> ::= [NOT] <b-factor>
   <b-factor> ::= <b-literal> | <b-variable> | <relation>
   <relation> ::= | <expression> [<relop> <expression]
   <expression> ::= <term> [<addop> <term>]*
   <term> ::= <signed factor> [<mulop> factor]*
   <signed factor>::= [<addop>] <factor>
   <factor> ::= <integer> | <variable> | (<b-expression>)
   Эта грамматика приводит к тому же самому набору семи уровней, которые я показал ранее. Действительно, это почти таже самая грамматика... я просто исключил заключенное в скобки b-выражение как возможный b-показатель и добавил отношение как допустимую форму b-показателя.
   Есть одно тонкое, но определяющее различие, которое заставляет все это работать. Обратите внимание на квадратные скобки в определении отношения. Это означает, что relop и второе выражение являются необязательными.
   Странным последствием этой грамматики (которое присутствует и в C) является то, что каждое выражения потенциально является булевым выражение. Синтаксический анализатор всегда будет искать булевское выражение, но «уладит» все до арифметического. Честно говоря, это будет замедлять синтаксический анализатор потому что он должен пройти через большее количество вызовов процедур. Это одна из причин, почему компиляторы Паскаля обычно быстрее выполяют компиляцию, чем компиляторы C. Если скорость для вас – больное место, придерживайтесь синтаксиса Паскаля.

Синтаксический анализатор

   Теперь, когда мы прошли через процесс принятия решений, мы можем поспешить с разработкой синтаксического анализатора. Вы делали это со мной несколько раз до этого, поэтому вы знаете последовательность: мы начнем с новой копии Cradle и будем добавлять процедуры одна за другой. Так что давайте сделаем это.
   Мы начинаем, как и в случае с арифметикой, работая с булевыми литералами а не с переменными. Это дает нам новый вид входного токена, поэтому нам также нужна новая программа распознавания и новая процедура для чтения экземпляров этого типа токенов. Давайте начнем, определив эти две новые процедуры:
   {–}
   { Recognize a Boolean Literal }
   function IsBoolean(c: char): Boolean;
   begin
   IsBoolean := UpCase(c) in ['T', 'F'];
   end;
   {–}
   { Get a Boolean Literal }
   function GetBoolean: Boolean;
   var c: char;
   begin
   if not IsBoolean(Look) then Expected('Boolean Literal');
   GetBoolean := UpCase(Look) = 'T';
   GetChar;
   end;
   {–}
   Внесите эти подпрограммы в вашу программу. Вы можете протестировать их, добавив в основную программу оператор печати:
   WriteLn(GetBoolean);
   Откомпилируйте программу и протестируйте ее. Как обычно пока не очень впечатляет но скоро будет.
   Теперь, когда мы работали с числовыми данными, мы должны были организовать генерацию кода для загрузки значений в D0. Нам необходимо сделать то же самое и для булевых данных. Обычным способом кодирования булевых переменных является использование 0 для представления FALSE и какого-либо другого значения для TRUE. Многие языки, как например C, используют для его представления целое число 1. Но я предпочитаю FFFF (или -1) потому что побитовое NOT также возвратит логическое NOT. Итак, нам теперь нужно выдать правильный ассемблерный код для загрузки этих значений. Первая засечка на синтаксическом анализаторе булевых выражений (BoolExpression, конечно):