Errors
Catch, throw and clean up with TRY, CATCH and FINALLY, decide what a failing command does with ErrorMode, and read the errors the editor reports.
In one line#
A program meets two kinds of errors. The compiler finds the first kind before
anything runs. The second kind happens while the game runs, and stops it unless
you catch it with TRY … CATCH. Catalogue commands have one more option:
ErrorMode decides whether their failures stop, report or stay silent.
Two kinds of errors#
| Compile error | Runtime error | |
|---|---|---|
| When | before the program starts | while it runs |
| Examples | a misspelled command, ENDIF without IF, RETHROW outside a CATCH | a command that fails, THROW, a DIV 0, a released handle |
| What happens | every error is listed, and nothing runs | the program stops at that line, unless a TRY catches it |
| Can you catch it | no, fix the source | yes, with TRY / CATCH |
What the console shows#
Both kinds go to the editor's console, on three lines at most:
BASIC · Compilation · <file> · Line <n>, column 1
SyntaxError: <message>
BASIC · Execution · <file> · Line <n>, column <c>
<name>: <message>
Command: <the failing call>
- The file is the script, or the module where the line lives when the
program is split with
#INCLUDE. See The preprocessor. - The
Command:line appears when a catalogue command failed. It shows the call with the values it received, for exampleMemblockByte(999, 0). - Double-click an error line to open the script at that line.
A command failure looks like this:
RangeError: BASIC MemblockByte(999, 0) failed: Classic BASIC memory block 999 does not exist. Create or load the referenced resource before running this command.
Command: MemblockByte(999, 0)
⚠️ A JavaScript message in the Compilation phase means a compiler gap. "Unexpected identifier", "missing ) after argument list", "await is only valid in async functions" or "Identifier … has already been declared" come from a form the compiler accepted when it should have refused it. The chapters mark those forms with ⚠️, and give the spelling that works.
TRY, CATCH, FINALLY#
TRY
<statements>
[CATCH [<name>[$]]
<statements>]
[FINALLY
<statements>]
ENDTRY
TRY
THROW "out of ammo"
CATCH problem$
Console("caught: " + problem$)
FINALLY
Console("cleanup")
ENDTRY
Console("after: " + problem$)
The program prints caught: out of ammo, then cleanup, then
after: out of ammo.
CATCHruns only if something in theTRYpart failed.FINALLYalways runs: after a normal end, after an error, afterEXITFUNCTIONorRETURN, afterENDand after aGOTOout of the block.CATCHandFINALLYare each optional, but aTRYneeds at least one: "TRY requires CATCH or FINALLY".- The order is fixed:
TRY, thenCATCH, thenFINALLY. OneCATCHat most: there is noCATCHper error type. Inside theCATCH, test the error'snameorcodeinstead. ENDTRYandEND TRYare the same.- A
TRYworks anywhere: in the main program, in the frame loop, in aFUNCTION, in a lifecycle function such asupdate.
The compile errors of this block:
| Message | Cause |
|---|---|
CATCH without TRY, FINALLY without TRY, ENDTRY without TRY | the opening TRY is missing |
Duplicate CATCH in TRY, Duplicate FINALLY in TRY | a second clause of the same kind |
CATCH cannot follow FINALLY | wrong order |
TRY requires ENDTRY | the block is never closed |
The catch variable#
The name after CATCH receives the error. Its sigil decides what you get.
| You write | The variable holds |
|---|---|
CATCH problem$ | the message, as a string |
CATCH problem | a read-only record with four fields |
CATCH | nothing: the error is caught and dropped |
| Field | Content |
|---|---|
problem.name | the kind of error: Error for a THROW, RangeError, TypeError… for the others |
problem.message | the message |
problem.code | the code given to THROW, or "" |
problem.command | the failing command call, such as MemblockByte(999, 0), or "" |
TRY
value = MemblockByte(999, 0)
CATCH failure
Console(failure.name) ' RangeError
Console(failure.command) ' MemblockByte(999, 0)
Console(failure.message) ' BASIC MemblockByte(999, 0) failed: Classic BASIC memory block 999 does not exist. …
ENDTRY
The variable stays readable after ENDTRY, as the first example shows.
Inside a FUNCTION, it belongs to the function.
⚠️ Give the catch variable a name of its own. If the same name is declared
or assigned anywhere else in the same scope, before or after the TRY, the
compiler accepts it but the program cannot start ("Identifier … has already
been declared"). To keep the message, copy it into another variable:
report$ = "last error: " + problem$.
OPTION EXPLICIT
GLOBAL message$ = ""
TRY
THROW "x"
CATCH message$
ENDTRYTHROW#
THROW <value>
THROW raises an error that a CATCH can receive. What you throw decides the
fields:
| You throw | message | code |
|---|---|---|
a string: THROW "save failed" | the string | "" |
a number: THROW 404 | the number as text, 404 | "" |
a record with a message field, and optionally a code field | the field message | the field code |
TYPE Failure
message AS string
code AS integer
ENDTYPE
f AS Failure
f.message = "save failed"
f.code = 7
TRY
THROW f
CATCH err
Console(err.message + " / " + Str(err.code)) ' save failed / 7
ENDTRY
THROWwithout a value is a compile error: "THROW requires a value".- A thrown error leaves every function on the way until a
CATCHtakes it, so a function can throw and its caller can catch. nameis alwaysErrorfor aTHROW.
OPTION EXPLICIT
GLOBAL attempts AS integer = 0
GLOBAL connected AS integer = 0
FUNCTION Connect()
attempts = attempts + 1
IF attempts < 3 THEN THROW "timeout"
ENDFUNCTION
WHILE connected = 0
TRY
Connect()
connected = 1
CATCH problem$
Console("attempt " + Str(attempts) + ": " + problem$)
ENDTRY
ENDWHILE
Console("connected after " + Str(attempts)) ' connected after 3
⚠️ Do not THROW the error you caught. THROW failure builds a new error
from its message and code: the name becomes Error and the command is lost.
To pass the same error on, use RETHROW.
RETHROW and nested blocks#
RETHROW
RETHROW sends the error being handled on to the next TRY outside, unchanged.
TRY
TRY
THROW "disk full"
CATCH inner$
Console("inner: " + inner$)
RETHROW
FINALLY
Console("inner finally")
ENDTRY
CATCH outer
Console("outer: " + outer.message)
ENDTRY
The program prints inner: disk full, then inner finally, then
outer: disk full. The inner FINALLY runs before the outer CATCH.
RETHROWis only allowed inside aCATCH(or inside a block nested in aCATCH). Anywhere else: "RETHROW requires an active CATCH block".- It takes no value: "RETHROW does not accept a value".
TRYblocks nest to any depth. An error goes to the nearest enclosingCATCH.
END and GOTO pass through#
END and GOTO are not errors, and a CATCH never sees them. FINALLY still
runs on the way out.
TRY
Console("before")
END
CATCH e
Console("caught END")
FINALLY
Console("finally runs")
ENDTRY
Console("never")
The program prints before, then finally runs, and stops. A GOTO to a
label outside the block behaves the same: FINALLY runs, then the jump
happens.
When a command fails: ErrorMode#
A catalogue command can fail: a missing file, an id that does not exist, a
value out of range. ErrorMode decides what happens then.
ErrorMode(<mode>)
ErrorMode()
| Mode | Value | What a failing command does |
|---|---|---|
#AK_ERROR_STOP | 2, the default | stops the program, or goes to the CATCH of the enclosing TRY |
#AK_ERROR_REPORT | 1 | writes the error to the console, returns a neutral value, and the program goes on |
#AK_ERROR_IGNORE | 0 | returns a neutral value in silence, and the program goes on |
The neutral value is 0 for a command that returns a number, "" for one that
returns a string, nothing otherwise.
ErrorMode(#AK_ERROR_IGNORE)
value = MemblockByte(999, 0)
Console(Str(value)) ' 0
IF ErrorOccurred() THEN Console("failed: " + LastError()) ' failed: Classic BASIC memory block 999 does not exist
Console(Str(ErrorMode())) ' 0
ErrorMode()without an argument returns the current mode.- The mode stays in force until the next
ErrorMode(...). - Any other value is an error: "Classic BASIC error mode must be #AK_ERROR_IGNORE, #AK_ERROR_REPORT, or #AK_ERROR_STOP".
ErrorModecovers catalogue commands, and nothing else. ATHROW, a division by zero withDIV, a released handle or a command called with the wrong number of arguments still raise an error in every mode.
ErrorMode(#AK_ERROR_IGNORE)
a = 1
b = 0
TRY
c = a DIV b
CATCH e$
Console("caught: " + e$) ' caught: BASIC integer division by zero
ENDTRY
A few commands finish their work after they return: the share sheet, the file
chooser, opening a tab. If they fail, it is too late to stop the program. They
set LastError and ErrorOccurred, and write to the console unless the mode is
#AK_ERROR_IGNORE.
LastError and ErrorOccurred#
| Command | Returns |
|---|---|
LastError() | the message of the last command failure, without the BASIC …() failed: prefix and the hint. It stays until the next failure. |
ErrorOccurred() | 1 if a command failed since the last call, then resets to 0 |
Both are set in every mode, #AK_ERROR_STOP included. Inside a CATCH you can
read them too.
TRY
value = MemblockByte(999, 0)
CATCH
ENDTRY
Console(Str(ErrorOccurred())) ' 1
Console(Str(ErrorOccurred())) ' 0
Console(LastError()) ' Classic BASIC memory block 999 does not exist
⚠️ Read ErrorOccurred() once. The second call already answers 0. Store
the result if you need it twice.
Runtime errors the language raises#
Besides commands and THROW, a few operations raise an error by themselves:
| Operation | Message |
|---|---|
a DIV 0 | BASIC integer division by zero |
storing 1 / 0 in an AS integer variable | BASIC integer requires a finite value |
FOR i = 1 TO 5 STEP 0 | BASIC FOR STEP cannot be zero |
GetRef or SetRef on a released handle | BASIC reference handle has been released |
| a command called with a number of arguments it does not accept | BASIC classique command <name> received <n> argument(s); expected … |
1 / 0 on an untyped variable does not raise an error: it gives Infinity.
Where an uncaught error goes#
| Program | What an uncaught runtime error does |
|---|---|
a straight program, or a DO … LOOP | stops the program at that line |
a lifecycle function (update, onCollisionEnter…) | reports the error. The Engine calls the function again on the next frame or event, so the error repeats |
See Functions for the lifecycle functions, and The game loop and tasks for errors inside a task.
Compared with classic dialects#
| Dialect | What it has | In aukimi |
|---|---|---|
| AGK | SetErrorMode with 0, 1, 2; GetLastError, GetErrorOccurred | same modes and values, without the verb: ErrorMode(...), LastError(), ErrorOccurred() |
| DarkBASIC Pro | no structured errors | TRY / CATCH / FINALLY |
| PureBasic | OnErrorCall, OnErrorGoto | TRY / CATCH, one handler per block instead of one per program |
| Visual Basic | On Error Resume Next, On Error GoTo | ErrorMode(#AK_ERROR_IGNORE) for commands, TRY for the rest |