Language Elements and Reference:xpplrm

Control structures Foundation

Control structures define the logical program flow and are determined by statements. Xbase++ provides statements for branches, loops, and error handling. Branching allows different program sections to be called depending on conditions. Loops repeat a specific program section. The control structure for error handling controls the program flow if a runtime error occurs that does not require the program to be terminated.

Branching

Xbase++ offers two syntaxes for branching in programs. The first is the DO CASE...ENDCASE control structure and the other consists of the IF...ENDIF statements. Both branch statements evaluate logical expressions and branch to a program section when a logical condition returns the value .T. (true). The statements DO CASE...ENDCASEand IF...ENDIF offer different syntaxes but have the same effect. The general scheme for both control structures is as follows:

DO CASE 
CASE <condition1>            IF <condition1> 
   <Code>                       <Code> 
CASE <condition2>            ELSEIF <condition2> 
   <Code>                       <Code> 
CASE <condition3>            ELSEIF <condition3> 
   <Code>                       <Code> 
OTHERWISE                    ELSE 
   <Code>                       <Code> 
ENDCASE                      ENDIF 

The simplest form of the IF statement tests only one condition and executes the program code appearing between the IF and ENDIFstatements when the condition is true.

nValue := 1 

IF nValue = 1 
   ? "The value is equal to one" 
ENDIF 

If only one condition will be tested, but one of two branches will always be executed, the keyword ELSE is used. This means that whenever the condition is false, the alternative program code found between the ELSE and ENDIF statements is executed. With ELSE, no further condition is tested:

nValue := 2 

IF nValue = 1 
   ? "The value is equal to one" 
ELSE 
   ? "The value is not equal to one" 
ENDIF 

Additional conditions within the IF..ENDIF control structure are defined using the ELSEIF statement. If the statement ELSE is used along with ELSEIF statements, ELSE must always appear after all ELSEIF statements. In this case, ELSE defines the program code which is to be executed if none of the specified conditions apply.

There is no limit to the number of conditions that can be tested using IF...ELSEIF...ENDIF. IF branches can also be nested to any depth. An example of nesting is shown in the following program code:

nValue := 250 

IF nValue < -10 
   ? "The number is less than -10" 
ELSEIF nValue < 0 
   ? "The number is negative" 
ELSEIF nValue > 0 
   IF nValue > 1000                        // nested IF 
      ? "The number is greater than 1000"  // branch 
   ELSEIF nValue > 100 
      ? "The number is between 101 and 1000" 
   ELSEIF nValue > 10 
      ? "The number is between 11 and 100" 
   ELSE 
      ? "The number is between 1 and 10" 
   ENDIF 
ELSE 
   ? "The number is 0" 
ENDIF 

The two branch control structures, IF...ENDIF and DO CASE...ENDCASE are merely different syntaxes and neither offers advantages in regard to runtime behavior. So the above branched code can also be written as follows:

nValue := 250 

DO CASE 
CASE nValue < -10 
   ? "The number is less than -10" 
CASE nValue < 0 
   ? "The number is negative" 
CASE nValue > 0 
   DO CASE                              // nested 
   CASE nValue > 1000                   // DO CASE branch 
      ? "The number is greater than 1000" 
   CASE nValue > 100 
      ? "The number is between 101 and 1000" 
   CASE nValue > 10 
      ? "The number is between 11 and 100" 
   OTHERWISE 
      ? "The number is between 1 and 10" 
   ENDCASE 
OTHERWISE 
   ? "The number is 0" 
ENDCASE 

A branch using IF...ENDIF is generally used only when only a few conditions are to be tested. When many possible conditions are tested in program code, it is generally done usingDO CASE.

Loops

The control structure "program loop" allows repeated execution of specific program sections. Xbase++ recognizes two sets of statements for loops: the DO WHILE...ENDDO and the FOR...NEXT statements. The DO WHILE loop tests a condition and repeats the section of program code as long as the condition returns the logical value .T. (true). The FOR loop is an iterative control structure that repeats a program section a specified number of times. Both control structures are described as follows:

DO WHILE <condition>     FOR <nStart>:=1 TO <nEnd> [STEP <nStep>] 
   <CodeA>                  <CodeA> 
   IF <condition1>          IF <condition1> 
      LOOP                     LOOP 
   ELSEIF <condition2>      ELSEIF <condition2> 
      EXIT                     EXIT 
   ENDIF                    ENDIF 
   <CodeB>                  <CodeB> 
ENDDO                    NEXT 

The DO WHILE loop is the simpler form of program looping because it tests only one condition each time the program repeats the loop. If the condition returns the value .F. (false) when the DO WHILE statement is first reached, the loop is not executed at all. Otherwise the program code appearing between DO WHILE and ENDDO, is executed repeatedly until the condition returns the value .F..

The FOR loop changes a counter variable by a specific value after each cycle through the loop. The loop is terminated as soon as the value of the counter variable exceeds a maximum (or goes under a minimum) value.

Within both of these control structures the statements LOOP and EXIT may be included. LOOP causes the immediate return to the opening statement of the loop (FOR.. or DO WHILE...) skipping any remaining code within the loop. This allows parts of the loop to be excluded in certain repetitions. The statement EXIT immediately terminates the loop without any further testing of the condition or counter variable.

An example for a simple DO WHILE loop is shown in the following code. Keyboard input is read as long as the user does not press either the "Y" or the "N" key. If the user presses the "Y" key the program terminates, otherwise it continues:

cKey := " " 
? "Do you want to terminate the program? Y/N" 

DO WHILE .NOT. cKey $ "nNyY"     // logical WHILE condition 
   cKey := Chr(Inkey(0))         // read keyboard 
ENDDO                            // end loop 

IF cKey $ "yY" 
   QUIT 
ENDIF 

An example of a typical use for DO WHILE loops is sequentially traversing data records in files after opening them using a DatabaseEngine.

FOR loops are used when the number of times a program section will be repeated is known. Each FOR loop contains an initialization part where the start value is assigned to the counter variable and where the final value (comparison value) for the counter is defined. In the following example, the end value is the constant 10 but it can also be a variable or an expression:

nTotal    := 0                  // start value for total 
nFactorial:= 1                  // start value for factorial 

FOR i:=1 TO 10 
   nTotal     += i              // calculate total 1-10 
   nFactorial *= i              // calculate factorial of 10 
NEXT 

? i                             // result: 11 
? nTotal                        // result: 55 
? nFactorial                    // result: 3628800 

By default, the counter variable is incremented by the value 1after each pass through the loop. The optional keyword STEPspecifies the value by which the counter variable is changed after each pass through the loop. If a negative value is specified, the loop counter is decremented. In this case, the start value must be greater than the end value. Decrementing is shown in the next example which totals all even numbers between 10 and 1:

nTotal    := 0                  // start value for total 

FOR i:=10 TO 1 STEP -2          // only even numbers 
   nTotal     += i              // calculate total 1-10 
NEXT 

? i                             // result: 0 
? nTotal                        // result: 30 

A very important area where FOR loops are used in programming is handling arrays. Within an iterative loop each array element can be sequentially accessed. The following code shows an example:

aChar := {"A","B","C","D","E"} 

FOR i:=1 TO 5 
   ?? aChar[i]                  // result: ABCDE 
NEXT 

Exceptions and error handling

Xbase++ provides a control structure designed especially for exceptions and handling runtime errors. It uses the statements BEGIN SEQUENCEand ENDSEQUENCE and represents a powerful tool for improving the reliability and stability of a program. This section deals only with the fundamentals of the BEGIN SEQUENCE control structure. A detailed look at strategies for handling runtime errors is found in the chapter "Error Handling Concepts".

BEGIN SEQUENCE and ENDSEQUENCE delimit a section of program code which can be terminated at any time using the statement BREAKor the function Break(). Besides terminating the control structure, ENDSEQUENCE is also be a point where program flow continues after a BREAK is issued. In its simplest form, the control structure can be used as follows:

FUNCTION BackupData() 
   LOCAL lBackupDone := .T. 

   BEGIN SEQUENCE 
      IF Day(Date()) % 7 == 0    // weekly backup 
         WeeklyBackup() 
         BREAK                   // jump to ENDSEQUENCE 
      ENDIF 

      IF Day(Date()) % 30 == 0   // monthly backup 
         MonthlyBackup() 
         BREAK                   // jump to ENDSEQUENCE 
      ENDIF 

      lBackupDone := .F.         // no backup 
   ENDSEQUENCE 

RETURN lBackup 

In this example, the program continues after a BREAK at the RETURN statement because it follows immediately after END SEQUENCE. The program code between BREAK and ENDSEQUENCEis skipped. This form of the control structure should generally be used only in exception situations. It is possible to exit deeply nested loops using a single BREAK if all loops are enclosed by BEGIN SEQUENCE and ENDSEQUENCE, but a branch is generally a better solution.

The most important use of this structure lies in error handling, which is performed using an additional RECOVER statement. RECOVERmarks a second entry point. If it is available, it marks the point where the program resumes after a BREAK is reached. If a BREAK does not occur, the program code betwen RECOVER and ENDSEQUENCE is not executed, and program flow moves to the first line following the RECOVER statement. Since the program code between RECOVER and ENDSEQUENCE is executed only after a BREAK, it generally contains program code that responds to runtime errors:

FUNCTION Divide( nDividend, nDivisor ) 
   LOCAL nResult 

   BEGIN SEQUENCE 
      nResult := nDividend / nDivisor 
   RECOVER 
      nResult := 0              // In case of an error 
   ENDSEQUENCE                  // division is always 0 

RETURN nResult 

In this example, there is no explicit BREAK statement because the default error handling system of Xbase++ automatically executes a BREAK as soon as a runtime error occurs. An error occurs in the example whenever division by zero is attempted or when the variables nDividend and nDivisor do not both have numeric values. These errors cause a BREAK and the program continues after RECOVER. The line appearing there defines a valid value for the return value of the function Divide() that will allow the program to continue. When no error occurs during the division, the program code between RECOVER and ENDSEQUENCE is not executed.

As versatile as the control structure BEGIN SEQUENCE...ENDSEQUENCE is, it has an important restriction: this control structure cannot be exited using RETURN, EXIT, or LOOP. It can only be exited using BREAK or Break(). The following code shows invalid use of statements leading to compiler errors such that this program code cannot be compiled:

DO WHILE .T.                    // begins loop 

   BEGIN SEQUENCE               // begins SEQUENCE 
      nKey := Inkey(0) 

      IF nKey == K_ESC 
         EXIT                   // invalid EXIT 
      ELSEIF nKey == K_F10 
         LOOP                   // invalid LOOP 
      ENDIF 

      <Code>                    // program code 

      RETURN                    // invalid RETURN 

   ENDSEQUENCE                  // ends SEQUENCE 

ENDDO                           // ends loop 

The above example can be compiled if the BEGIN SEQUENCE...ENDSEQUENCE encloses the DO WHILE loop and the RETURN statement does not appear within the loop. In this case, the jump to the top of the DO WHILE loop caused by LOOP and the loop termination caused by EXIT are still within the control structure BEGIN SEQUENCE...ENDSEQUENCE:

BEGIN SEQUENCE                  // begins SEQUENCE 

   DO WHILE .T.                 // begins loop 
      nKey := Inkey(0) 

      IF nKey == K_ESC 
         EXIT                   // valid EXIT 
      ELSEIF nKey == K_F10 
         LOOP                   // valid LOOP 
      ENDIF 

      <Code>                    // program code 
   ENDDO                        // ends loop 

ENDSEQUENCE                     // ends SEQUENCE 

When a RECOVER statement appears within BEGIN SEQUENCE..END SEQUENCE, the statements LOOP, EXIT or RETURN can be used between RECOVER and ENDSEQUENCE to exit the BEGIN SEQUENCE..ENDSEQUENCE control structure. This is allowed because the entry point defined by RECOVER (which can only be reached after a BREAK) has already been reached. The following program code is valid and can be compiled:

DO WHILE .T.                    // begins loop 

   BEGIN SEQUENCE               // begins SEQUENCE 

      <Code>                    // program code 

   RECOVER                      // first entry point after BREAK 
      nKey := Inkey(0) 

      IF nKey == K_ESC 
         EXIT                   // valid EXIT 
      ELSEIF nKey == K_F10 
         LOOP                   // valid LOOP 
      ENDIF 

      RETURN                    // valid RETURN 
   ENDSEQUENCE                  // second entry point after BREAK 

ENDDO                           // ends loop 

The most important task of the BEGIN SEQUENCE...ENDSEQUENCEcontrol structure is to define the entry point where the program resumes if an exception or an error occurs. An entry point is defined using either the statement RECOVER or ENDSEQUENCE. Both entry points are reached after a BREAK or Break(). When the program does not execute a BREAK, the program code between RECOVER and ENDSEQUENCE is skipped and is not executed.

Within BEGIN SEQUENCE and ENDSEQUENCE, two alternative program sections can be programmed. The first alternative is executed when the program runs normally and the second is delimited byRECOVER and only executes following a BREAK. Possible program errors may be anticipated during program development and handled as exception situations. During normal program execution, the program code for correcting runtime errors is not executed. It is only executed when an error occurs.

The control structure BEGIN SEQUENCE...ENDSEQUENCE is augmented by the function ErrorBlock(). This function can be used to replace the default error handling system of Xbase++ with a user defined error system. Further information is found in the chapter "Error Handling Concepts".

Feedback

If you see anything in the documentation that is not correct, does not match your experience with the particular feature or requires further clarification, please use this form to report a documentation issue.