Давайте создадим компилятор!
Шрифт:
Для начала нам понадобится функция GetNum. Мы уже видели ее несколько версий, некоторые возвращают только одиночный символ, некоторые строку, а некоторые целое число. Та, которая нам здесь нужна будет возвращать длинное целое, так что она может обрабатывать все, что мы ей подбросим. Обратите внимание, что здесь не возвращается никакой информации о типах: GetNum не интересуется тем, как будет использоваться число:
{–}
{ Get a Number }
function GetNum: LongInt;
var Val: LongInt;
begin
if not IsDigit(Look) then Expected('Integer');
Val := 0;
while IsDigit(Look) do begin
Val := 10 * Val + Ord(Look) – Ord('0');
GetChar;
end;
GetNum := Val;
SkipWhite;
end;
{–}
Теперь,
Лучшим подходом было бы выбрать тип, основанный на значении литерала, как показано далее:
{–}
{ Load a Constant to the Primary Register }
function LoadNum(N: LongInt): char;
var Typ : char;
begin
if abs(N) <= 127 then
Typ := 'B'
else if abs(N) <= 32767 then
Typ := 'W'
else Typ := 'L';
LoadConst(N, Typ);
LoadNum := Typ;
end;
{–}
(Я знаю, знаю, база числа не является в действительности симметричной. Вы можете хранить -128 в одиночном байте и -32768 в слове. Но это легко исправить и не стоит затраченного времени или дополнительной сложности возиться с этим сейчас. Стоящая мысль.)
Заметьте, что LoadNum вызывает новую версию подпрограммы генерации кода LoadConst, которая имеет дополнительный параметр для определения типа:
{–}
{ Load a Constant to the Primary Register }
procedure LoadConst(N: LongInt; Typ: char);
var temp:string;
begin
Str(N, temp);
Move(Typ, '#' + temp, 'D0');
end;
{–}
Теперь мы можем изменить процедуру Expression для использования двух возможных видов показателей:
{–}
{ Parse and Translate an Expression }
function Expression: char;
begin
if IsAlpha(Look) then
Expression := Load(GetName)
else
Expression := LoadNum(GetNum);
end;
{–}
(Вау, это, уверен, не причинило слишком большого вреда! Всего несколько дополнительных строк делают всю работу.)
ОК, соберите этот код в вашу программу и испытайте ее. Вы увидите, что она теперь работает и для переменных и для констант как допустимых выражений.
Аддитивные выражения
Если вы следовали за этой серией с самого начала, я уверен вы знаете, что будет дальше. Мы расширим форму выражения для поддержки сначала аддитивных выражений, затем мультипликативных, а затем общих выражений со скобками.
Хорошо, что мы уже имеем модель для работы с этими более сложными выражениями. Все,
Первый шаг прост: мы должны переименовать нашу существующую версию Expression в Term, как мы делали много раз раньше и создать новую версию Expression:
{–}
{ Parse and Translate an Expression }
function Expression: char;
var Typ: char;
begin
if IsAddop(Look) then
Typ := Unop
else
Typ := Term;
while IsAddop(Look) do begin
Push(Typ);
case Look of
'+': Typ := Add(Typ);
'-': Typ := Subtract(Typ);
end;
end;
Expression := Typ;
end;
{–}
Обратите внимание, как в этой подпрограмме каждый вызов процедуры стал вызовом функции и как локальная переменная Typ модифицируется при каждом проходе.
Обратите внимание также на новый вызов функции Unop, которая позволяет нам работать с ведущим унарным минусом. Это изменение не является необходимым... мы все еще можем использовать форму более похожую на ту, что мы использовали ранее. Я решил представить Unop как отдельную подпрограмму потому что позднее это позволит производить несколько лучший код, чем мы делали. Другими словами, я смотрю вперед на проблему оптимизации.
Для этой версии, тем не менее, мы сохраним тот же самый примитивный старый код, который делает новую подпрограмму тривиальной:
{–}
{ Process a Term with Leading Unary Operator }
function Unop: char;
begin
Clear;
Unop := 'W';
end;
{–}
Процедура Push – это подпрограмма генерации кода, которая теперь имеет параметр, указывающий тип:
{–}
{ Push Primary onto Stack }
procedure Push(Size: char);
begin
Move(Size, 'D0', '-(SP)');
end;
{–}
Теперь давайте взглянем на функции Add и Subtract. В более старых версиях этих подпрограмм мы позволяем им вызывать подпрограммы генерации кода PopAdd и PopSub. Мы продолжим делать это, что делает сами функции чрезвачайно простыми:
{–}
{ Recognize and Translate an Add }
function Add(T1: char): char;
begin
Match('+');
Add := PopAdd(T1, Term);
end;
{–}
{ Recognize and Translate a Subtract }
function Subtract(T1: char): char;
begin
Match('-');
Subtract := PopSub(T1, Term);
end;
{–}
Но простота обманчива, поскольку мы переложили всю логику на PopAdd и PopSub, которые больше не являются просто подпрограммами генерации кода. Они также должны теперь заботиться о необходимых преобразованиях типов.
Какие это преобразования? Простые: оба аргумента должны иметь тот же самый размер и результат также такой размер. Меньший из двух параметров должен быть «приведен» до размера большего.
Но это представляет небольшую проблему. Если переводимый параметр – второй (т.е. в основном регистре D0) мы в отличной форме. Если же нет, мы в затруднении: мы не можем изменить размер данных, которые уже затолкнуты в стек.