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.
| Construct | Opens with | Closes with |
|---|---|---|
| One-line IF | IF … THEN … | the end of the line |
| Block IF | IF … | ENDIF or END IF |
| SELECT | SELECT … | ENDSELECT or END SELECT |
| FOR | FOR … TO … | NEXT |
| FOREACH | FOREACH list() | NEXT, see Collections |
| WHILE | WHILE … | ENDWHILE, END WHILE or WEND |
| REPEAT | REPEAT | UNTIL … |
| DO | DO | LOOP, 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
THENis required on this form. It marks where the condition ends. Without it, the line opens a block IF and the compiler asks forENDIF.- Statements joined by
:afterTHENall belong to the THEN branch. AfterELSE, they all belong to the ELSE branch. - In a condition,
=compares. It never assigns.AND,ORandNOTare 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") ' bothBlock 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
| Part | Accepted spellings |
|---|---|
| Opening | IF <condition> or IF <condition> THEN, with nothing after THEN |
| Middle branch | ELSEIF or ELSE IF, with or without THEN |
| Last branch | ELSE |
| Closing | ENDIF or END IF |
- The first true branch runs. The others are skipped.
THENis optional on the block form. AGK programs usually leave it out, and they compile as they are.ELSEcomes once, and last.ELSEIFafterELSEis a compile error, and so is a secondELSE.- A line
IF … THENwith something afterTHENis a one-line IF. With nothing afterTHEN, 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
| Part | Accepted spellings |
|---|---|
| Opening | SELECT <expression> or SELECT CASE <expression> |
| Default branch | CASE DEFAULT or DEFAULT CASE |
| End of a branch (optional) | ENDCASE or END CASE |
| Closing | ENDSELECT or END SELECT |
One branch runs, and only one. There is no fall-through: an empty
CASEdoes 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 matchCASE 5.2.0matchesCASE 2, because they are the same number. - A
CASEvalue is an expression: a literal, a variable,2 * 2. The values are tried in order. CASE DEFAULTruns when nothing else matched. It comes once, and last: aCASEafter it is a compile error.ENDCASEcloses a branch. It is optional: the nextCASEcloses it too.EXITinside a SELECT leaves the loop around it, not only the SELECT. See EXIT and CONTINUE.
Not supported, and what to write instead:
| You may know | In 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 1runs nothing: the step is+1and 5 is already past 1. STEP 0stops 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.25gives 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.
NEXTalone closes the innermost FOR.NEXT imust 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 end | end + step (FOR i = 1 TO 5 leaves 6) |
was left by EXIT | its value at the EXIT |
| never ran | the 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
LOOPDO … 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.
EXITleaves the loop. The program continues after itsNEXT,ENDWHILE,UNTILorLOOP.CONTINUEstarts 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,
ENDstops the loop and everything after it. - In a GOSUB subroutine,
ENDstops 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,
ENDis a compile error. Leave the function withEXITFUNCTION, see Functions. ENDis not an error, so aCATCHdoes 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,GOTOandSTARTTASK. Inside aDATASECTION, it is the target ofRESTORE, 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 theGOSUB. - 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
ENDbefore them does no harm. TheENDabove is still good style. GOSUBto 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
ENDon 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,
RETURNleaves the function. See Functions. ON … GOSUBandON … GOTOare not supported. Use SELECT with oneGOSUBper 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:
| Jump | Allowed? |
|---|---|
| backward, to a label at the same block level | yes: it repeats the code in between |
| forward, to a label later in the same block or in an enclosing block | yes: it skips the code in between, and can leave loops |
| from a subroutine, back to a label of the main program | yes: the program restarts from that label (see below) |
| into a block (an IF, a FOR, a WHILE…) from outside it | no: "GOTO … jumps across block boundaries" |
| backward, from inside a block to a label outside it | no: 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 bwhen both labels come later. Use IF … ELSE instead. - A restart jump on one line. In a subroutine,
IF c THEN GOTO restartdoes not work: putGOTO restarton 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,
EXITandCONTINUE.GOTOexists so that classic programs run, and it is the construct with the most limits.
Coming from another BASIC#
| You may write | In aukimi |
|---|---|
EXIT FOR, EXIT WHILE, EXIT DO, Break | EXIT |
Continue (PureBasic) | CONTINUE, the same word |
CASE 1 TO 5, CASE IS > 5 | IF … ELSEIF |
CASE ELSE, Default | CASE DEFAULT |
DO WHILE …, LOOP UNTIL … | WHILE … ENDWHILE, REPEAT … UNTIL |
Wend | accepted |
ON n GOSUB a, b | SELECT n with one GOSUB per CASE |
IF x THEN IF y THEN … | IF x AND y THEN … |
RETURN in the middle of a subroutine | IF … ENDIF around the rest |