Итак, если мы удовлетворены синтаксисом, представленным выше, то соответствующий код показан ниже:
{–}
{ Recognize and Translate a Relational «Equals» }
procedure Equals;
begin
Match('=');
Expression;
PopCompare;
SetEqual;
end;
{–}
{ Recognize and Translate a Relational «Not Equals» }
procedure NotEquals;
begin
Match('#');
Expression;
PopCompare;
SetNEqual;
end;
{–}
{ Recognize and Translate a Relational «Less Than» }
procedure Less;
begin
Match('<');
Expression;
PopCompare;
SetLess;
end;
{–}
{ Recognize and Translate a Relational «Greater Than» }
procedure Greater;
begin
Match('>');
Expression;
PopCompare;
SetGreater;
end;
{–}
{ Parse and Translate a Relation }
procedure Relation;
begin
Expression;
if IsRelop(Look) then begin
Push;
case Look of
'=': Equals;
'#': NotEquals;
'<': Less;
'>': Greater;
end;
end;
end;
{–}
{ Parse and Translate a Boolean Factor with Leading NOT }
procedure NotFactor;
begin
if Look = '!' then begin
Match('!');
Relation;
NotIt;
end
else
Relation;
end;
{–}
{ Parse and Translate a Boolean Term }
procedure BoolTerm;
begin
NotFactor;
while Look = '&' do begin
Push;
Match('&');
NotFactor;
PopAnd;
end;
end;
{–}
{ Recognize and Translate a Boolean OR }
procedure BoolOr;
begin
Match('|');
BoolTerm;
PopOr;
end;
{–}
{ Recognize and Translate an Exclusive Or }
procedure BoolXor;
begin
Match('~');
BoolTerm;
PopXor;
end;
{–}
{ Parse and Translate a Boolean Expression }
procedure BoolExpression;
begin
BoolTerm;
while IsOrOp(Look) do begin
Push;
case Look of
'|': BoolOr;
'~': BoolXor;
end;
end;
end;
{–}
Чтобы связать все это вместе не забудьте изменить обращение к Expression в процедурах Factor и Assignment на вызов BoolExpression.
Хорошо, если вы набрали все это, откомпилируйте и погоняйте эту версию. Сначала удостоверьтесь, что вы все еще можете анализировать обычные арифметические выражения. Затем попробуйте булевские. Наконец удостоверьтесь, что вы можете присваивать результат сравнения. Попробуйте к примеру:
pvx,y,zbx=z>ye.
что означает
PROGRAM
VAR X,Y,Z
BEGIN
X = Z > Y
END.
Видите как происходит присваивание булевского значения X?
Управляющие структуры
Мы почти дома. Имея булевы выражения легко добавить управляющие структуры. Для TINY мы разрешим только две из них, IF и WHILE:
<if> ::= IF <bool-expression> <block> [ ELSE <block>] ENDIF
<while> ::= WHILE <bool-expression> <block> ENDWHILE
Еще раз позвольте мне разъяснить решения, подразумевающиеся в этом синтаксисе, который сильно отличается от синтаксиса C или Pascal. В обоих этих языках «тело» IF или WHILE расценивается как одиночный оператор. Если вы предполагаете использовать блок из более чем одного оператора вы должны создать составной утверждение использую BEGIN-END (в Pascal) или '{}' (в C). В TINY (и KISS) нет таких вещей как составное утверждение... одиночное или множественное, они являются в этом языке просто блоками.
В KISS все управляющие структуры имеют явные и уникальные ключевые слова, выделяющие операторный блок поэтому не может быть никакой путаницы где он начинается и заканчивается. Это современный подход, используемый в таких уважаемых языках, как Ada и Modula-2 и он полностью устраняет проблему «висячих else».
Обратите внимание, что я мог бы использовать то же самое ключевое слово END для завершения всех конструкций, как это сделано в Pascal. (Закрывающая '}' в C служит той же самой цели.) Но это всегда вело к неразберихе, вот почему программисты на Pascal предпочитают писать так:
end { loop }
или end { if }
Как я объяснил в пятой части, использование уникальных терминальных ключевых слов увеличивает размер списка ключевых слов и, следовательно, замедляет лексический анализ, но в данном случае это кажется небольшой ценой за дополнительную подстраховку. Лучше обнаруживать ошибки во время компиляции, чем во время выполнения.
Одна последняя мысль: каждая из двух конструкций выше имеют нетерминалы
<bool-expression> и <block>,
расположенные рядом без разделяющих ключевых слов. В Паскале мы ожидали бы в этом месте ключевые слова THEN и DO.
Я не вижу проблем в том, чтобы опустить эти ключевые слова, и синтаксический анализатор также не будет иметь проблем, при условии, что мы не сделаем ошибок в bool-expression. С другой стороны, если мы включим эти дополнительные ключевые слова мы получили бы еще один уровень подстраховки за малые деньги, и с этим у меня также нет проблем. Примите правильное решение каким путем пойти.
ОК, после этого небольшого объяснения давайте продолжим. Как обычно нам понадобятся несколько новых подпрограмм генерации кода. Они генерируют код для условных и безусловных переходов:
{–}
{ Branch Unconditional }
procedure Branch(L: string);
begin
EmitLn('BRA ' + L);
end;
{–}
{ Branch False }
procedure BranchFalse(L: string);
begin
EmitLn('TST D0');
EmitLn('BEQ ' + L);
end;
{–}
Исключая изоляцию подпрограмм генератора кода, код для анализа управляющих конструкций такой же, как вы видели прежде:
{–}
{ Recognize and Translate an IF Construct }
procedure Block; Forward;
procedure DoIf;
var L1, L2: string;
begin
Match('i');
BoolExpression;
L1 := NewLabel;
L2 := L1;
BranchFalse(L1);
Block;
if Look = 'l' then begin
Match('l');
L2 := NewLabel;
Branch(L2);
PostLabel(L1);
Block;
end;
PostLabel(L2);
Match('e');
end;
{–}
{ Parse and Translate a WHILE Statement }
procedure DoWhile;
var L1, L2: string;
begin
Match('w');
L1 := NewLabel;
L2 := NewLabel;
PostLabel(L1);
BoolExpression;
BranchFalse(L2);
Block;
Match('e');
Branch(L1);
PostLabel(L2);
end;
{–}
Чтобы связать все это вместе нам нужно только изменить процедуру Block чтобы распознавать ключевые слова IF и WHILE. Как обычно мы расширим определение блока так:
<block> ::= ( <statement> )*
где
<statement> ::= <if> | <while> | <assignment>
Соответствующий код:
{–}
{ Parse and Translate a Block of Statements }
procedure Block;
begin
while not(Look in ['e', 'l']) do begin
case Look of
'i': DoIf;
'w': DoWhile;
else Assignment;
end;
end;
end;
{–}
Добавьте подпрограммы, которые я дал, откомпилируйте и протестируйте их. У вас должна быть возможность анализировать односимвольные версии любых управляющих конструкции. Выглядит довольно хорошо!
Фактически, за исключением односимвольного ограничения, мы получили практически полную версию TINY. Я назову его TINY Version 0.1.
Лексический анализ
Конечно, вы знаете, что будет дальше: Мы должны преобразовать программу так, чтобы она могла работать с многосимвольными ключевыми словами, переводами строк и пробелами. Мы только что прошли все это в седьмой главе. Мы будем использовать метод распределенного сканера, который я показал вам в этой главе. Фактическая реализация немного отличается, потому что различается способ, которым я обрабатываю переводы строк.