Теперь, когда работаем с литералами, мы имеем одну небольшую проблему. С переменными мы знаем какого типа они должны быть потому что они были объявлены с таким типом. Мы не имеем такой информации о типе для литералов. Когда программист говорит «-1», означает ли это байт, слово или длинное слово? Мы не имеем никаких сведений. Очевидным способом было бы использование наибольшего возможного типа, т.е. длинного слова. Но это плохая идея, потому что когда мы примемся за более сложные выражения, мы обнаружим, что это заставит каждое выражение включающее литералы, также переводить в длинное.
Лучшим подходом было бы выбрать тип, основанный на значении литерала, как показано далее:
{–}
{ 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, Factor и т.д.) всегда возвращают идентификатор типа. Если мы сделаем это, то структура программы едва ли вообще изменится.
Первый шаг прост: мы должны переименовать нашу существующую версию 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;
{–}