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

       

ПЕРЕДАЧА ПАРАМЕТРОВ


Снова, все мы знаем основную идею передачи параметров, но давайте просто для  надежности разберем ее заново.

Вообще, процедуре предоставляется список параметров, например:

    PROCEDURE FOO(X, Y, Z)

В объявлении процедуры параметры называются формальными параметрами и могут упоминаться в теле процедуры по своим именам. Имена, используемые для формальных параметров в действительности произвольны. Учитывается только позиция. В примере выше имя 'X' просто означает "первый параметр" везде, где он используется.

Когда процедура вызывается, "фактические параметры" переданные ей, связаны с формальными параметрами на взаимно-однозначном принципе.

БНФ для синтаксиса выглядит приблизительно так:

    <procedure> ::= PROCEDURE <ident> '(' <param-list> ')' <begin-block>

    <param_list> ::= <parameter> ( ',' <parameter> )* | null

Аналогично, вызов процедуры выглядит так:

    <proc call> ::= <ident> '(' <param-list> ')'

Обратите внимание, что здесь уже есть неявное решение, встроенное в синтаксис. Некоторые языки, такие как Pascal и Ada разрешают списку параметров быть необязательным. Если нет никаких параметров, вы просто полностью отбрасываете скобки. Другие языки, типа C и Modula-2, требуют скобок даже если список пустой.     Ясно, что пример, который мы  только что привели, соответствует первой точке зрения. Но, сказать правду, я предпочитаю последний. Для одних процедур решение кажется должно быть в пользу "без списочного" подхода. Оператор

    Initialize; ,

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



Но позднее мы также собираемся использовать и функции. И так как функции могут появляться в тех же самым местах что и простые скалярные идентификаторы, вы не сможете сказать об их различиях. Вы должны вернуться к объявлениям, чтобы выяснить это. Некоторые люди полагают, что это преимущество. Их аргументы в том, что идентификатор замещается значением и почему вас заботит, сделано ли это с помощью подстановки или функции? Но нас это иногда заботит, потому что функция может выполняться довольно долго. Если написав простой идентификатор в данном выражении мы можем понести большие затраты во время выполнения,  то мне кажется, что мы должны быть осведомлены об этом.


В любом случае, Никлаус Вирт разработал и Pascal и Modula-2. Я оправдаю его и полагаю что он имел веские причины для изменения правил во втором случае!

Само собой разумеется, легко принять любую точку зрения на то, как разрабатывать язык, так что это строго вопрос персонального предпочтения. Делайте это таким способом, какой вам больше нравится.

Перед тем как пойти дальше, давайте изменим транслятор для поддержки списка параметров (возможно пустого). Пока мы не будем генерировать никакого дополнительного кода... просто анализировать синтаксис. Код для обработки объявления имеет ту же самую форму, которую мы видели раньше когда работали со списками переменных:

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

{ Process the Formal Parameter List of a Procedure }

procedure FormalList;

begin

     Match('(');

     if Look <> ')' then begin

          FormalParam;

          while Look = ',' do begin

               Match(',');

               FormalParam;

          end;

     end;

     Match(')');

end;

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

В процедуру DoProc необходимо добавить строчку для вызова FormalList:

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

{ Parse and Translate a Procedure Declaration }

procedure DoProc;

var N: char;

begin

     Match('p');

     N := GetName;

     FormalList;

     Fin;

     if InTable(N) then Duplicate(N);

     ST[N] := 'p';

     PostLabel(N);

     BeginBlock;



     Return;

end;

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

Сейчас код для FormalParam всего лишь пустышка, который просто пропускает имена переменных:

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

{ Process a Formal Parameter }

procedure FormalParam;

var Name:  char;

begin

     Name := GetName;

end;

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

Для фактического вызова процедуры должен быть аналогичный код для обработки списка фактических параметров:

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

{ Process an Actual Parameter }

procedure Param;

var Name:  char;

begin

     Name := GetName;

end;

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

{ Process the Parameter List for a Procedure  Call }

procedure ParamList;

begin

     Match('(');

     if Look <> ')' then begin

          Param;

          while Look = ',' do begin

               Match(',');

               Param;

          end;

     end;

     Match(')');

end;

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

{ Process a Procedure Call }

procedure CallProc(Name: char);

begin

     ParamList;

     Call(Name);

end;

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

Обратите внимание, что CallProc больше не является просто простой подпрограммой генерации кода. Она имеет некоторую структуру. Для обработки я переименовал подпрограмму генерации кода  в просто Call и вызвал ее из CallProc.

Итак, если вы добавите весь этот код в ваш транслятор и протестируете его, вы обнаружите, что действительно можете правильно анализировать синтаксис. Обращаю ваше внимание на то, что здесь нет никакой проверки того, что количество (и, позднее, тип) формальных и фактических параметров совпадает. В промышленном компиляторе, мы конечно должны делать это. Сейчас мы игнорируем эту проблему той причине, что структура нашей таблицы идентификаторов пока не дает нам места для сохранения необходимой информации. Позднее мы подготовим место для этих данных и тогда сможем работать с этой проблемой.


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