Audela
Audela

Débuter en langage Tcl

Débuter en langage Tcl

1. Qu'est-ce que Tcl ?

Tcl est un langage script interprété, ce qui le distingue des langages courants tels que C, Java, etc. Le langage Tcl est très efficace pour écrire des scripts, c'est à dire des enchaînements d'actions à faire. Or ces enchaînements d'actions élémentaires sont légions lorsque l'on observe en astronomie CCD. Ecrire un script Tcl sert donc, en premier lieu, à automatiser des séquences répétitives. En second lieu, nous découvrirons aussi que Tk, la boite à outil de Tcl, permet aussi de dessiner entièrement une interface. Cette dernière partie sera abordée dans une autre page. Comme tout langage, Tcl a sa syntaxe, ses avantages et ses inconvénients. Ce texte a pour but de présenter le plus simplement possible les bases de ce langage.

2. La philosophie de base

Le moteur de Tcl est son interpréteur. L'interpréteur Tcl analyse une ligne de texte, la décode et l'exécute. Ces trois actions se font séquentiellement. Prenons un exemple. La ligne suivante :

set a 5

est analysée par l'interpréteur Tcl de la façon suivante :

L'interpréteur Tcl ne connaît que les variables alphanumériques (l'équivalent des string ou char en d'autres langages de programmation). Pour transformer une variable Tcl en un nombre, on utilise la fonction expr :

expr $a+10

est analysée par l'interpréteur Tcl de la façon suivante :

Le résultat de l'opération est le mot "15". Si l'on voulait stocker le mot 15 dans une variable on aurait écrit :

set b [expr $a+10]

L'usage des crochets signifie à l'interpréteur d'effectuer le calcul de la fonction à l'intérieur des crochets avant d'exécuter la fonction set.

3. Equivalences BASIC - C- Tcl

On rappelle que Tcl est un langage manipulant uniquement des variables de type chaîne de caractère. Le classique A$="Hello" en basic se traduit par set a Hello en Tcl. Voici quelques autres équivalences BASIC - Tcl :

BASIC
Tcl
a$ = "toto titi" set a "toto titi"
b = c + cos(5) set b [expr $c+cos(5) ]
if (b<5) then e$=a$+" tutu" endif if {$b<5} { set e [append a " tutu"] }
for i=1 to imax f(i)=i endfor for {set i 1} {$i<$imax} {incr i} { set f($i) $i }
l=len(a$) aa$=a$(i) a$=ucase$(a$) set l [string length $a] set aa [string index $a $i] set a [string toupper $a]
call func1(a$) func1 $a
sub func1(a$) print a$ endsub proc func1 {a} { puts $a }
open "toto.txt" for output as #1 print #1, a$ close #1 set handler [open "toto.txt" w] puts $handler $a close $handler
files * glob *

De même, les équivalences C - Tcl :

C Ansi
Tcl
strcpy(a,"toto titi"); set a "toto titi"
b = c + cos(5); set b [expr $c+cos(5) ]
if (b<5) { sprintf(e,"%s tutu",a); } if {$b<5} { set e [append a " tutu"] }
for (i=1;i<imax;i++) { f[i]=i; } for {set i 1} {$i<$imax} {incr i} { set f($i) $i }
l=strlen(a) ; aa=a[i]; a=strupr(a); set l [string length $a] set aa [string index $a $i] set a [string toupper $a]
func1(a); func1 $a
void func1(char *a) printf("%s",a); } proc func1 {a} { puts $a }
handler=fopen("toto.txt","wt"); fprintf(handler,"%s",a); fclose(handler); set handler [open "toto.txt" w] puts $handler $a close $handler
pas d'équivalent en C Ansi glob *

4. Les principes élémentaires du langage Tcl

4.1. La ligne de commande

Une ligne de commande Tcl est composée de mots. Le premier mot est toujours le nom d'une fonction. Les mots suivants sont toujours les arguments. Il n'y a jamais d'exception à cette règle. L'espace ou le Tab constituent la séparation entre les mots. Une ligne qui commence par # est suivie d'un commentaire qui ne sera pas interprété. Le retour chariot ou le ; constituent la séparation entre les lignes de commande. Par exemple, la ligne

set b 5 ; # blabla

est interprétée comme deux lignes. La première set b 5 et une seconde, # balbla qui est un commentaire.

4.2. La substitution et l'évaluation

Chaque ligne d'un script Tcl subit une étape de substitution avant d'être interprétée. Par exemple :

set b 5
set c $b

La première ligne affecte le mot "5" à la variable b. Dans la deuxième ligne, le symbole $ signifie qu'il faut substituer le mot qui suit par sa valeur : 5 dans le cas présent. Ainsi, la ligne s'écrit set c 5 après substitution. Etudions un cas un peu plus compliqué:

set a set
set b 5
$a c $b

La première ligne affecte le mot de trois lettres "set" à la variable a. La deuxième ligne affecte le mot "5" à la variable b. Dans la troisième ligne, les symboles $ signifient qu'il faut substituer les mots qui suivent par leur valeur : respectivement set et 5. Ainsi, la ligne s'écrit set c 5 après substitution. L'interprétation est alors effectuée et la variable c vaut 5. Le langage Tcl permet donc de substituer n'importe quel mot d'une ligne de commande, même le mot de la fonction. Il faut donc se souvenir qu'une ligne de commande commence toujours par les substutions avant l'évaluation. Cette façon de procéder peut être avantageusement exploitée par l'utilisation des crochets [].

Les crochets [] permettent d'évaluer le résultat de la chaîne concernée avant d'effectuer l'évaluation de la ligne. Ainsi, les trois lignes précédentes peuvent être écrites sur deux lignes:

set a set
$a c [$a b 5]

L'interpréteur Tcl commence par sustituer les mots commençant par $ par leur valeur. Après la phase de subtitution, la ligne "$a c [$a b 5]" devient "set c [set b 5]". Avant d'évaluer la ligne substituée, l'interpréteur analyse la présence des crochets et traite chaque expression entre crochet comme une ligne de commande. Après cette étape, la ligne est devenue "set c 5" et elle est alors évaluée.

4.3. Toutes les variables sont du type chaîne de caractères

Le langage Tcl n'utilise qu'un seul type de variable: les chaînes de caractères. Ceci permet une manipulation très simple. Par exemple :

set a hello
set b Audela
set c "$a $b"

La variable c est la concaténation des mots "hello" et "Audela" (avec un espace entre les deux mots). Le langage Tcl permet d'écrire facilement du texte contenant des variables :

set infoversion "Vous utilisez Tcl version $tcl_patchLevel"

La variable tcl_patchLevel existe dans l'interpréteur Tcl dès sa création. D'autres variables contiennent diverses informations.

Rappelons que l'on peut évaluer une expression mathématique en utilisant la fonction expr. Par exemple :

set b 5
set d [expr sin($b)+2]

Les variables b et d sont des chaînes de caractères. La fonction expr traite ses arguments comme une expression mathématique à calculer numériquement.

4.4. Les listes

Tcl contient des fonctions très puissantes pour traiter les chaînes de caractères. Il est souvent utile d'utiliser des listes de chaînes. Par exemple :

set a [list "tata" "titi" "tutu"]

Cette ligne est interprétée de la façon suivante. L'interpréteur va d'abord évaluer l'expression entre crochets

list "tata" "titi" "tutu"
et va donc créer une liste contenant trois éléments. Le premier élément est tata, le deuxième est titi et le troisième est tutu. Cette liste est ensuite stockée dans la variable a (set a ). Les fonctions de liste permettent d'ajouter, de retirer, de trier, de remplacer, de rechercher, etc. Par exemple, la fonction lindex permet d'extraire un élément à une liste :

set b [lindex $a 0]

Cette ligne applique la fonction lindex sur la liste $a. Le mot 0 (zéro) représente la valeur de l'indice de l'élément à extraire : par convention 0 pour le premier élément. Ainsi, la variable b prendra la valeur tata. Pour ajouter un élément à la liste, on utilisera la fonction lappend :

set a [lappend $a "tyty"]

L'exemple ci dessous montre la puissance des fonctions de Tcl qui remplacent avantageusement (et sans bug !) de multiples lignes de Basic,

BASIC
Tcl
a$ = "toto titi" nwords=1 for k=1 to len$(a$) if (a$(k)=" ") then nwords=nwords+1; endif endfor tosearch="titi" for k=1 to len$(a$)-len$(tosearch) if (a$(k)=" ") then nwords=nwords+1 endif if (mid$(a$,k,k+len$(tosearch))=tosearch$) then index=nwords break endif endfor set a [list toto titi] set nwords [llength $a] set tosearch "titi" set index [lsearch $a $tosearch]

5. Pour en savoir plus

Les fonctions Tcl sont très nombreuses. Nous recommandons d'abord de lire les tutorials du site http://tcltk.free.fr ainsi que le cours d'Anne Possoz qui permettent de comprendre le B-A BA du Tcl/Tk. Citons aussi un livre en français : "Tcl/Tk précis&concis" éditions O'Reilly (http://www.oreilly.fr).

Sur le WEB, on peut consulter la liste exhaustive des fonctions disponibles

http://www.scriptics.com/man/tcl8.0/contents.htm.

Enfin, le Guide de référence Tcl/Tk (pages 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13) permet d'avoir la liste de toutes les fonctions dans un format très pratique pour consulter près du clavier. Attention il s'agit de la version 7.6. Audela utilise la version 8.0 qui est enrichie de quelques fonctions supplémentaires.

6. Compléments

Lorsqu'un script devient complexe, il est utile de bien le structurer. Tcl permet de gérer les variables et les fonctions des scripts. La liste qui permet d'assembler de multiples valeurs entres-elles.

Les arrays ont à peu près la même fonction que les listes à cela près que l'indexation des éléments n'est pas ordinale (avec des chiffres) mais est nominale (avec des noms). Enfin, les fonctions peuvent elles aussi être assemblées sous forme de namespaces afin d'être repérables facilement.

6.1. Les arrays

Il est souvent utile de regrouper plusieurs variables dans une même catégorie. Par exemple, on souhaite calculer la masse d'air à partir des informations contenues dans l'entête FITS d'une image. Pour cela, on a besoin de la date de début de pose, du temps de pose, des coordonnées célestes de l'objet pointé et des coordonnées géographiques du lieu d'observation. La fonction "calcul_masse_dair" va donc avoir 6 paramètres :

proc calcul_masse_dair { date_obs exposure ra dec longitude latitude
      } {
 # --- date_obs : une liste {yyyy mm dd hh mm ss}en TU
 # --- exposure : en secondes
 # --- ra : en degrés
 # --- dec : en degrés
 # --- longitude : en degrés (négatif vers l'est)
 # --- latitude : en degrés (négatif dans l'hémisphère sud)
 #
 # --- jour julien de debut de pose
 set jj_debut_de_pose [mc_date2jd $date_obs]
 # --- jour julien du milieu de pose
 set jj_milieu_de_pose [expr jj_debut_de_pose+$exposure/86400./2.]
 # --- mise en forme du lieu
 if {$longitude<0} {
 set longitude [expr -1.*$longitude]
 set sens E
 } else {
 set sens W
 }
 set home "GPS $longitude $sens $latitude"
 # --- calcul des paramètres altaz
 set resultat [mc_radec2altaz $ra $dec $home $jj_milieu_de_pose]
 # --- extrait la valeur de la hauteur sur l'horizon
 set hauteur [lindex $resultat 1]
 # --- calcul de la masse d'air
 set masse_dair [expr 1./sin($hauteur)]
 # --- retourne le résultat
 return $masse_dair
}

mc_radec2altaz est une fonction de la librairie d'extension libmc. Les 6 arguments d'entrée sont lourds à manipuler. C'est pour soulager l'écriture que les "arrays" existent. Avant d'appeler la fonction, on enregistre la valeur des 6 paramètres dans un array que nous allons appeler info_image :

set info_image(date_obs) {2000 10 8 14 05 21}
set info_image(exposure) 60
set info_image(ra) 34.5678
set info_image(dec) 83.1235
set info_image(longitude) -2.8763
set info_image(latitude) 48.5423
proc calcul_masse_dair { info_image } {
 # --- jour julien de debut de pose
 set jj_debut_de_pose [mc_date2jd $info_image(date_obs)]
 # --- jour julien du milieu de pose
 set jj_milieu_de_pose [expr jj_debut_de_pose+$info_image(exposure)/86400./2.]
 # --- mise en forme du lieu
 if {$info_image(longitude)<0} {
 set longitude [expr -1.*$info_image(longitude)]
 set sens E
 } else {
 set longitude $info_image(longitude)
 set sens W
 }
 set home "GPS $longitude $sens $info_image(latitude)"
 # --- calcul des paramètres altaz
 set resultat [mc_radec2altaz $info_image(ra) $info_image(dec)
    
$home $jj_milieu_de_pose]
 # --- extrait la valeur de la hauteur sur l'horizon
 set hauteur [lindex $resultat 1]
 # --- calcul de la masse d'air
 set masse_dair [expr 1./sin($hauteur)]
 # --- retourne le résultat
 return $masse_dair
}

Le regroupement des 6 paramètres dans un seul paramètre simplifie l'écriture de la fonction de calcul de la masse d'air. Si un jour vous enregistrez la valeur de la pression et de la température ambiante dans l'entête de l'image, il suffit d'ajouter deux éléments à l'array info_image, info_image(temperature) et info_image(pression) et de modifier le calcul de la masse d'air, à l'intérieur de la fonction calcul_masse_dair sans avoir à changer le nombre de paramètres d'entrée (ce qui évite une source d'erreur).

Les arrays sont très couramment employés dans l'interface Audace.

Pour les programmeurs C, les arrays s'apparentent beaucoup aux structures. Ainsi pourrait on transcrire l'array info_image sous la forme d'une structure en langage C :

struct {
 char date_obs[255];
 double exposure;
 double ra;
 double dec;
 double longitude;
 double latitude;
} info_image ;

6.2. Les namespaces

Les arrays permettent de regrouper un ensemble cohérent de variables.Un namespace permet de regrouper un ensemble cohérent de fonctions de même catégorie. Le nom du namespace est toujours suivi de :: pour le distinguer. Les fonctions Tcl de base (set, for list, etc.) font partie du namespace :: et on devrait donc, en toute rigueur, parler des fonctions ::set, ::for, ::list ,etc. Pour ne pas alourdir la syntaxe, l'interpréteur Tcl accepte que l'on supprime les deux premiers double points.

Pour comprendre l'intérêt d'un namespace, prenons l'exemple de fonctions de traitement d'images que vous utilisez couramment : offset, sub, div, smedian, etc. Ces fonctions vous paraissent peut être insuffisamment complètes et vous aimeriez les enrober un peu pour vos besoins personnels. Idée recevable qui mérite l'usage d'un namespace. Nommons le trt (pour traitements) :

namespace eval ::trt {
 proc subd { {filename dark.fit} } {
 ::sub $filename 0
 ::visu
 }
 proc divf { {filename flat.fit} } {
 ::div $filename 10000
 ::visu
 }
}

Nous venons ainsi de créer deux fonctions qui seront appelées ::trt::subd et ::trt::divf (si on veut les utiliser depuis la console de Audace ou bien dans un script). En quoi ces fonctions ont-elles un intérêt ?

La fonction ::sub de l'interface Audace exige deux arguments d'entrée : un nom de fichier et la valeur d'une constante à ajouter aux pixels. Or vous avez l'habitude de vous servir de la fonction sub pour soustraire une image dark à votre image et de ne pas ajouter de constante. Par défaut, on a défini, dans le namespace, que la fonction ::trt::subd allait soustraire l'image "dark.fit" a celle en mémoire. Enfin, vous voulez visualiser l'image après le traitement (fonction ::visu de l'interface Audace). La fonction subd du namespace trt est donc facile d'emploi :

::trt::subd

C'est tout ! la soustraction du dark est réalisée et l'image est affichée à l'écran. La fonction ::trt::divf permet de diviser par un flat bien connu. Ces deux fonctions reposent sur les mêmes démarches et méritent d'être assemblées dans la même catégorie, c'est à dira au même namespace.

Afin de structurer les différentes fonctions d'un grand script, il est conseillé d'user des namespace. C'est ainsi que cela fonctionne dans les scripts de l'interface Audace.

Les programmeurs de C++ auront remarqué que le namespace est assez proche de la notion d'objet à cela près que la définition de la classe et l'instanciation de l'objet se passent en même temps dans un namespace.

| English Version | Mailing List Audela | ©2004 Audela | Design TechnoSpeak