Синтаксические и контекстные правила для процедур с параметрами.

Введение процедур с параметрами требует изменений в синтаксических правилах CFPascal.

 

SR 27. <процедура> ::= <заголовок процедуры>; <оператор BEGIN>

 

SR 28. <оператор процедуры> ::= <идентификатор>

| <идентификатор>(<список фактических параметров>)

SR 30. <список фактических параметров> ::= <список идентификаторов>

 

SR 31. <заголовок процедуры> ::= PROCEDURE <идентификатор>

| PROCEDURE <идентификатор>(<список формальных параметров>)

 

SR 32. <список формальных параметров> ::= VAR <список идентификаторов> : <тип>

| <список формальных параметров>; VAR <список идентификаторов> : <тип>

 

В контекстные правила также должны быть внесены изменения

 

CR4.Идентификатор процедуры, использованный в <операторе процедуры> должен появиться в <заголовке процедуры> при объявлении <процедуры>, <список фактических параметров> должен быть такой же длины что и <список формальных параметров> соответствующего <заголовка процедуры>. Типы идентификаторов в <списке формальных параметров> и <списке фактических параметров> должны соотвествовать.

 

CR6.Любой идентификатор, объявленный в<списке формальных параметров> процедуры может быть использован только в <операторе BEGIN> данной процедуры. Такие идентификаторы называются локальными.

 

Разделение файлов.

В разделе 4.1.5 была разработана программа для копирования данных из INPUT в OUTPUT с разделением на нечетные (odd), которые печатались первыми и четные (even).

Процедуры с параметрами могут упростить ее дизайн, дополнительно введем обработку маркеров конца строки вместо использования символа #.

Проект программы был такой:

PROGRAM Split(INPUT, OUTPUT);

{Копировать INPUT to OUTPUT, сначала нечетные, потом четные}

VAR

Odds, Evens: TEXT;

BEGIN {Split}

{Разделить INPUT на четные и нечетные}

{Копировать нечетные в OUTPUT}

{Копировать четные в OUTPUT}

END. {Split}

 

Две задачи

{Копировать нечетные в OUTPUT}

{Копировать четные в OUTPUT}

Могут быть выполнены одной процедурой CopyOut, если ей задать параметр, вместо которого будут поставлены либо Odds, либо Evens.

 

PROGRAM Split(INPUT, OUTPUT);

{Копировать INPUT to OUTPUT, сначала нечетные, потом четные}

VAR

Odds, Evens: TEXT;

PROCEDURE CopyOut(VAR F1: TEXT; VAR Ch1: CHAR);

{Копирует F1 в OUTPUT, игнорируя маркеры конца строк}

BEGIN {Split}

{Разделить INPUT на четные и нечетные}

CopyOut(Odds, Ch);

CopyOut(Evens, Ch);

END. {Split}

 

Вот определение CopyOut

 

PROCEDURE CopyOut(VAR F1 : TEXT; VAR Ch1 : CHAR);

{Копирует F1 в OUTPUT, игнорируя маркеры конца строк}

BEGIN {CopyOut}

RESET(F1);

WHILE NOT EOF(F1)

DO

BEGIN

WHILE NOT EOLN(F1)

DO

BEGIN

READ(F1, Ch);

WRITE(Ch)

END;

READLN(F1)

END

END {CopyOut}

Оператор BEGIN процедуры CopyOut использует только параметры F1 и Ch, объявленные в заголовке процедуры. Напротив, процедура без параметров может использовать переменные объявленные в теле программы, содержащей процедуру. Процедура с параметрами может использовать смешанный подход – одновременно пользоваться переменными окружающей программы и параметрами. Однако, процедуры, использующие только собственные параметры более легки для понимания и повторного использования. Эта идея использования одних только параметров в процедурах получила название правила «все или ничего».

 

Исчисление параметров.

 

Текст оператора BEGIN процедуры сохраняется в состоянии выполнения как значение процедурного идентификатора. Этот текст может содержать ссылки на формальные параметры. Предположим на момент, что переменные с таким же идентификатором, что и формальные параметры используются как фактические параметры при вызове оператора процедуры. Тогда определение для простых процедур может дать верный результат. Например:

 

PROGRAM Sample(INPUT, OUTPUT);

VAR

Vin: CHAR;

PROCEDURE Bump(VAR Vin: CHAR);

BEGIN {Bump}

IF Vin = ‘A’

THEN

Vin := ‘B’
END; {Bump}

BEGIN {Sample}

Vin := ’Q’;

Bump(Vin)

END. {Sample}

 

PROCEDURE Bump … END (s)

= s È {<†Bump†, †BEGIN IF Vin = ‘A’ THEN Vin := ‘B’ END†>}

 

т.е. простое определение даст

 

Bump(Vin) (s) = s(Bump) (s) = BEGIN IF Vin = ‘A’ THEN Vin := ‘B’ END (s)

 

что является корректным значением.

 

Когда функция частного значения вычисляется отдельно, только исходя из декларации, идентификаторы параметров появляются в результатах. В процедуре Bump

 

IF Vin = ‘A’ THEN Vin := ‘B’ = (Vin = ‘A’ -> Vin := ‘B’) | ( )

 

идентификатор Vin является параметром функции значения, в математическом смысле.

 

Формальные и фактические параметры не всегда являются одним и тем же, но такое определение могло бы работать, когда при использовании значения текста процедуры он будет иметь фактические параметры, вставленные вместо формальных. То есть, если в приведенном выше примере вызов процедурного оператора выглядел бы как

 

Bumo(DiffV)

 

где фактический параметр DiffV предположительно соответственно объявлен как CHAR, тогда значение могло быть получено вставкой DiffV везде в тексте Bump, где встречаетcя Vin. Таким образом,

 

Bumo(DiffV) = BEGIN IF DiffV = ‘A’ THEN DiffV := ‘B’ END

 

В общем случае, если P – идентификатор процедуры, и его объявление имеет формальный параметр X, тогда для фактического параметра Y, определим

 

P (Y) (s) = s (P) X<-Y (s)

 

где X <- Y означает, что внутри s(P) каждое вхождение X заменяется на Y. Обобщение до более чем одного параметра очевидно: идентификатор каждого фактического параметра заменяет соответствующий идентификатор формального параметра.

 

Значение оператора процедуры Bump также может быть получено из параметризованного условного присваивания заменой формального параметра на фактический. Т.е.

 

Bump(DiffV) = (Vin = ‘A’ -> Vin := ‘B’) Vin <-DiffV | ( )

= (DiffV = ‘A’ -> DiffV := ‘B’) | ( )

 

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

 

PROGRAM Alias(INPUT, OUTPUT);

VAR

One, Two : CHAR;

PROCEDURE SwitchThese2(VAR V1, V2, Temp: CHAR);

{ V1, V2, Temp := V2, V1, V1 }

BEGIN { SwitchThese2}

Temp := V1;

V1 := V2;

V2 := Temp

END; {SwitchThese2}

BEGIN {Alias }

SwitchThese2(One, Two, Two);

END. {Alias}

 

имеет

 

SwitchThese2(One, Two, Two) = Two := One; One := Two, Two := Two; = (Two := One)

 

Но замена в условном присваивании полученного из раздела объявлений дает неправильный результат.

(Two, Two, One) := (Two, One := One, Two)

Сложность с именами параметров называется «Алиасинг», потому что один фактический идентификатор имеет два формальных имени, в пример Two имеет два различных имени V2 и Temp.

Если происходит Алиасинг и параметризованное определение дает неверные результаты, обычно это значит, что процедура была написана не очень аккуратно и она выдает непредусмотренный результат.

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

 

{Фактические параметры должны быть различными идетификаторами}

 

могли бы быть полезными.

Без алиасинга, можно безопасно вычислить функцию частного значения процедуры и подставить фактические параметры в результат. Когда это случается, тем не менее, определение может быть использовано напрямую, копируя фактический параметр до вычисления функции частного значения.

 

Процедуры с объявлениями.

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

 

Новые идеи: объявление локальной переменной в процедуре, область видимости переменной, разрешение конфликтов имен переменных, сравнение строк, лексикографический порядок.

 

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

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

 

PROCEDURE SwitchThese2(VAR V1, V2, temp: CHAR);

BEGIN

Temp := V1;

V1 := V2;

V2 := Temp

END

с использованием Temp как параметра. При использовании процедуры для обмена двух переменных, скажем, One и Two, вызывающая программа должна была предоставить третью, временную переменную, например

 

SwitchThese2(One, Two, Ch)

обменяет значения One и Two и также поместит в Ch старое значение One.

Возможность объявления локальных переменных в CF Pascal позволяет таким переменным как Temp быть скрытыми внутри процедуры. Например:

 

PROCEDURE Exchange(VAR Ch1, Ch2: CHAR);

{Обменивает Ch1 и Ch2}

VAR

Temp: CHAR;

BEGIN

Temp := Ch1;

Ch1 := Ch2;

Ch2 := Temp

END

 

Как и формальные параметры Ch1 и Ch2, локальная переменная Temp не известна и не может быть использована извне процедуры Exchange. Оператор процедуры

 

Exchange(One, Two)

 

обменяет значения One и Two. Exchange может быть проще описан для повторного использования, поскольку он использует и изменяет только две переменные вместо трех.

 

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

Таблицы выполнения показывают поведение переменных в процедурах также, как они делали это для программ. Например, программа

 

PROGRAM Copy2(INPUT, OUTPUT);

{Копирует первые два символа из INPUT в OUTPUT}

PROCEDURE CopyChar(VAR F1, F2: TEXT);

{Копирует в F2 следующий символ их F1}

VAR

Ch: CHAR;

BEGIN {CopyChar}

IF NOT EOF(F1)

THEN

BEGIN

READ(F1, Ch);

WRITE(F2, Ch)

END

END; {CopyChar}

BEGIN {Copy2}

CopyChar(INPUT, OUTPUT);

CopyChar(INPUT, OUTPUT);

WRITELN

END. {Copy2}

 

 

имеет следующую таблицу выполнения.

 

 

  INPUT OUTPUT Ch EOF
  PROGRAM Copy2(INPUT, OUTPUT); BEGIN {Copy2} CopyChar (INPUT, OUTPUT) VAR Ch: CHAR; BEGIN {CopyChar} IF NOT EOF(INPUT) READ(INPUT, Ch); WRITE(OUTPUT, Ch) END; { CopyChar} CopyChar (INPUT, OUTPUT)ж VAR Ch: CHAR; BEGIN {CopyChar} IF NOT EOF(INPUT) READ(INPUT, Ch); WRITE(OUTPUT, Ch) END; { CopyChar} WRITELN END. {Copy2} ABC ABC/   ABC/     ABC/   ABC   _     A_     AB_   AB/_ AB     ?     A   §   ?     B   §     FALSE     FALSE     FALSE  

 

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

Внутри процедуры могут быть не только объявления локальных переменных, но и объявления локальных процедур. Локальные процедуры могут вызываться только в пределах процедуры, где они объявлены, и полностью невидимы за ее пределами. Значение, которое приходит в состояние выполнения для локальной процедуры – это ее текст. В целом дела с объявлениями внутри процедур обстоят примерно также, как и с объявлениями в программе за исключением того, что идентификаторы могут быть использованы только в пределах процедуры и входят и выходят из состояния выполнения при каждом вызове процедурного оператора.