Programmation et simulation en Tcl

 

Auteur : David Cobac

e-mail : dcobac@free.fr

Date : 15 décembre 2001

Révision : 19 août 2002

Version : 0.2.1


1 Introduction

Tcl est l'acronyme de « Tool Command Language ». Tcl est un langage interprété portable sur de nombreuses plateformes. Tk est un "Tool Kit" permettant d'élaborer facilement des interfaces graphiques. L'ensemble Tcl/Tk forme un langage facile à mettre en œuvre.


2 Débutants

Vous n'avez jamais programmé, même votre calculatrice, vous connaissez à peine les termes informatiques, ce n'est pas grave, Tcl ne nécessite pas de connaissance préalable. Pas de prise en main de logiciel ou d'icônes bizarres. Il s'agit bien de programmation et non d'utilisation de logiciel : c'est vous qui créez le logiciel. En langage Tcl, on appelle cela un script. Tout ce que nous allons écrire sur l'ordinateur en langage Tcl s'appelle du code. Nous taperons ce code à l'aide du clavier dans un éditeur de textes : c'est-à-dire une sorte de bloc-note sans mise en forme de texte (nous n'utiliserons pas MS-WORD ou WordPro) comme notepad, Ased ou editt qui est spécialisé dans l'édition de scripts Tcl. Tous les scripts seront enregistrés avec l'extension tcl : ainsi un fichier monscript sera sauvegardé sous le nom monscript.tcl de telle manière que Windows le reconnaisse comme un script Tcl. Pour interpréter votre script, il faut taper la commande MS-DOS : tclsh monscript.tcl et là...soit tout va bien soit les messages d'erreur apparaissent. Pour apprendre le langage, rien de mieux que de pouvoir tester les commandes une à une, l'application (écrite en Tcl/Tk) tkcon permet de passer des commandes dans une console ainsi que de charger toutes les librairies correctement installées sur le système, remarquons d'ailleurs que cette application remplace très avantageusement la console MS-DOS... Notons aussi qu'il existe un excellent logiciel écrit lui aussi en Tcl/Tk : TclTutor qui permet d'apprendre le langage, il s'agit d'un logiciel anglophone dont on peut trouver un lien sur http ://mini.net/tcl/1681.html

3 Utiliser editt

Développé par David Zolli à partir de Ased, il se révèle être un excellent outil de développement de scripts Tcl/Tk. editt permet la coloration syntaxique (coloration des mots-clés du langage permettant de mieux visualiser ce que l'on écrit) ainsi que d'autres fonctionnalités intéressantes. Voilà une copie d'écran qui permet de mieux comprendre :

Pour les scripts développés avec la librairie graphique Tk, le bouton fléché dans la barre d'outils prêt du bouton stop permet de tester directement le programme. Pour ceux développés en pur Tcl, le logiciel propose une console (voir section suivante) intégrée qui permet de lancer son programme. Le logiciel editt est téléchargeable gratuitement sur editt.

4 Applications graphiques ou non

Nous avons vu que si vous voulez faire fonctionner votre application il faut taper dans une console de commande MS-DOS (sous Windows) ou bash (par exemple sous linux) : tclsh monscript.tcl En fait tclsh est la commande qui demande l'interprétation du script ... si celui-ci ne comporte pas de partie graphique, pour un script avec une partie graphique, on lance la commande : wish monscript.tcl, la commande wish est la commande d'interprétation avec gestion de la librairie Tk.

5 Quelques règles communes de programmation

Il faut essayer de commenter le plus possible son programme (sans tomber sur la surcharge d'informations) cela permet à un lecteur extérieur de mieux comprendre ou même à l'auteur de pouvoir relire facilement le code qu'il a tapé des mois auparavant. En Tcl, les lignes de commentaires commencent par un #. Il faut espacer les commandes et indenter le code pour le rendre lisible et clair. En Tcl, on sépare deux commandes soit par un ; soit par un saut de ligne. Les indentations (touche de tabulation) sont considérées comme un espace. Exemple :

  >set varhexa 0xa;set var [format %i $varhexa]
  >for {set i 1} {$i<=$var} {incr i} {puts $var
  >puts "incrémentation $i"}

on préférera écrire pour plus de clarté :

  >set varhexa 0xa
  ># je transforme mon hexadécimal en base 10
  >set var [format %i $varhexa]
  >for {set i 1} {$i<=$var} {incr i} {
  >      puts $var
  >      puts "incrémentation $i"
  >}

6 Premières commandes

Pour information, tous les exemples de code du document suivent cette règle : les lignes de commandes commencent avec le symbole : >, les autres lignes sont des résultats à l'écran de ces commandes comme s'il s'agissait d'un programme et non de commande tapée dans une console telle que tkcon. Pour tester un programme nommé monprogramme.tcl dans la console tkcon, il suffit de se mettre dans le bon répertoire puis de taper la commande source monprogramme.tcl Toute ligne commençant par # est un commentaire (qui permet d'insérer des phrases d'explications dans le code) donc n'est pas interprété. Le séparateur de commandes est le « ; » ou le saut de ligne. Si une ligne de commande est trop longue, on peut sauter à la ligne sans que ce soit interprété comme une nouvelle commande en insérant avant le saut de ligne le caractère \. Ces dernières choses sont assez « classiques » dans un langage de programmation.

a) Que fait une commande ?

Une commande exécute avant tout une action que vous voulez effectuer, mais la plupart du temps renvoie une valeur qui parfois fait doublon avec l'action, mais qui peut être différente. C'est la notion même de procédure qui « agit » couplée à celle de fonction qui « retourne » un résultat.

b) Variables

Une variable peut être tout objet : un entier, un décimal, une chaîne de caractères, une liste, une liste de listes ou d'autres objets... Définition d'une variable : set nom valeur Afficher/écrire une valeur par défaut sur l'écran : puts chaine Accrocher une valeur à une variable : append nom valeur Substituer/évaluer la valeur des variables : subst nom Remplacement d'une commande par son résultat : [maCommande]

Deux commandes doivent être séparées par un point-virgule, néanmoins un passage à la ligne suffit.

  >set a 3
  >puts a
  a>puts $a;# pour récupérer la valeur de a on utilise $a
  3>set b bonjour
  >puts $b
  bonjour
  >append b utilisateur
  >puts $b
  bonjourutilisateur
  # ce n'est pas hélas le résultat attendu donc on teste la ligne suivante :
  >set c bonjour utilisateur
  wrong # args: should be "set varName ?newValue?"
  # l'interpréteur vient de rencontrer une erreur
  >set c {bonjour utilisateur}
  >puts $c
  bonjour utilisateur
  >set d c
  >puts $d
  c>puts $$d
  $c
  >puts [subst $$d]
  bonjour utilisateur
  # enfin !

c) Accolades ou guillemets

On a vu précédemment que les accolades permettaient de grouper une expression comme étant une entité unique, les guillemets permettent cela aussi à un détail très important près :

  >set debut {Une fonction majorée et minorée}
  >set fin {est une fonction bornée}
  >set phrase $debut $fin
  wrong # args: should be "set varName ?newValue?"
  # évidemment !
  >set phrase {$debut $fin}
  >puts $phrase
  $debut $fin
  # Hé oui ! les accolades ont empêchées l'évaluation de la valeur des variables.
  >set phrase "$debut $fin"
  Une fonction majorée et minorée est une fonction bornée
  # Ce qui est fait par contre avec les guillemets.

d) Crochets : illustration avec les listes

Nous avons vu au b) que les crochets permettent de remplacer (substituer) ce qui se situe à l'intérieur par le résultat de cette commande. C'est aussi l'occasion de parler des listes. Évaluer la longueur d'une liste : llength liste Extraire un élément d'une liste : lindex liste indice Extraire une partie précise de la liste : lrange liste premier_indice dernier_indice Ordonner de manière croissante une liste : lsort liste Créer une liste : list liste Ajouter un ou plusieurs éléments à une liste : lappend nom_liste élément(s) Faire un calcul numérique : expr calcul

  >set liste {Une fonction majorée et minorée est une fonction bornée}
  >set longueur [llength $liste]
  >puts $longueur
  9>puts [lindex $liste 0]
  Une
  # attention : les indices commencent à 0 !!!
  >puts [lindex $liste 5]
  est
  >puts [lindex $liste [expr $longueur-1]]
  bornée
  >set souschaine [lrange $liste 3 8]
  >puts [llength $souschaine]
  6# référence à 8-3=5 : nb d'étapes de 3 à 8
  # et 8-3+1=6 : nb d'éléments de 3 à 8
  >puts $souschaine
  et minorée est une fonction bornée
  >puts [lsort $souschaine]
  bornée est et fonction minorée une
  >puts "[lsort $souschaine]"
  bornée est et fonction minorée une
  >puts {[lsort $souschaine]}
  [lsort $souschaine]

e) Calculs mathématiques

Comme nous l'avons vu précédemment, il faut utiliser la commande expr pour effectuer tout calcul !. Les opérations mathématiques ont les notations usuelles, notons quand même que : sqrt(nombre) renvoie la racine carrée du nombre (SQuareRooT) pow(nombre,puissance) renvoie l'élévation du nombre à la puissance (POWer) rand() produit un nombre aléatoire dans l'intervalle [0;1[ abs(nombre) renvoie la valeur absolue du nombre exp(nombre) renvoie l'exponentielle du nombre log(nombre) renvoie la logarithme népérien (base e) du nombre log10(nombre) renvoie le logarithme décimal (base 10) du nombre floor(nombre) renvoie la partie entière du nombre int(nombre) renvoie une fausse partie entière : int(-0.5)=0 ! round(nombre) renvoie un arrondi à l'entier le plus proche. Le réglage de la précision est fixé par une variable interne tcl_precision dont on peut modifier la valeur.

  >set addition [expr 2+1]
  >puts "2+1 fait $addition"
  2+1 fait 3
  >puts [expr 2/3]
  0#la division de deux entiers est un entier (quelquepart ça peut se comprendre) !!!
  >puts [expr 2.0/3]
  0.666666666667
  # il faut retenir cette astuce dans les calculs !!! sous peine
  # d'obtenir des résultats faux.
  >puts [expr pow(sqrt(3),4)]
  9.0
  >puts [expr int(36.0/5)]
  7

Notons que la commande expr 36/5 aurait dans le dernier exemple donné le même résultat ! Pour finir, un petit détail très important : Tcl est un langage interprété donc relativement lent, tout calcul est susceptible de ralentir le script, pour remédier partiellement à ce problème, il convient de prendre l'habitude de mettre son calcul entre accolades qui permet une « pré-évaluation » du contenu ce qui améliore considérablement la rapidité. Ainsi, on notera :

  >puts [expr {pow(sqrt(3),4)}]
  9.0
  >puts [expr {int(36.0/5)}]
  7

Bien entendu, cette dernière remarque peut paraître étonnante sachant que tout contenu entre accolades ne se voit pas remplacé par sa valeur mais remarquons pour nous rassurer ce petit calcul :

  >set valeur [expr {pow(sqrt(3),4)}]
  9.0
  >puts [expr {int($valeur/5)}]
  1

Vous l'aurez compris, entre crochets, les accolades « ne comptent pas » !

f) Les tableaux

Les tableaux permettent de réunir dans un seul nom de variable plusieurs quantités/entités. Attention de ne pas les confondre avec une liste ! Les instructions de contrôle des tableaux existent mais dans la plupart du temps sont inutiles. Prenons une classe de 33 élèves que l'on numérote de 1 à 33, lors d'un contrôle, ils ont tous obtenus une note dans une échelle de 0 à 20.

  >set note(1) 12
  >set note(2) 8
  >set note(3) 7.4
  # on notera l'utilisation du symbole . comme séparateur décimal
  >set note(4) 18
  etc...
  >set note(33) 5

Nous venons de créer un tableau simple appelé note. Mais bien entendu, il y a d'autres devoirs à prendre en compte alors, en supposant qu'il s'agisse du ds2, nous pouvons plutôt créer ce même tableau de la manière suivante :

  >set note(ds2,1) 12
  >set note(ds2,2) 8
  >set note(ds2,3) 7.4
  >set note(ds2,4) 18
  etc...
  >set note(ds2,33) 5
  # exemple d'utilisation du tableau
  >puts $note(ds2,4)
  18
  >set monds ds2
  >set eleve 3
  >puts $note($monds,$eleve)
  7.4

Bien entendu, comme à toute variable, on peut attribuer à un élément du tableau n'importe quelle valeur : numérique, chaîne de caractère, liste etc.

7 Instructions de contrôle

Nous allons dans cette partie définir des commandes permettant d'effectuer des boucles ou des tests.

a) Boucles

for La boucle for est très utilisée, elle permet d'itérer (de répéter) de nombreuses opérations. Bien que sa syntaxe soit assez libre, on se fixera celle-ci qui est la plus communément utilisée :

  for {initialisations} {tests_de_fin_de_boucle} {incrémentations} {
         indentation_et_corps_de_la_boucle
  }

Cette syntaxe permet une lecture rapide des différents composants. L'incrémentation se fait très souvent avec la commande incr qui incrémente (augmente) d'une unité la valeur de la variable passée en arguments (voir exemples). Voici quelques exemples d'utilisation :

  >for {set i 0} {$i<=5} {incr i} {
  >      puts $i
  >}
  012345
  >for{seti0}{$i<=2}{incri}{
  >      for {set j 0} {$j<3} {incr j} {
  >              puts "$i --> $j"
  >      }
  >}
  0 --> 0
  0 --> 1
  0 --> 2
  1 --> 0
  1 --> 1
  1 --> 2
  2 --> 0
  2 --> 1
  2 --> 2
  # voici la production de 50 nombres aléatoires
  >for {set i 1} {$i<=10} {incr i} {
  >      puts -nonewline "[expr {rand()}] "
  >}
  # l'option nonewline permet de ne pas passer à la ligne systématiquement.
  0.301876634034 0.640588213988 0.366112495012 0.252703661682 0.190441896296 0.756951052117
  0.0763329249231 0.92746918226 0.974546245287 0.198744545783
        Pour faire de cette suite une liste à part entière :
  >set maliste {}
  >for {set i 1} {$i<=10} {incr i} {
  >      lappend maliste [expr {rand()}]
  >}
  >puts $maliste
  0.200260822754 0.783648034457 0.772515118016 0.661588497768 0.317881986181 0.642541747374
  0.199148111138 0.0823038979817 0.281613377985 0.076043800020

Notons que la définition de maliste est en fait inutile puisque si Tcl ne la trouve pas quand elle rencontre lappend, elle la crée. foreach Comme nous l'avons vu, la boucle for est utilisée pour les nombres, on peut aussi imaginer une boucle permettant de répéter la même opération avec n'importe quoi, il s'agit alors de foreach. La syntaxe est là encore fort simple :

  foreach variable {liste_de_valeurs_a_prendre} {
         corps_de_boucle
  }

Voici deux exemples illustrant la chose, le deuxième est particulièrement intéressant :

  # premier exemple
  >foreach eleve {Karima Émilie Mohamed Audrey Gabriel Camille} {
  >      puts "$eleve est en première S4"
  >}
  Karima est en première S4
  Émilie est en première S4
  Mohamed est en première S4
  Audrey est en première S4
  Gabriel est en première S4
  Camille est en première S4
  # deuxième exemple
  >set F fille
  >set G garçon
  >foreach {eleve sexe} "Karima $F Émilie $F Mohamed $G Audrey $F Gabriel $G Camille $F" {
  >      puts "$eleve est un(e) $sexe en première S4"
  >}
  Karima est un(e) fille en première S4
  Émilie est un(e) fille en première S4
  Mohamed est un(e) garçon en première S4
  Audrey est un(e) fille en première S4
  Gabriel est un(e) garçon en première S4
  Camille est un(e) fille en première S4

Bien sûr le deuxième exemple est plus évolué : on notera l'utilisation des guillemets pour permettre le remplacement des variables F et G par leur valeur et bien entendu, l'utilisation conjointe de deux variables pour la boucle. Mais pour que ce dernier exemple soit tout à fait satisfaisant, il faudrait tester dans la boucle si on a affaire à un garçon ou à une fille...

b) Test

Le test quasi-unique est le test if. Sa syntaxe est plus complexe : plusieurs possibilités existent suivant la structure du test : Un test simple

         if {test} {
                 corps_de_test_:_choses_à_faire_si_le_test_est_vérifié
         }

Une alternative

         if {test} {
                 corps_de_test_:_choses_à_faire_si_le_test_est_vérifié
         } else {
                 corps_de_test_:_choses_à_faire_si_le_test_n'est_pas_vérifié
         }

Deux tests

         if {test1} {
                 corps_de_test1_:_choses_à_faire_si_le_test1_est_vérifié
         } elseif {test2} {
                 corps_de_test2_:_choses_à_faire_si_le_test1_n'est_pas_vérifié_et_le_test2_est_vérifié
         } else {
                 corps_de_test_:_choses_à_faire_si_aucun_test_n'a_été_vérifié
         }

Notons donc que les parties elseif et else sont optionnelles. Voici donc l'exemple précédent remanié :

  >set F fille
  >set G garçon
  >foreach {eleve sexe} "Karima $F Émilie $F Mohamed $G Audrey $F Gabriel $G Camille $F" {
  >      if {$sexe=="fille"} {
  >              set article une
  >      } else {
  >      set article un
  >  }
  >      puts "$eleve est $article $sexe en première S4"
  >}
  Karima est une fille en première S4
  Émilie est une fille en première S4
  Mohamed est un garçon en première S4
  Audrey est une fille en première S4
  Gabriel est un garçon en première S4
  Camille est une fille en première S4

Bien entendu, le test est de la même nature que celui des boucles : c'est un test dit "booléen" : il est soit VRAI (1) soit FAUX (0). Les tests se font donc à l'aide d'opérateurs booléens : "==", ">=", "<=", ">" et "<". Attention il s'agit de tester si c'est vrai et non pas d'attribuer une valeur à une variable : on notera la forme particulière de la vérification de l'égalité : "==".

8 Procédures

Voici le concept sans doute le plus important : celui de procédure, similaire à celui des fonctions il prend un ou plusieurs arguments et renvoie une valeur par l'instruction return. La procédure est avant tout un groupement de commandes qu'on peut appeler à loisir. La syntaxe est simple :

         proc NomDeProcedure {arguments} {
         corps_de_la_procédure
         }

Voici un exemple qui renvoie les valeurs prises par la fonction carre :

  >proc fcarre {valeur} {
  >      set image [expr {pow($valeur,2)}]
  >      return $image
  >}
  >set x 2
  >puts [fcarre $x]
  4.0
  >set x -5
  >puts [fcarre $x]
  25

On peut évidemment simplifier la procédure de cette manière :

  >proc fcarre {valeur} {
  >      return [expr {pow($valeur,2)}]
  >}

Mais en fait, si la commande return est omise, la procédure renvoie le résultat de la dernière commande, comme ici il n'y en a qu'une, on aurait pu oser :

  >proc fcarre {valeur} {
  >      expr {pow($valeur,2)}
  >}

ATTENTION Dans l'exemple précédent, la variable valeur n'a de sens qu'à l'intérieur de la procédure, elle est appelée « variable locale » ! ! ! Quelques exemples de procédures : Calcul du produit scalaire :

  >proc prodscal1 {x0 y0 x1 y1} {
  >      expr {$x0*$y0+$x1*$y1}
  >}

Une autre possibilité de codage du produit scalaire, avec vérification que la liste placée en argument est bien constituée de 4 éléments.

  >proc prodscal2 {liste} {
  >      if {[llength $liste]==4} {
  >              for {set i 0} {$i<=3} {incr i} {
  >              set x($i) [lindex $liste $i]
  >      }
  >  return [expr {$x(0)*$x(1)+$x(2)*$x(3)}]
  >      }
  >}

Calcul du discriminant d'un trinôme du 2nd degré :

  >proc discriminant {a b c} {
  >      expr {pow($b,2)-4*$a*$c}
  >}

Calcul de la moyenne des éléments d'une liste, on notera l'ajout de « .0 » à la variable long ce qui permet de récupérer un décimal et non pas un entier dans le cas où la liste ne contient que des entiers (la moyenne pouvant être décimale).

  >proc moyenne {liste} {
  >      set long [llength $liste]
  >      if {$long>0} {
  >              set somme 0
  >              for {set i 0} {$i<=[expr {$long-1}]} {incr i} {
  >                      incr somme [lindex $liste $i]
  >              }
  >              append long .0
  >      return [expr {$somme/$long}]
  >      }
  >}

Somme des nb premiers entiers naturels (on supposera que nb est un entier).

  >proc s1 {nb} {
  >      set somme 0
  >      for {set i 1} {$i<=$nb} {incr i} {
  >              incr somme $i
  >      }
  >      return $somme
  >}

Même procédure que la précédente mais ce dernier exemple est appelé « récursif » car la procédure fait appel à elle-même.Il est à noter qu'habituellement les procédures récursives sont plus lentes que celles itératives (associées aux boucles comme dans la procédure s1 mais sont plus « élégantes ».

  >proc s2 {nb} {
  >      if {$nb==0} {
  >              return 0
  >      } else {
  >              set nb [expr {$nb+[s2 [expr {$nb-1}]]}]
  >      }
  >      return $nb
  >}

Il s'agit d'émuler le comportement de la fonction NB.SI de MS-Excel, son utilistation donnera :

  >set maliste {1 1 1 2 2 5 4 1}
  >puts [nbsi {=="1"} $maliste]
  4
  >puts [nbsi {>"2"} $maliste]
  2

C'est d'autant plus intéressant qu'il s'agit bien de « construire » un test de manière interactive.

  >proc nbsi {compar liste} {
  >      set i 0
  >      foreach objet $liste {
  >              if $objet$compar {incr i}
  >      }
  >      return $i
  >}

9 Exercices corrigés

Exercice 1

Écrire une procédure, prenant une liste d'entiers comme argument, renvoyant la valeur maximum de cette liste.

  >proc maximum {liste} {
  >      set longueur [llength $liste]
  >      set max [lindex $liste 0]
  >      for {set i 1} {$i<=[expr $longueur-1]} {incr i} {
  >              set valeur [lindex $liste $i]
  >              if {$valeur>$max} {
  >                      set max $valeur
  >              }
  >      }
  >      return $max
  >}

Exercice 2

Écrire une procédure sans argument renvoyant une liste de 100 résultats du lancer d'un dé.

  >proc 100lancers {} {
  >      for {set i 1} {$i<=100} {incr i} {
  >              lappend maliste [expr {int(rand()*6+1}]
  >      }
  >      return $maliste
  >}

Exercice 3

En reprenant/complétant l'exercice précédent, créer une procédure renvoyant une liste des fréquences d'apparition des 6 faces sur les 100 lancers. On utilisera un tableau de 6 valeurs s'incrémentant suivant le résultat obtenu.

  >proc freq100lancers {} {
  >      for {set i 1} {$i<=6} {incr i} {
  >              set eff($i) 0
  >      }
  >      for {set i 1} {$i<=100} {incr i} {
  >              set monalea [expr {int(rand()*6+1}]
  >              incr eff($monalea)
  >      }
  >      for {set i 1} {$i<=6} {incr i} {
  >              lappend maliste [expr {$eff($i)/100.0}]
  >      }
  >      return $maliste
  >}

Exercice 4

Que faut-il modifier dans l'exemple précédent pour que la procédure prenne en argument le nombre de lancers considéré ?

  >proc freqnblancers {nb} {
  >      for {set i 1} {$i<=6} {incr i} {
  >              set eff($i) 0
  >      }
  >      for {set i 1} {$i<=$nb} {incr i} {
  >              set monalea [expr {int(rand()*6+1}]
  >              incr eff($monalea)
  >      }
  >      append nb .0
  >      for {set i 1} {$i<=6} {incr i} {
  >              lappend maliste [expr {$eff($i)/$nb}]
  >      }
  >      return $maliste
  >}

Exercice 5

Écrire une procédure sans argument renvoyant une liste de 100 résultats du tirage d'une boule dans une urne qui contient 3 boules vertes et 7 boules rouges.

  >proc 100tirages {} {
  >      for {set i 1} {$i<=100} {incr i} {
  >              set monalea [expr {int(rand()*10+1}]
  >              if {$monalea<=3} {
  >                      lappend maliste V
  >              } else {
  >                      lappend maliste R
  >              }
  >      }
  >      return $maliste
  >}

Exercice 6

Écrire une procédure, prenant en arguments les trois coefficients trinomiaux, sur l'étude d'un trinôme du 2nd degré : discriminant, racines éventuelles, sommet de la parabole, orientation des branches. On renverra donc une liste avec ces renseignements, pour ce faire on déterminera chaque résultat avec une variable et à la fin on constituera une liste avec la commande list :

  return [list $discriminant $racines $sommet $orient]

Bien entendu, certaines de ces variables seront elles-aussi des listes !

  >proc secondegre {a b c} {
  >      if {$a==0} {return "erreur"}
  >      set delta [expr {$b*$b-4*$a*$c}]
  >      if {$delta>0} {
  >              lappend racines [expr {(-$b-sqrt($delta))*1.0/(2*$a)}]
  >              lappend racines [expr {(-$b+sqrt($delta))*1.0/(2*$a)}]
  >      } elseif {$delta<0} {
  >              lappend racines {pas de racines}
  >      } else {
  >              lappend racines [expr {-$b*1.0/(2*$a)}]
  >      }
  >      lappend sommet [expr {-$b*1.0/2*$a}]
  >      lappend sommet [expr {$a*pow($xsommet,2)+$b*$xsommet+$c}]
  >      if {$a>0} {
  >              set orient "haut"
  >      } else {
  >              set orient "bas"
  >      }
  >      return [list $delta $racines $sommet $orient]
  >}

10 Les chaînes

Les variables de Tcl sont toutes reconnues comme des chaînes ; nous avons vu que nous pouvions utiliser des commandes spécifiques pour chaque type reconnu par l'utilisateur. Les commandes associées au type chaîne ressemblent à celles du type liste, à ceci près que chaque caractère est un élément de chaîne. L'exemple suivant est des plus clairs :

  >set chaine {123456 89}
  >puts [string length $chaine]
  9
  >puts [llength $chaine]
  2

L'abscence de typage dans Tcl permet beaucoup de choses, bien entendu le danger de mal définir un objet reste présent. On évitera au maximum la confusion des genres. Les instructions et commandes liées aux chaînes : string length renvoie la longueur d'une chaîne : string length chaine string index renvoie l'élément positionné à l'indice correspondant : string index chaine indice string range renvoie le morceau de chaine situé entre deux indices : string range chaine indice1 indice2 De nombreuses autres commandes existent et sont très utiles pour comparer, modifier etc. des chaînes.

11 Boucle while

Les boucles for et foreach nécessitent une bonne connaissance du processus itératif, ce qui n'est hélas pas toujours le cas. Prenons une procédure qui affiche des nombres aléatoires tant qu'ils sont supérieurs à 0,5. Il est évident que nous ne pouvons pas savoir à l'avance si la procédure donnera un résultat et si elle en donne combien elle en fournira. C'est dans ce genre de situation qu'on envisage une boucle while. La syntaxe est très simple :

  while {test} {
         corps_de_la_boucle_si_le_test_est_vérifié
  }

La procédure envisagée ci-dessus aurait cette allure :

  >proc aleademi1 {} {
  >      set nb [expr rand()]
  >      while {$nb>0.5} {
  >              puts $nb
  >      }
  >}

Attention il est évident que quelque chose du genre :

  >proc aleademi2 {} {
  >      while {[expr rand{}>0.5} {
  >              puts [expr rand()]
  >      }
  >}

fournirai des résultats faux puisque le test se fait sur un autre nombre que celui affiché ! Pour terminer, le test est un booléen donc 0 ou 1 ainsi le code :

  >while {1} {
  >      puts "ça n'en finit plus !"
  >}

produit une boucle infinie...

12 Manipulation de fichiers

a) Ouverture, lecture et fermeture

De nombreuses données statistiques sont récupérables sur l'Internet, la plupart sont sous la forme de fichiers de nombres ou de dates, voici en exemple le début du fichier des résultats du loto dans l'ordre :

  38;39;26;41;45;36;20
  19;1;25;20;22;39;48
  etc.

On pourrait, à l'instar de l'exercice sur les lancers de dé, essayer de créer un tableau de 49 valeurs s'incrémentant au fur et à mesure des tirages. Pour cela il faudrait parcourir ce fichier de résultats et lire ligne par ligne, valeur par valeur. Tcl dispose des instructions suivantes : open ouvre un fichier et renvoie son identifiant : open nom_fichier gets renvoie le contenu d'une ligne : gets identifiant_fichier read renvoie le contenu du fichier entier : read identifiant_fichier close ferme le fichier ouvert : close identifiant_fichier eof renvoie 1 si on est en fin de fichier sinon 0 : eof identifiant_fichier Ainsi, le nom du fichier ne sert qu'une fois : à l'ouverture étape indispensable pour obtenir l'identifiant. Prenons un exemple de lecture d'un fichier loto.dat que l'on lira avec les deux méthodes.

  ># lecture en une seule fois
  >set id [open loto.dat];# nous récupérons l'identifiant du fichier
  >puts [read $id]
  >close $id
  38;39;26;41;45;36;20
  19;1;25;20;22;39;48
  36;25;31;29;32;37;17
  23;21;7;11;31;27;29
  >#lecture ligne par ligne
  >set id [open loto.dat]
  >while {[eof $id]==0} {
  >      puts "ligne->[gets $id]"
  >}
  >close $id
  ligne->38;39;26;41;45;36;20
  ligne->19;1;25;20;22;39;48
  ligne->36;25;31;29;32;37;17
  ligne->23;21;7;11;31;27;29
  ligne->

L'apparition de la dernière ligne peut paraître étonnante mais chaque ligne de fichier se termine par un retour à la ligne (symbolisé en Tcl par \n) qui donne accès à une dernière ligne vide... Remarquons que dans la première méthode, une ligne vide apparaît aussi, on peut la supprimer avec l'option nonewline :

  >puts [read -nonewline $id]

Cette commande aurait supprimer cette dernière ligne. Les deux méthodes ont leurs avantages et leurs inconvénients, la première méthode permet d'avoir tout le fichier dans une seule variable avec l'instruction :

  >set contenutotal [read $id]

ce qui peut être intéressant pour une recherche globale dans le fichier. La deuxième méthode pré-découpe le fichier en lignes ce qui permet un traitement dans notre cas tirage par tirage. La boucle while est naturellement utilisée pour lire « tant que » la fin du fichier n'est pas apparue. Notons au passage qu'il est assez usuel de mettre le résultat de gets dans une variable souvent appelée ligne (évidemment), on pense immédiatement à la commande :

  >set ligne [gets $id]

Mais comme Tcl fait bien les choses, on peut se contenter de :

  >gets $id ligne

Bien entendu, une fois le fichier mis d'une manière ou d'une autre dans une variable (ou plusieurs), on peut le fermer avec close et manipuler à loisir nos variables.

b) Traitement de données importées

Bien souvent, les données sont fournies de manière brute comme dans notre exemple. Prenons la méthode gets et travaillons ligne par ligne. Tcl conçoit cette ligne comme une chaîne unique de caractères et non comme 7 résultats de loto ! Tcl fournit l'instruction : split qui renvoie une liste à partir d'une chaîne dont on veut séparer les éléments : split chaine séparateur

  >set id [open loto.dat];# ouverture de notre fichier
  >for {set i 1} {$i<=49} {incr i} {
  >      set boule($i) 0;# création/initialisation des vatiables de fréquences d'apparition de chaque numéro
  >}
  >while {[eof $id]==0} {
  >      gets $id ligne
  >      set decoupe [split $ligne ";"];# découpe de notre ligne de résultat en une liste de 7 éléments
  >      for {set i 0} {$i<=6} {incr i} {
  >              incr boule([lindex $decoupe $i])
         # récupération de chaque boule et incrémentation de la variable associée
  >      }
  >}
  >close $id
  >for {set i 1} {$i<=49} {incr i} {
  >      puts "$i->$boule($i)"
  >}

Tout paraît parfait et pourtant à l'exécution on obtient le message d'erreur :

  can't read "boule()": no such element in array
          (reading value of variable to increment)
          invoked from within
  "incr boule([lindex $decoupe $i])"
          ("while" body line 5)
          invoked from within
  "while {[eof $id]==0} {
                           gets $id ligne
                 set decoupe [split $ligne ";"];# découpe de notre ligne de résultat en une liste de 7 éléments
                 for {set i..."
          (file "temp.tcl" line 100)

La raison est pourtant simple : notre ligne vide ! On rajoute donc un petit test pour savoir si la variable ligne n'est pas vide :

  >set id [open loto.dat];# ouverture de notre fichier
  >for {set i 1} {$i<=49} {incr i} {
  >               set boule($i) 0;# création/initialisation des vatiables de fréquences d'apparition de chaque numéro
  >}
  >while {[eof $id]==0} {
  >               gets $id ligne
  >               if {$ligne!=""} {
  >                        set decoupe [split $ligne ";"]
  >                        for {set i 0} {$i<=6} {incr i} {
  >                                    incr boule([lindex $decoupe $i])
  >                        }
  >               }
  >}
  >close $id
  >for {set i 1} {$i<=49} {incr i} {
  >               puts -nonewline "Boule $i : $boule($i) occurences "
  >}

Avec un fichier de résultats assez étoffés (depuis le début) on obtient :

  Boule 1 : 443 occurences Boule 2 : 408 occurences Boule 3 : 399 occurences Boule 4 : 451 occurences
  Boule 5 : 400 occurences Boule 6 : 442 occurences Boule 7 : 450 occurences Boule 8 : 398 occurences
  Boule 9 : 421 occurences Boule 10 : 411 occurences Boule 11 : 395 occurences Boule 12 : 422 occurences
  Boule 13 : 403 occurences Boule 14 : 412 occurences Boule 15 : 420 occurences Boule 16 : 451 occurences
  Boule 17 : 387 occurences Boule 18 : 429 occurences Boule 19 : 430 occurences Boule 20 : 427 occurences
  Boule 21 : 410 occurences Boule 22 : 415 occurences Boule 23 : 431 occurences Boule 24 : 419 occurences
  Boule 25 : 411 occurences Boule 26 : 403 occurences Boule 27 : 430 occurences Boule 28 : 414 occurences
  Boule 29 : 388 occurences Boule 30 : 425 occurences Boule 31 : 425 occurences Boule 32 : 420 occurences
  Boule 33 : 375 occurences Boule 34 : 442 occurences Boule 35 : 417 occurences Boule 36 : 443 occurences
  Boule 37 : 432 occurences Boule 38 : 444 occurences Boule 39 : 439 occurences Boule 40 : 422 occurences
  Boule 41 : 408 occurences Boule 42 : 420 occurences Boule 43 : 431 occurences Boule 44 : 408 occurences
  Boule 45 : 441 occurences Boule 46 : 420 occurences Boule 47 : 399 occurences Boule 48 : 413 occurences
  Boule 49 : 448 occurences

Il est à noter que la commande split a ici nécessité de mettre le séparateur entre des guillements puisqu'il s'agit d'un point-virgule considéré par Tcl comme le séparateur d'instructions...

c) Ouverture, écriture et fermeture

Le processus est quasi-identique au a), à ce détail que l'on ouvre pour écrire ce qui nécessite quelques précisions sur la commande open. En effet cette commande permet éventuellement de signaler le type accès que vous envisagez : lecture seule, lecture et écriture, etc. La syntaxe est la suivante : open nom_fichier type_accès. L'accès est du type suivant :

r Lecture seule (r)ead le fichier doit exister r+ Lecture et écriture (r)ead le fichier doit exister w Écriture seule (w)rite écrase le fichier s'il existe w+ Lecture et écriture (w)rite écrase le fichier s'il existe a Écriture seule (a)ppend ajoute à la fin a+ Lecture et écriture (a)ppend ajoute à la fin

Maintenant que les ouvertures sont plus claires, plutôt que de faire s'afficher les résultats à l'écran, écrivons-les dans un fichier. On utilise aussi la commande puts mais avec un argument supplémentaire : puts identifiant_fichier chaîne.

  >set id [open loto.dat r]
  >set stock [open stock.res w]
  >for {set i 1} {$i<=49} {incr i} {
  >      set boule($i) 0
  >}
  >while {[eof $id]==0} {
  >      gets $id ligne
  >      if {$ligne!=""} {
  >              set decoupe [split $ligne ";"]
  >              for {set i 0} {$i<=6} {incr i} {
  >                      incr boule([lindex $decoupe $i])
  >              }
  >      }
  >}
  >close $id
  >for {set i 1} {$i<=49} {incr i} {
  >       puts $stock -nonewline "Boule $i : $boule($i) occurences "
  >}
  >close $stock

Rien n'apparaît donc à l'écran, mais un fichier stock.res contient les résultats du script.

13 Quelques commandes supplémentaires

Notre aperçu de Tcl se termine par un petit catalogue de commandes intéressantes avec un exemple d'utilisation : global pour rendre une variable globale14 dans une procédure : global nom_variable exit arrête l'interprétation, le programme s'arrête array names renvoie des noms d'éléments d'un tableau : array names nom_tableau clock seconds renvoie la date et l'heure courante sous forme d'un entier. clock format convertit un entier en une date/heure lisible : clock format entier -format mon_format exec exécute un programme exécutable : exec programme eval évalue la chaine comme une commandes Tcl : eval chaine

  >set var 1
  >proc maprocedure {valeur} {
  >      if {$valeur==0} {
  >              puts $var
  >      } else {
  >              global var
  >              puts $var
  >      }
  >}
  >maprocedure 0
  can't read "var": no such variable
          while executing
  "puts $var"
          (procedure "maprocedure" line 3)
          invoked from within
  "maprocedure 0"

La dernière commande produit évidemment une erreur alors que l'appel suivant n'en produit pas :

  >maprocedure 1
  1

La commande global donne à la variable var la valeur qu'elle a en dehors de la procédure.

  >set heure(nonlisible) [clock seconds]
  >set heure(lisible) [clock format $heure(nonlisible) -format %d/%m/%Y--%H:%M\ %S]
  >puts [array names heure]
  >foreach bidule [lsort -decreasing [array names heure]] {
  >      puts $heure($bidule)
  >}
  lisible nonlisible
  1008408290
  15/12/2001--10:24 50

On notera l'utilisation de l'option decreasing de lsort qui permet d'ordonner dans l'ordre décroissant.

  >set macommande puts
  >append macommande { {Voila une étrange chose}}
  >puts $macommande
  puts {Voila une étrange chose}
  >eval $macommande
  Voila une étrange chose

14 Présence d'un motif : regexp

Cette commande permet de vérifier la présence d'un certain motif dans une chaine et d'extraire la ou les parties contenant le motif. Cette commande renvoie 1 si une correpondance a été trouvée, 0 sinon. Le motif est ce qu'on appelle une expression rationnelle. Regardons quelques exemples d'utlisation simple :

  >puts [regexp david "david cobac"]
  1

le motif david a été trouvé dans la chaine "david cobac"... Maintenant cherchons si, dans la même chaîne, il existe une partie en trois lettres consécutives qui commençe par d et finit par v :

  >puts [regexp d.v "david cobac"]
  1

Le caractère . joue dans l'expression rationnelle le rôle de n'importe quel autre caractère. Mais évidemment une question se pose : le renvoi de 1 nous indique que le motif correspond à une partie (ou au tout) de la chaîne mais quelle est cette partie ? Pour ce faire, Tcl permet de rajouter à la commande précédente un nom de variable :

  >puts [regexp d.v "david cobac" macorrespondance]
  1
  >puts $macorrespondance
  dav

Si on cherche une correspondance avec un début en d et une fin en o sans connaître le nombre de caractères séparant les deux lettres, on peut alors utiliser le quantificateur + :

  >puts [regexp d.+o "david cobac" macorrespondance]
  1
  >puts $macorrespondance
  david co

Ce quantificateur associé à . permet de dire qu'il y a peut-être 0 ou plus caractères entre le d et le o, notons que l'utilisation du quantificateur * aurait dit quant à lui qu'il y a peut-être 1 ou plus caractères et pour finir, l'utilisation du quantificateur ? aurait dit qu'il y a 0 ou 1 caractère. On peut très bien ne récupérer qu'une partie identifiée préalablement de la chaîne. On peut, en mettant entre parenthèses dans l'expression rationnelle, récupérer des sous-parties de notre correspondance globale, il suffit de mettre plus de nom de variables dans l'appel de la commande regexp, par exemple, prenons l'ultra-classique récupération d'un hyperlien dans un fichier html :

 <a href="http://wfr.tcl.tk/fichiers/images/resultat.jpg">

Nous voulons simplement récupérer l'adresse. Ce qui va ici identifier ce qu'on veut récupérer est qu'il se situe entre les guillemets donc :

  >set monlien {<a href="}">http://wfr.tcl.tk/fichiers/images/resultat.jpg">}
  >puts [regexp {<a href="(.*)">} $monlien corr corr1]
  1>puts $corr
  <a href="http://wfr.tcl.tk/fichiers/images/resultat.jpg">
  >puts $corr1
  http://wfr.tcl.tk/fichiers/images/resultat.jpg

Nous remarquons deux choses, la définition de la variable monlien a nécessité des accolades, en effet comme il y a un espace entre <a et href=".., Tcl ne reconnaît pas le bon nombre d'arguments pour l'utilisation de la commande set ; il faut donc regrouper sachant que l'expression contient des guillemets qui sont susceptibles d'induire en erreur l'interpréteur sur le fait qu'il ne s'agit pas de guillemets de délimitation de notre variable, le choix se porte nécessairement sur un regroupement avec des accolades. Pour les mêmes raisons, on a entouré l'expression rationnelle d'accolades...bien qu'on aurait pu faire :

  >puts [regexp "<a href=\"(.*)\">" $monlien corr corr1]
  1

En effet, le caractère \ est un caractère dit d'échappement et empêche tout caractère qui le suit d'être interprété comme un caractère « spécial ». À vrai dire, les expressions rationnelles sont complexes à manipuler et il faut beaucoup d'expérience pour savoir les utiliser judicieusement, je vous renvoie donc à des sites internet traitant spécifiquement du sujet. Ce que nous avons vu ici ne constitue qu'une maigrissime approche.

15 Remplacement d'un motif : regsub

La commande regsub remplace dans une chaine la première correspondance au motif par une autre expression et met le résultat global de substitution dans une autre variable : regsub expression_rationnelle chaine expression variable Pour effectuer le remplacement dans chaque occurence dans la chaine on emploie l'option -all. Dans tous les cas, cette commande renvoie le nombre de remplacement effectué. Quelques exemples :

  ># illustration de l'option -all
  >set phrase {Une fonction majorée et minorée est une fonction bornée}
  >regsub fonction $phrase suite phrase2
  1
  >puts $phrase2
  Une suite majorée et minorée est une fonction bornée
  >regsub -all fonction $phrase suite phrase2
  2
  >puts $phrase2
  Une suite majorée et minorée est une suite bornée
  ># utilisation d'expressions rationnelles
  >set prenom "David david daviD"
  >regsub -all (d|D) $prenom d prenomunifie
  6
  >puts $prenomunifie
  david david david
  ># le carctère | permet de dire le "ou" inclusif
  ># cette dernière aurait pu être réalisée par :
  >regsub -nocase -all d $prenom d prenomunifie
  6
  ># en effet l'option -nocase permet de ne pas tenir
  ># compte dans la correspondance de la casse (majuscule
  ># ou minuscule

Je termine par « l'astuce » qui m'a permis de réaliser un traceur de fonctions sans construire d'arbre de calculs :

  >set mafonction {pow(u,2)+u*sin(u)}
  >set i 3
  >regsub -all u $mafonction $i mafonctionmodifiee
  3
  >puts [expr $mafonctionmodifiee]
  9.42336002418

Tout cela peut paraître parfait mais regardez :

  >set i 4
  >puts [expr $mafonctionmodifiee]
  9.42336002418

Bref il faudrait le refaire à chaque calcul mais voilà :

  >set mafonction {pow(u,2)+u*sin(u)}
  >set i 3
  >regsub -all u $mafonction \$i mafonctionmodifiee
  ># attention cela remplace tous les u dans l'expreesion mathématique
  ># ce qui peut poser d'éventuels problèmes avec des mots-clés en contenant
  ># un tel que 'round' !
  >puts [expr $mafonctionmodifiee]
  9.42336002418
  >set i 4
  >puts [expr $mafonctionmodifiee]
  12.9727900188

L'astuce consiste à remplacer u non pas la valeur de i mais par la chaine $i qui dans le contexte d'une évaluation sera alors remplacée par la valeur actuelle de i ! C'est une possibilité fantastique due à l'absence de typage dans ce langage.

16 Remplacements simples : string map

La commande string map permet de faire un remplacement massif dans une chaîne mais n'utilise pas les expressions rationnelles, elle renvoie le résultat : string map {anciennevaleur nouvellevaleur} chaine En option, elle prend aussi -nocase. Voici quelques exemples pour mieux comprendre son fonctionnement :

  >set chaine {Tool Command Language}
  >puts [string map {Tool Tickeul} $chaine]
  Tickeul Command Language
  >puts [string map {1 0} 0101]
  0000
  ># deux remplacements les 0 deviennent 1
  ># et les 1 deviennent 0 :
  >puts [string map {0 1 1 0} 0101]
  1010
  >puts [string map {00 ff 0 a a 0} \#00010a]
  #ffa1a0

Les deux derniers exemples illustrent parfaitement bien comment fonctionne cette commande dans des cas plus complexes où, à tort, on peut craindre un déréglement de la commande. On peut revisiter notre extraction de lien :

  >set monlien {<a href="}">http://wfr.tcl.tk/fichiers/images/resultat.jpg">}
  >puts [string map {"<a href=\"" "" "\">" ""} $monlien]
  http://wfr.tcl.tk/fichiers/images/resultat.jpg

Je vous laisse méditer la commande où on remplace l'inutile par...rien.

17 Entrées et sorties conversationnelles

L'attrait principal de Tcl est son toolkit Tk, néanmoins lorsqu'on débute il vaut mieux maîtriser dans un premier temps Tcl. Ainsi les premières applications sont avant tout en mode console. Le widget entry de Tk permet de « dialoguer » avec l'application mais Tcl seul doit se débrouiller avec la console dans laquelle vous avez lancée votre application. Le dialogue se fait naturellement de la part de Tcl qui, par défaut, renvoie en « sortie standard » c'est-à-dire en stdout les sorties de la commande puts. Ainsi la commande puts sans précision d'identifiant de fichier est en fait la commande puts stdout ! Par contre, comment l'utilisateur peut-il rentrer de manière interactive des renseignements par la console à l'application ? Eh bien avec les mêmes commandes que l'on a déjà encontré sur le dialogue avec les fichiers : read et gets. Pour dire qu'il faut lire sur la console où on a lancé l'application, on dit qu'il faut lire sur stdin :

  >puts [gets stdin maligne]
  voilà une belle ligne
  21

Une fois la ligne gets stdin maligne tapée, la console passe à la ligne et attend une entrée de ma part, je tape ma phrase suivie d'un appui sur la touche Entrée, on me répond alors 21 qui est le nombre de caractères (espaces inclus) tapés (ce nombre n'apparaît pas si la fonction est utilisée dans un programme). Ainsi, comme attendu, le passage à la ligne interrompt la commande gets. Voyons maintenant la commande read :

  >set contenu [read stdin]
  voilà une belle ligne
  et cela continue meme si j'appuie sur entrée
  c'est la grande différence avec gets, on s'arrete
  en utilisant la touche Ctrl avec la touche dvoilà une belle ligne
  et cela continue meme si j'appuie sur entrée
  c'est la grande différence avec gets, on s'arrete
  en utilisant la touche Ctrl avec la touche d

La différence est nette, le passage à la ligne n'est pas un critère d'arrêt, celui-ci est l'appui sur . La commande renvoie le texte tapé qui est donc dans la variable contenu. Regardons un petit exemple de script calculant interactivement des images par une fonction, on prendra comme critère d'arrêt l'entrée du mot « stop » :

  >proc images {nombre} {
  >      global mafonction
  >      set u $nombre
  >      return [expr $mafonction]
  >}
  >>puts "Entrer votre fonction : (exemple : pow(x,3)-x+5/x)"
  >gets stdin mafonction
  >regsub -all x $mafonction "\$u" mafonction
  >while {1} {
  >      puts "Entrez votre abscisse :"
  >      gets stdin abscisse
  >      if {$abscisse=="stop"} {break}
  >      puts [images $abscisse]
  >}

Voilà constitué en quelques lignes une petite calculatrice de fonction. Bien sûr, il reste toujours le cas où vous rentrez une « valeur interdite » pour le calcul...et là l'interpréteur se fâche :

  divide by zero
           while executing
  "expr $mafonction"
           (procedure "images" line 4)
           invoked from within

etc. J'ai simplement introduit une fonction n'admettant pas 0 comme valeur possible, bien entendu il faut comprendre que ce n'est pas le seul cas d'erreur possible. Tcl offre une commande très sympathique pour traiter cela : catch.

18 Interception des erreurs : catch

Cette commande permet d'intercepter les erreurs. Sa syntaxe est simple : catch mescommandes Par exemple, divisons par 0 juste pour voir ce que renvoie la commande :

  >catch {expr 1/3}
  0
  >catch {expr 1/0}
  1

Voilà l'erreur est « absorbée » et catch renvoie 1 ! Pour reprendre mon exemple précédent (les valeurs d'une fonction), je vais insérer cette commande pour intercepter d'éventuelles valeurs interdites, on modifie le renvoi de la procédure :

  >proc images {nombre} {
  >      global mafonction
  >      set u $nombre
  >      set res erreur
  >      catch {set res [expr $mafonction]}
  >      return $res
  >}

Pour ceux qui veulent aller plus loin, on peut récupérer le code l'erreur en le mettant dans une variable : catch mescommandes mavariable Sur le même exemple, en rajoutant ma variable et un petit test :

  >      catch {set res [expr $mafonction]} mavariable
  >      if {$res=="erreur"} {puts "Erreur : $mavariable"}

qui produit en cas d'erreur par division par 0 une ligne :

  Erreur : divide by zero

19 Simulations d'expériences aléatoires

a) Lancers de dés

1 lancer de dé à N faces :

  >expr {int(rand()*$N+1)}

somme du résultat de 2 dés à N et M faces :

  >expr {int(rand()*$N+1)+int(rand()*$M+1)}

b) Tirages avec remise

P tirages dans une urne de N boules numérotées de 1 à N avec remise après tirage :

  >for {set i 1} {$i<=$P} {incr i} {
  >      set tirage($i) [expr {int(rand()*$N+1)}]
  >}

On aurait pu aussi faire une liste :

  >for {set i 1} {$i<=$P} {incr i} {
  >      lappend tirage [expr {int(rand()*$N+1)}]
  >}

c) Tirages sans remise

P tirages dans une urne de N boules numérotées de 1 à N sans remise après tirage (ici P Solution 1 :

  >for {set i 1} {$i<=$N} {incr i} {set boule($i) 0}
  >set compteur 0
  >while {$compteur<$P} {
  >      set choix [expr {int(rand()*$N+1)}]
  >      if {$boule($choix)==0} {
  >              incr boule($choix)
  >              incr compteur
  >      }
  >}

Je ne trouve pas cette solution très « naturelle » car on fait un tirage et on le rejette s'il a déjà été fait... Solution 2 :

  >for {set i 1} {$i<=$N} {incr i} {lappend mesboules $i}
  >for {set i 1} {$i<=$P} {incr i} {
  >      set choix [expr {int(rand()*[llength $mesboules]+1)}]
  >      set element [lindex $mesboules [expr {$choix-1}]]
  >      set mesboules [concat [lrange $mesboules 0 [expr {$choix-2}]]\
  >              [lrange $mesboules [expr {$choix}] end] ]
  >      lappend tirage $element
  >}

L'expérience se fait à l'instar de la vraie en P étapes ! La commande concat permet de ne former qu'une seule liste avec plusieurs. L'étape la plus longue est celle de la reconstruction de la liste des éléments restants après chaque tirage.

20 Références

Aucun livre français n'existe actuellement en librairie, on peut néanmoins trouver dans la collection O'Reilly un petit ouvrage de Paul Raines intitulé Tcl/Tk précis et concis qui récapitule l'ensemble des commandes. Notons qu'en Septembre 2002 sortira un livrea aux éditions Vuibert écrit par Bernard Desgraupes intitulé Tcl/Tk, apprentissage et référence. D'autres ouvrages en langue anglaise existent et sont commandables. _______________________________________________________

Pour se procurer le langage gratuit Tcl/Tk, il suffit de le télécharger à l'adresse http://www.scriptics.com ou ]a href="http://www.activestate.com/Products/Download/Get.plex?id=ActiveTcl"> http://www.activestate.com/Products/Download/Get.plex?id=ActiveTcl Cette distribution est constituée non seulement du langage en lui-même mais aussi d'une multitude de modules intéressants. Sous linux Tcl/Tk est la plupart du temps installé par défaut par quasiment toutes les distributions. Néanmoins, ces ditributions n'incluent la plupart du temps que Tcl et Tk sans aucune extension. Pour commencer une recherche sur l'Internet, un bon point de départ est le portail francophone : http://wfr.tcl.tk D'innombrables ressources sont trouvables sur le site de C. Vidal : http://tcltk.free.fr Le site actif des utilisateurs du langage est : http://wiki.tcl.tk qui s'avère être une mine d'informations sur le langage et son actualité. Les forums de discussion (newsgroups) sont aussi une excellente source de documentation, la plupart sont accessibles via un lecteur de news dédié, le newsgroup français sur Tcl/Tk est fr.comp.lang.tcl lisible aussi en html avec n'importe quel navigateur (lien trouvable sur le portail francophone), quant au newsgroup anglophone, il s'agit de comp.lang.tcl Et pour terminer sachez qu'il existe un courrier intitulé Dr. Dobb's Tcl-URL ! - weekly Tcl news and links, pour le recevoir, allez à l'adresse http://www.ddj.com


Annexes


A La couleur en Tk

Tk semble accepter les noms de couleur défini usuellement en html, mais bien sûr il accepte aussi les codes de couleur RGB par l'intermédiaire d'un code du style #RRGGBB où RR, GG et BB sont les niveaux de couleur pour respectivement le rouge, le vert et le bleu, ces niveaux sont exprimés sous forme hexadécimale par un entier compris entre 0 et 255 en base 10 c'est-à-dire entre 00 et ff en base 16.

B Interfacer Tcl et C avec swig

Il est relativement aisé d'écrire des fonctions C et de les importer avec swig pour utilisation par Tcl. swig est téléchargeable à l'adresse : http://www.swig.org et se révèle assez simple d'utilisation pour par exemple construire une librairie de commandes. Imaginons vouloir créer une librairie associée aux calculs suivants : lancer de 1 dé à N faces : N comme argument et la valeur renvoyée est un entier compris entre 1 et N calcul d'une valeur approchée de pi par la méthode de Monte-Carlo : nb comme argument est le nombre de points à considérer, la valeur renvoyée est l'approximation de pi Voici le fichier malibrairie.c :

  #include
  #include
  int lancer_de (int N) {
                 srand(time(NULL));
                 return (int) (rand()/((double) RAND_MAX+1.)*N);
  }
  double pi_montecarlo (unsigned long int nb) {
                 int i;
                 double x,y;
                 int ok=0;
                 srand(time(NULL));
                 for (i=1;i<=nb;i++) {
                           x=rand()/((double) RAND_MAX+1.);
                           y=rand()/((double) RAND_MAX+1.);
                           if (x*x+y*y<=1) ok++;
                 }return 4.0*ok/nb;
  }

Pour pouvoir l'« exporter » grâce à swig, on crée un fichier dans lequel on trouvera le nom de la librairie, les différentes librairies et nos protoytpes, ce fichier sera nommé malibrairie.i :

  %module malibrairie
  %{
  #include
  #include
  %}
  extern int lancer_de (int N);
  extern double pi_montecarlo (int nb);

Il suffit maintenant de compiler :

  swig -tcl malibrairie.i
  gcc -c malibrairie.c
  gcc -c malibrairie_wrap.c
  gcc -shared malibrairie.o malibrairie_wrap.o -o malibrairie.so

Le fichier malibrairie_wrap.c a été créé par swig avec la première ligne de commande, nous lions nos deux fichiers objets dans la dernière compilation où linux oblige je crée non pas un fichier dll mais un fichier so. L'utilisation est fort simple, il suffit d'importer la librairie créée et d'utiliser nos fonctions comme des commandes Tcl :

  >load ./malibrairie.so malibrairie
  >puts [lancer_de 6]
  5
  >puts [pi_montecarlo 1000]
  3.172

Pour terminer je signale que la compilation avec :

  swig -tcl -namespace malibrairie.i

produit automatiquement un namespace malibrairie et l'appel se fera par :

  >load ./malibrairie.so malibrairie
  >puts [malibrairie::lancer_de 6]
  4
  >puts [malibrairie::pi_montecarlo 1000]
  3.128

ce qui est plus satisfaisant pour les puristes, on aurait pu même faire :

  swig -tcl -namespace -prefix david malibrairie.i

et l'appel se faisait alors de cette manière :

  >load ./malibrairie.so malibrairie
  >puts [david::lancer_de 6]
  3
  >puts [david::pi_montecarlo 1000]
  3.144

Bref, swig répond présent pour un interfaçage simple et efficace. Les trois types C basiquement reconnu par swig sont int, double et char *. swig supporte notamment le type struct avec des commandes Tcl créées pour les manipuler. Pour finir, le manuel bien qu'en anglais se trouve être simple pour qui s'intéresse à la chose.

C Tableaux systèmes

Il existe quelques tableaux prédéfinis dans le langage tels que env ou encore tcl_platform, regardez ce que donne les commandes suivantes :

  >array names env
  LS_COLORS HOME EROOT LYNX_CFG LANG LOGNAME TEXINPUTS XPPATH ECACHEDIR SHELL LESSCHARSET
  MACHTYPE OSTYPE QTDIR TERMINFO INPUTRC USER CFLAGS HHHOME MANPATH XAPPLRESDIR USERNAME
  MOZILLA_HOME METAMAIL_PAGER TEX OPENWINHOME SHLVL PATH MM_RUNASROOT PAGER STRIPFLAGS LC_ALL
  EDITOR LD_LIBRARY_PATH LESSOPEN HISTCONTROL HZ EPID ECONFDIR PWD LGRINDEF MINICOM
  MESA_GLX_FX BASH_ENV DISPLAY NLSPATH KDEDIR BOOT_CFLAGS MM_USEPAGER HOSTNAME CXXFLAGS
  MAIL CHROMIUM_DATA TERM HOSTTYPE ETHEME CVS_RSH XAUTHORITY HUSHLOGIN EVERSION HISTIGNORE
  EBIN ignoreeof LIBCFLAGS LESS
  ># il s'agit de la liste des variables d'environnement de mon système !
  >puts $env(USER)
  david
  # c'est bien mon nom d'utilisateur...
  >array names tcl_platform
  osVersion byteOrder machine platform os user
  >puts $tcl_platform(os)
  Linux
  >puts $tcl_platform(osVersion)
  2.5.0
  #version du noyau linux que j'utilise

Bien entendu, ces valeurs dépendent de la machine sur laquelle vous faites fonctionner votre programme ce qui permet de différencier dans un programme les actions à faire suivant l'utilisateur ou le système d'exploitation ...

D Introduction à Tk

Qu'est-ce que c'est ? Tk est un ensemble d'objets graphiques appelés communément « widgets » permettant d'interfacer agréablement et facilement les commandes et procédures : fenêtres, boutons, étiquettes, canevas, zone de texte etc. Apprentissage Je renvoie le lecteur à la lecture de l'excellente partie sur le sujet du tutoriel d'Emmanuel Grolleau Bases de Tcl/Tk. Les widgets usuels y sont clairement détaillés et leur utilisation montrée sur des exemples simples. Il existe des extensions qui permettent d'utiliser d'autres widgets que ceux installés par défaut : des barres de progression, des fenêtres de choix de police etc. On trouvera ces extensions sur l'Internet assez facilement ou sur le site : http://tcltk.free.fr Un exemple simple Voici le code complet d'un petit simulateur de lancer de dé.

 ># auteur : D.Cobac
 ># date de création : 15 décembre 2001
 >
 >set nblance 0
 >for {set i 1} {$i<=6} {incr i} {
 >       set lance($i) 0
 >}
 >
 >proc gui {} {
 >       wm title . "Lancers de dé"
 >       wm resizable . false false
 >       # fenêtre du lancer d'un seul dé
 >       pack [button .b1 -text "Lancer de 1 dé" -relief flat -background red -foreground white\
 >        -command {jelance;moncanvas .c1 [freq]}] -side left
 >       pack [label .l1 -text "" -width 3 -relief ridge] -side left -padx 3
 >       pack [frame .f1] -side left
 >       pack [label .l2 -text "Nb de lancers   " -relief groove] -side left
 >       pack [label .l3 -text "" -width 3 -relief sunken] -side left
 >       pack [canvas .c1 -width 100 -height 100 -background white -borderwidth 3 -relief raised]\
 >        -side left -padx 2 -pady 2
 >       foreach i {1 2} {
 >               pack [frame .f1.sf$i] -side left
 >       }
 >       foreach nombre {1 2 3} {
 >               set f .f1.sf1.ssf$nombre
 >               pack [frame $f]
 >               pack [label $f.l$nombre -text "$nombre :" -width 3  -relief groove] -side left
 >               pack [label $f.m$nombre -text "" -width 3  -relief sunken] -side left
 >       }
 >       foreach nombre {4 5 6} {
 >               set g .f1.sf2.ssf$nombre
 >               pack [frame $g]
 >               pack [label $g.l$nombre -text "$nombre :" -width 3  -relief groove] -side left
 >               pack [label $g.m$nombre -text "" -width 3  -relief sunken] -side left
 >       }
 >       bind .  "destroy ."
 >}
 >
 >proc jelance {} {
 >       global nblance lance
 >       incr nblance;.l3 configure -text $nblance
 >       set alea [expr int(rand()*6+1)]
 >       .l1 configure -text $alea
 >       incr lance($alea)
 >       if {$alea <= 3} {
 >               .f1.sf1.ssf$alea.m$alea configure -text $lance($alea)
 >       } else {
 >               .f1.sf2.ssf$alea.m$alea configure -text $lance($alea)
 >       }
 >}
 >
 >proc freq {} {
 >       global nblance lance
 >       for {set i 1} {$i<=6} {incr i} {
 >               lappend frq [expr int($lance($i)*100.0/$nblance)]
 >       }
 >       return $frq
 >}
 >
 >proc moncanvas {w liste} {
 >       $w delete withtags all
 >       for {set i 1} {$i<=6} {incr i} {
 >               set coordos "[expr int(($i-1)*100/6.0)] 100 [expr int($i*100/6.0)]\
 >                [expr 100-[lindex $liste [expr $i-1]]]"
 >               $w create rectangle $coordos -fill red -tags $i
 >       }
 >}
 >
 >gui


Catégorie Tutoriel