Control Flow Statements

FOR ... NEXT statement


 Syntax:

 FOR Counter = StartNumber TO EndNumber [ STEP StepNumber% ]
 Statements
 [ EXIT FOR ]
 NEXT

 Parameters:

  • Counter Integer variable or array.
  • StartNumber Integer, single or double variable or literal number.
  • EndNumber Integer, single or double variable or literal number.
  • StepNumber Integer variable or literal number.

Here is an easy to follow example that shows how BCX handles positive and negative STEP values(step up and step down).


 CLS

 DIM i, j, k, q

 j = 10
 k =  1
 q = -1

 FOR i = j TO k STEP q
   PRINT i
 NEXT

 PRINT

 j =  1
 k = 10
 q =  1

 FOR i = j TO k STEP q
   PRINT i
 NEXT

 KEYPRESS

To start the next iteration of a NEXT early in control loops see the description of the ITERATE function.

FOR INTEGER | SINGLE | DOUBLE ... NEXT statement

BCX allows INTEGER, SINGLE, and DOUBLE loop variable declarations. Using this option makes the variable LOCAL to the loop. Variables used in defining a loop are LOCAL to the loop and cannot be accessed from outside the loop. Here is an example.


 DIM I = 100, J = 200

 FOR DOUBLE I = 1.1 TO 18.7 STEP 1.1
   FOR INTEGER J = 1 TO 10 STEP 5
     ? USING$("#.##",I), " .....", J
   NEXT
 NEXT

 ? : ? : ? I , " ....." , J

Result:


 1.10 ..... 1
 1.10 ..... 6
 2.20 ..... 1
 2.20 ..... 6
 3.30 ..... 1
 3.30 ..... 6
 4.40 ..... 1
 4.40 ..... 6
 5.50 ..... 1
 5.50 ..... 6
 6.60 ..... 1
 6.60 ..... 6
 7.70 ..... 1
 7.70 ..... 6
 8.80 ..... 1
 8.80 ..... 6
 9.90 ..... 1
 9.90 ..... 6
 11.00 ..... 1
 11.00 ..... 6
 12.10 ..... 1
 12.10 ..... 6
 13.20 ..... 1
 13.20 ..... 6
 14.30 ..... 1
 14.30 ..... 6
 15.40 ..... 1
 15.40 ..... 6
 16.50 ..... 1
 16.50 ..... 6
 17.60 ..... 1
 17.60 ..... 6
 18.70 ..... 1
 18.70 ..... 6

 100 ..... 200

Note well ! Using floating point numbers for the start and end value of a FOR ... NEXT loop can cause problems. The basis of the difficulty is that some floating point numbers will be rounded up or down because they can not be represented, with absolute accuracy, bit for bit. One specific problem in a FOR ... NEXT loop is that this rounding of the floating point numbers, and more specifically that the rounding up of the accumulated value, may push beyond the end value causing the loop to end prematurely as in the following example.


 DIM I = 100, J = 200

 FOR DOUBLE I = 9.9 TO 18.7 STEP 1.1
   FOR INTEGER J = 1 TO 10 STEP 5
     ? USING$("#.##",I), " .....", J
   NEXT
 NEXT

 ? : ? : ? I , " ....." , J

Result:


 9.90 ..... 1
 9.90 ..... 6
 11.00 ..... 1
 11.00 ..... 6
 12.10 ..... 1
 12.10 ..... 6
 13.20 ..... 1
 13.20 ..... 6
 14.30 ..... 1
 14.30 ..... 6
 15.40 ..... 1
 15.40 ..... 6
 16.50 ..... 1
 16.50 ..... 6
 17.60 ..... 1
 17.60 ..... 6

 100 ..... 200

The problem is not simply the number of steps. For example, in the example above the start value is different but the end value and STEP are the same as in the previous example which works as expected. The upward rounding problem can be guarded against by adding one-half of the STEP value to the end value. To apply this correction in the example above, the line


 FOR DOUBLE I = 9.9 TO 18.7 STEP 1.1

would be changed to


 FOR DOUBLE I = 9.9 TO 19.25 STEP 1.1

To start the next iteration of a NEXT early in control loops see the description of the ITERATE function.

XFOR ... XNEXT statement


 Syntax:

 XFOR [StartParameters] [ WHILE ... UNTIL Condition] [ BY VariableChanges] 
 Statements
 [ EXIT XFOR ]
 XNEXT

 Parameters:

  • StartParameters This can be any of the normal variables in a typical FOR ... NEXT loop and, as well, can be a pointer to a type or string.
  • WHILE ... UNTIL Condition This can be any condition that applies to WHILE and UNTIL.
  • BY VariableChanges This parameter can increment and decrement variables and reassign pointers.

Remarks:

With all of the parameters being optional the following is legal and results in generation of a for ever loop


 XFOR WHILE BY
   Statements
 XNEXT

Example 1:

Here is an example that uses XFOR ... XNEXT in the program which calculates and prints the combinations of a subset of items in a set.


 DIM SetItemCount%
 DIM SubSetItemCount%
 DIM SubSetCount%
 DIM start AS clock_t
 DIM finish AS clock_t
 DIM duration#
 
 INPUT "Total number of items in set? ", SetItemCount%
 INPUT "Number of items in one subset combination? ", SubSetItemCount%
 PRINT
 
 start = clock()
 SubSetCount% = SubSets(SetItemCount%, SubSetItemCount%)
 finish = clock()
 duration# = ROUND((double)(finish - start) / CLOCKS_PER_SEC, 2)
 
 PRINT
 PRINT "It took", duration#, " seconds to calculate that"
 PRINT "there are", SubSetCount%, " combinations of", SubSetItemCount%, " item subsets in a set of", SetItemCount%, " items."
 
 
 FUNCTION SubSets%(n%, k%)
   RAW buffer%[100]
   DIM i% = 0
   DIM j%
   DIM SSCnt%
 
   XFOR j = 0 WHILE j <= n BY j++
     buffer[j] = 0
   XNEXT
 
   WHILE i >= 0
     IF (buffer[i] < n + i - k + 1) THEN
       buffer[i]++
       IF (i = k - 1) THEN
         XFOR j = 0 WHILE j < k BY j++
           PRINT buffer[j];
         XNEXT
         PRINT
         SSCnt++
       ELSE
         buffer[++i] = buffer[i - 1]
       END IF
     ELSE
       i--
     END IF
   WEND
   FUNCTION = SSCnt%
 END FUNCTION

Example 2:

Here is a more complex example using XFOR ... XNEXT.


 ' Make a file of names.
 
 OPEN "NAMES.TXT" FOR OUTPUT AS FPOUT
 FPRINT FPOUT, "Robert";",";"Wishlaw"
 FPRINT FPOUT, "Kevin";",";"Diggins"
 FPRINT FPOUT, "Wayne";",";"Halsdorf"
 CLOSE
 
 ' Read in from file
 ' and display input.
 
 TYPE tagLink
   DIM szFirst$
   DIM szLast$
   DIM ptNxt AS tagLink PTR
 END TYPE
 
 DIM pzNames AS tagLink PTR
 DIM ptCurrent AS tagLink PTR
 pzNames = (tagLink*)calloc(1, sizeof(tagLink))
 OPEN "NAMES.TXT" FOR INPUT AS FPIN
 
 XFOR ptCurrent = pzNames WHILE NOT EOF(FPIN) BY ptCurrent = ptCurrent->ptNxt = (tagLink*)calloc(1,sizeof(tagLink))
   FINPUT FPIN, ptCurrent->szFirst$, ptCurrent->szLast$
 XNEXT
 
 XFOR ptCurrent = pzNames WHILE ptCurrent BY ptCurrent = ptCurrent->ptNxt
   PRINT ptCurrent->szFirst$; " "; ptCurrent->szLast$
 XNEXT
 PAUSE

Example 3:

And here is an even more complex example using XFOR ... XNEXT.


 TYPE tagRECORD
   DIM sName[16] AS CHAR
   DIM iAge AS int
   DIM ptPRV AS tagRECORD PTR
   DIM ptNXT AS tagRECORD PTR
 END TYPE
  
 DIM szBuf$
 DIM ptHIGH AS tagRECORD PTR
 DIM ptLOW AS tagRECORD PTR
 DIM ptHead AS tagRECORD PTR
  
 AddRec("Jim",22)
 AddRec("Jane",23)
 AddRec("Alex",22)
 AddRec("Mary",24)
 AddRec("John",26)
 AddRec("Steve",29)
 AddRec("Sally",25)
 AddRec("Zak",21)
 AddRec("Betty",29)
 AddRec("Clive",26)
 AddRec("Doris",28)
 AddRec("Kelly",22)
  
 sprintf(szBuf, "%12s%4s%3s", "Name", "", "Age")
 PRINT szBuf$
    
 XFOR INT i = 1,  tagRECORD PTR ptRL = ptLOW WHILE ptRL <> NULL BY i++, ptRL = ptRL->ptNXT
 sprintf(szBuf, "%14s  %3i", ptRL->sName, ptRL->iAge)
 PRINT szBuf$
 XNEXT
  
 PRINT
 sprintf(szBuf, "%12s%4s%3s", "Name", "", "Age")
 PRINT szBuf$;" ";szBuf$
 XFOR INT i = 1, INT j = 12, tagRECORD PTR ptRL = ptLOW, tagRECORD PTR ptRH = ptHIGH  WHILE i < j BY i++, j--, ptRL = ptRL->ptNXT, ptRH = ptRH->ptPRV
 sprintf(szBuf, "%14s  %3i %14s  %3i", ptRL->sName, ptRL->iAge, ptRH->sName, ptRH->iAge)
 PRINT szBuf$
 XNEXT
  
 PAUSE
  
 SUB AddRec(pszN$, iA AS INT)
   RAW ptTMP AS tagRECORD PTR
   ptTMP = (tagRECORD *)calloc(1,SIZEOF(tagRECORD))
   ptTMP->sName$ = pszN$
   ptTMP->iAge = iA
  
   IF ptHead = NULL THEN
     ptHIGH = ptLOW = ptHead = ptTMP
     EXIT SUB
   END IF
   
   RAW ptREC AS tagRECORD PTR
   XFOR ptREC = ptHead WHILE ptREC <> NULL AND ptREC->iAge < iA BY ptREC = ptREC->ptNXT
   XNEXT
  
   IF ptREC = NULL THEN
     ptHIGH->ptNXT = ptTMP
     ptHIGH->ptNXT->ptPRV = ptHIGH
     ptHIGH = ptTMP
     EXIT SUB
   END IF
  
   IF ptREC = ptHead THEN
     ptTMP->ptNXT = ptHead
     ptHead->ptPRV = ptTMP
     ptHead = ptLOW = ptTMP
   ELSE
     ptTMP->ptNXT = ptREC
     ptREC->ptPRV->ptNXT = ptTMP
     ptTMP->ptPRV = ptREC->ptPRV
     ptREC->ptPRV = ptTMP
   END IF
    
 END SUB

WHILE ... WEND statement


 Syntax:

 [ DO ] WHILE expression ' "DO" is optional
  EXIT LOOP  OR EXIT DO
 WEND

To start the next iteration of a WEND early in control loops see the description of the ITERATE function.

DO [UNTIL] ... LOOP [UNTIL] [WHILE] statement


 Syntax 1:

 DO
  Statements
 IF Condition THEN
  EXIT LOOP | EXIT DO
 END IF
  More Statements
 LOOP

or


 Syntax 2:

 DO UNTIL Condition
  Statements
 LOOP

or


 Syntax 3:

 DO
  Statements
 LOOP UNTIL Condition

or


Syntax 4:

 DO
   Statements
 LOOP WHILE Condition

Remarks: See ITERATE to start the next iteration of a LOOP early in control loops.

IF...THEN...ELSE...ELSEIF...END IF [ENDIF] statements


 Syntax 1:

 IF Expression THEN
  Statement
 END IF


 Syntax 2:

 IF  Expression1 THEN
  Statements
 ELSEIF Expression THEN
  Statements
 ELSE
  Statements
 END IF

SELECT CASE statement


 Syntax:

 SELECT CASE  Expression
  CASE  Expression1
   your code here
  CASE Expression2
    ... ' CASE Expression is tested for equality against all
    ... ' SELECT CASE expression and executes the instructions
    ... ' following if CASE found TRUE.
  CASE  Expression N
    your code here
  CASE ELSE
    your default code here
 END SELECT

Remarks:

CASE statements allow the following common construct:


 CASE 1 TO 10

which will capture the flow if the CASE is any number between 1 and 10.

Also allowed are less than greater than comparisons like


 CASE > 5 AND < 9

The OR operator also may be used, for example,


 CASE < 4 OR > 9

The NOT operator may be used as well, for example,


 CASE <> "BCX"

When a conditional operator(AND or OR) is used, scalar operators(= or < or > etc.) preceding the test expression must be used.


 CASE = "Selector" OR = "Selectee"

The following line is not valid and will cause an error.


 CASE "Selector" OR "Selectee"

However, in the example above, the OR can be replaced with a comma to form a valid CASE test statement.


 CASE "Selector", "Selectee"

Arrays, functions and variations of variables that contain the dereferencing operator(->) may be used as arguments to the CASE statement, for example,


 CASE A[1] TO A[9]
 
 CASE foo->f,foo->g
 
 CASE Funcfoo(A[foo->f],"nada"), foo->g
 
 CASE > foo->f AND < Funfoo(A[1])

Example 1:


 DIM Choose$
 
 Choose$ = "SelectOR"
 
 SELECT CASE Choose$
  CASE = "SelectOR" OR = "SelectXOR"
  PRINT "Selected"
 END SELECT

Example 2:


SELECT CASE

variants:


 DIM i
  
 INPUT "Enter a number: ", i
  
 SELECT CASE i
   CASE <1
   PRINT "less than 1"
  
   CASE 1
   PRINT "1"
  
   CASE 2 TO 4
   PRINT "2 to 4 inclusive"
  
   CASE 5
   PRINT "5"
  
   CASE >5 AND <9
   PRINT "greater than 5 and less than 9"
  
   CASE 9
   PRINT "9"
  
   CASE >9
   PRINT "greater than 9"
 END SELECT

SELECT CASE BAND

may be used. This statement suppresses breaks between CASE statements and performs a binary AND on all CASE statements. Here is an example.


 SELECT CASE BAND Style
 CASE WS_CHILD : CONCAT(StyleString$,"WS_CHILD,")
 CASE WS_VISIBLE : CONCAT(StyleString$,"WS_VISIBLE,")
 CASE WS_TABSTOP : CONCAT(StyleString$,"WS_TABSTOP")
 END SELECT

Here is an another example using

SELECT CASE BAND:

A status code is returned in lParam from which, depending on the bits set, the messages can be determined. This is how it would normally be written:


 ' IF lParam BAND CE_BREAK THEN err$=err$ + "CE_BREAK "
 ' IF lParam BAND CE_FRAME THEN err$=err$ + "CE_FRAME "
 ' IF lParam BAND CE_IOE THEN err$=err$ + "CE_IOE "
 ' IF lParam BAND CE_MODE THEN err$=err$ + "CE_MODE "
 ' IF lParam BAND CE_OVERRUN THEN err$=err$ + "CE_OVERRUN "
 ' IF lParam BAND CE_RXOVER THEN err$=err$ + "CE_RXOVER "
 ' IF lParam BAND CE_RXPARITY THEN err$=err$ + "CE_RXPARITY "
 ' IF lParam BAND CE_TXFULL THEN err$=err$ + "CE_TXFULL"

Here is a similar solution using


SELECT CASE BAND.

Note well that breaks are suppressed when using the


SELECT CASE BAND,

otherwise the flow would exit after the first match.


 SELECT CASE BAND lParam
 CASE CE_BREAK : CONCAT(err$,"CE_BREAK ")
 CASE CE_FRAME : CONCAT(err$,"CE_FRAME ")
 CASE CE_IOE : CONCAT(err$,"CE_IOE ")
 CASE CE_MODE : CONCAT(err$,"CE_MODE ")
 CASE CE_OVERRUN : CONCAT(err$,"CE_OVERRUN ")
 CASE CE_RXOVER : CONCAT(err$,"CE_RXOVER ")
 CASE CE_RXPARITY : CONCAT(err$,"CE_RXPARITY ")
 CASE CE_TXFULL : CONCAT(err$,"CE_TXFULL")
 END SELECT

GOTO statement

Purpose: Redirects program flow to a label.


 Syntax:

 GOTO Label

Remarks: A note about labels.

BCX Console Sample Programs using GOTO function.

S01.bas, S134.bas

GOSUB ... RETURN statement

Purpose: GOSUB redirects program flow to a label. The flow continues from the label until a RETURN statement is encountered and the flow is returned to the line following the GOSUB Label statement.


 Syntax:

 GOSUB Label

 Label:
  Statements
 RETURN

Remarks: A note about labels.

BCX Console Sample Programs using GOSUB statement.

S104.bas, S107.bas, S134.bas,

EXIT statement

Purpose: Causes EXIT from a DO...LOOP, FOR...NEXT, SELECT ... END SELECT, REPEAT... END REPEAT loop, or FUNCTION or SUB.


 Syntax:

 EXIT CASE

 EXIT DO

 EXIT FOR

 EXIT LOOP

 EXIT REPEAT

 EXIT SELECT

 EXIT WHILE

 EXIT FUNCTION

 EXIT SUB

When translated with the BCX -w flag, the following code generates a warning with the code generated exiting out of inner most FOR/NEXT loop


 DIM iW, iX, iY
 FOR iW = 1 TO 2
   FOR iX = 1 TO 5
     iY = iX+1
     WHILE iY < 10
       PRINT iX;iY
       IF iY = iX THEN EXIT FOR
       iY++
       iY = IMOD(iY,10)
     WEND
   NEXT
   PRINT iX;iY
 NEXT

When translated with the BCX -w flag, the following code generates a warning with code generated exiting out of the XFOR/XNEXT loop


 DIM iW, iX, iY
 XFOR iW = 1 WHILE iW < 2 BY iW++
 FOR iX = 1 TO 5
   FOR iY = 1 TO 5
     IF iY + iX + iW = 10 THEN EXIT XFOR
     PRINT iW;iX;iY
   NEXT
 NEXT
 XNEXT
 PRINT iW;iX;iY

When translated with BCX, the following code will generate an error since there is no DO/LOOP.


 DIM iX, iY
 FOR iX = 1 TO 5
   FOR iY = iX+1 TO 5
     PRINT iX;iY
     IF iY +iY > 7 THEN EXIT DO
   NEXT
 NEXT

EXIT without a control named results in a warning message with the inner most loop named. The code generated results in the exiting of the inner most control loop.

EXIT NEST statement

Purpose: Causes an EXIT out of the nest of current loop types to the positon in the code where a different loop type is encountered.


 Syntax:

 EXIT NEST

Example 1: This example will EXIT from the nested DO loops when (w+x)*(y+z) > 20.


 DIM w, x, y, z
 w = 0
 
 WHILE w < 10
   x = 0
   WHILE x < 5
     y = 0
     DO
       z = 0
       DO
         IF (w+x)*(y+z) > 20 THEN 
         PRINT "Exit Nest"
         EXIT NEST
         END IF
         z++
         PRINT "z Pass"; z
       LOOP UNTIL z > 4
       y++
     LOOP UNTIL y > 4
     PRINT "w equals"; w
     x++
   WEND
   w++
 WEND
 PRINT "At end w equals"; w
 PAUSE
 

EXIT SELECT statement

Purpose: Causes an EXIT out of the SELECT ... END SELECT.


 Syntax:

 EXIT SELECT

Example 1: Here is a general example of when one might need EXIT SELECT


 SELECT CASE SomeCondition
 
   CASE Condition_1
 
   IF SomeFlagIsSet THEN
     EXIT SELECT
   ELSE
     Do_Condition_1_Thing() ' Only if SomeFlagIsSet = FALSE
   END IF
 
   CASE Condition_2
   Do_Condition_2_Thing()
 
   CASE Condition_3
   Do_Condition_3_Thing()
 
 END SELECT

Example 2: Here is an specific example using EXIT SELECT


 $BCXVERSION "6.50"
 
 DIM WatchaGonnaDo$, IsItReadyToBeDone$, TheThing$, What$
 
 IsItReadyToBeDone$ = "Not Yet"
 What$ = "One"
 TheThing$ = ToBeDone$(What$)
 PRINT WatchaGonnaDo$
 PRINT TheThing$
 PRINT
 
 IsItReadyToBeDone$ = "Dunno"
 What$ = "Two"
 TheThing$ = ToBeDone$(What$)
 PRINT WatchaGonnaDo$
 PRINT TheThing$
 PRINT
  
 IsItReadyToBeDone$ = "Yeah"
 What$ = "One"
 TheThing$ = ToBeDone$(What$)
 PRINT WatchaGonnaDo$
 PRINT TheThing$
 PRINT
 
 IsItReadyToBeDone$ = "What?"
 What$ = "Three"
 TheThing$ = ToBeDone$(What$)
 PRINT WatchaGonnaDo$
 PRINT TheThing$
 
 FUNCTION ToBeDone$(DoSomething$)
 
   DIM Retstr$
 
   SELECT CASE DoSomething$
 
     CASE "One"
 
     IF IsItReadyToBeDone$ = "Not Yet" THEN
       WatchaGonnaDo$ = "Then wait some more ... "
       Retstr$ = "forever if you have to!"
       EXIT SELECT
     ELSE
       WatchaGonnaDo$ = "Good! "
       Retstr$ = "O.K. then do it!"    ' Only if IsItReadyToBeDone is not equal to "Not Yet"
     END IF
 
     CASE "Two"
     WatchaGonnaDo$ = "Why? "
     Retstr$ = "You'd better figure it out!"
 
     CASE ELSE
     WatchaGonnaDo$ = "Can't. "
     Retstr$ = "There's more than Two."
 
   END SELECT
 
   FUNCTION = Retstr$
 
 END FUNCTION

END statement

Purpose: Immediately terminates a running program


 Syntax:

  END

REPEAT ... END REPEAT statement

Purpose: REPEAT / END REPEAT blocks allow you to create loops without the need of a loop variable. BCX translates these to "C" style for/next loops that instead use TEMPORARY variables that are LOCAL TO THE LOOP.

Example:


 CLS

 REPEAT 2
   REPEAT 5
     PRINT "Here is an example of nested REPEATS"
   END REPEAT
 END REPEAT

 PRINT

 DIM Iterations

 INPUT "Type a number, press ENTER ", Iterations

 REPEAT(Iterations+1+((Iterations+1)*2))
   PRINT "Calculated Expressions Allowed in REPEAT/END REPEAT"
 END REPEAT

To count backwards, the argument must begin with a minus sign. The "add a minus sign" rule is the same rule that BCX has always used in FOR/NEXT statements that contain a negative STEP clause. "add a minus sign" is the only way that BCX can detect and respond to the logic flow at compile time.

Examples :


 REPEAT -10
 REPEAT -A
 REPEAT -(A*2+100)

See ITERATE to start the next iteration of a END REPEAT early in control loops.

EXIT REPEAT immediately breaks the flow out of a REPEAT block.

BCX_REPEAT variable

The BCX_REPEAT variable automatically keeps count of how many times REPEAT has been executed within a REPEAT ... END REPEAT block.

Example:


  REPEAT 2
  PRINT BCX_REPEAT
   REPEAT 5
    PRINT "... ", BCX_REPEAT
   END REPEAT
 END REPEAT

Here's another sample.


  DIM a
  a = -10

 REPEAT -ABS(a)
  PRINT BCX_REPEAT
  IF BCX_REPEAT = 5 THEN EXIT REPEAT
 END REPEAT

WITH ... END WITH statement

Purpose: WITH ... END WITH allows repeated reference to be made to an user defined type object or structure.


 Syntax:

 WITH UserDefinedTypeObject
   [ statements ]
 END WITH

Example 1:


 TYPE QWERTY
  DIM a
  DIM b!
  DIM c[80] AS CHAR
  DIM q AS RECT
 END TYPE

 GLOBAL MyType [10,10,10] AS QWERTY

 WITH MyType[2,3,4]
 .a = 1
 .b! = 2.345
 .c$ = "Hello world from a poly-dimensional udt!"
 WITH .q
 .left = 100
 .right = 200
 .top = 150
 .bottom = 300
 END WITH
 PRINT .a
 PRINT .b!
 PRINT UCASE$(.c$)
 END WITH

Example 2:


 TYPE Foo
 One AS LONG
 Two AS LONG
 Three AS LONG
 END TYPE

 DIM MyArray AS Foo

 WITH MyArray
 .One = 1
 .Two = 2
 .Three= 3
 PRINT .One
 PRINT .Two
 PRINT .Three
 END WITH