Flow control is about deciding which line of code runs next . Calling a subroutine or function is a simple form of unconditional flow control. More complicated flow control involves branching and looping. Flow control allows macros to have complicated behavior that changes based on the current data.
Branching statements cause the program flow to change. Calling a subroutine or function is an unconditional branch. OOo Basic supports conditional branching statements such as "if x, then do y". Looping statements cause the program to repeat sections of code. Looping statements allow for a section to be repeated a specific number of times or until a specific "exit" condition has been achieved.
Some flow control statements, such as GoSub, GoTo, and On Error, require a label to mark a point in the code. Label names are subject to the same rules as variable names. Label names are immediately followed by a colon. Remember that a colon is also used as a statement separator that allows multiple statements to occupy the same line. Space between the label name and the colon causes the colon to be used as a statement separator, which means the label won't be defined. The following lines all represent valid OOo Basic code.
<statements> i% = 5 : z = q + 4.77 MyCoolLabel: <more statements> JumpTarget: <more statements> REM no space between label and colon
Tip | Inserting a space between a label and the colon causes the colon to be used as a statement separator, and the label is not defined. |
The GoSub statement causes execution to jump to a defined label in the current routine. It isn't possible to jump outside of the current routine. When the Return statement is reached, execution continues from the point of the original call. A Return statement with no previous GoSub produces a run-time error. In other words, Return is not a substitute for Exit Sub or Exit Function. It is generally assumed that functions and subroutines produce more understandable code than GoSub and GoTo. See Listing 43 .
Sub ExampleGoSub Dim i As Integer GoSub Line2 REM Jump to line 2 then return, i is 1 GoSub [Line 1] REM Jump to line 1 then return, i is 2 MsgBox "i = " + i, 0, "GoSub Example" REM i is now 2 Exit Sub REM Leave the current subroutine. [Line 1]: REM this label has a space in it i = i + 1 REM Add one to i Return REM return to the calling location Line2: REM this label is more typical, no spaces i = 1 REM Set i to 1 Return REM return to the calling location End Sub
Tip | GoSub is a persistent remnant from old BASIC dialects, retained for compatibility. GoSub is strongly discouraged because it tends to produce unreadable code. Use a subroutine or function instead. |
Compatibility | Visual Basic .NET does not support the GoSub keyword. |
The GoTo statement causes execution to jump to a defined label in the current routine. It isn't possible to jump outside of the current routine. Unlike the GoSub statement, the GoTo statement doesn't know from where it came. See Listing 44 .
Sub ExampleGoTo Dim i As Integer GoTo Line2 REM Okay, this looks easy enough Linel: REM but I am becoming confused i = i + 1 REM I wish that GoTo was not used GoTo TheEnd REM This is crazy, makes me think of spaghetti, Line2: REM Tangled strands going in and out; spaghetti code. i = 1 REM If you have to do it, you probably GoTo Linel REM did something poorly. TheEnd: REM Do not use GoTo. MsgBox "i = " + i, 0, "GoTo Example" End Sub
Tip | GoTo is a persistent remnant from old BASIC dialects, retained for compatibility. GoTo is strongly discouraged because it tends to produce unreadable code. Use a subroutine or function instead. |
These statements cause the execution to branch to a label based on a numeric expression N. If N is zero, no branching occurs. The numeric expression N must be in the range of 0 through 255. This is sometimes called a "computed goto," because a computation is used to direct the program flow. It isn't possible to jump outside of the current subroutine.
Syntax: On N GoSub Label1[, Label2[, Label3[,...]]] Syntax: On N GoTo Label1[, Labe12[, Labe13[,... ]]]
To reiterate how this works, if N = 1 then branch to Label 1, if N = 2 then branch to Label 3... If N is less than 1 or if N is greater than the number of labels, then the branch is not taken; it is simply ignored. See Listing 45 and Figure 15 .
Sub ExampleOnGoTo Dim i As Integer Dim s As String i = 1 On i+1 GoSub Subl, Sub2 s = s & Chr(13) On i GoTo Line1, Line2 REM The exit causes us to exit if we do not continue execution Exit Sub Sub1: s = s & "In Sub 1" : Return Sub2: s = s & "In Sub 2" : Return Linel: s = s & "At Label 1" : GoTo TheEnd Line2: s = s & "At Label 2" TheEnd: MsgBox s, 0, "On GoTo Example" End Sub
Compatibility | Visual Basic .NET does not support the On GoSub and On GoTo statements. |
The If construct is used to execute a block of code based on an expression. Although you can use GoTo or GoSub to jump out of an If block, you cannot jump into an If block. The simplest If statement has the following form:
If Condition Then Statement
The condition can be any expression that either evaluates to-or is convertible to-True or False. Use a slightly more complicated version to control more than a single statement.
If Condition Then Statementblock [Elself Condition Then] Statementblock [Else] Statementblock End If
If the first condition evaluates to True, the first block runs. The ElseIf statement allows multiple If statements to be tested in sequence. The statement block for the first true condition runs. The Else statement block runs if no other condition evaluates to True. See Listing 46 .
Sub ExampleIf Dim i% i% = 4 If i = 4 Then Print "i is four" If i <> 3 Then Print "i is not three" End If If i < 1 Then Print "i is less than 1" elseif i = 1 Then Print "i is 1" elseif i = 2 Then Print "i is 2" else Print "i is greater than 2" End If End Sub
If statements can be nested.
If i <> 3 THEN If k = 4 Then Print "k is four" If j = 7 Then Print "j is seven" End If End If
The IIf ("Immediate If") function returns one of two values based on a conditional expression.
Syntax: object = IIf (Condition, TrueExpression, FalseExpression)
This is very similar to the following code:
If Condition Then object = TrueExpression Else object = FalseExpression End If
This works as a great single-line If-Then-Else statement.
max_age = IIf(johns_age > bills_age, johns_age, bills_age)
The Choose statement selects from a list of values based on an index.
Syntax: obj = Choose (expression, Select_1[, Select_2, ... [,Select_n]])
The Choose statement returns a null if the expression is less than 1 or greater than the number of selection arguments. Choose returns "select_1" if the expression evaluates to 1, and "select_2" if the expression evaluates to 2. The result is similar to storing the selections in an array with a lower bound of 1 and then indexing into the array. See Listing 47 .
i% = 3 Print Choose (i%, 1/(i+l), 1/(i-1), 1/(i-2), 1/(i-3))
Selections can be expressions and they can contain function calls. Every function is called and every expression is evaluated in the argument list for the call to the Choose statement. The code in Listing 47 causes a division-by-zero error because every argument is evaluated, not just the argument that will be returned. Listing 48 calls the functions Choose1, Choose2, and Choose3.
Sub ExampleChoose Print Choose(2, "One", "Two", "Three") 'Two Print Choose(2, Choose1(), Choose2(), Choose3()) 'Two End Sub Function Choose1$() Print "I am in Choose1" Choose1 = "One" End Function Function Choose2$() Print "I am in Choose2" Choose2 = "Two" End Function Function Choose3$() Print "I am in Choose3" Choose3 = "Three" End Function
Note | If functions are used in the arguments for Choose, they are all called. |
The Select Case statement is similar to an If statement with multiple Else If blocks. A single-condition expression is specified and this is compared against multiple values for a match as follows :
Select Case condition_expression Case case_expression1 StatementBlock1 Case case_expression2 StatementBlock2 Case Else StatementBlock3 End Select
The condition_expression is compared in each Case statement. The first statement block to match is executed. The optional Case Else block runs if no condition matches. It is not an error if nothing matches and no Case Else block is present.
The conditional expression is evaluated once and then it is compared to each case expression until a match is found. A case expression is usually a constant, such as "Case 4" or "Case "hello"".
Select Case 2 Case 1 Print "One" Case 3 Print "Three" End Select
You can specify multiple values by separating them with commas: "Case 3, 5, 7". The keyword To checks a range of values-for example, "Case 5 To 10". Open -ended ranges are checked as "Case < 10" or as "Case IS < 10".
Note | The Case IS statement is different than the IS operator that decides if two objects are the same. |
Every Case statement written "Case op expression" is shorthand for writing "Case IS op expression". The form "Case expression" is shorthand for "Case IS = expression". For example, "Case >= 5" is equivalent to "Case IS >= 5", and "Case 1+3" is equivalent to "Case IS = 1+3".
Select Case i Case 1, 3, 5 Print "i is one, three, or five" Case 6 To 10 Print "i is a value from 6 through 10" Case < -10 Print "i is less than -10" Case IS > 10 Print "i is greater than 10" Case Else Print "No idea what i is" End Select
A Case statement can contain a list of expressions separated by commas. Each expression can include an open-ended range. Each expression can use the statement Case IS (see Listing 49 ).
Select Case i% Case 6, Is = 7, Is = 8, Is > 15, Is < 0 Print "" &i & " matched" Case Else Print "" & i & " is out of range" End Select
I frequently see incorrect examples of Case statements. It's instructive to see what is repeatedly done incorrectly. Consider the simple examples in Table 14 . The last examples are written correctly in Table 16 .
Example | Valid | Description |
---|---|---|
Select Case i Case 2 | Correct | The Case expression 2 is evaluated as two. Two is compared to i. |
Select Case i Case Is = 2 | Correct | The Case expression 2 is evaluated as two. Two is compared to i. |
Select Case i Case Is > 7 | Correct | The expression 7 is evaluated as seven. Seven is compared to i. |
Select Case i Case 4, 7, 9 | Correct | The conditional expression i is compared individually to 4, 7, and 9. |
Select Case x Case 1.3 TO 5.7 | Correct | You can specify a range and use floating-point numbers . |
Select Case i Case i = 2 | Incorrect | The Case expression (i=2) is evaluated as True or False. True or False is compared to i. This is reduced to "IS = (i=2)". |
Select Case i Case i<2 OR i>9 | Incorrect | The Case expression (i<2 OR 9<i) is evaluated as True or False. True or False is compared to i. This is reduced to "IS = (i<2 OR 9<i)". |
Select Case i% Case i%>2 AND i%<10 | Incorrect | The Case expression (i>2 AND i < 10) is evaluated as True or False. True or False is compared to i. This is reduced to "IS = (i>2 AND i<10)". |
Select Case i% Case IS>8 And i<11 | Incorrect | Again, True and False are compared to i. This is reduced to "IS > (8 AND i<11)". The precedence rules cause this to be reduced to "IS > (8 AND (i<11))". This is usually not what's intended. |
Select Case i% Case IS>8 And IS<11 | Incorrect | Compile error. The keyword IS must immediately follow Case. |
Tip | The OpenOffice.org help and a few programming books contain incorrect examples such as "Case i > 2 AND i < 10". This fails. Don't believe it even though you see it in print. Understand why this fails and you have mastered Case statements. |
The final incorrect example in Table 14 is representative of the most common error that I see with Case expressions. Listing 50 considers the case when i is less than 11, and the case when i is greater than or equal to 11. To put it simply, IS>8 AND i<11 has the Case statement comparing the value of i to the result of a Boolean expression, which can only be 0 or -1. The big difficulty with Case statements is that they look like If statements, which are looking for a True or False, but Case statements are looking for a particular value against which to match the conditional, and 0 or -1 is not helpful.
IS > (8 AND i < 11) => IS > (8 AND -1) => IS > 8 'Assume i < 11 is correct IS > (8 AND i < 11) => IS > (8 AND 0) => IS > 0 'Assume i >= 11 is incorrect
Consider the second case in Listing 50, i >= 11. The operator < has higher precedence than the operator AND, so it is evaluated first. The expression i<11 evaluates to False (because I assumed that i>=11). False is internally represented as 0. Zero has no bits set, so 8 AND 0 evaluates to zero. For values of i greater than or equal to 11, the entire expression is equivalent to "IS > 0". In other words, for i = 45, this Case statement is incorrectly accepted.
A similar argument for values of i less than 11, left as an exercise for the reader, demonstrates that the Case statement is equivalent to "Case IS > 8". Therefore, values of i less than 11 are correctly evaluated, but values of i greater than or equal to 11 are not.
After you learn a few simple examples, it's easy to write correct Case expressions. Table 15 abstractly enumerates the varieties, and Listing 54 concretely demonstrates the varieties.
Example | Description |
---|---|
Case IS operator expression | This is both the simplest case and the most difficult case. If the expression is a constant, it'ss easy to understand. If the expression is more complicated, the only difficult part is building the expression. |
Case expression | This is a reduction of "Case IS operator expression" when the operator checks for equality. |
Case expression TO expression | Check an inclusive range. This is usually done correctly. |
Case expression, expression, | Each expression is compared. This is usually done correctly. |
For the difficult cases, it suffices to produce an expression that evaluates to the Case condition expression if it is correct, and anything else if it is not. In other words, for Select Case 4, the expression must evaluate to 4 for the statement block to run. See Listing 51 .
Select Case x Case IIF(Boolean expression, x, x&"1") ' Assumes that x is a string
In Listing 51, x is returned if the Boolean expression is True. The expression x=x is True, so the Case statement passes . If the Boolean expression is False, x&" 1" is returned. This is not the same string as x, so the Case statement will not pass. A similar method is used for numerical values. See Listing 52 .
Select Case x Case IIF(Boolean expression, x, x+1) ' Assumes that x is numeric
In Listing 52, x is returned if the Boolean expression is True. The expression x=x is True, so the Case statement passes. If the Boolean expression is False, x+1 is returned. For numerical values, x=x+1 is not True, so the Case statement will not pass. There is a possibility of numerical overflow, but in general this works. A brilliant and more elegant solution for numerical values is provided by Bernard Marcelly, a member of the OOo French translation project.
Case x XOR NOT (Boolean Expression)
This assumes that the Boolean expression returns True (-1) if it should pass and False (0) if it should not (see Listing 53 ).
x XOR NOT(True) = x XOR NOT(-1) = x XOR 0 = x x XOR NOT(False) = x XOR NOT( 0) = x XOR -1 <> x
After my initial confusion, I realized how brilliant this really is. There are no problems with overflow and it works for all Integer values of x. Do not simplify this to the incorrect reduction "x AND (Boolean expression)" because it fails if x is 0. See Listing 54 .
Sub ExampleSelectCase Dim i% i = Int((20 * Rnd) -2) 'Rnd generates a random number between zero and one Select Case i% Case 0 Print "" & i & " is Zero" Case 1 To 5 Print "" & i & " is a number from 1 through 5" Case 6, 7, 8 Print "" & i & " is the number 6, 7, or 8" Case IIf(i > 8 And i < 11, i, i+1) Print "" & i & " is greater than 8 and less than 11" Case i% XOR NOT(i% > 10 AND i% < 16) Print "" & i & " is greater than 10 and less than 16" Case Else Print "" & i & " is out of range 0 to 15" End Select End Sub
ExampleSelectCase in Listing 54 generates a random integer from -2 through 18 each time it runs. Run this repeatedly to see each Case statement used. Each of the cases could have used the IIF construct.
Now that I've explained the different methods to deal with ranges, it's time to reevaluate the incorrect cases in Table 14. The solutions in Table 16 are not the only possible solutions, but they use some of the solutions presented.
Incorrect | Correct | Description |
---|---|---|
Select Case i Case i = 2 | Select Case i Case 2 | The variable i is compared to 2. |
Select Case i Case i = 2 | Select Case i Case Is = 2 | The variable i is compared to 2. |
Select Case i Case i<2 OR i>9 | Select Case i Case IIf(i<2 OR i>9, i, i+1) | This works even if i is not an integer. |
Select Case i% Case i%>2 AND i%<10 | Select Case i% Case 3 TO 9 | i% is an integer so the range is from 3 through 9. |
Select Case i% Case IS>8 And i<11 | Select Case i% Case i XOR NOT(i>8 AND i< 11) | This works because i% is an integer. |
Use the While ...Wend statement to repeat a block of statements while a condition is true. This construct has limitations that do not exist with the Do While ... Loop construct and offers no particular benefits. The While ... Wend statement does not support an Exit statement. You cannot use GoTo to exit a While ... Wend statement.
While Condition StatementBlock Wend
Compatibility | Visual Basic .NET does not support the keyword Wend. This is another reason to use the Do While ... Loop instead. |
The Loop construct has different forms and is used to continue executing a block of code while, or until, a condition is true. The most common form checks the condition before the loop starts, and repeatedly executes a block of code as long as the condition is true. If the initial condition is false, the loop is never executed.
Do While condition Block [Exit Do] Block Loop
A similar, but much less common form, repeatedly executes the code as long as the condition is false. In other words, the code is executed until the condition becomes true. If the condition evaluates to true immediately, the loop never runs.
Do Until condition Block [Exit Do] Block Loop
You can place the check at the end of the loop, in which case the block of code is executed at least once. With the following construct, the loop runs at least once and then repeatedly runs as long as the condition is true:
Do Block [Exit Do] Block Loop While condition
To execute the loop at least once and then continue as long as the condition is false, use the following construct:
Do Block [Exit Do] Block Loop Until condition
The Exit Do statement causes an immediate exit from the loop. The Exit Do statement is valid only within a Do ... Loop. Program execution continues with the statement that follows the innermost Loop statement. The subroutine ExampleDo in Listing 55 demonstrates a Do While Loop by searching an array for a number.
Sub ExampleDo Dim a(), i%, x% a() = Array(2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30) x = Int(32 * Rnd) REM random integer between 0 and 32 i = LBound(a()) REM i is the lower bound of the array. Do While a(i) <> x REM while a(i) is not equal to x i = i + 1 REM Increment i If i > UBound(a()) Then Exit Do REM If i is too large, then exit Loop REM Loop back to the Do While If i <= UBound(a()) Then REM If i is not too large then found x MsgBox "Found " & x & " at location " & i, 0, "Example Do" Else MsgBox "Could not find " & x & " in the array", 0, "Example Do" End If End Sub
OOo Basic supports four variants of the Do Loop construct. Each variant has a particular purpose and time for use. The most common problem is that the loop runs one time too few or one time too many because the conditional expression was incorrectly placed at the wrong location.
Tip | The first question to ask when considering a Do Loop is this: "Must the loop always run at least once?" |
When deciding where to place the conditional expression of a Do Loop, ask yourself this question: "Must the loop always run at least once?" If the answer is no, the conditional expression must be at the top. This will prevent the code in the loop from running if the conditional expression fails. Consider, for example, printing all of the elements in an array of unknown size . The array might contain no elements at all, in which case the code in the loop shouldn't run (see Table 17 ).
Do While | Do Until |
---|---|
i% = LBound(a()) Do While i% <= UBound(a()) Print a(i%) i% = i% + 1 Loop | i% = LBound(a()) Do Until i% > UBound(a()) Print a(i%) i% = i% + 1 Loop |
In each case in Table 17, if the array is empty, the loop never runs. Before the condition is evaluated, i% is set to the lower bound of the array. In each case, the loop continues to run while i% is not larger than the upper bound.
Consider the difference between a While loop and an Until loop with a simple example. While the car has gas, you may drive it. Until the car does not have gas, you may drive it. The primary difference between the While and the Until is the word NOT. Tending more toward OOo Basic, I can write "Until NOT (the car has gas)." The choice between While and Until is usually based on which one you can write without the NOT.
If the loop should run at least once, move the conditional expression to the end of the Do Loop. Consider requesting user input until a valid value has been entered. The most natural choice is to place the conditional expression at the end.
Dim s$, x As Double Do s$ = InputBox("Enter a number from 1 through 5") x = CDbl(s$) 'Convert the string to a Double Loop Until x >= 1 AND x <= 5
The loop must run at least once so that at least one number is entered. The loop repeats until a valid value is entered. As an exercise, consider how to write this as a While loop.
The For ... Next statement repeats a block of statements a specified number of times.
For counter=start To end [Step stepValue] statement block1 [Exit For] statement block2 Next [counter]
The numeric "counter" is initially assigned the "start" value. When the program reaches the Next statement, the counter is incremented by the "step" value, or incremented by one if a "step" value is not specified. If the "counter" is still less than or equal to the "end" value, the statement blocks run. An equivalent Do While Loop follows:
counter = start Do While counter <= end statement block1 [Exit Do] statement block2 counter = counter + step Loop
The "counter" is optional on the "Next" statement, and it automatically refers to the most recent "For" statement.
For i = 1 To 4 Step 2 Print i ' Prints 1 then 3 Next i ' The i in this statement is optional.
The Exit For statement leaves the For statement immediately. The most recent For statement is exited. Listing 56 demonstrates this with a sorting routine. An array is filled with random integers, and then sorted using two nested loops. This technique is called a "modified bubble sort ."
Sub ExampleForNextSort Dim iEntry(10) As Integer Dim iOuter As Integer, iInner As Integer, iTemp As Integer Dim bSomethingChanged As Boolean ' Fill the array with integers between -10 and 10 For iOuter = LBound(iEntry()) To Ubound(iEntry()) iEntry(iOuter) = Int((20 * Rnd) -10) Next iOuter ' iOuter runs from the highest item to the lowest For iOuter = UBound(iEntry()) To LBound(iEntry()) Step -1 'Assume that the array is already sorted and see if this is incorrect bSomethingChanged = False For iInner = LBound(iEntry()) To iOuter-1 If iEntry(iInner) > iEntry(iInner+l) Then iTemp = iEntry(iInner) iEntry(iInner) = iEntry(iInner+1) iEntry(iInner+1) = iTemp bSomethingChanged = True End If Next iInner 'If the array is already sorted then stop looping! If Not bSomethingChanged Then Exit For Next iOuter Dim s$ For iOuter = 1 To 10 s = s & iOuter & " : " & iEntry(iOuter) & CHR$(10) Next iOuter MsgBox s, 0, "Sorted Array" End Sub
First, iOuter is set to the last number in the array. The inner loop, using iInner, compares every number to the one after it. If the first number is larger than the second number, the two numbers are swapped. The second number is then compared to the third number. After the inner loop is finished, the largest number is guaranteed to be at the last position in the array.
Next, iOuter is decremented by 1. The inner loop this time ignores the last number in the array, and compares every number to the one after it. At the end of the inner loop, the second-highest number is second from the end.
With each iteration, one more number is moved into position. If no numbers are exchanged, the list is sorted (see Figure 16 ).
The Exit Sub statement exits a subroutine, and the Exit Function statement exits a function. The routine is exited immediately. The macro continues running at the statement following the statement that called the current routine. These statements only work to exit the currently running routine and they are only valid in their respective types. For example, you can't use Exit Sub in a function.