Выполнимые утверждения

К этому времени мы можем генерировать пустую программу, которая имеет несколько объявленных переменных и возможно инициализированных. Но пока мы не генерировали ни строки выполнимого кода.
Верите ли вы или нет, но мы почти имеем пригодный для использования компилятор! Отсутствует только выполнимый код, который должен входить в основную программу. Но этот код – это только операции присваивания и операторы управления… все вещи, которые мы сделали раньше. Так что у нас не должно занять слишком много времени предусмотреть также и их.
БНФ определение, данное раньше для основной программы, включало операторный блок, который мы пока что игнорировали:
<main> ::= BEGIN <block> END
Сейчас мы можем рассматривать блок просто как серию операций присваивания:
<block> ::= (Assignment)*
Давайте начнем с добавления синтаксического анализатора для блока. Мы начнем с процедуры-заглушки для операции присваивания:

Parse and Translate an Assignment Statement
procedure Assignment;
begin
GetChar;
end;

Parse and Translate a Block of Statements
procedure Block;
begin
while Look <> ‘e’ do
Assignment;
end;

Измените процедуру Main чтобы она вызывала Block как показано ниже:

Parse and Translate a Main Program
procedure Main;
begin
Match(‘b’);
Prolog;
Block;
Match(‘e’);
Epilog;
end;

Эта версия все еще не генерирует никакого кода для «операций присваивания»… все что она делает это съедает символы до тех пор, пока не увидит “e”, означающее «END». Но она устанавливает основу для того, что следует дальше.
Следующий шаг, конечно, – это расширение кода для операций присваивания. Это то, что мы делали много раз до этого, поэтому я не буду задерживаться на этом. На этот раз, однако, я хотел бы работать с генерацией кода немного по-другому. До настоящего времени мы всегда просто вставляли Emits, которые генерируют выходной код в соответствии с подпрограммами синтасического анализа. Немного неструктурно, возможно, но это кажется самым простым способом и помогает видеть, какой код должен быть выдан для каждой конструкции.
Однако, я понимаю, что большинство из вас используют компьютер 80×86, так что от кода, сгенерированного для 68000 вам мало пользы. Некоторые из вас спрашивали меня, что если бы машинозависимый код мог бы быть собран в одном месте, то было бы проще перенастроить его на другой ЦПУ. Ответ конечно да.
Чтобы сделать это вставьте следующие подпрограммы «генерации кода»:

Clear the Primary Register
procedure Clear;
begin
EmitLn(‘CLR D0′);
end;

Negate the Primary Register
procedure Negate;
begin
EmitLn(‘NEG D0′);
end;

Load a Constant Value to Primary Register
procedure LoadConst(n: integer);
begin
Emit(‘MOVE #’);
WriteLn(n, ‘,D0′);
end;

Load a Variable to Primary Register
procedure LoadVar(Name: char);
begin
if not InTable(Name) then Undefined(Name);
EmitLn(‘MOVE ‘ + Name + ‘(PC),D0′);
end;

Push Primary onto Stack
procedure Push;
begin
EmitLn(‘MOVE D0,-(SP)’);
end;

Add Top of Stack to Primary
procedure PopAdd;
begin
EmitLn(‘ADD (SP)+,D0′);
end;

Subtract Primary from Top of Stack
procedure PopSub;
begin
EmitLn(‘SUB (SP)+,D0′);
EmitLn(‘NEG D0′);
end;

Multiply Top of Stack by Primary
procedure PopMul;
begin
EmitLn(‘MULS (SP)+,D0′);
end;

Divide Top of Stack by Primary
procedure PopDiv;
begin
EmitLn(‘MOVE (SP)+,D7′);
EmitLn(‘EXT.L D7′);
EmitLn(‘DIVS D0,D7′);
EmitLn(‘MOVE D7,D0′);
end;

Store Primary to Variable
procedure Store(Name: char);
begin
if not InTable(Name) then Undefined(Name);
EmitLn(‘LEA ‘ + Name + ‘(PC),A0′);
EmitLn(‘MOVE D0,(A0)’)
end;

Приятная особенность такого подхода, конечно, в том что мы можем перенастроить компилятор на новый ЦПУ просто переписав эти процедуры «генератора кода». Кроме того, позднее мы обнаружим что можем улучшить качество кода немного подправляя эти процедуры без необходимости изменения компилятора.
Обратите внимание, что и LoadVar и Store проверяют таблицу идентификаторов чтобы удостовериться, что переменная определена. Обработчик ошибки Undefined просто вызывает Abort:

Report an Undefined Identifier
procedure Undefined(n: string);
begin
Abort(‘Undefined Identifier ‘ + n);
end;

Итак, теперь мы наконец готовы начать обработку выполнимого кода. Мы сделаем это заменив пустую версию процедуры Assignment.
Мы проходили этот путь много раз прежде, так что все это должно быть вам знакомо. Фактически, если бы не изменения, связанные с генерацией кода, мы могли бы просто скопировать процедуры из седьмой части. Так как мы сделали некоторые изменения я не буду их просто копировать, но мы пройдем немного быстрее, чем обычно.
БНФ для операций присваивания:
<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;

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