One issue that might bother students is: what if you had a program written with GOTOs whose logic was so complicated that you could not understand it. It turns out that there are powerful theoretical results which show that any program written using GOTOs can be rewritten without GOTOs so that it carries out exactly the same computation. It is important to realize that it is not necessary to understand the program in order to rewrite it.
This recitation illustrates the above result starting with a moderately confusing initial program with GOTOs. The whole idea is not to unravel the logic of this weird program, but to see that it can be transformed to a program without GOTOs, without understanding how the program works.
Several examples below ocurr in Fortran and in Pascal. It is not necessary to completely understand either Fortran or Pascal in order to understand these examples.
Initial example: A Fortran program that translates an integer to Roman numberals: Consider the following Fortran program. This program uses only GOTOs and IFs followed by a GOTO. It translates an integer less than 4000 into Roman numerals. (The line numbers at the right have nothing to do with the program itself.) Note that this is intended to be an obscure program, whose mechanism may not be clear.
runner% cat rom.for
* Fortran program to translate to Roman numberals
PROGRAM ROMAN
INTEGER ROM, I, L, N, K
CHARACTER*1 S(15), R(7)
DATA R /'I','V','X','L','C','D','M'/
ROM = 3947 Line 1
I = 1 Line 2
L = 1000 Line 3
N = 13 Line 4
20 IF (MOD(N,4) .NE. 1) GOTO 30 Line 5
K = L Line 6
L = K/10 Line 7
30 K = K - L*(MOD(N,4)-1)**2 Line 8
40 IF (ROM .LT. K) GOTO 60 Line 9
ROM = ROM - K Line 10
IF (MOD(N,2) .NE. 0) GOTO 50 Line 11
S(I) = R(((N-2)/4)*2 + 1) Line 12
I = I + 1 Line 13
50 S(I) = R((N+2)/2) Line 14
I = I + 1 Line 15
GOTO 40 Line 16
60 N = N - 1 Line 17
IF (N .GE. 1) GOTO 20 Line 18
PRINT *, S
STOP
END
runner% f77 -o rom_for rom.for
rom.for:
MAIN:
runner% rom_for
MMMCMXLVII
Here the variable LINE keeps track of which of the original 18 lines are executing. Each case (1-18) corresponds to a line number. After executing a case, the program either increments LINE to the next line number, or else changes LINE to a different line number altogether, corresponding to a GOTO statement.
Do you think the program below is better because it has no GOTO statements?
runner% cat rom.pas
(* Transformed Pascal version of the *)
(* original Fortran program *)
(* No goto statements in this program *)
program ROMAN;
var
ROM, I, L, N, K, LINE: integer;
S: array[1..15] of char;
R: array[1..7] of char;
begin
R := 'IVXLCDM';
LINE := 1;
while LINE <> 19 do
case LINE of
1: begin ROM := 3947; LINE := LINE + 1 end;
2: begin I := 1; LINE := LINE + 1 end;
3: begin L := 1000; LINE := LINE + 1 end;
4: begin N := 13; LINE := LINE + 1 end;
5: if (N mod 4) <> 1 then LINE := 8 else LINE := LINE + 1;
6: begin K := L; LINE := LINE + 1 end;
7: begin L := K div 10; LINE := LINE + 1 end;
8: begin K := K - L*((N mod 4) - 1)*((N mod 4) - 1);
LINE := LINE + 1 end;
9: if ROM < K then LINE := 17 else LINE := LINE + 1;
10: begin ROM := ROM - K; LINE := LINE + 1 end;
11: if (N mod 2) <> 0 then LINE := 14 else LINE := LINE + 1;
12: begin S[I] := R[((N-2)div 4)*2 + 1]; LINE := LINE + 1 end;
13: begin I := I + 1; LINE := LINE + 1 end;
14: begin S[I] := R[(N+2)div 2]; LINE := LINE + 1 end;
15: begin I := I + 1; LINE := LINE + 1 end;
16: LINE := 9;
17: begin N := N - 1; LINE := LINE + 1 end;
18: if N >= 1 then LINE := 5 else LINE := LINE + 1
end; (* case and while*)
writeln(S)
end.
runner% pc -o rom_pas rom.pas
runner% rom_pas
MMMCMXLVII
runner% cat rom3.pas
(* Pascal program exactly equivalent *)
(* to the original Fortran program *)
program ROMAN;
var
ROM, I, L, N, K: integer;
S: array[1..15] of char;
R: array[1..7] of char;
label
L1, L2, L3, L4, L5, L6;
begin
L1: R := 'IVXLCDM';
ROM := 3947;
I := 1;
L := 1000;
N := 13;
L2: if (N mod 4) <> 1 then goto L3;
K := L;
L := K div 10;
L3: K := K - L*((N mod 4) - 1)*((N mod 4) - 1);
L4: if ROM < K then goto L6;
ROM := ROM - K;
if (N mod 2) <> 0 then goto L5;
S[I] := R[((N-2)div 4)*2 + 1];
I := I + 1;
L5: S[I] := R[(N+2)div 2];
I := I + 1;
goto L4;
L6: N := N - 1;
if N >= 1 then goto L2;
writeln(S)
end.
runner% pc -o rom3_pas rom3.pas
runner% rom3_pas
MMMCMXLVII
Here each block of statements corresponding to the target of a goto is treated as a unit, rather than focusing on the individual lines.
runner% cat rom4.pas
(* Equivalent Pascal version of the *)
(* original Fortran progra *)
(* Illustrates another translation strategy *)
(* No goto statements in this program *)
program ROMAN;
var
ROM, I, L, N, K, LABEL: integer;
S: array[1..15] of char;
R: array[1..7] of char;
begin
LABEL := 1;
while LABEL <> 0 do
case LABEL of
1: begin
R := 'IVXLCDM';
ROM := 3947;
I := 1;
L := 1000;
N := 13;
LABEL := 2
end;
2: begin
if (N mod 4) <> 1 then LABEL := 3 else
begin
K := L;
L := K div 10;
LABEL := 3
end
end;
3: begin
K := K - L*((N mod 4) - 1)*((N mod 4) - 1);
LABEL := 4
end;
4: begin
if ROM < K then LABEL := 6 else
begin
ROM := ROM - K;
if (N mod 2) <> 0 then LABEL := 5 else
begin
S[I] := R[((N-2)div 4)*2 + 1];
I := I + 1;
LABEL := 5
end
end
end;
5: begin
S[I] := R[(N+2)div 2];
I := I + 1;
LABEL := 4;
end;
6: begin
N := N - 1;
if N >= 1 then LABEL := 2
else LABEL := 0;
end
end; (* case and while *)
writeln(S)
end.
runner% pc -o rom4_pas rom4.pas
runner% rom4_pas
MMMCMXLVII
runner% cat rom.c
/* C version of the original Fortran program */
#include <stdio.h>
int rom, i, l, n, k;
char s[15];
char r[8]= " ivxlcdm"; /* initial blank because of C's 0-based arrays */
int main()
{
rom = 3947;
s[0] = ' ';
i = 1;
l = 1000;
n = 13;
L2: if ((n%4) != 1) goto L3;
k = l;
l = k/10;
L3: k = k - l*((n%4) - 1)*((n%4) - 1);
L4: if (rom < k) goto L6;
rom = rom - k;
if ((n%2) != 0) goto L5;
s[i] = r[((n-2)/4)*2 + 1];
i = i + 1;
L5: s[i] = r[(n+2)/2];
i = i + 1;
goto L4;
L6: n = n - 1;
if (n >= 1) goto L2;
s[i] = '\0';
printf("%s\n", s);
}
runner% cc -o rom rom.c
runner% rom
mmmcmxlvii
What to turn in to the recitation instructor: Turn is a source listing along with the results of a run.
Key idea: It always possible to mechanically translate a program with GOTOs into an equivalent program without GOTOs, without necessarily understanding the logic of the program. This will work no matter how complicated the original program is.