Литмир - Электронная Библиотека
Содержание  
A
A

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

БНФ для операций присваивания:

<assignment> ::= <ident> = <expression>

<expression> ::= <first term> ( <addop> <term> )*

<first term> ::= <first factor> <rest>

<term> ::= <factor> <rest>

<rest> ::= ( <mulop> <factor> )*

<first factor> ::= [ <addop> ] <factor>

<factor> ::= <var> | <number> | ( <expression> )

Эта БНФ также немного отличается от той, что мы использовали раньше... еще одна «вариация на тему выражений». Эта специфичная версия имеет то, что я считаю лучшей обработкой унарного минуса. Как вы увидите позднее, это позволит нам очень эффективно обрабатывать отрицательные константы. Здесь стоит упомянуть, что мы часто видели преимущества «подстраивания» БНФ по ходу дела, с цель сделать язык легким для анализа. То, что вы видете здесь, немного другое: мы подстраиваем БНФ для того, чтобы сделать генерацию кода более эффективной! Это происходит впервые в этой серии.

Во всяком случае, следующий код реализует эту БНФ:

{–}

{ Parse and Translate a Math Factor }

procedure Expression; Forward;

procedure Factor;

begin

if Look = '(' then begin

Match('(');

Expression;

Match(')');

end

else if IsAlpha(Look) then

LoadVar(GetName)

else

LoadConst(GetNum);

end;

{–}

{ Parse and Translate a Negative Factor }

procedure NegFactor;

begin

Match('-');

if IsDigit(Look) then

LoadConst(-GetNum)

else begin

Factor;

Negate;

end;

end;

{–}

{ Parse and Translate a Leading Factor }

procedure FirstFactor;

begin

case Look of

'+': begin

Match('+');

Factor;

end;

'-': NegFactor;

else Factor;

end;

end;

{–}

{ Recognize and Translate a Multiply }

procedure Multiply;

begin

Match('*');

Factor;

PopMul;

end;

{–}

{ Recognize and Translate a Divide }

procedure Divide;

begin

Match('/');

Factor;

PopDiv;

end;

{–}

{ Common Code Used by Term and FirstTerm }

procedure Term1;

begin

while IsMulop(Look) do begin

Push;

case Look of

'*': Multiply;

'/': Divide;

end;

end;

end;

{–}

{ Parse and Translate a Math Term }

procedure Term;

begin

Factor;

Term1;

end;

{–}

{ Parse and Translate a Leading Term }

procedure FirstTerm;

begin

FirstFactor;

Term1;

end;

{–}

{ Recognize and Translate an Add }

procedure Add;

begin

Match('+');

Term;

PopAdd;

end;

{–}

{ Recognize and Translate a Subtract }

procedure Subtract;

begin

Match('-');

Term;

PopSub;

end;

{–}

{ Parse and Translate an Expression }

procedure Expression;

begin

FirstTerm;

while IsAddop(Look) do begin

Push;

case Look of

'+': Add;

'-': Subtract;

end;

end;

end;

{–}

{ Parse and Translate an Assignment Statement }

procedure Assignment;

var Name: char;

begin

Name := GetName;

Match('=');

Expression;

Store(Name);

end;

{–}

ОК, если вы вставили весь этот код, тогда откомпилируйте и проверьте его. Вы должны увидеть приемлемо выглядящий код, представляющий собой законченную программу, которая будет ассемблироваться и выполняться. У нас есть компилятор!

Булева логика

Следующий шаг также должен быть вам знаком. Мы должны добавить булевы выражения и операторы отношений. Снова, так как мы работали с ними не один раз, я не буду подробно разбирать их за исключением моментов, в которых они отличаются от того, что мы делали прежде. Снова, мы не будем просто копировать их из других файлов потому что я немного изменил некоторые вещи. Большинство изменений просто включают изоляцию машинозависимых частей как мы делали для арифметических операций. Я также несколько изменил процедуру NotFactor для соответствия структуре FirstFactor. Наконец я исправил ошибку в объектном коде для операторов отношений: в инструкции Scc я использовал только младшие 8 бит D0. Нам нужно установить логическую истину для всех 16 битов поэтому я добавил инструкцию для изменения младшего байта.

Для начала нам понадобятся несколько подпрограмм распознавания:

{–}

{ Recognize a Boolean Orop }

function IsOrop(c: char): boolean;

begin

IsOrop := c in ['|', '~'];

end;

{–}

{ Recognize a Relop }

function IsRelop(c: char): boolean;

begin

IsRelop := c in ['=', '#', '<', '>'];

end;

{–}

Также нам понадобятся несколько подпрограмм генерации кода:

{–}

{ Complement the Primary Register }

procedure NotIt;

begin

EmitLn('NOT D0');

end;

{–}

.

.

.

{–}

{ AND Top of Stack with Primary }

procedure PopAnd;

begin

EmitLn('AND (SP)+,D0');

end;

{–}

{ OR Top of Stack with Primary }

procedure PopOr;

begin

EmitLn('OR (SP)+,D0');

end;

{–}

{ XOR Top of Stack with Primary }

procedure PopXor;

begin

EmitLn('EOR (SP)+,D0');

end;

{–}

{ Compare Top of Stack with Primary }

procedure PopCompare;

begin

EmitLn('CMP (SP)+,D0');

end;

{–}

{ Set D0 If Compare was = }

procedure SetEqual;

begin

EmitLn('SEQ D0');

EmitLn('EXT D0');

end;

{–}

{ Set D0 If Compare was != }

procedure SetNEqual;

begin

EmitLn('SNE D0');

EmitLn('EXT D0');

end;

{–}

{ Set D0 If Compare was > }

procedure SetGreater;

begin

EmitLn('SLT D0');

EmitLn('EXT D0');

end;

{–}

{ Set D0 If Compare was < }

procedure SetLess;

begin

EmitLn('SGT D0');

EmitLn('EXT D0');

end;

{–}

Все это дает нам необходимые инструменты. БНФ для булевых выражений такая:

<bool-expr> ::= <bool-term> ( <orop> <bool-term> )*

<bool-term> ::= <not-factor> ( <andop> <not-factor> )*

<not-factor> ::= [ '!' ] <relation>

<relation> ::= <expression> [ <relop> <expression> ]

Зоркие читатели могли бы заметить, что этот синтаксис не включает нетерминал «bool-factor» используемый в ранних версиях. Тогда он был необходим потому, что я также разрешал булевы константы TRUE и FALSE. Но не забудьте, что в TINY нет никакого различия между булевыми и арифметическими типами... они могут свободно смешиваться. Так что нет нужды в этих предопределенных значениях... мы можем просто использовать -1 и 0 соответственно.

В терминологии C мы могли бы всегда использовать определения:

#define TRUE -1

#define FALSE 0

(Так было бы, если бы TINY имел препроцессор.) Позднее, когда мы разрешим объявление констант, эти два значения будут предопределены языком.

Причина того, что я заостряю на этом ваше внимание, в том что я пытался использовать альтернативный путь, который заключался в использовании TRUE и FALSE как ключевых слов. Проблема с этим подходом в том, что он требует лексического анализа каждого имени переменной в каждом выражении. Как вы помните, я указал в главе 7, что это значительно замедляет компилятор. Пока ключевые слова не могут быть в выражениях нам нужно выполнять сканирование только в начале каждого нового оператора... значительное улучшение. Так что использование вышеуказанного синтаксиса не только упрощает синтаксический анализ, но также ускоряет сканирование.

36
{"b":"48699","o":1}