Tcl n’a rien à envier aux langages évolués proposant des méthodes de traitement des exceptions, tels Delphi, pascal ou même Java. En effet Tcl permet par le biais de la fonction catch de récupérer toute erreur, et par les options de la commande return de lever des exceptions agrémentées de texte.
Dans le cas normal, une fonction retourne une valeur donnée par return une_valeur qui rend la main au bloc ayant appelé la fonction.
Son code de terminaison est TCL_OK dans ce cas, et l’interpréteur continue l’exécution de façon normale.
Il est possible de modifier le comportement de l’interpréteur en modifiant le code de terminaison d’une fonction par return -code erreur une_chaîne_décrivant_l’erreur.
De cette manière, l’exception TCL_ERROR remonte la pile d’appel jusqu’au premier bloc appelant ayant prévu de traiter les exceptions.
Si aucun bloc appelant ne contenait de catch, c’est interpréteur lui-même qui traite l’erreur.
Dans le cas de l’interpréteur wish, une fenêtre est affichée contenant le texte de une_chaîne_décrivant_l’erreur, un bouton ok, un bouton skip message, et un bouton stack trace permettant de visualiser la pile d’appel (en source Tcl) que l’exception a remontée.
Par contre, si l’exception rencontre un catch, celle-ci n’est plus levée et la fonction catch renvoie un résultat non nul.
Par exemple :
Si l'on veut écrire une fonction ouvrant un fichier en lecture, et en lisant 1 chiffre sur la première ligne qui indique le nombre de lignes du fichier à afficher, il est très laborieux d’écrire une fonction qui testera si le fichier existe, si le programme a les privilèges
suffisants pour le lire, si la première ligne existe, si elle contient un chiffre, si le fichier contient un nombre de ligne supérieur au chiffre lu en premier. Ce cas est typique d’une fonction contenant un traite-exceptions:
proc AfficheContenu {NomFichier} { if {[catch { #Récupération d’une éventuelle erreur set F [open $NomFichier r] # open sert à ouvrir un fichier et renvoie son descripteur gets $F NbLigneALire # gets lit dans un fichier for {set i 1} {$i<=$NbLigneALire} {incr i 1} { gets $F ligne puts $ligne } close $F } Erreurs]!=0} { #Si catch renvoie un résultat non nul il y a une erreur puts $Erreurs #dont le texte explicatif est dans Erreurs }
Il est également possible de définir ses propres exceptions.
Prenons l’exemple d’un petit interpréteur, sensible aux majuscules mais « intelligent », écrit en Tcl, qui pourra traiter un fichier mot par mot, et réagir suivant la syntaxe et la sémantique définie pour le langage.
On écrira sans doute une fonction qui prend un mot en argument, change l’état de l’automate du langage en fonction du contexte et exécute des commandes Tcl avant de rendre la main à la fonction appelante qui lui passera le mot suivant, et ainsi de suite jusqu’à trouver une erreur ou jusqu’à la fin du fichier à traiter.
Cet interpréteur possédera une fenêtre dans laquelle seront affichés les éventuels avertissements et erreurs détectés durant l’interprétation du fichier mais qui laissera remonter les erreurs liées aux commandes appelées par l’utilisateur.
Voici à quoi pourra ressembler la fonction traitant les entrées mot par mot:
proc traite_mot {mot} { global EtatActuel Automate Actions if {[info exists Automate($EtatActuel,$mot)]} { #Le mot était attendu dans le contexte set EtatActuel $Automate($EtatActuel,$mot) #Changement d’état eval $Actions($EtatActuel) #Actions associées } else { if {[info exists Automate($EtatActuel,[string tolower $mot])]} { set EtatActuel $Automate($EtatActuel, [string tolower $mot]) #Il n'y a peut-être qu'une erreur de casse eval $Actions($EtatActuel) #Actions associées return -code 1 "Assuming $mot is [string tolower $mot]" #on lève une exception qui sera considérée comme un warning } else { return -code 2 "Unexpected $mot" #Sinon on lève une exception qui sera considérée comme une erreur } }
La fonction appelante passera chaque mot du fichier à la fonction traite_mot en ne récupérant que les exceptions de type 1 et 2 qu'elle considérera respectivement comme des warnings et des erreurs.
proc Interprete {NomFic} { ... #Création d'une fenêtre de texte dans laquelle seront affichés les éventuels warnings et erreurs set F [open $NomFic r] while {[eof $F]==0} { #Tant qu'il reste des lignes à lire gets $F ligne foreach mot $ligne { set Resultat [catch {traite_mot $mot} Erreurs] switch $Resultat { 1 {#Affichage de Warning:$Erreurs dans la fenêtre de texte} 2 { #Affichage de Erreur:$Erreurs dans la fenêtre de texte close $F return 1 } default { close $F return -code $Resultat $Erreurs } #Les autres exceptions ne sont pas stoppées à ce niveau } } } close $F }
Il existe une autre méthode de récupérer les erreurs, non pas pour faire un traitement spécial suivant l’erreur levée comme pour les exceptions, mais tout simplement pour éviter qu’une erreur non récupérée puisse s’afficher et être vue par l’utilisateur.
En effet Tcl/Tk appelle une procédure nommée bgerror lorsqu’une erreur arrive au niveau de l’interpréteur. Ceci se traduit par l’apparition d’une boîte de dialogue contenant la trace du programme lorsque l’on se trouve sous wish. Cela peut être gênant lorsque l’application est développée, d’autant plus que la plupart des erreurs qui peuvent advenir lorsque le programme est débuggé sont des erreurs d’interface : elles sont souvent dûes à des erreurs internes de déroulement de menu.
Pour éviter ce genre de désagrément, il suffit de créer une nouvelle procédure bgerror qui ne fait rien ou bien qui place l’erreur dans un fichier quelconque. C’est un des avantages des langages interprétés.
Cependant, il peut être intéressant de conserver le code de ces fonctions et de pouvoir les appeler dans certains cas. Pour cela, il suffit d’utiliser la commande :
proc bgerror {args} { puts "Je fais ce que je veux avec les erreurs en background" }