• Keine Ergebnisse gefunden

A Procedure That Calls Itself'

7 .2. Recursion and Forward Procedures

7.2.1. A Procedure That Calls Itself'

On a recursive procedure invocation, new copies of the procedure's parameters and local variables are made, as described in Section 5.3. Consider the procedure of Example 7.2.1-1, which performs integer multiplication by repeated addition (not a very fast method). In brief,

BEGIN "sums"

*

Sum the integers input from cmdFile, one on each line.

=If: Require that each integer be in a valid format.

BOOLEAN PROCEDURE validInteger (STRING s);

BEGIN

INTEGER Chi

IF first(s) = ' - ' THEN cRead(s);

*

May be initial "-"

WHILE s DOB

ch := cRead(s);

IF ch < '0' OR ch > '9' THEN RETURN(FALSE) END;

RETURN(TRUE); =If: If made i t to here, must be OK END;

INITIAL PROCEDURE;

BEGIN

INTEGER i,sum;

STRING s;

sum := 0;

DOB write(logFile,"Next integer «eol> to stop): ");

read(cmdFile,s); IF s

= ""

THEN DONE;

END;

IF NOT validInteger(s) THENB

write(logFile,s,": invalid integer" & eol);

CONTINUE END;

read"(s,i); sum := sum

+

i;

write (logFile, "Sum: ",sum,eol) END;

END "sums"

Example 7.1.2-1. Use of the Continue Statement

the call "mul(5,2)" returns "mul(5,1) + 5". In turn, "mul(5,1)" returns "mul(5,0) + 5";

"mul(5,0)" returns 0. Therefore, the call "mul(5,2)" returns (0 + 5) + 5, or 10, as desired.

Consider that the original call "mul(5,2)" is "invocation #1" of the procedure, as shown in Example 7.2.1-2.

INTEGER PROCEDURE mul (INTEGER i,j);

# Calculate the product of i and j by repeated addition.

# j must be nonnegative.

BEGIN

IF j - 0 THEN RETURN(O); # Zero times anything is zero

# otherwise j

>

0:

RETURN(mul(i,j - 1)

+

i); # (i

*

(j - 1» + i

=

j

*

i

Example 7.2.1-1. A Recursive Procedure to Perform Multiplication

+---+

I i

=

5 I

+---+

j

=

2 I

+---+

\

>

Invocation #1

/

Example 7.2.1-2. Invocation #1 of "mul" on Entry to the Procedure

Invocation #1 finds that j is not zero, so it executes the second statement of the procedure, which requires it to evaluate "mul(ij - 1)", i.e., "mul(5,1)". Invocation #1 now calls "mul" with the arguments 5 and 1, resulting in invocation #2. See Example 7.2.1-3

+---+

i

=

5

+---+

I j

=

1 I

+---+

i

=

5

+---+

I j

=

2 I

+---+

\

> Invocation #2 /

\

> Invocation #1 (inactive)

/

Example 7.2.1-3. Invocation #2 of "mul" on Entry to the Procedure

Invocation #2 again finds that j is not zero, so it too executes the second statement, which requires it to evaluate "mul(5,O)", creating invocation #3. The situation then looks like Example 7.2.1-4.

Example 7.2.1-4. Invocation #3 of "mul" on Entry to the Procedure

Invocation #3 finds that j is zero, so it returns zero. The Return Statement immediately terminates invocation #3 so that invocation #2 becomes active again. See Example 7.2.1-5.

+---+

Example 7.2.1-5. Invocation #2 of "mul" upon Return from #3

Invocation #2, which was in the middle of evaluating the expression in the Return Statement, receives the value 0 returned by invocation #3, and so calculates "0 + i", i.e., "0 + 5".

Invocation #2 therefore terminates, returning the value 5. Consequently, invocation #1

+---+

i

=

5

+---+

I j

=

2 I

+---+

\

> Invocation #1 /

Example 7.2.1-6. Invocation #1 of "mul" upon Return from #2

Invocation #1, which was also evaluating the expression in the middle of the Return Statement, receives the value 5 returned by invocation #2. Therefore, it calculates and returns "5

+

5", i.e.,

1 0, as expected.

7.2.2. Mutual Recursion and the "FORWARD" Qualifier

Recursion may occur when two procedures call each other ("mutual recursion"). MAINSAIL requires every procedure's declaration to appear in the source file prior to any calls to the procedure. If procedure a calls procedure b, and b calls a, this poses a problem, since b must be declared before a, but a must be declared before b. The "forward declaration" provides a solution to this problem. An example is shown in Example 7.2.2-2, where the procedures expression and value are given forward declarations.

The program CALC of Example 7.2.2-2 is an interactive calculator, like that of Example 6.4-1.

However, CALC accepts a more sophisticated syntax; a summary of its grammar is shown in Example 7.2.2-1. The technique used in Example 7.2.2-2 to parse the grammar is called

"recursive descent", since it uses recursion in "descending" (breaking down) sentences of the grammar.

Each line of CALC input must be a "command" in accordance with the rules of Example 7.2.2-1. A sample run is shown in Example 7.2.2-3.

The forward declaration of a procedure appears before the first call to a procedure. It is composed of the keyword "FORWARD" followed by the header of the procedure. Unlike Pascal, MAINSAIL requires that the full header of the procedure be given again when the full procedure declaration (including the procedure body) appears. The full declaration is said to

"give the forward procedure a body".

"=>" means "is defined as". Single quote marks are placed around commands typed literally. Comments appear in parentheses. Square brackets are used for grouping,

"!" appears between alternatives, and

"*"

indicates zero or more repetitions of the preceding item.

command => ' S' (show all accumulators)

=> ' Q' (quit)

=>

' ,

(empty command line)

=> value (print the value) value => assignment

=> expression

assignment => register ,=, value

(do the assignment and return the value assigned) expression => term [[ I +, term]

term => factor [['*' factor]

factor => unsignedInteger

=> register

=> I ( ' expression ')'

[' -' term]]

*

[' I' factor]]

*

An unsignedInteger is just an unsigned integer.

register => 'A' (there are three registers)

=>

'B'

=> ' C'

Example 7.2.2-1. The Grammar of CALC Commands

BEGIN "calc"

INTEGER a,b,c;

*

Values in the accumulators Example 7.2.2-2. Mutual Recursion (continued)

STRING PROCEDURE getToken (MODIFIES STRING s);

#: Remove the next thing from s.

BEGIN

INTEGER Chi STRING t;

WHILE first(s)

= , ,

OR first(s)

=

first(tab) DO cRead(s);

IF NOT s THEN RETURN ('It'); #: End of string ch := cRead(s);

#: Everything but integers is one character:

IF ch

<

'0' OR ch

>

'9' THEN RETURN(cvcs(ch»i

#: Now i t must be an integer:

t := cvcs(ch);

WHILE first(s) GEQ '0' AND first(s) LEQ '9' DO cWrite(t,cRead(s»;

RETURN(t);

END;

PROCEDURE ungetToken (MODIFIES STRING s;

REPEATABLE STRING token);

#: Put token back at the front of s.

BEGIN

s := token & " " & s;

END;

BOOLEAN PROCEDURE validInteger (STRING s);

#: Returns true if s is an unsigned integer.

BEGIN

INTEGER Chi

IF NOT s THEN RETURN(FALSE);

WHILE s DOB

ch

:=

cRead(s);

IF ch <

'0'

OR ch >

'9'

THEN RETURN(FALSE) END;

RETURN(TRUE); #: If made i t to here, must be OK END;

Example 7.2.2-2. Mutual Recursion (continued)

FORWARD INTEGER PROCEDURE expression (MODIFIES STRING s);

INTEGER PROCEDURE factor (MODIFIES STRING s);

BEGIN INTEGER i;

STRING t;

t := getToken(s);

IF t "A" THEN RETURN(a) EF t "B" THEN RETURN(b) EF t

EF t

"e" THEN RETURN(c)

"(" THENB

i

:=

expression(s); t

:=

getToken(s);

IF t NEQ ")" THEN

write(logFile,"Factor: missing ' ) ' " & eol);

RETURN (i) END

EF validInteger(t) THENB read(t,i); RETURN (i) END EB write (logFile, "Factor: illegal factor ",t,eol);

RETURN (0) END;

*

Treat garbage as a zero END;

INTEGER PROCEDURE term (MODIFIES STRING s);

BEGIN

INTEGER product,i;

STRING t;

product := factor(s);

DOB t

:=

getToken(s);

IF t

=

"*" THEN product := product * factor(s) EF t

=

"I" THENB

i := factor(s);

IF i NEQ 0 THEN product := product DIV i

EB write(logFile,"Term: division by zero" & eol);

product

:=

0 END END

*

Give zero result EB ungetToken(s,t); DONE END END;

RETURN (product) ; END;

Example 7.2.2-2. Mutual Recursion (continued)

INTEGER PROCEDURE expression (MODIFIES STRING s);

BEGIN

INTEGER sum;

STRING t;

sum := term(s);

DOB t

:=

getToken(s);

IF t

=

"+" THEN sum

:=

sum

+

term(s) EF t

= U_"

THEN sum :== sum - term(s) EB ungetToken(s,t); DONE END END;

RETURN (sum) ; END;

FORWARD INTEGER PROCEDURE value (MODIFIES STRING s);

INTEGER PROCEDURE assignment (MODIFIES STRING s);

BEGIN

STRING regName;

INTEGER val;

regName := getToken(s); getToken(s); # Discard

"="

val := value(s);

IF regName "An THEN a := val EF regName

=

"B" THEN b

:=

val EF regName

= "c"

THEN c ,= val

EL write(logFile,"Assignment: illegal register name

If,

regName,eol);

RETURN(val);

END;

INTEGER PROCEDURE value (MODIFIEB STRING s);

BEGIN

BOOLEAN isAssignment;

STRING t,u;

Example 7.2.2-2. Mutual Recursion (continued)

t

:=

getToken(s); u

:=

getToken(s);

isAssignment := (u == "_");

ungetToken(s,u,t);

IF isAssignment THEN RETURN(assignment(s»

EL RETURN(expression(s»;

END;

BOOLEAN PROCEDURE doCommand (STRING s);

*

Return false if the command was "Q", true otherwise.

BEGIN STRING t;

t

:=

getToken(s);

IF t

=

"Q" THEN RETURN(FALSE);

IF t

=

"S" THEN

write(logFile,"A: ",a," B:

",b,"

C: ",c,eol) EF t THENB

ungetToken(s,t); write(logFile,value(s),eol) END;

IF t

:=

getToken(s) THEN

*

Something was left over write (logFile, "Illegal token ",t,eol);

RETURN (TRUE) ; END;

INITIAL PROCEDURE;

BEGIN STRING s-;

*

Set up the accumulators:

a := 0; b := 0; c := 0;

DOB write(logFile,"CALC command ('Q' to quit): n);

read(cmdFile,s) END UNTIL NOT doCommand(cvu(s»;

END;

END "calc"

Example 7.2.2-2. Mutual Recursion (end)

*QglQ<~Ql>

CALC command (' Q' to quit) : J<~QJ..>

3

CALC command (' Q' to quit) : ~<~Ql>

A: 0 B: 0 C: 0

CALC command (' Q' to quit) : g

= Q =

J<~Ql>

3

CALC command (' Q' to quit) : ~<~Ql>

A: 3 B: 3 C: 0

CALC command (' Q' to quit) : Q

= Q

+

9 L

{a

-

l)<~Ql>

7

CALC command (' Q' to quit) : 2+2<~Ql>

4

CALC command (' Q' to quit) : ~<eQl>

Factor: illegal factor Z

0

CALC command (' Q' to quit) : ~<~Ql>

A: 3 B: 3 C: 7

CALC command (' Q' to quit) : g<eQJ..>

*

Example 7.2.2-3. Sample Execution of CALC