Les erreurs

Attraper, lever et nettoyer avec TRY, CATCH et FINALLY, choisir ce que fait une commande qui échoue avec ErrorMode, et lire les erreurs que signale l'éditeur.

En un mot#

Un programme rencontre deux sortes d'erreurs. Le compilateur trouve la première avant que rien ne tourne. La seconde survient pendant que le jeu tourne, et l'arrête sauf si vous l'attrapez avec TRY … CATCH. Les commandes du catalogue ont une option de plus : ErrorMode décide si leurs échecs arrêtent, signalent ou restent muets.

Deux sortes d'erreurs#

Erreur de compilationErreur d'exécution
Quandavant que le programme démarrependant qu'il tourne
Exemplesune commande mal orthographiée, ENDIF sans IF, RETHROW hors d'un CATCHune commande qui échoue, THROW, a DIV 0, une poignée libérée
Ce qui se passechaque erreur est listée, et rien ne tournele programme s'arrête à cette ligne, sauf si un TRY l'attrape
Peut-on l'attrapernon, corrigez la sourceoui, avec TRY / CATCH

Ce qu'affiche la console#

Les deux sortes vont dans la console de l'éditeur, sur trois lignes au plus :

BASIC · Compilation · <fichier> · Ligne <n>, colonne 1
SyntaxError: <message>

BASIC · Exécution · <fichier> · Ligne <n>, colonne <c>
<nom>: <message>
Commande : <l'appel qui a échoué>
  • Le fichier est le script, ou le module où se trouve la ligne quand le programme est découpé avec #INCLUDE. Voir Le préprocesseur.
  • La ligne Commande : apparaît quand une commande du catalogue a échoué. Elle montre l'appel avec les valeurs reçues, par exemple MemblockByte(999, 0).
  • Double-cliquez une ligne d'erreur pour ouvrir le script à cette ligne.

L'échec d'une commande ressemble à ceci :

RangeError: BASIC MemblockByte(999, 0) failed: Classic BASIC memory block 999 does not exist. Create or load the referenced resource before running this command.
Commande : MemblockByte(999, 0)

⚠️ Un message JavaScript en phase Compilation signale une faille du compilateur. « Unexpected identifier », « missing ) after argument list », « await is only valid in async functions » ou « Identifier … has already been declared » viennent d'une forme que le compilateur a acceptée alors qu'il aurait dû la refuser. Les chapitres signalent ces formes d'un ⚠️ et donnent la graphie qui marche.

TRY, CATCH, FINALLY#

TRY
  <statements>
[CATCH [<name>[$]]
  <statements>]
[FINALLY
  <statements>]
ENDTRY
TRY
  THROW "plus de munitions"
CATCH problem$
  Console("attrapé : " + problem$)
FINALLY
  Console("nettoyage")
ENDTRY
Console("après : " + problem$)

Le programme affiche attrapé : plus de munitions, puis nettoyage, puis après : plus de munitions.

  • CATCH ne s'exécute que si quelque chose a échoué dans la partie TRY.
  • FINALLY s'exécute toujours : après une fin normale, après une erreur, après EXITFUNCTION ou RETURN, après END et après un GOTO qui sort du bloc.
  • CATCH et FINALLY sont chacun optionnels, mais un TRY en demande au moins un : « TRY requires CATCH or FINALLY ».
  • L'ordre est fixe : TRY, puis CATCH, puis FINALLY. Un seul CATCH au plus : il n'y a pas de CATCH par type d'erreur. Dans le CATCH, testez plutôt le name ou le code de l'erreur.
  • ENDTRY et END TRY sont la même chose.
  • Un TRY marche partout : dans le programme principal, dans la boucle d'images, dans une FUNCTION, dans une fonction du cycle de vie comme update.

Les erreurs de compilation de ce bloc :

MessageCause
CATCH without TRY, FINALLY without TRY, ENDTRY without TRYle TRY d'ouverture manque
Duplicate CATCH in TRY, Duplicate FINALLY in TRYune seconde clause du même genre
CATCH cannot follow FINALLYmauvais ordre
TRY requires ENDTRYle bloc n'est jamais fermé

La variable du CATCH#

Le nom placé après CATCH reçoit l'erreur. Son sigil décide de ce que vous obtenez.

Vous écrivezLa variable contient
CATCH problem$le message, sous forme de chaîne
CATCH problemun enregistrement en lecture seule, à quatre champs
CATCHrien : l'erreur est attrapée puis oubliée
ChampContenu
problem.namela sorte d'erreur : Error pour un THROW, RangeError, TypeError… pour les autres
problem.messagele message
problem.codele code donné à THROW, ou ""
problem.commandl'appel de commande qui a échoué, comme MemblockByte(999, 0), ou ""
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

La variable reste lisible après ENDTRY, comme le montre le premier exemple. Dans une FUNCTION, elle appartient à la fonction.

⚠️ Donnez à la variable du CATCH un nom bien à elle. Si le même nom est déclaré ou affecté ailleurs dans la même portée, avant ou après le TRY, le compilateur l'accepte mais le programme ne peut pas démarrer (« Identifier … has already been declared »). Pour garder le message, copiez-le dans une autre variable : report$ = "dernière erreur : " + problem$.

OPTION EXPLICIT
GLOBAL message$ = ""
TRY
  THROW "x"
CATCH message$
ENDTRY

THROW#

THROW <value>

THROW lève une erreur qu'un CATCH peut recevoir. Ce que vous levez décide des champs :

Vous levezmessagecode
une chaîne : THROW "échec de la sauvegarde"la chaîne""
un nombre : THROW 404le nombre en texte, 404""
un enregistrement avec un champ message, et éventuellement un champ codele champ messagele champ code
TYPE Failure
  message AS string
  code AS integer
ENDTYPE

f AS Failure
f.message = "échec de la sauvegarde"
f.code = 7

TRY
  THROW f
CATCH err
  Console(err.message + " / " + Str(err.code))   ' échec de la sauvegarde / 7
ENDTRY
  • THROW sans valeur est une erreur de compilation : « THROW requires a value ».
  • Une erreur levée quitte toutes les fonctions sur son chemin jusqu'à ce qu'un CATCH la prenne : une fonction peut donc lever, et son appelant attraper.
  • name vaut toujours Error pour un THROW.
OPTION EXPLICIT
GLOBAL attempts AS integer = 0
GLOBAL connected AS integer = 0

FUNCTION Connect()
  attempts = attempts + 1
  IF attempts < 3 THEN THROW "délai dépassé"
ENDFUNCTION

WHILE connected = 0
  TRY
    Connect()
    connected = 1
  CATCH problem$
    Console("essai " + Str(attempts) + " : " + problem$)
  ENDTRY
ENDWHILE
Console("connecté après " + Str(attempts))   ' connecté après 3

⚠️ Ne relancez pas avec THROW l'erreur que vous avez attrapée. THROW failure fabrique une nouvelle erreur à partir de son message et de son code : le name devient Error et la command est perdue. Pour transmettre la même erreur, utilisez RETHROW.

RETHROW et les blocs imbriqués#

RETHROW

RETHROW transmet l'erreur en cours de traitement au TRY extérieur suivant, sans la changer.

TRY
  TRY
    THROW "disque plein"
  CATCH inner$
    Console("intérieur : " + inner$)
    RETHROW
  FINALLY
    Console("finally intérieur")
  ENDTRY
CATCH outer
  Console("extérieur : " + outer.message)
ENDTRY

Le programme affiche intérieur : disque plein, puis finally intérieur, puis extérieur : disque plein. Le FINALLY intérieur s'exécute avant le CATCH extérieur.

  • RETHROW n'est permis que dans un CATCH (ou dans un bloc imbriqué dans un CATCH). Ailleurs : « RETHROW requires an active CATCH block ».
  • Il ne prend pas de valeur : « RETHROW does not accept a value ».
  • Les blocs TRY s'imbriquent à n'importe quelle profondeur. Une erreur va au CATCH englobant le plus proche.

END et GOTO passent au travers#

END et GOTO ne sont pas des erreurs, et un CATCH ne les voit jamais. FINALLY s'exécute quand même au passage.

TRY
  Console("avant")
  END
CATCH e
  Console("END attrapé")
FINALLY
  Console("finally s'exécute")
ENDTRY
Console("jamais")

Le programme affiche avant, puis finally s'exécute, et s'arrête. Un GOTO vers une étiquette hors du bloc se comporte de même : FINALLY s'exécute, puis le saut a lieu.

Quand une commande échoue : ErrorMode#

Une commande du catalogue peut échouer : un fichier absent, un identifiant qui n'existe pas, une valeur hors limites. ErrorMode décide de la suite.

ErrorMode(<mode>)
ErrorMode()
ModeValeurCe que fait une commande qui échoue
#AK_ERROR_STOP2, la valeur par défautarrête le programme, ou passe au CATCH du TRY englobant
#AK_ERROR_REPORT1écrit l'erreur dans la console, rend une valeur neutre, et le programme continue
#AK_ERROR_IGNORE0rend une valeur neutre sans rien dire, et le programme continue

La valeur neutre est 0 pour une commande qui rend un nombre, "" pour une commande qui rend une chaîne, rien sinon.

ErrorMode(#AK_ERROR_IGNORE)
value = MemblockByte(999, 0)
Console(Str(value))                                    ' 0
IF ErrorOccurred() THEN Console("échec : " + LastError())   ' échec : Classic BASIC memory block 999 does not exist
Console(Str(ErrorMode()))                              ' 0
  • ErrorMode() sans argument rend le mode en cours.
  • Le mode reste en vigueur jusqu'au prochain ErrorMode(...).
  • Toute autre valeur est une erreur : « Classic BASIC error mode must be #AK_ERROR_IGNORE, #AK_ERROR_REPORT, or #AK_ERROR_STOP ».

ErrorMode couvre les commandes du catalogue, et rien d'autre. Un THROW, une division par zéro avec DIV, une poignée libérée ou une commande appelée avec un mauvais nombre d'arguments lèvent une erreur dans tous les modes.

ErrorMode(#AK_ERROR_IGNORE)
a = 1
b = 0
TRY
  c = a DIV b
CATCH e$
  Console("attrapé : " + e$)   ' attrapé : BASIC integer division by zero
ENDTRY

Quelques commandes finissent leur travail après avoir rendu la main : la feuille de partage, le sélecteur de fichier, l'ouverture d'un onglet. Si elles échouent, il est trop tard pour arrêter le programme. Elles renseignent LastError et ErrorOccurred, et écrivent dans la console sauf en mode #AK_ERROR_IGNORE.

LastError et ErrorOccurred#

CommandeRend
LastError()le message du dernier échec de commande, sans le préfixe BASIC …() failed: ni le conseil. Il reste jusqu'au prochain échec.
ErrorOccurred()1 si une commande a échoué depuis l'appel précédent, puis revient à 0

Les deux sont renseignés dans tous les modes, #AK_ERROR_STOP compris. Vous pouvez aussi les lire dans un CATCH.

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

⚠️ Lisez ErrorOccurred() une seule fois. Le deuxième appel répond déjà 0. Rangez le résultat si vous en avez besoin deux fois.

Les erreurs d'exécution que lève le langage#

En plus des commandes et de THROW, quelques opérations lèvent une erreur d'elles-mêmes :

OpérationMessage
a DIV 0BASIC integer division by zero
ranger 1 / 0 dans une variable AS integerBASIC integer requires a finite value
FOR i = 1 TO 5 STEP 0BASIC FOR STEP cannot be zero
GetRef ou SetRef sur une poignée libéréeBASIC reference handle has been released
une commande appelée avec un nombre d'arguments qu'elle n'accepte pasBASIC classique command <name> received <n> argument(s); expected …

1 / 0 sur une variable sans type ne lève pas d'erreur : cela donne Infinity.

Où va une erreur non attrapée#

ProgrammeCe que fait une erreur d'exécution non attrapée
un programme linéaire, ou un DO … LOOParrête le programme à cette ligne
une fonction du cycle de vie (update, onCollisionEnter…)signale l'erreur. L'Engine rappelle la fonction à l'image ou à l'événement suivant, donc l'erreur se répète

Voir Les fonctions pour les fonctions du cycle de vie, et La boucle de jeu et les tâches pour les erreurs dans une tâche.

Comparé aux dialectes classiques#

DialecteCe qu'il aDans aukimi
AGKSetErrorMode avec 0, 1, 2 ; GetLastError, GetErrorOccurredmêmes modes et mêmes valeurs, sans le verbe : ErrorMode(...), LastError(), ErrorOccurred()
DarkBASIC Propas d'erreurs structuréesTRY / CATCH / FINALLY
PureBasicOnErrorCall, OnErrorGotoTRY / CATCH, un gestionnaire par bloc au lieu d'un par programme
Visual BasicOn Error Resume Next, On Error GoToErrorMode(#AK_ERROR_IGNORE) pour les commandes, TRY pour le reste