Control Flow in BBj

The loop and branch verbs behave the same in PRO/5-style line-numbered code and in BBj. The examples on this page use unnumbered code with labels, the style recommended for new code; every rule below applies equally to line-numbered programs.

The Shared Loop and Subroutine Stack

Rule

FOR, WHILE, REPEAT and GOSUB each push an entry onto one shared FOR/GOSUB stack. Leave a construct only through its own end verb (NEXT, WEND, UNTIL, RETURN), or through BREAK, CONTINUE or EXITTO -- never through a bare GOTO.

Why

The stack is how NEXT, WEND, UNTIL and RETURN find and validate the construct they close; EXITTO explicitly discards the top entry, while a bare GOTO out of the construct does not.

Edge

A GOTO out of a loop or subroutine leaves a stale entry on the stack, so a later NEXT, WEND, UNTIL or RETURN finds the wrong top entry and raises an error. RESET clears all incomplete FOR, GOSUB, REPEAT and WHILE entries.

Evidence

EXITTO Verb, WHILE .. WEND Verbs, GOSUB Verb, RETURN Verb, RESET Verb (PRO/5, state the shared stack and the RESET rule).

BBj Modified/Enhanced Verbs (BBj generation: none of these five verbs is itself modified in BBj, so the PRO/5 pages remain the BBj documentation).

Example

rem 'leave a loop with BREAK, not GOTO, so the stack stays clean
for i=1 to 10
  if i=5 then break
next i

FOR .. NEXT

Rule

FOR var=start TO end {STEP step} ... NEXT {var} -- in BBj, the var on NEXT is optional. The loop body always runs at least once, because the termination check happens at NEXT, not at FOR. Guard an empty range before the loop if the body must not run when there is nothing to iterate.

Why

TO and STEP are evaluated once, when FOR runs, and their results are saved; the test that ends the loop sits at NEXT, after the body has already executed once.

Edge

FOR i=0 TO -1 still runs once, with i=0 (primer gotchas/6, owner-signed). Changing the limit variable inside the loop body does not change how many times the loop runs -- the limit was already evaluated and saved. STEP cannot be zero. The loop's entry sits on the shared FOR/GOSUB stack (see "The Shared Loop and Subroutine Stack" above).

Evidence

FOR .. NEXT Verbs (PRO/5) -- states the rule directly: "A FOR .. NEXT loop always executes at least once because the termination check is performed at the NEXT statement."

FOR .. NEXT Verbs - BBj (BBj) -- states the same rule for BBj.

Owner's observation, 2026-09-29 (.planning/OWNER-NOTES.md): the BBj page omitted this fact even though the PRO/5 page states it.

Example

rem 'guard an empty vector before the loop -- FOR still runs once otherwise
items! = new BBjVector()
if items!.size() > 0 then
  for i=0 to items!.size()-1
    print items!.get(i)
  next i
endif

WHILE .. WEND

Rule

WHILE expr ... WEND -- the loop body runs only while expr is nonzero (true). A condition already false when WHILE is reached is never entered.

Why

The test sits at WHILE, before the body -- the opposite of FOR .. NEXT, whose test sits at NEXT, after the body.

Edge

A condition false at the start skips the loop entirely: zero iterations, unlike FOR, which always runs once. WHILE shares the FOR/GOSUB stack; leave early with BREAK or EXITTO, never GOTO (see "The Shared Loop and Subroutine Stack" above).

Evidence

WHILE .. WEND Verbs (PRO/5, states the skip-when-false behavior and the shared stack).

BBj Modified/Enhanced Verbs (BBj generation: WHILE .. WEND is not modified in BBj).

Example

rem 'a false condition at the start skips the loop entirely
count=0
while count<0
  count=count+1
wend
print count

REPEAT .. UNTIL

Rule

REPEAT ... UNTIL expr -- the body always runs at least once, because the test sits at UNTIL, after the body.

Why

Like FOR .. NEXT (and unlike WHILE .. WEND), the terminating test is evaluated after the body executes, not before it.

Edge

The expr on UNTIL is numeric, and the loop repeats until it evaluates true. Leave early with BREAK or EXITTO, never GOTO.

Evidence

REPEAT .. UNTIL Verbs (PRO/5, describes the loop and the EXITTO-to-exit-early rule).

BBj Modified/Enhanced Verbs (BBj generation: REPEAT .. UNTIL is not modified in BBj).

Example

rem 'the body runs once even though the test starts true
count=1
repeat
  print count
until count>=1

BREAK and CONTINUE

Rule

BREAK ends the innermost loop or SWITCH. CONTINUE jumps to the next UNTIL, WEND or NEXT.

Why

Both verbs find their target by scanning forward from their own location and counting nested constructs of the same kind.

Edge

Inside a SWITCH that is itself inside a loop, BREAK ends only the SWITCH, not the loop -- use EXITTO to leave the loop from there. CONTINUE in a SWITCH inside a loop ends the SWITCH and continues with the loop's next iteration. CONTINUE with no active loop raises !ERROR=27.

Evidence

BREAK Verb, CONTINUE Verb (PRO/5).

BBj Modified/Enhanced Verbs (BBj generation: neither verb is modified in BBj).

Example

rem 'BREAK inside a SWITCH inside a loop ends only the SWITCH
for i=1 to 3
  switch i
    case 2
      break
    case default
      print i
  swend
next i

EXITTO

Rule

EXITTO lineref -- branches like GOTO, but also discards the top entry on the FOR/GOSUB/WHILE stack.

Why

EXITTO is the verb built to leave a loop or subroutine cleanly from inside nested code, since it retires the stack entry a bare GOTO would leave stale.

Edge

In a compound statement, only ELSE, FI or REM may follow EXITTO -- never BREAK.

Evidence

EXITTO Verb (PRO/5).

BBj Modified/Enhanced Verbs (BBj generation: EXITTO is not modified in BBj).

Example

rem 'EXITTO leaves the FOR loop cleanly, unlike a bare GOTO
for i=1 to 10
  if i=5 then exitto done
next i
done: print "left the loop"

SWITCH .. CASE .. SWEND

Rule

SWITCH expr ... CASE value ... CASE DEFAULT ... SWEND -- CASE bodies fall through into the next CASE until BREAK, EXITTO or SWEND ends them.

Why

SWITCH is a multi-way decision that scans CASE values in order for a match, then keeps executing statements -- including any following CASE lines, which are then skipped over like labels -- until something stops it.

Edge

Mark any intentional fallthrough with a comment. A CASE DEFAULT placed before a matching CASE wins, so put CASE DEFAULT last. SWITCH, CASE and SWEND must start a line; in a compound statement, SWITCH may be followed only by REM; there is one SWEND per SWITCH. PRO/5 accepts 32-bit integers only; BBj 17.00 and later accepts any value, including strings and objects.

Evidence

SWITCH .. CASE .. SWEND Verbs (PRO/5, states fallthrough and the CASE DEFAULT order).

SWITCH .. CASE .. SWEND Verbs - BBj (BBj, states the any-value support added in BBj 17.00).

Example

rem 'BBj 17.00+ SWITCH accepts a string, not only an integer
kind$="b"
switch kind$
  case "a"
    print "first"
    break
  case "b"
    print "second"
    break
  case default
    print "other"
swend

IF .. THEN .. ELSE .. FI

Rule

IF expr THEN statements {ELSE statements} {FI | ENDIF} -- expr is numeric (nonzero is true); on a single line, everything after THEN or ELSE stays conditional until FI or ENDIF.

Why

PRO/5 must know where a conditional clause ends when several statements share one line; FI/ENDIF marks that boundary explicitly.

Edge

ELSE pairs with the nearest unpaired THEN. No ; before ELSE or FI -- a semicolon there is a compile error (OWNER-NOTES #17). BBj's multi-line IF..THEN..ELSE must close with FI or ENDIF; a single-line IF with no FI/ENDIF closes implicitly at the end of its line, so a THEN clause cannot continue onto the next line the way some BBx code assumed.

Evidence

IF Verb (PRO/5, ELSE/FI pairing and the no-semicolon rule).

IF..THEN..ELSE Statements (BBj, the multi-line form and the implicit-ENDIF note).

Example

rem 'multi-line IF..THEN..ELSE must close with ENDIF in BBj
x=1
if x=1 then
  y=8
else
  y=9
endif
print y

GOSUB .. RETURN, GOTO and ON .. GOTO/GOSUB

Rule

GOSUB lineref ... RETURN transfers to a subroutine and back; GOTO lineref branches unconditionally; ON int GOTO lineref{,lineref...} and ON int GOSUB lineref{,lineref...} branch to one of several targets by index.

Why

GOSUB saves the return point on the shared FOR/GOSUB stack so RETURN knows where to resume; ON's index picks which of the listed targets GOTO or GOSUB actually uses.

Edge

RETURN errors when the top stack entry is not a GOSUB -- for example, an open loop left inside the subroutine. Only ELSE, FI or REM may follow GOTO, RETURN or ON GOTO/GOSUB in a compound statement; a BREAK after GOTO is silently ignored. ON's index takes the first target for 0 or less, the second for 1, and the last for anything past the end; a non-integer index raises !ERROR=41.

Evidence

GOSUB Verb, RETURN Verb, GOTO Verb, ON GOTO/GOSUB Verbs (PRO/5).

BBj Modified/Enhanced Verbs (BBj generation: none of these four verbs is modified in BBj).

Example

rem 'ON GOSUB picks a target subroutine by index, then RETURN comes back
choice=1
on choice gosub sub_one,sub_two
goto finish
sub_one:
  print "one"
  return
sub_two:
  print "two"
  return
finish:
  print "done"