Chapter 4 : Control Structures
All programs consist essentially of two types of instruction, those that instruct the computer to perform some sort of action, such as the addition of two numbers or the printing of a text message, and those that control the order of execution of instructions in a program. Although BASIC has generally been weak in such control structures, BBC BASIC has traditionally provided more facilities than most.
Three control structures exist, namely FOR...NEXT, REPEAT...UNTIL and IF...THEN...ELSE. The first two provide alternative methods of controlling loops, but in both cases the body of the loop, that is the group of instructions contained within the loop, is always executed at least once. Even in instances where a FOR...NEXT loop is used logically one would not expect this to happen, but it still does. This results from the fact that, in both structures, the test to determine whether the body of the loop should be executed (again) is only reached after the loop has been executed at least once. The problem can be avoided, but only artificially.
The existing IF...THEN...ELSE statement also suffers in not providing a true block-structured format, and unless used with extreme care it is extraordinarily easy to become lost in the logic of this instruction when nested to any degree.
BASIC V extends the range of control structures considerably. In particular there is now a CASE statement, in effect a multi-branched IF...THEN...ELSE. A new variant of the IF...THEN...ELSE statement itself has been added, which implements proper block structuring, and a new WHILE...ENDWHILE structure provides a loop format, where the test comes at the commencement of the loop rather than at the end.
WHILE Loops
Students of computer science will be quite familiar with the concept of a WHILE loop, which is considered to be one of the most fundamental (and most natural) of control structures. It takes the form:
WHILE <condition>:<statements>
<statements>
<statements>
. . . . . .
ENDWHILE
While the specified condition remains true, the statements between WHILE and ENDWHILE will continue to be executed. The statements may be on the same line as the WHILE or on separate lines. For example, you might write:
100 Sum=0:k=1
110 WHILE Sum<=10:Sum=Sum+1/k:k=K+1:ENDWHILE
120 PRINT k
130 END
This program determines the number of terms in the series:
1+1/2+1/3+1/4+1/5+....
required to produce a sum greater than 10. The great advantage of the WHILE loop is that if the condition fails (ie, returns a value FALSE) the very first time it is tested, then the following statements will not be executed at all, just what we would logically expect.
Here is another very simple example:
i=1
WHILE (i<max AND list(i)<>key):i+=1:ENDWHILE
The coding assumes that an array called list() has been dimensioned to size 'max', and searches the list for an element whose value matches that of 'key'. When the loop terminates, 'i' contains the value of the matched element, or is set to the value of 'max' if no match has been found. Notice how, in this example, more work is done in evaluating the condition than in the body of the loop. This is not uncommon.
WHILE...ENDWHILE structures may also be nested in the same way as FOR loops and REPEAT...UNTIL loops. Each loop must be correctly terminated with an ENDWHILE statement. Likewise, the statements within a WHILE...ENDWHILE construction may include other forms of control structure such as CASE...ENDCASE, REPEAT...UNTIL and FOR...NEXT.
Block-Structured IF
BBC BASIC has always supported and IF...THEN...ELSE statement, but this has been limited in two separate but linked ways, which particularly affect the nested use of IF...THEN...ELSE. The complete structured must be contained within a single line of BASIC (thus limited to a maximum of 255 characters), while the logical interpretation of nested IF...THEN...ELSE statements frequently leads to confusion. As a result, many instances of more complex usage often fail to work correctly, and much time can be wasted in sorting this out.
For example, consider the following:
IF (B^2-4*a*c)<>0 THEN IF (b^-4*a*c)>0 THEN PRINT"Roots are real" ELSE
IF (B^2-4*a*c)<0 THEN PRINT "Roots are complex" ELSE PRINT "Roots are equal"
Now most programmers are familiar with solving quadratic equations, and can therefore follow what the above code is intended to achieve, but could you say with confidence whether this form of coding is correct? The new block-structured form of IF...THEN...ELSE should help to bring new clarity to such situations as we shall see. It also removes the 255 character limitation on the length of such statements by allowing them to spread over as many separate lines of BASIC as required. In addition, the old form of IF...THEN...ELSE is still retained, and may continue to be used wherever appropriate. Indeed, its simpler form should always be used if possible.
The syntax of the block-structured IF takes the following form:
IF <condition> THEN
<statements>
ELSE
<statements>
END IF
The structure is reasonably flexible, but the following constraints must always be observed. The THEN must be the last item on a line, while ELSE and ENDIF must be the first items on their respective lines. In addition, the ELSE clause is optional, the ENDIF is not. Thus, the example on the solution of quadratic equations could now be coded as:
IF (b^2-4*a*c)<>0 THEN
IF (b^2-4*a*c)>0 THEN
PRINT "Roots are real"
ENDIF
ELSE
IF (b^2-4*a*c)<0 THEN
PRINT "Roots are complex"
ELSE
PRINT "Roots are equal"
ENDIF
Each line given above would be written as a separate numbered line of BASIC. There are two nested IF...THEN...ELSE constructions, the outer one concerned with whether the formula equates to zero or not, and, in the case of a non-zero value, a further IF...THEN...ELSE to distinguish between positive and negative values. You will probably find that it still helps to indent some of the lines as above to make the logic quite clear.
The example I have used above, and in particular the manner in which I have expressed it, is somewhat artificial, though it serves to illustrate the point. Keeping the same overall structure, the block-structured version could be more succinctly written as:
IF (b^2-4*a*c)<>0 THEN
IF (b^2-4*a*c)>0 THEN PRINT"Roots are real"
IF (b^2-4*a*c)<0 THEN PRINT"Roots are complex"
ELSE
PRINT"Roots are equal"
ENDIF
There are, no doubt, many other variations and it is perfectly possible to code the example using the simple IF...THEN...ELSE construction, all on one line of BASIC:
IF (B^2-4*a*c)=0 THEN PRINT"Roots are equal" ELSE IF (B^2-4*a*c)>0 THEN
PRINT"Roots are real" ELSE IF (B^2-4*a*c)<0 THEN PRINT"Roots are complex"
If you are using the simple form of IF...THEN...ELSE, you will generally find that else followed by IF (as in the example above) gives no cause for concern, but that THEN followed by IF (as in the original coding) frequently leads to confusion and incorrect logic.
The moral is to think carefully about the logic of the algorithm you are about to code. There is every reason to retain the simpler form of IF...THEN...ELSE where this is clear and unambiguous. If you need to exceed the limited capacity of one line of BASIC, or the logic is more than the simpler form can adequately cope with, bring the block-standard form into play.
The CASE Statement
A CASE statement is, in effect, a generalised form of IF...THEN...ELSE. The latter essentially allows a program to distinguish between two alternatives based on whether a condition is true or false. The CASE statement allows the condition (or state) which is to be tested to have as many alternative values as you wish, and specifies the action to be followed in each 'case'.
The syntax for the CASE statement takes the following form:
CASE OF
WHEN <states>: <statements>
WHEN <states>: <statements>
. . . . . . . . . . . . . .
. . . . . . . . . . . . . .
OTHERWISE <statements>
ENDCASE
A simple example will help to make this clear. The Archimedes mouse, for example, has three buttons generally referred to as select, menu and adjust. Thus a routine to provide mouse control in a program could be written as:
MOUSE x,y,z
CASE z OF
WHEN 4: PROCselect
WHEN 2: PROCmenu
WHEN 1: PROCadjust
ENDCASE
This coding would probably be embedded in some sort of loop so that the program can continually follow the movement of the mouse. The value of z determines which mouse button has been pressed by the user (BASIC allocated 4 for select, 2 for menu and 1 for adjust). The three WHEN statements each call an appropriate procedure corresponding to the button pressed.
BASIC goes through the list of WHEN statements until it finds a match between the value specified and the value of the expression following CASE. The instructions specified by tat WHEN statement are then executed and control passes to the first statement following the ENDCASE, even if more matches exist in subsequent WHEN statements, as yet untested. This is important to understand. You could not use this example, as it stands, to test for two buttons being pressed together. Hence, however many WHEN statements are specified in a CASE construction, only one, if any, will be executed. Of course, a CASE statement may produce no match at all.
The WHEN statement can specify more than one value, and these are separated by commas. With the mouse, pressing two or more buttons together produces values which are the sum of the values that would have resulted from pressing the same buttons individually. Adding the following line could cater for any two or three buttons being pressed by mistake:
WHEN 3,5,6,7: PROCerror(z)
In such as case, BASIC seeks for a match between the value of the CASE expression and any of the values listed after WHEN. These alternatives are simply separated by commas.
It is also possible to include an OTHERWISE clause in a CASE statement. This must follow the last WHEN statement in a list and stipulates the action to be taken if no specific match occurs. For example, we could package up our whole mouse routine as follows:
REPEAT
MOUSE x,y,z
CASE z OF
WHEN 4: PROCselect
WHEN 2: PROCmenu
WHEN 1: PROCadjust
WHEN 7: exit%=TRUE
OTHERWISE PROCerror(z)
ENDCASE
UNTIL exit%
There is now a loop which repeatedly returns information about the position of the mouse pointer and the state of the mouse buttons. Appropriate action is determined by a CASE statement in response to the buttons pressed.
When using the CASE statement, CASE...OF must be the last item on a line of BASIC, while WHEN, OTHERWISE and ENDCASE must be the first objects on a line. Note also that a colon is essential to terminate the list of possible matches in a WHEN statement, though no colon is required after OTHERWISE (as there is no list here).
It is worthwhile to consider the workings of the CASE statements further, as there are alternative ways of using such statements which may not be immediately obvious. The CASE statement is concerned with finding a match between the values of expressions, where the term expression also includes constants. Thus an equation is an expression, so is z and so is 5.2 (with this interpretation). One of the expressions to be matched, the key expression if you like, is specified after CASE, and other lists of expressions are given after each WHEN. In the examples so far, the key expression has been given a simple variable, and the WHEN expressions as constants.
Furthermore, expressions may produce a numeric value, a string value or a logical value. For example, a typical command-driven system for a file handling program might have the following structure:
REPEAT:exit%=FALSE
command$=FNmenu
CASE command$ OF
WHEN "OPEN": PROCopen_file
WHEN "CLOSE": PROCclose_file
WHEN "ADD": PROCadd_record
WHEN "DELETE": PROCdelete_record
WHEN "UPDATE": PROCupdate_record
WHEN "DISPLAY": PROCdisplay_record
WHEN "EXIT": exit%=TRUE
OTHERWISE PROCerror
ENDCASE
UNTIL exit%
It is natural to think of the WHEN statements as specifying instances of the CASE...OF expression, but it is quite possible to turn it round, so that this becomes a (single) instance of a WHEN expression. Consider the following routine:
MOUSE x,y,z
CASE TRUE OF
WHEN FNin(x,y,z,120,100,200,100): PROCload
WHEN FNin(x,y,z,400,100,200,100): PROCsave
WHEN FNin(x,y,z,680,100,200,100): PROCedit
WHEN FNin(x,y,z,960,100,200,100): PROCexit
ENDCASE
Assume that the screen display consists of four boxes across the foot of the screen labelled LOAD, SAVE, EDIT and EXIT. FNin is a function with seven parameters. The first three are the position of the mouse pointer and status of the mouse buttons. The other four are the x and y co-ordinates of the bottom left-hand corner and the width and height of a rectangular area on the screen. FNin returns a logical value of TRUE if the mouse pointer is within the specified area with the select button depressed. The CASE statement given above is one way of detecting which part of the screen is being pointed to before taking appropriate action.
The CASE construction is an important addition to BASIC V, one that will do much to avoid some of the more tortuous programming previously required to meet the need for a multiple-choice structure. It will also do much to enhance further the reputation of BBC BASIC. It does require the programmer to exercise careful consideration of its use, both in getting the syntax correct and in applying its logic in the most effective way. With practice you should find that CASE...OF becomes a frequently used feature in your programs.
|