Control flow

Branch with IF and SELECT, repeat with FOR, WHILE and REPEAT, leave a loop early, and jump with GOSUB and GOTO.

In one line#

A program runs top to bottom. The keywords on this page change that order: they choose a branch, repeat a block, leave a loop, or jump to a label. Keywords are case-insensitive: endif, EndIf and ENDIF are the same word.

ConstructOpens withCloses with
One-line IFIF … THEN …the end of the line
Block IFIF …ENDIF or END IF
SELECTSELECT …ENDSELECT or END SELECT
FORFOR … TO …NEXT
FOREACHFOREACH list()NEXT, see Collections
WHILEWHILE …ENDWHILE, END WHILE or WEND
REPEATREPEATUNTIL …
DODOLOOP, the frame loop, see The game loop and tasks

A block does not create a variable scope. A variable first written inside an IF or a loop is still readable after it. See Variables and types.

IF on one line#

IF <condition> THEN <statement> [: <statement>…] [ELSE <statement> [: <statement>…]]
score = 75
IF score >= 50 THEN Console("pass")
IF score > 90 THEN Console("A") ELSE Console("not an A")
IF score > 50 THEN bonus = 10 : lives = 3 ELSE bonus = 0 : lives = 1
Console(Str(bonus) + " " + Str(lives))   ' 10 3
  • THEN is required on this form. It marks where the condition ends. Without it, the line opens a block IF and the compiler asks for ENDIF.
  • Statements joined by : after THEN all belong to the THEN branch. After ELSE, they all belong to the ELSE branch.
  • In a condition, = compares. It never assigns. AND, OR and NOT are the logical words. See Constants and operators.

⚠️ A one-line IF cannot contain another one-line IF. The compiler accepts the line, but the program it produces is broken:

x = 1
y = 2
IF x = 1 THEN IF y = 2 THEN Console("both")

Join the two conditions with AND, or write a block:

x = 1
y = 2
IF x = 1 AND y = 2 THEN Console("both")   ' both

Block IF#

IF <condition> [THEN]
  <statements>
[ELSEIF <condition> [THEN]
  <statements>]…
[ELSE
  <statements>]
ENDIF
score = 75
IF score >= 90
  Console("A")
ELSEIF score >= 70 THEN
  Console("B")
ELSE IF score >= 50
  Console("C")
ELSE
  Console("F")
END IF
' B
PartAccepted spellings
OpeningIF <condition> or IF <condition> THEN, with nothing after THEN
Middle branchELSEIF or ELSE IF, with or without THEN
Last branchELSE
ClosingENDIF or END IF
  • The first true branch runs. The others are skipped.
  • THEN is optional on the block form. AGK programs usually leave it out, and they compile as they are.
  • ELSE comes once, and last. ELSEIF after ELSE is a compile error, and so is a second ELSE.
  • A line IF … THEN with something after THEN is a one-line IF. With nothing after THEN, it opens a block.

SELECT#

SELECT [CASE] <expression>
  CASE <value> [, <value>…]
    <statements>
  [ENDCASE]
  …
  [CASE DEFAULT
    <statements>]
ENDSELECT
weapon = 2
SELECT weapon
  CASE 1
    Console("sword")
  CASE 2, 3
    Console("bow")
  CASE DEFAULT
    Console("fists")
ENDSELECT
' bow

The same, with the long spellings and a string:

name$ = "bob"
SELECT CASE name$
  CASE "alice"
    Console("hi alice")
  ENDCASE
  CASE "bob"
    Console("hi bob")
  ENDCASE
  DEFAULT CASE
    Console("who?")
  ENDCASE
END SELECT
' hi bob
PartAccepted spellings
OpeningSELECT <expression> or SELECT CASE <expression>
Default branchCASE DEFAULT or DEFAULT CASE
End of a branch (optional)ENDCASE or END CASE
ClosingENDSELECT or END SELECT

One branch runs, and only one. There is no fall-through: an empty CASE does nothing and does not run the next one. To share a body, list the values: CASE 2, 3.

  • Matching is strict equality. A string never matches a number: "5" does not match CASE 5. 2.0 matches CASE 2, because they are the same number.
  • A CASE value is an expression: a literal, a variable, 2 * 2. The values are tried in order.
  • CASE DEFAULT runs when nothing else matched. It comes once, and last: a CASE after it is a compile error.
  • ENDCASE closes a branch. It is optional: the next CASE closes it too.
  • EXIT inside a SELECT leaves the loop around it, not only the SELECT. See EXIT and CONTINUE.

Not supported, and what to write instead:

You may knowIn aukimi
CASE 1 TO 5 (VB, PureBasic)an IF … ELSEIF chain
CASE IS > 5 (VB)an IF … ELSEIF chain
CASE ELSE (VB), Default (PureBasic)CASE DEFAULT
x = 3
SELECT x
  CASE 1 TO 5
    Console("low")
ENDSELECT
x = 3
IF x >= 1 AND x <= 5
  Console("low")
ELSE
  Console("high")
ENDIF
' low

⚠️ CASE IS > 5, CASE ELSE and a bare DEFAULT all compile, and all go wrong when the program runs. The first two stop it with an error when they are tested. A bare DEFAULT is not a branch at all: its lines end up inside the CASE above it, and that CASE stops with an error when it runs.

⚠️ Do not write a comparison with = in a CASE. The SELECT TRUE trick from other dialects does not work here: CASE x = 7 stores 7 in x instead of comparing.

FOR … NEXT#

FOR <var> = <start> TO <end> [STEP <step>]
  <statements>
NEXT [<var>]
total = 0
FOR i = 1 TO 5
  total = total + i
NEXT i
Console(Str(total))   ' 15

FOR n = 10 TO 1 STEP -3
  Console(Str(n))   ' 10, 7, 4, 1
NEXT

<end> and <step> are read once, before the first pass. Changing a variable used in them does not change the loop.

count = 3
FOR i = 1 TO count
  count = 10
  Console(Str(i))   ' 1, 2, 3
NEXT
  • The sign of the step picks the direction. A positive step counts up while the counter is <= end. A negative step counts down while it is >= end.
  • The loop can run zero times. FOR i = 5 TO 1 runs nothing: the step is +1 and 5 is already past 1.
  • STEP 0 stops the program with the error "BASIC FOR STEP cannot be zero". It is not an endless loop.
  • Steps can be decimal: FOR t# = 0 TO 1 STEP 0.25 gives 0, 0.25, 0.5, 0.75, 1.
  • The counter is an ordinary variable. You can change it inside the loop, and the next pass adds the step to the new value.
  • NEXT alone closes the innermost FOR. NEXT i must name that FOR's counter, or the compiler says "NEXT variable j does not match FOR variable i".

The counter after the loop keeps its last value:

The loop…The counter after NEXT
ran to the endend + step (FOR i = 1 TO 5 leaves 6)
was left by EXITits value at the EXIT
never ranthe start value
FOR i = 1 TO 10
  IF i * i > 20 THEN EXIT
NEXT
Console(Str(i))   ' 5

⚠️ Known flaw. When the counter is read after NEXT, the loop's end and step are kept in one place shared by every run of that loop. A FUNCTION that calls itself from inside such a loop can cut the outer loop short. In that case, copy the value you need into your own variable inside the loop, and do not read the counter after NEXT.

WHILE … ENDWHILE#

WHILE <condition>
  <statements>
ENDWHILE

END WHILE and WEND close it too.

n = 1
WHILE n < 100
  n = n * 2
ENDWHILE
Console(Str(n))   ' 128

The condition is tested before each pass, so the body can run zero times.

REPEAT … UNTIL#

REPEAT
  <statements>
UNTIL <condition>
tries = 10
REPEAT
  tries = tries + 1
UNTIL tries > 5
Console(Str(tries))   ' 11

The condition is tested after each pass, so the body runs at least once. The loop stops when the condition becomes true.

There is no DO WHILE, DO UNTIL, LOOP WHILE or LOOP UNTIL. Write WHILE … ENDWHILE or REPEAT … UNTIL. In aukimi, DO … LOOP is reserved for the frame loop:

n = 0
DO WHILE n < 3
  n = n + 1
LOOP

DO … LOOP#

DO … LOOP is the frame loop of a classic program. It may appear once, at the top level, and its body must call Sync() so a frame can be drawn. It has its own page: The game loop and tasks. EXIT and CONTINUE work in it like in any loop, and the code after LOOP runs once EXIT leaves it.

EXIT and CONTINUE#

EXIT
CONTINUE
total = 0
FOR i = 1 TO 10
  IF Mod(i, 2) = 0 THEN CONTINUE
  IF i > 7 THEN EXIT
  total = total + i
NEXT
Console(Str(total))   ' 16 (1 + 3 + 5 + 7)
  • Both act on the innermost loop: FOR, FOREACH, WHILE, REPEAT, or the DO frame loop.
  • EXIT leaves the loop. The program continues after its NEXT, ENDWHILE, UNTIL or LOOP.
  • CONTINUE starts the next pass. In a FOR, the step is added first. In a WHILE or a REPEAT, the condition is tested first.
  • Outside any loop, both are compile errors: "EXIT without an active loop", "CONTINUE without an active loop". That includes a SELECT that is not inside a loop.

Inside a SELECT, they still act on the loop.

s = 0
FOR i = 1 TO 5
  SELECT i
    CASE 3
      EXIT
    CASE DEFAULT
      s = s + i
  ENDSELECT
NEXT
Console(Str(s))   ' 3, the loop stopped at i = 3

There is only one spelling. EXIT FOR, EXIT WHILE and EXIT DO (QBasic, VB) are not supported, nor Break (PureBasic, C). Write EXIT:

FOR i = 1 TO 3
  EXIT FOR
NEXT

EXIT leaves one loop. To leave two nested loops at once, set a flag and test it in the outer loop, or jump forward with GOTO.

⚠️ CONTINUE in the frame loop skips Sync(). If the CONTINUE fires on every pass, the loop never gives a frame back and the game freezes. Put Sync() before the CONTINUE, or use IF … ELSE instead.

END#

END

END stops the program.

lives = 0
Console("start")
IF lives = 0
  END
ENDIF
Console("never shown")
' start
  • In a program with a DO loop, END stops the loop and everything after it.
  • In a GOSUB subroutine, END stops the whole program, not only the subroutine. In a program with a DO loop, the editor then reports it as an error named "BASIC END", although the stop was intended.
  • Inside a FUNCTION, END is a compile error. Leave the function with EXITFUNCTION, see Functions.
  • END is not an error, so a CATCH does not stop it. See Errors.

⚠️ Write END on its own line. In a program without a DO loop, IF lives = 0 THEN END on one line is silently ignored, unless an END on its own line also exists elsewhere in the program. A block IF, as above, always works.

Labels#

<name>:

A label names a place in the program. It stands alone on its line.

  • A label is the target of GOSUB, GOTO and STARTTASK. Inside a DATASECTION, it is the target of RESTORE, see Collections.
  • Label names are case-insensitive. Two labels with the same name are a compile error: "Duplicate BASIC label".
  • A label can carry a short body on the same line, joined with :: AddFour: value = value + 4 : RETURN.

⚠️ A label cannot be named like a command. Second: is refused, because Second is a command of the catalogue, and the error message asks for parentheses. Pick another name, such as StepTwo:.

GOSUB and RETURN#

GOSUB <label>

<label>:
  <statements>
RETURN
score = 10
GOSUB AddBonus
GOSUB AddBonus
Console(Str(score))   ' 30
END

AddBonus:
  score = score + 10
RETURN
  • A subroutine runs from its label to its RETURN, then the program continues after the GOSUB.
  • A subroutine shares every variable of the program. It has no parameters and no local variables. When you need them, write a FUNCTION instead, see Functions.
  • The main program never runs into a subroutine by accident. Subroutines are set apart from the main flow, so a missing END before them does no harm. The END above is still good style.
  • GOSUB to a label that does not exist is a compile error: "Unknown BASIC label".

A label without RETURN continues into the next label. The RETURN of the next one then returns to the GOSUB:

GOSUB StepOne
Console("back")
END

StepOne:
  Console("one")
StepTwo:
  Console("two")
RETURN
' one, two, back

The last label of such a chain must end with RETURN. If it does not, the compiler says "BASIC label … requires RETURN".

⚠️ A subroutine has one RETURN, at its end. An early RETURN inside an IF breaks the program. On one line (IF x > 5 THEN RETURN), the program stops with an error when it reaches that line. In a block IF, the compiler cuts the subroutine at that RETURN, and the rest of the program is lost without any message. Put the rest of the subroutine inside the IF instead:

x = 3
GOSUB Grow
Console(Str(x))   ' 4
END

Grow:
  IF x <= 5
    x = x + 1
  ENDIF
RETURN

Subroutines that wait#

A subroutine can call Sync(), Swap(), Wait(), Sleep() or YieldTask(). It then waits for frames, and so does any subroutine that calls it. That is allowed in three places:

  • in a program with a DO loop, anywhere, including before the DO;
  • in a program without a DO loop that contains an END on its own line;
  • inside another subroutine that waits.

Anywhere else, the compiler says "GOSUB … yields a frame and needs the DO/LOOP main loop".

GOSUB Loading
DO
  Print("Ready")
  Sync()
LOOP

Loading:
  FOR part = 1 TO 3
    Print("Loading " + Str(part) + "/3")
    Sync()
  NEXT
RETURN
  • RETURN <value> ends a task label and gives the task its result. See The game loop and tasks.
  • Inside a FUNCTION, RETURN leaves the function. See Functions.
  • ON … GOSUB and ON … GOTO are not supported. Use SELECT with one GOSUB per CASE.

GOTO#

GOTO <label>
IF <condition> THEN GOTO <label> [ELSE GOTO <label>]

A backward jump repeats code:

n = 0
again:
n = n + 1
IF n < 3 THEN GOTO again
Console(Str(n))   ' 3

A forward jump skips code, and can leave loops:

FOR i = 1 TO 10
  IF i * 3 > 7 THEN GOTO found
NEXT
Console("not found")
found:
Console("found " + Str(i))   ' found 3

JavaScript has no goto, so the compiler turns each jump into a loop or a block it can leave. That works for the common shapes, and the others are refused:

JumpAllowed?
backward, to a label at the same block levelyes: it repeats the code in between
forward, to a label later in the same block or in an enclosing blockyes: it skips the code in between, and can leave loops
from a subroutine, back to a label of the main programyes: the program restarts from that label (see below)
into a block (an IF, a FOR, a WHILE…) from outside itno: "GOTO … jumps across block boundaries"
backward, from inside a block to a label outside itno: same error

Restarting from a subroutine. A GOTO written in a subroutine, back to a label of the main program, abandons the subroutine and restarts from that label. It is the classic "play again" pattern:

round = 0
restart:
round = round + 1
Console("round " + Str(round))
GOSUB Play
Console("over")
END

Play:
  IF round < 3
    GOTO restart
  ENDIF
RETURN
' round 1, round 2, round 3, over

⚠️ Limits the compiler does not catch. These compile, and the program they produce is broken:

  • Two forward jumps to two different labels in the same block. Only one forward target per block works. That includes IF c THEN GOTO a ELSE GOTO b when both labels come later. Use IF … ELSE instead.
  • A restart jump on one line. In a subroutine, IF c THEN GOTO restart does not work: put GOTO restart on its own line inside a block IF, as above.
  • A GOTO in the code after LOOP. It fails when the program runs.
x = 1
IF x = 1 THEN GOTO skipOne
Console("1")
skipOne:
IF x = 1 THEN GOTO skipTwo
Console("2")
skipTwo:
Console("end")

Prefer loops, EXIT and CONTINUE. GOTO exists so that classic programs run, and it is the construct with the most limits.

Coming from another BASIC#

You may writeIn aukimi
EXIT FOR, EXIT WHILE, EXIT DO, BreakEXIT
Continue (PureBasic)CONTINUE, the same word
CASE 1 TO 5, CASE IS > 5IF … ELSEIF
CASE ELSE, DefaultCASE DEFAULT
DO WHILE …, LOOP UNTIL …WHILE … ENDWHILE, REPEAT … UNTIL
Wendaccepted
ON n GOSUB a, bSELECT n with one GOSUB per CASE
IF x THEN IF y THEN …IF x AND y THEN …
RETURN in the middle of a subroutineIF … ENDIF around the rest