contexte

 

On peut parfois lire qu'un script de callback s'exécute dans un contexte global ou que le script d'une commande trace s'exécute dans le contexte de l'évènement déclencheur.

Que sont ces mystérieux contextes ?

Le contexte (d'exécution) d'une commande est l'ensemble des variables et procédures qu'elle peut référencer directement (sans préfixer le nom de la variable ou de la procédure).

Avant de préciser cela il faut définir quelques notions.

variables locales

Les variables locales d'un script sont les variables définies dans ce script.

Pour les variables du script le plus externe (hors toute procédure), on parle de variables globales (nous verrons plus loin pourquoi).

portée de la déclaration d'une variable

Lorsqu'une variable est définie dans un script elle n'est visible que par les commandes qui la suivent dans le script et qui ne sont pas dans les sous-scripts inclus dans ce script. (nous verrons comment font les if, les while et autres switch).

  set msg "Coucou !"
  proc printMsg {} { puts $msg }

Le script précédent crée une erreur car la variable msg, définie dans le script externe, n'appartient pas au contexte de la commande puts qui est dans un script interne.

Le script suivant, où l'on tente de référencer une variable déclarée dans un script interne créera aussi une erreur :

  proc createVar {} { set msg "Coucou !" }
  puts $msg

portée de la déclaration d'une procédure

Par défaut, une procédure est globale (plus précisément elle est définie dans le namespace courant; voir plus loin). Elle est visible dans tous les contextes.

  proc a {} { proc b {} { puts "b" } }
  b

La procédure b définie dans le script interne est globale et visible en dehors de la procédure a. Le script précédent affichera b sur la console.

contexte d'une commande

Selon la définition précédente, c'est l'ensemble des variables et procédures que la commande peut référencer directement.

Dans le corps d'une procédure, ce sont les paramètres, ainsi que les variables locales et les procédures déjà définies.

  proc hello {who} { set hello "hello"; puts "$hello $who!" }
  hello World

Dans le script externe (en dehors du corps d'une procédure), le contexte d'une commande est l'ensemble des variables globales et des procédures globales déjà définies.

contexte de definition, contexte d'exécution

Les callback sont des scripts qui sont définis et enregistrés avant d'être exécutés (c'est un script en conserve en quelque sorte).

Le contexte de définition dans lequel un callback est défini est différent du contexte d'exécution dans lequel le callback est exécuté :

  proc prepareHello {who} { after 1000 puts "hello $who!" }
  prepareHello World

Dans le script précédent la commande after va créer un script de callback à partir du paramètre de la procédure prepareHello :

  { puts "hello World!" }

Lorsque ce script de callback s'exécutera (une seconde après) la procédure sera terminée depuis longtemps et le paramètre who n'existera plus. Cela ne gênera pas l'exécution du script car la commande after aura construit le script de callback à partir de la valeur du paramètre avant de l'enregistrer.

Par contre, exécuté, le script suivant aurait un gros ennui : la variable référencée dans le script de callback ne serait pas définie dans le contexte d'exécution du script.

  proc prepareHello {who} { after 1000 { puts "hello $who!" } }
  prepareHello World

En effet le script enregistré sera :

  { puts "hello $who!" }

et cherchera la valeur de la variable who au moment de son exécution. Cette variable n'étant pas définie dans le script, cela créera une erreur (d'autant plus pernicieuse qu'elle sera silencieuse).

contexte d'un évènement

Le contexte d'un évènement c'est le contexte d'exécution de la commande qui est à l'origine de l'évènement s'il y en a une (ex : set modifiant une variable pour un évènement trace variable). Sinon c'est le contexte global (ex : évènement lié à la souris ou au clavier).

autres adjectifs pour les contextes

Le contexte d'une procédure s'appelle son contexte local.

Le contexte du script externe s'appelle le contexte global (plus d'info bientôt).

Lorsqu'on appelle une procédure, l'appel se fait dans le contexte appelant. L'exécution de la procédure échange ce contexte par le contexte local à la procédure : le contexte appelé. Lorsque la procédure retourne, on revient au contexte appelant.

On appelle contexte englobant le contexte appelant. Ce contexte est lui-même englobé dans le contexte de la procédure qui a appelé la procédure courante.

contexte d'un script immédiat

Dans un script immédiat (entre crochets) le contexte d'exécution est le contexte englobant.

  set x 123
  set y [set z $x; incr z]
  puts $z

Le script précédent affiche 124 sur la console. En effet :

modification de contexte

La commande upvar permet d'introduire une variable d'un contexte englobant dans le contexte local d'une procédure.

  set who "Peter"
  proc hello {} { upvar who external; puts "hello $external!" }
  hello

La commande uplevel change carrément le contexte local pour le contexte englobant.

  set who "Peter"
  proc hello {} { set who "Dick"; uplevel { puts "hello $who!" } }
  hello

la magie du if

Nous avons vu qu'une variable définie dans un script interne n'est pas vue dans le contexte englobant. Et qu'une variable définie dans le contexte englobant n'est pas vue dans le script interne :

  proc IF {COND BLOCK} { if {$COND} { eval $BLOCK } }
  set who "World"
  set cond 1
  IF $cond { puts "Hello $who!" }

Le script précédent crée une erreur car la variable who n'est pas connue dans la procédure IF.

Mais alors, comment la commande if réussit-elle cet exploit ?

  if {$cond} { puts "Hello $who!" }

En trichant ! et nous pouvons en faire autant :

  proc IF2 {COND BLOCK} { if [uplevel [list expr $COND]] {uplevel [list eval $BLOCK]} }
  IF2 $cond { puts "Hello $who!" }

La commande if ramène tous les scripts qu'elle traite au niveau du script englobant. Ainsi les variables des scripts internes sont celles du contexte englobant le if.

Inutile de dire que les commandes while et switch trichent de la même façon. Et d'autres...

les contextes nommés

Ce sont les namespaces.

Un namespace est un contexte contenant des variables et des procédures, et qui a un nom.

On peut définir une procédure ou une variable dans un namespace :

Remarque : une procédure définie dans le script d'une procédure (pseudo sous-procédure) est en fait définie dans le namespace courant : celui dans laquelle est définie la procédure englobante.

On peut accèder à une procédure ou une variable d'un namespace :

  namespace eval ns \
  {
    variable world "World"
    proc hello {who} { puts "hello $who!" }
    hello $world
  }
  ns::hello $ns::world

Un namespace peut aussi contenir un namespace :

  namespace eval ns1 \
  {
    namespace eval ns2 \
    {
      variable world "World"
    }
    proc hello {who} { puts "hello $who!" }
  }
  ns1::hello $ns1::ns2::world

Et tous les namespaces sont contenus dans le namespace de nom vide :

  namespace eval {} \
  {
    namespace eval ns \
    {
      variable world "World"
    }
  }
  namespace eval ns \
  {
    proc hello {who} { puts "hello $who!" }
  }
  ::ns::hello $::ns::world

Ainsi, contrairement aux apparences, les deux namespaces ns précédents sont au même niveau d'imbrication.

le contexte global

Le namespace de nom vide EST le namespace global, alias le contexte global. C'est aussi le contexte du script le plus externe (celui qui n'est dans aucun script).

Et définir une variable comme globale ou la préfixer par :: revient au même : c'est une variable globale, qui appartient au namespace de nom vide.

modification du contexte (suite et fin)

Une procédure (sans préfixe) est définie dans le namespace courant (qui, la plupart du temps, est le namespace global). La commande variable permet de rajouter au contexte local d'une procédure une variable du namespace où est définie cette procédure.

  namespace eval ns \
  {
    variable who "World"
    proc hello {} \
    {
      variable who
      puts "hello $who!"
    }
  }
  ns::hello

Et comme le contexte global est le namespace de nom vide :

  variable who "World"
  proc hello {} \
  {
    variable who
    puts "hello $who!"
  }
  ::hello

Voir aussi


auteurs de l'article


Encyclopédie Tcl