Лекции по построению компилятора на Pascal

       

ИНТЕРПРЕТАТОР


Итак, теперь, когда вы знаете почему мы принялись за все это, давайте начнем.  Просто для того, чтобы дать вам практику, мы начнем с пустого Cradle и создадим транслятор заново. На этот раз, конечно, мы сможем двигаться немного быстрее.

Так как сейчас мы собираемся выполнять арифметические действия, то первое, что мы должны сделать – изменить функцию GetNum, которая до настоящего момента всегда возвращала символ (или строку). Лучше если сейчас она будет возвращать целое число. Сделайте копию Cradle (на всякий случай не изменяйте сам Cradle!!) и модифицируйте GetNum следующим образом:

{--------------------------------------------------------------}

{ Get a Number }

function GetNum: integer;

begin

   if not IsDigit(Look) then Expected('Integer');

   GetNum := Ord(Look) - Ord('0');

   GetChar;

end;

{--------------------------------------------------------------}

Затем напишите следующую версию Expression:

{---------------------------------------------------------------}



{ Parse and Translate an Expression }

function Expression: integer;

begin

   Expression := GetNum;

end;

{--------------------------------------------------------------}

И, наконец, вставьте

     Writeln(Expression);

в конец основной программы. Теперь откомпилируйте и протестируйте.

Все, что эта программа делает - это "анализ" и трансляция "выражения", состоящего из одиночного целого числа. Как обычно, вы должны удостовериться, что она обрабатывает числа от 0 до 9 и выдает сообщение об ошибке для чего-либо другого. Это не должно занять у вас много времени!

Теперь давайте расширим ее, включив поддержку операций сложения. Измените Expression так:

{---------------------------------------------------------------}

{ Parse and Translate an Expression }

function Expression: integer;

var Value: integer;

begin

   if IsAddop(Look) then

      Value := 0

   else


      Value := GetNum;

   while IsAddop(Look) do begin

      case Look of

       '+': begin

               Match('+');

               Value := Value + GetNum;

            end;

       '-': begin

               Match('-');

               Value := Value - GetNum;

            end;

      end;

   end;

   Expression := Value;

end;

{--------------------------------------------------------------}

Структура Expression, конечно, схожа с тем, что мы делали ранее, так что мы не будем иметь слишком много проблем при ее отладке. Тем не менее это была серьезная разработка, не так ли? Процедуры Add и Subtract исчезли! Причина в том, что для выполнения необходимых действий нужны оба аргумента операции. Я мог бы сохранить эти процедуры и передавать в них значение выражения на данный момент, содержащееся в Value. Но мне показалось более правильным оставить Value как строго локальную переменную, что означает, что код для Add и Subtract должен быть помещен вместе. Этот результат наводит на мысль, что хотя разработанная нами структура была хорошей и проверенной для нашей бесхитростной схемы трансляции, она возможно не могла бы использоваться с ленивой оценкой. Эту небольшую интересную новость нам возможно необходимо иметь в виду в будущем.

Итак, транслятор работает? Тогда давайте сделаем следующий шаг. Несложно понять, что процедура Term должна выглядеть также. Замените каждый вызов GetNum в функции Expression на вызов Term и затем наберите следующую версию Term:



{---------------------------------------------------------------}

{ Parse and Translate a Math Term }

function Term: integer;

var Value: integer;

begin

   Value := GetNum;

   while Look in ['*', '/'] do begin

      case Look of

       '*': begin

               Match('*');

               Value := Value * GetNum;

            end;

       '/': begin

               Match('/');

               Value := Value div GetNum;

            end;

      end;

   end;

   Term := Value;

end;

{--------------------------------------------------------------}

Теперь испробуйте. Не забудьте двух вещей: во-первых мы имеем дело с целочисленным делением, поэтому, например, 1/3 выдаст ноль. Во-вторых, даже если мы можем получать на выходе многозначные числа, входные числа все еще ограничены одиночной цифрой.

Сейчас это выглядит как глупое ограничение, так как мы уже видели как легко может быть расширена функция GetNum. Так что давайте исправим ее прямо сейчас. Вот новая версия:

{--------------------------------------------------------------}

{ Get a Number }

function GetNum: integer;

var Value: integer;

begin

   Value := 0;

   if not IsDigit(Look) then Expected('Integer');

   while IsDigit(Look) do begin

      Value := 10 * Value + Ord(Look) - Ord('0');

      GetChar;

   end;

   GetNum := Value;

end;

{--------------------------------------------------------------}



Если вы откомпилировали и протестировали эту версию интерпретатора, следующим шагом должна быть установка функции Factor, поддерживающей выражения в скобках. Мы задержимся немного дольше на именах переменных. Сначала измените ссылку на GetNum в функции Term, чтобы вместо нее вызывалась функция Factor. Теперь наберите следующую версию Factor:

{---------------------------------------------------------------}

{ Parse and Translate a Math Factor }

function Expression: integer; Forward;

function Factor: integer;

begin

   if Look = '(' then begin

      Match('(');

      Factor := Expression;

      Match(')');

      end

   else

       Factor := GetNum;

end;

{---------------------------------------------------------------}

Это было довольно легко, а? Мы быстро пришли к полезному интерпретатору.


Содержание раздела