Structured programming in Basic; part 5: control structures in ANSI Basic. Arthur Luehrmann.
The first three articles in this series [May, June, July, 1984] introdued the main ideas of structured programming: (1) the top-down method of planning a program and (2) the use of three types of formal control blocks to handle all problems of program logic. These ideas were introduced using the version of Basic available on most personal computers. The fourth article showed how the new ANSI Basic language encourages top-down planning and modular programming. This final article describes the formal control structures which come ready-made in ANSI Basic. ANSI Basic, Macintosh Basic, True Basic, et al.
As stated in the previous article in this series, microcomputer Basics have been left in the lurch by the past 15 years of advances in the design of programming languages. Since about 1970, nearly all new languages have been based on the concepts of structured programming. Although Pascal is best known, this is also true of C, PL/I, Modula-2, and Ada. But the Basics one finds on nearly all personal computers are relics of a 20-year-old language, the original Dartmouth Basic of 1964. (This historic language is now standardized and has the name Minimal Basic.)
Although microcomputer Basics have remained unchanged, Basic has not. Nearly 15 years ago, Dartmouth Basic added independent subprograms with local variables and a parameter-passing mechanism. About eight years ago, Dartmouth Basic added a full set of control structures, which eliminated the need for using jump statements with line number references--the bane of good programming practice. Unfortunately, all these good developments at Dartmouth went unnoticed by the first developers of microcomputer Basics.
For the past half-dozen years, Committee X3J2 of the American National Standards Institute (ANSI) has been at work developing a draft standard for a modern Basic that includes the above structured programming elements, elaborate file I/O, and device-independent graphics. At this writing, the draft is available for public comment. It things go as expected, a new ANSI standard for Basic will probably become a reality in a year or two.
In the meantime, new commercial products based on the ANSI Basic draft and intended for personal computers should be available by the time this article appears in print. Apple Computer expects its new Macintosh Basic to be in dealers' hands in September. At about the same time, True Basic, Inc., founded by the creators of Basic, John Kemeny and Thomas Kurtz, and a small team of Dartmouth programmers, will deliver an IBM PC version of its product, called True Basic, to Addison Wesley Publishing Company for distribution this fall. A Macintosh version of True Basic will follow shortly thereafter.
The previous article in this series described the elements of ANSI Basic that make top-down design and modular programming attractive and easy to do. The present article discusses the built-in loop and branch control structures that are available to ANSI Basic programmers. ANSI Basic Loop Blocks
The June and July articles in this series showed how to build loop and branch control structures out of REM, IF, and GOTO statements. A programming example in the July article was a guessing game in which the computer has a secret word and asks the player to guess again and again, receiving hints each time as to whether the secret word is earlier or later in the dictionary than the guess, and stopping when the guess is correct.
The part of the program that asks again and again for guesses is a loop block, of course. In the Minimal Basic version of the guessing game program, the loop looks like this: 410'LOOP 420 PRINT "WHAT'S YOUR GUESS"; 430 INPUT G$ 440 IF G$ = S$ THEN 500 450 GOSUB 800' HINT 490 GOTO 410 500'END LOOP
In Minimal Basic, the line number references in the IF and GOTO statements are necessary to exit the loop and to continue the loop. ANSI Basic, on the other hand, has a multi-line loop block already built into the language. Here is how one would write this same loop in ANSI Basic: Do Print "What's your guess"; Input guess$ If guess$ = secret$ then exit do Call Hint Loop
The keywords Do and Loop mark the beginning and end of the block. The computer processes a loop block by performing the body statements in top to bottom order. Note that the If statements ends with the phrase "exit do." If the condition in the If statement is true, the computer skips to the next statement after the Do/Loop block, thus exiting the loop. If the condition is false, the computer continues to perform statements until it reaches the Loop statement. Then the computer jumps back to the beginning of the loop automatically. The process continues until the exit condition becomes true.
A small change in this program will bring out another point about the Do/Loop block. Suppose you wanted the program to begin with the prompt "What's your first gues?" and after that switch to "Next guess?" for the rest of the prompts. Here is one way to do that: Input "What's your first guess?"; guess$ Do If guess$ = secret$ then exit do Call Hint Input "Next guess?"; guess$ Loop
The Input statement in ANSI Basic allows a user-defined prompt string to substitute for the built-in question mark prompt. The first Input statement is outside the loop; so the first action inside the loop is to see whether or not the guess was correct. Whenever the exit test occurs at the beginning of a loop, ANSI Basic allows this abbreviation: Input "What's your first guess?"; guess$ Do until guess$ = secret$ Call Hint Input "Next guess?"; guess$ Loop Or else this: Input "What's your first guess?; guess$ Do while guess$ <> secret$ Call Hint Input "Next guess?"; guess$ Loop
Note that all three versions of this new loop have exactly the same meaning. In every case, the exit test is performed before any body statements are performed. The phrases "until..." and "while..." may also be appended to the Loop statement when the exit test is to be performed after all the body statements are performed at least once. (Pascal programmers will recognize that these two abbreviations give the Basic programmer the equivalent fo the While and the Repeat statements.)
Whether to use these abbreviations is a matter of taste. I prefer to write the exit test explicitly as an If statement, since it seems clearer to me that the test is to be performed at that particular point in the loop. Words like while and until are vague about the time that the test is to happen. Furthermore, during program development it often happens that the exit test migrates around nside the loop. Sometimes it is at the top, sometimes in the middle, sometimes at the bottom. Making such changes is easier if the exit test is on a line by itself and not combined with the Do or Loop lines.
A final point about the Do/Loop block: It is perfectly legal in ANSI Basic for a loop to have more than one exit test. One can have, for example, a while...phrase on the Do line, and until...phrase on the Loop line, and three If lines with "exit do" phrases. In fact, "exit do" is a statement in its own right and can go anywhere inside a loop. Although legal, these are horrible practices and should be avoided. Loops with multiple or complex exits are unreadable. (I tried to convince my X3J2 colleagues of this, but it was too late in the day.) ANSI Basic Branch Blocks
The two most important multi-line control blocks in any programming language are loop blocks and branch blocks. They are the backbone of structured programming. The HINT subroutine in the Minimal Basic version of the guessing game program contains a branch block built from REM, IF and GOTO statements. Here is how it looks: 8101If S$ < G$ Then 850 820 'FALSE' 830 PRINT "LATER THAN"; G$ 840 GOTO 870 850 'TRUE 860 PRINT "EARLIER THAN"; G$ 870'END IF
The line number references in the IF and GOTO statements are necessary, first to cause the branch to occur and second to skip over the true branch if the condition was false. ANSI Basic offers a ready-made branch block that needs no line number references. Hre is the same branch in ANSI Basic: If secret$ < guess$ then Print "Earlier than";guess$ Else Print "Later than";guess$ End if
The main difference here is in the order of the cases: The true case comes first and the false case is second. However, the meaning of both versions is the same. The computer is to test whether the secret word is less than the guess and then either print one hint or the other one.
In ANSI Basic, a branch block begins with an If statement that ends with the word then. The block ends with an End if statement. The Else statement is optional. The computer begins processing the block by evaluating the condition in the If statement.
If it is true, the computer performs as the statement or statements between the If statement and the Else statement. If the condition is false, the computer performs the statements between the Else and the End-if statements. (If there is no Else statement and the condition is false, the computer simply skips to the next statement after the entire block.) Multiway Branches
The famous Boehm and Jacopini theorem of 1966 assures us that all problems in program logic can be handled by means of only two control structures: a loop block and a branch block. If ANSI Basic offered only the Do/Loop and the If/End-if blocks, therefore, it would give us all the tools necessary for avoiding the wild jumps that make programs hard to read.
In fact, ANSI Basic goes a step further. It gives additional control structures that add greatly to the readability of programs. Essentially, these are not new blocks. Rather, they are just abbreviations for certain commonly occurring situations in which one branch block is nested inside another one.
Suppose, for example, that a program has just received a one-character string input from a user. The program must detect and do different things, depending upon whether the character is a lowercase letter, an uppercase letter, a numeric digit, a plus or minus sign, or anything else. Here is how one might use the two-way branch block to construct the needed five-way branch: Input a$ If "a" <= a$ and a$ <= "z" then Call Lowercase Else If "A" <= a$ and a$ <= "Z" then Call Uppercase Else If "0" <= a$ and a$ <= "9" then Call Digit Else If a$ ="+" or a$ = "-" then Call Sign Else Call OtherChar End if End if End if End if
This structure does the job by nesting block inside of block inside of block, and so on. The outermost block is a signle two-way branch. However, another completed two-way branch is nested inside the Else part of the outer branch. Furthermore, the Else part of this inner branch contains yet another complete branch block. Finally, four levels deep, there is still another branch block.
Such a structure works, but it is very hard to make sense of. The four End-if lines are especially troublesome. Do we have the right number? Does each one match up with a corresponding If line? It takes close inspection to answer these questions. To make nested Ifs more legible, ANSI Basic allows the following abbreviation of the above five-way branch: Input a$ If "a" <= a$ and a$ <= "z" then Call Lowercase Else If "A" <= a$ and a$ <= "z" then Call Uppercase Else If "0" <= a$ and a$ <= "9" then Call Digit Else If a$ = "+" or a$ = "-" then Call Sign Else Call OtherChar End if
Each Else line in the first version is combined with the If line that follows the Else. Furthermore, a single End-if line closes the entire block. The result is both shorter and far easier to read. However, it has exactly the same meaning as the longer version: the conditions in each If line are tested in order until one is found to be true. If none is true, the last Else part is performed.
ANSI Basic offers still another way to write a multiway branch block like this one. Here it is: Input a$ Select case a$ Case "a" to "z" Call Lowercase Case "A" to "Z" Call Uppercase Case "0" to "9" Call Digit Case "+", "-" Call Sign Case Else Call OtherChar End Select
Again, this version has exactly the same meaning as the two previous ones. The computer performs a Select block by first evaluating the expression after the word Case in the Select line. Then it searches, from the top, for the first case that matches the value of the expression. The first three cases here are ranges. That is, if the string value of a$ is greater or equal to "a" and less than or equal to "z," the computer will perform the first case. Otherwise it will try the second case, and so on. If no match is found and if there is a Case-else statement, that case is the one performed by the computer. If there is no Case-else statement, control passes to the first staement after the Select block.
Note that the fourth case is a list of possible matches. This case will be performed if the value of a$ is either "+" or "-." In general, items in a case statement may consist of one or more constants or ranges of constants. Constants and ranges may be combined in a single Case statement. Commas separate constants or ranges from one another. It is also possible to have open-ended ranges. For example, the following Select block will tell whether a number is positive, negative, or zero. The phrases "is > 0" and "is < 0" are examples of open-ended ranges. Select case number Case is > 0 Print "Positive" Case is < 0 Print "Negative" Case else Print "Zero" End select
Again, it is worth pointing out that there is nothing that can be written in the form of a Select block that cannot also be handled with nested two-way If blocks. That, however, is an academic point. The truth is that any multi-way branch that can be put in the form of Select block is vastly more readable that way. (Unfortunately, not all multi-way branches can be recast as Select blocks.) A Final Example
Perhaps the best way to wrap up this series on structured programming in Basic is to look at an example written originally in a highly unstructured way in Minimal Basic, and then to look at the same program in ANSI Basic. the problem is this: to write a program that simulates a dice game for a thousand rounds and then reports the number of wins and losses.
In the standard dice game, the players wins on the first roll if it is a 7 or 11. The player loses if the first roll is 2, 3, or 12. If the first roll is anything else, that becomes the player's point. The round continues with more rolls until the player rolls the point (a win) or a 7 (a loss).
Here is how one might write such a program in Minimal Basic without any effort to structure the program: 100 DEF FNR = INT (6 RND) + INT (6* RND) + 2 110 LET W = 0 120 LET L = 0 130 RANDOMIZE 140 FOR J = 1 TO 1000 150 LET F = FNR 160 IF F = 7 OR F = 11 THEN 230 170 IF F = 2 OR F = 3 OR F = 12 THEN 250 180 LET P = F 190 LET N = FNR 200 IF N = P THEN 230 210 IF N = 7 THEN 250 220 GOTO 190 230 LET W = W + 1 240 GOTO 260 250 LET L = L + 1 260 NEXT J 270 PRINT "WINS = "; W 280 PRINT "LOSSES = "; L 290 PRINT "* WINS" ="; 100 * W / (W + L) 300 END
Now let's see how to solve the same programming problem by using the principles of structured programming and the elements of ANSI Basic. We begin with an outline of the main parts of the program: Program DiceGame initialize variables For j = 1 to 1000 play one round Next j print statistics End
The next step is to translate the English phrases into Basic. As usual, the best approach is to substitute calls to procedures and then create skeleton procedures for debugging purposes. Since the whole program will be fairly short, we choose to use internal procedures. Here is the next phase of the program development: Program DiceGame Call Initialize For j = 1 to 1000 Call OneRound Next j Call Statistics Sub initialize Print "Initialize" End sub Sub OneRound Print "OneRound" End Sub Sub Statistics Print "Statistics" End sub End
The next step is to flesh out the body of the three subprograms. The first is easy. The program must keep track of wins and losses, so there are two variables to be initialized to zero. In addition, the random number function needs to be reseeded. Here is subprogram Initialize: Sub Initialize Let wins = 0 Let losses = 0 Randomize End sub
Next comes the subprogram to play a single round in the series of dice games. The round begins with a single roll of the dice. After that there are three cases: a win or 7 or 11, a loss on 2, 3, or 12, and more rolls on any other point. Here is an outline of subprogram OneRound: Sub OneRound roll dice; value = firstroll Select case firstroll Case 7, 11 count it a win Case 2, o, 12 count it a loss Case else roll some more End select End sub
The Select block is a convenient one to use here for the three cases possible in the dice game. The next step is to convert the English phrases into Basic statements. Since the dice roll will be needed in two different places, it is a good idea to make it into a separate function, which we shall call Roll.
The next two phrases are easy to handle with single Let statements. The phrase "roll some more" will contain numerous details about subsequent rolls, so it is best to replace the phrase by a Call statement to a new subprogram. Here is the next phrase of development: Sub OneRound Let firstroll = Roll Select case firstroll Case 7, 11 Let wins = wins + 1 Case 2, 3, 12 Let losses = + 1 Case else Call RollAgain End select End sub
Before getting bogged down in the details of function Roll and subprogram RollAgain, it is a good idea to finish the original three subprograms. The Statistics subprogram still needs fleshing out. Since it will contain nothing but Print statements, we might as well skip the outline stage. Here is the finished subprogram: Sub Statistics Print "Wins ="; wins Print "Losses ="; losses Print "% Wins ="; 100 * wins / (wins + losses) End sub
Having finished the three original subprograms, it is time to turn attention to function Roll and subprogram RollAgain. The function is quite straightforward, given an understanding of the way the built-in rnd and int functions work. Here it is: Function Roll Let Roll = int (6 * rnd) + int (6 * rnd) + 2 End function
Subprogram RollAgain is a good deal more complex. First, it must remember the first roll as the "point" to be made. Next it must use a loop block to roll the dice again and again until either the point is made or the roll is 7. Finally, it muse use a branch block to increment either the wins counter or the losses counter, depending on whether or not the point was made. Here is an outline of the subprogram: Sub RollAgain Save first roll as point Do Roll dice If roll is point or 7 then exit do Loop If roll is point then Count it a win Else Count it a loss End if End sub
Each phrase above is easily translated into simple Basic statements or calls to procedures already defined. Here is the final version of RollAgain: Sub RollAgain Let point = first Do Let next = Roll If next = point or next = 7 then exit do Loop If next = point then Let wins = wins + 1 Else Let losses = losses + 1 End if End sub
Notice that the Do/Loop block keeps on rolling the dice until the roll is equal to the point or to 7. Once the loop block is finished, a branch block decides whether to increment the wins counter or the losses counter.
The preceding subroutine completes the list of tasks remaining to be done. The dice game program is now complete. Here is the whole program as it might be written in ANSI Basic: Program DiceGame Call Initialize For j = 1 to 1000 Call OneRound Next j Call Statistics Sub Initialize Let wins = 0 Let losses = 0 Randomize End sub Sub OneRound Let firstroll = Roll Select case firstroll Case 7, 11 Let wins = wins + 1 Case 2, 3, 12 Let losses = losses + 1 Case else Call RollAgain End select End sub Sub Statistics Print "Wins ="; wins Print "Losses ="; losses Print "% Wins ="; 100 * wins / (wins + losses) End sub Function Roll Let Roll = int(6 * rnd) + int(6 * rnd) +2 End function Sub RollAgain Let point = first Do Let next = Roll If next = point or next = 7 then exit do Loop If next = point then Let wins = wins + 1 Else Let losses = losses + 1 End if End sub End
This simple program displays both top-down organization and the use of formal control structures to handle all logical problems. Since ANSI Basic has all these program structuring tools built in, the programmer's job is a good deal simpler than it would be when using Minimal Basic. A Final Thought
As ANSI Basic becomes available more widely on personal computers, increasing numbers of people may discover the advantages of thinking about programming as an orderly, constructive design process and not just a brute force effort to get the thing to work. Perhaps...just perhaps...it may turn out that Basic programmers will no longer deserve the reputation of being, in Professor Dijkstra's phrase, "mentally mutilated beyond the hope of regeneration."