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

       

ТЕРМЫ И ВЫРАЖЕНИЯ


Я уверен вы знаете, что будет дальше. Мы должны еще раз создать остальные процедуры, которые реализуют синтаксический анализ выражений по методу рекурсивного спуска. Все мы знаем, что иерархия процедур для арифметических выражений такая:

выражение

        

терм

             

показатель

Однако сейчас давайте продолжим разработку по шагам и рассмотрим выражения только с аддитивными термами. Код для реализации выражений, включающих возможно первый терм со знаком, показан ниже:

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

{ Parse and Translate an Expression }

procedure Expression;

begin

 SignedFactor;

 while IsAddop(Look) do



  case Look of

   '+': Add;

   '-': Subtract;

  end;

end;

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

Эта процедура вызывает две другие процедуры для обработки операций:

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

{ Parse and Translate an Addition Operation }

procedure Add;

begin

 Match('+');

 Push;

 Factor;

 PopAdd;

end;

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

{ Parse and Translate a Subtraction Operation }

procedure Subtract;

begin

 Match('-');

 Push;

 Factor;

 PopSub;

end;

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

Эти три процедуры Push, PopAdd и PopSub - новые подпрограммы генерации кода. Как подразумевает имя, процедура Push генерирует код для помещения основного регистра (D0 в нашей реализации для 68000) в стек. PopAdd и PopSub выталкивают вершину стека и прибавляют или вычитают ее из основного регистра. Код показан ниже:

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

{ Push Primary to Stack }

procedure Push;

begin

 EmitLn('MOVE D0,-(SP)');

end;

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


{ Add TOS to Primary }

procedure PopAdd;

begin

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

end;

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

{ Subtract TOS from Primary }

procedure PopSub;

begin

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

 Negate;

end;

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

Добавьте эти подпрограммы в Parser и CodeGen и измените основную программу для вызова Expression. Вуа ля!

Следующий шаг, конечно, это добавление возможности работы с мульпликативными термами. С этой целью мы добавим процедуру Term и процедуры генерации кода PopMul и PopDiv. Эти процедуры генерации кода показаны ниже:

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

{ Multiply TOS by Primary }

procedure PopMul;

begin

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

end;

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

{ Divide Primary by TOS }

procedure PopDiv;

begin

 EmitLn('MOVE (SP)+,D7');

 EmitLn('EXT.L D7');

 EmitLn('DIVS D0,D7');

 EmitLn('MOVE D7,D0');

end;

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

Я должен признать, что подпрограмма деления немного перегружена, но с этим ничего нельзя поделать. К сожалению, хотя процессор 68000 позволяет выполнять деление используя вершину стека (TOS), он требует аргументы в неправильном порядке, подобно тому как для вычитания. Поэтому наше единственное спасение в том чтобы вытолкнуть стек в рабочий регистр (D7), выполнить там деление, и затем поместить результат обратно в наш основной регистр D0. Обратите внимание на использование знаковых операций умножения и деления. Этим неявно подразумевается что все наши переменные будут 16-разрядными целыми числами со знаком. Это решение затронет нас позднее, когда мы начнем рассматривать множественные типы данных, преобразования типов и т.п.

Наша процедура Term это практически аналог Expression и выглядит так:

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

{ Parse and Translate a Term }



procedure Term;

begin

 Factor;

 while IsMulop(Look) do

  case Look of

   '*': Multiply;

   '/': Divide;

  end;

end;

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

Наш следующий шаг - изменение некоторых имен. SignedFactor теперь становится SignedTerm а вызовы Factor в Expression, Add, Subtract и SignedTerm заменяются на вызов Term:

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

{ Parse and Translate a Term with Optional Leading Sign }

procedure SignedTerm;

var Sign: char;

begin

 Sign := Look;

 if IsAddop(Look) then

  GetChar;

 Term;

 if Sign = '-' then Negate;

end;

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

...

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

{ Parse and Translate an Expression }

procedure Expression;

begin

 SignedTerm;

 while IsAddop(Look) do

  case Look of

   '+': Add;

   '-': Subtract;

  end;

end;

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

Если память мне не изменяет мы однажды уже имели и процедуру SignedFactor и SignedTerm. У меня были причины сделать так в то время... они имели отношение к обработке булевой алгебры и, в частности, булевой функции "not". Но, конечно, для арифметических операций дублирование не нужно.  В выражении типа:

    -x*y

очевидно, что знак идет со всем термом x*y а не просто с показателем x и таким способом Expression и закодирован.

Протестируйте этот новый код, выполнив Main. Она все еще вызывает Expression, так что теперь вы должны быть способны работать с выражениями, содержащими любую из четырех арифметических операций.

Наше последнее дело, относительно выражений, это модификация процедуры Factor для разрешения выражений в скобках. Используя рекурсивный вызов Expression мы можем уменьшить необходимый код практически до нуля. Пять строк, добавленные в Factor, выполнят эту работу:

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

{ Parse and Translate a Factor }

procedure Factor;

begin

 if Look ='(' then begin

  Match('(');

  Expression;

  Match(')');

  end

 else if IsDigit(Look) then

  LoadConstant(GetNumber)

 else if IsAlpha(Look)then

  LoadVariable(GetName)

 else

  Error('Unrecognized character ' + Look);

end;

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

К этому моменту ваш "компилятор" должен уметь обрабатывать любые допустимые выражения, которые вы ему подбросите. Еще лучше, что он должен отклонить все недопустимые!


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