2.53   Tcl

Tcl (Tool Command Language) è, in origine, uno strumento per controllare l'esecuzione di attività batch, su computer e nasce verso la fine del 1987 ad opera di John Ousterhout dell'Università della California a Berkeley.

Le variabili elementari non sono tipizzate, la tipizzazione è insita nell'utilzzo, in particolare string introduce varie operazioni su stringhe di caratteri ed expr su espressioni numeriche:

set string 33

puts "Ci sono [string length $string] caratteri in \"$string\""

set x [expr $string / 11]

puts "$string / 11 e' $x"

La cui esecuzione produce:

Ci sono 2 caratteri in "33"

33 / 11 e' 3

Dall'esempio emerge il carattere di linguaggio a liste, racchiuse da [ e ], e la caratteristica funzionale principale cioè il meccanismo di sostituzione delle variabili: l'interprete di Tcl analizza il comando effettuando delle sostituzioni:

§   le variabili, se precedute da $, sono sostituite dal loro valore (ma \$ è usato per impedire la sostituzione e \ come ultimo carattere della linea, fa ignorare il ritorno a capo, di fatto agisce da segno di continuazione dell'istruzione),

§   quanto è racchiuso in parentesi quadre, in genere delle istruzioni, è valutato

Tcl ha due strutture di dati complesse, le già incontrate liste e gli array associativi. Le prime sono prolisse da utilizzare, i secondi hanno gli usuali vantaggi di queste strutture.

Le parentesi graffe, essendo anche dei delimitatori di blocco di istruzioni, inibiscono la sostituzione delle variabili, a meno che non siano già dentro una stringa delimitata (% è il prompt della shell wish di Tcl,  # è il carattere di commento, è preceduto dal separatore di istruzioni " ;" :

% set Z " Condor "   ;# Condor    nella variabile Z
% puts {$Z: }
$Z: 
% puts $Z
Condor

La sintassi non sempre è unitaria, per scorrere una matrice associativa  occorre trattarla come un file: inizio della lettura, lettura, test di fine ed eventuale chiusura, a meno di non utilizzare la lista delle chiavi, molto elegante, ma consumatrice di risorse. Qui di seguito due frammenti con la scansione di una lista e di un array.

# stampa gli elementi della lista listobj
   foreach itm $listobj {puts $flID "$itm"}
   # stampa gli elementi dell'array array_oby
   foreach itm [lsort [array names array_obj]] { # array names lista gli indici
     puts $flID "$itm $array_obj($itm)"
     }

Tcl è estensibile, ci sono estensioni che gli forniscono funzionalità Object Oriented (XOTcl, incr Tcl), ma forse la più diffusa estensione è Tk per gestire interfacce grafiche; Tk è portabile e disponibile in diversi ambienti (Ruby , Tkinter di Python ). Il programma che segue genera il grafico della Figura 2‑3 (Ruby)

# Tcl 8.3.2 Tk 8.3.2 
#
# Tree.tcl
#
 
# Il programma lgge da file una lista con livello del nodo, progressivo e nome
# e genera il grafico delle dipendenze
# come parametri riceve il nome del file, il titolo del grafico e 
# la disposizione (o per orizzontale, v (il default) per verticale)
proc CreaNodo {x y nome} {     
  global nodoX nodoY     ;# array con coordinate 
  global vert            ;# orientamento (-1 = verticale)
  .c create oval [expr $x-5] [expr $y-5] [expr $x+5] [expr $y+5] \
      -outline magenta -fill magenta
  set xlabel [expr $x - 40]
  set ylabel [expr $y]
  if {$vert < 0} {set xlabel [expr $x + 40]; set ylabel $y}
  .c create text $xlabel $ylabel -text $nome -fill magenta -font fnt 
  # genero array con coordinate del nodo
  set nodoX($nome) $x
  set nodoY($nome) $y
}
proc CollegaNodi {daNodo aNodo} { 
  # Crea collegamento fra nodi
  global nodoX nodoY 
  set linea [.c create line $nodoX($daNodo) $nodoY($daNodo) \
             $nodoX($aNodo) $nodoY($aNodo) -fill blue]
  .c lower $linea  ;# nasconde la linea sotto i nodi
}
 
set lArea 650
set hArea 650
canvas .c -width [expr $lArea + 50]  -height [expr $hArea + 50]  ;# finestra grafica
pack .c
set titolo [lindex $argv 1]                         ;# prelevo secondo elemento del vettore argv
set vert [string first [string range [lindex $argv 2] 0 0] "oO"]  ;# verifico se o(rizzontale)
.c create text [expr $lArea/2] 25 -text $titolo -font 12 -fill red
font create fnt -size 10 -family Courier ;# creo font Courier 10
set NodoPrec 0
set passo 120       ;# profondità successive
set Margine 80
set largNodo "0 0 0 0 0 0 0" ;# lista ampiezza nodi
set lst_nodi {}     ;# lista righe lette
# lettura dati
set flid [open [lindex $argv 0] r]
while {[eof $flid] == 0}  {
   gets $flid row;
   if {[string length $row] > 0} {lappend lst_nodi $row }
}
close $flid
# prepara array con numero figli
set num_el [llength $lst_nodi]
for {set i 0} {$i < $num_el} {incr i} {
  set counter 0
  set Level [string range [lindex $lst_nodi $i] 0 1];
  for {set j [expr $i + 1]} {$j <= $num_el} {incr j} {
    if {[string range [lindex $lst_nodi $j] 0 1] == $Level} break;
    if {[string range [lindex $lst_nodi $j] 0 1] == [expr $Level + 1]} {incr counter}
  }
  set Nome [string range [lindex $lst_nodi $i] 6 [expr [string length [lindex $lst_nodi $i]]-1]]
  if {$counter == 0} {set counter 2}
  set array_fil($Nome) $counter
}
foreach row $lst_nodi {
    set Nodo [string range $row 0 1];
    set subNodo [string range $row 3 4];
    set Nome [string range $row 6 [expr [string length $row]-1]];
    if {$Nodo == 0} {
      if {$vert < 0} {
        set px [expr - 10 + $lArea / 2]
        set py  $Margine
      } else {
        set py [expr $lArea / 2]
        set px $Margine
      }
      CreaNodo $px $py $Nome;
      set array_livelli(0) $Nome             ;# array con i nomi di livello
      set largNodo [lreplace $largNodo 0 0 $lArea]
    } else {
        if {$Nodo > $NodoPrec} {
            set largNodo [lreplace $largNodo $Nodo $Nodo \
            [expr [lindex $largNodo [expr $Nodo - 1]] /$array_fil($array_livelli([expr $Nodo - 1]))]]
          }
          set var [lindex $largNodo $Nodo]
          if {$vert < 0} {
            set px [expr $var * $subNodo - 1.5 * $var + \
               [expr $nodoX($array_livelli([expr $Nodo - 1]))]]; 
            set py [expr $Nodo * $passo + $Margine]
          } else {
            set py [expr $var * $subNodo - 1.5 * $var + \
               [expr $nodoY($array_livelli([expr $Nodo - 1]))]]; 
            set px [expr $Nodo * $passo + $Margine]
          }
        CreaNodo $px $py $Nome
        CollegaNodi $Nome $array_livelli([expr $Nodo - 1]);
      } 
    set array_livelli([expr $Nodo]) $Nome;
    set NodoPrec $Nodo;
  }
set flImg [lindex $argv 0] 
.c postscript -file "$flImg.eps" -height [expr $hArea + 50] -width $lArea