[Richard Suchenwirth] - ''This page ("simply Tcl") is in German for even more i18n;-) It is the draft of a planned presentation. First thought of ppt or Word, but somehow I preferred the Wiki - plus allowing at least German speakers from Tclworld to discuss this while I'm writing it - best place your comments etc. at bottom of page!'' For a French course, see [http://freealter.com/fr/ProjetsLibres/tcltk/] ---- '''Tcl''' (Tool command Language, ausgesprochen "tickel") ist eine einfache, aber m�chtige '''Programmiersprache'''. Da Turing-vollst�ndig, kann im Prinzip jede Aufgabe in Tcl gel�st werden, jedoch nicht immer optimal. Tcl wird (zusammen mit Perl, Python, VB) auch als '''Scriptingsprache''' bezeichnet. Zu "System-Programmiersprachen" wie C, Java, Assembler bestehen folgende Unterschiede: * nahezu typfrei ("Everything is a string" / intern schon getypt) * Variablen m�ssen nicht deklariert werden - automatische Speicherverwaltung * Ausf�hrung durch Interpreter (byte-kompilierte Prozeduren) * Interaktive Nutzung m�glich, reichhaltige Introspektionsm�glichkeiten * Aussagekr�ftige Fehlermeldungen (fast "Online-Hilfe"); Weiterarbeit m�glich * Compile-Link-Zyklus entf�llt, daher schnelle Entwicklung * Wartbarkeit durch Script-Editieren bis zur Zielmaschine * Laufzeitverhalten teilweise schlechter als vollkompilierter Code Tcl kann mit '''C/C++/Java-Code f�r laufzeitkritische Teile''' auf verschiedene Weisen verbunden werden: * Einbettung (Applikation enth�lt Tcl-Interpreter) * Erweiterung (statischer oder dynamisch gelinkter Code wird f�r Tcl zug�nglich gemacht) * ''Tcl_LinkVar'': Bindung von Tcl-Variablen an C-Variablen (int, double, boolean, string) * ''Tcl_CreateObjCommand'': Bindung eines Tcl-Kommandos an eine C-Implementation * externe Server ([exec], [open]: namenlose bidirektionale Pipes) '''Tk''' (ToolKit) ist die wichtigste Tcl-Erweiterung, ein plattformunabh�ngiger GUI-Toolkit (X11, Windows, Macintosh): * extrem einfache GUI-Spezifikation * gleiche Quelldatei l�uft auf mehreren Plattformen mit "native look & feel" * Leistungsf�hige "Widgets": canvas, text, menu, scrollbar, Buttons... * Einfache aber m�chtige Geometrie-Manager: pack, place, grid * Tk wird auch mit Perl und Python als GUI-Toolkit verwendet Weitere bekannte '''Erweiterungen''': * [[incr Tcl]] - OO-Unterst�tzung �hnlich C++ * Expect - Unterst�tzung von Kommunikation zwischen verschiedenen Rechnern, "Fernsteuerung" von Applikationen * BWidget - Erweiterung von Tk um komplexere "Megawidgets" in purem Tcl * TclX - Erweiterung um Posix-Systemfunktionalit�ten Tcl bietet starke Unterst�tzung von '''Internationalisierung''' mit '''Unicode''': * alle Strings intern in Unicode/UTF-8 * Stringkonstanten mit ''\uxxxx'' k�nnen jeden Unicode darstellen * Hin- und Herkonvertierung in viele andere internationale Encodierungen * Tk: automatische Zeichensuche, falls der aktuelle Font ein gew�nschtes Zeichen nicht enth�lt * Einfache Lokalisierung mit [msgcat] Tcl vereint Konzepte aus verschiedenen Software-Welten: * LISP/Scheme: Liste als zentrale Datenstruktur, polnische Notation, Code = Daten * C: Kontrollstrukturen (for, while, if); Arithmetik * Shells: Trennung von Parsing und Ausf�hrung; cd, pwd, glob... * SNOBOL/awk: assoziative Arrays (Hashtables) * X/...: Eventmodell, ereignisgesteuerte Verarbeitung * ?: Traces - Notifikation bei Lesen/Setzen/L�schen einer Variablen Tcl hat die '''einfachste Syntax''' aller Scriptingsprachen: * keine Schl�sselw�rter, alle Kommandos werden gleich behandelt * ''Script'': Folge von Kommandos, durch \n oder ";" getrennt * ''Kommando'': Folge von W�rtern, durch Whitespace getrennt; erstes Wort ist Kommandoname * ''Wort'': whitespacelose Zeichenkette, oder gruppiert mit ".." {..} * "..": "substituiere und gruppiere mich zu einem Wort" * {..}: "gruppiere mich, aber r�hr meinen Inhalt nicht an" * Rekursiver Interpreteraufruf f�r [[..]]-eingebettete Scripts * [[..]]: "evaluiere mich zuerst zu einem String" * Variablensubstitution durch ''$name'' , Array-Elemente: ''$name($key)'' * Namensraum-Syntax: ''::foo::bar''; :: ist der globale Namensraum * Alle weitere Syntax liegt in Verantwortung der Kommandos '''Kommandos''' haben folgende Eigenschaften: * Implementation in C oder als Tcl-''proc'' * Argumente mit Defaultwert oder variabler Anzahl m�glich * jedes Kommando parst seinen Input nach eigenem Bedarf * Kommandos k�nnen umbenannt und �berladen werden * Bytecompilation beim ersten Aufruf, danach Wiederverwendung des Bytecodes * Neudefinition zur Laufzeit m�glich (''proc'' ist auch nur ein Kommando) * auch Kontrollstrukturen sind "nur" Kommandos, k�nnen �berladen oder erweitert werden * alle Variablen von Procs sind lokal, k�nnen aber mit globalen Variablen oder Variablen h�her im Callstack verbunden werden '''Strukturierung''' von Tcl-Applikationen: * Auch in Tcl kann man unwartbaren Code schreiben, mu� es aber nicht * Explizites Laden anderer Scripte mit ''source'' (''#include'' zur Laufzeit) * Automatisches Laden anderer Scripte bei Bedarf ''(auto_index)'' * Modularisierung durch ladbare ''packages'' (Script und/oder kompiliert) * Daten- und Prozedurkapselung durch Namensr�ume '''Datenhaltung in Tcl''': * Listen mit automatischer Speicherverwaltung * Assoziative Arrays (Hashtables): Schl�ssel -> Wert * �ber geeignete Schl�ssel ($i,$j) mehrdimensionale Arrays simulierbar * Formatierung von Daten aller Art (auch bin�r, mit Nullbytes) in Strings * Implizite Datenwandlung Liste <-> String <-> Zahl bei Bedarf '''Ablaufsteuerung in Tcl''': * z.B. konventionell sequentiell bzw. ''if, for, while, foreach'' * Bindung von Kommandos an GUI-Ereignisse (Maus, Tastatur) * Bindung von Kommandos an Datei-Ereignisse ''(fileevent)'' * Bindung von Kommandos an Timer-Ereignisse ''(after)'' * Bindung von Kommandos an Daten-Ereignisse (Variablen-Trace) '''Die wichtigsten Kommandos''': * [set] varname value ;# Wertzuweisung * [expr] mathexpression ;# Arithmetik und Vergleiche * [proc] name arglist {body} ;# Definition einer Prozedur * [if] {condition} {body} * [while] {condition} {body} * [foreach] iterator liste {body} ;# Iteration �ber Liste * [for] {set i 0} {$i<$max} {incr i} {...} ;# analog zu C * [return] ?value? ;# kann weitgehend vermieden werden * [break] ;# aus while/foreach/for-Schleifen * [catch] {body} ?result? ;# f�ngt Fehler in ''body'' auf * [error] "deutliche Fehlerbeschreibung" '''Die wichtigsten Listen-Kommandos:''' * [list] el... ;# Anlage einer Liste aus Einzelelementen * [lappend] listname el... ;# Anh�ngen weiterer Elemente * [lindex] list index ;# Extraktion eines Elements anhand Position * [lrange] list from to ;# Extraktion eines zusammenh�ngenden Bereichs * [lsearch] list term ;# Suche eines Elements in einer Liste * [llength] list ;# gibt die L�nge der Liste zur�ck * [split] string ?splitchars? ;# Zerlegt einen String an ''splitchars'' in Listenelemente * [join] list ?joinchars? ;# F�gt ''joinchars'' zwischen je zwei Listenelemente, erzeugt einen String '''Die wichtigsten String-Kommandos''': * [string] length/is/compare/first... * [append] varname string.. ;# verl�ngert den String in ''varname'' * [regexp] re string.. ?-> varname1 varname2..? * [regsub] re instring replacement outvarname ;# ersetze ''re'' in ''instring'' durch ''replacement'', Ergebnis geht nach ''outvarname'' '''Die wichtigsten I/O-Kommandos:''' * [gets] channel ?varname? ;# bis Zeilenende (exklusiv) * [puts] channel string ;# mit Zeilenvorschub * [open] filename ?mode? ;# gibt Channel-Id zur�ck * [close] channel * [read] channel ?howmuch? ;# ohne R�cksicht auf Zeilenenden ---- Tcl hat eine kleine, aber qualifizierte weltweite '''Anwendergemeinschaft''', die vor allem auf folgenden �ffentlichen Wegen kommuniziert: * Newsgroup news:comp.lang.tcl ("die freundlichste Gruppe im Usenet") * Tcl'ers Wiki, bidirektionale Website mit reichhaltigem Inhalt: http://mini.net/tcl/ * Tcl'ers Chat (manchmal weltweites Debugging in quasi-Echtzeit) http://mini.net/cgi-bin/chat.cgi * Tcl Developer Xchange, Download: http://tcl.activestate.com * Fehlermeldungen und Patchvorschl�ge an http://sourceforge.net/projects/tcl/ ---- '''Beispiel: Mittelwert einer Zahlenliste - prozedural oder funktional''' proc mean {L} { set sum 0 foreach element $L { set sum [expr {$sum+$element}] } return [expr {$sum/[llength $L]}] } #### or: #### proc mean L {expr double([join $L +]) / [llength $L]} ;# funktional Die Elemente der Liste L werden mittels ''[join]'' durch "+"-Zeichen zu einem String verbunden. Dieser wird in ''[expr]'' ausgewertet, durch ''double'' in Gleitkommazahl gewandelt (um Integer-Division zu vermeiden) und durch die L�nge der Liste geteilt. Das Ergebnis von ''[expr]'' ist implizit der R�ckgabewert von ''mean''. ---- '''Beispiel: deutsche Umlaute normieren''' proc noUmlaut s {string map {� AE � OE � UE � SS} $s} ''[string] map'' wird mit einer Liste von alternierend von-nach und einem String aufgerufen nimmt Ersetzungen von ''von'' nach ''nach'' vor und gibt den ge�nderten String zur�ck, der auch R�ckgabewert von ''noUmlaut'' ist. ---- '''Beispiel: Zuf�lliges Ziehen ohne Zur�cklegen aus einer Liste''' proc ldraw {_L} { upvar 1 $_L L set pos [expr {int(rand()*[llength $L])}] set res [lindex $L $pos] set L [lreplace $L $pos $pos] set res } Die ''rand''-Funktion von [expr] liefert eine Zufallszahl zwischen 0 und <1, die mit der Listenl�nge multipliziert und abgerundet einen g�ltigen Listenindex ''pos'' ergibt. Das dortige Element wird geholt und mit [lreplace] aus der Liste entfernt, die deshalb �ber Name und [upvar] �bergeben werden mu�. ---- '''Beispiel: Spiegeln einer Liste''' proc lrevert L { set i [llength $L] set res {} while {$i} {lappend res [lindex $L [incr $i -1]]} set res } % lrevert {foo bar grill test} test grill bar foo Die Schleife �ber die Elemente der Liste beginnt mit N-1 (N als L�nge der Liste), da String- und Listenindizes ab 0 z�hlen, und endet mit 0. Die entsprechenden Listenelemente werden mit [lindex] geholt und mit [lappend] an die Ergebnisliste ''res'' geh�ngt. ''[set] res'' ist als letztes Kommando einer [proc] �quivalent zu ''[return] $res''. ---- '''Beispiel: Introspektion des eigenen Proc-Namens''' proc myProcName {} {lindex [info level -1] 0} ''[info] level'' gibt Zugriff auf den Aufrufstapel (callstack), ''info level -1'' das Kommando, mit der der Aufrufer dieser Prozedur aufgerufen wurde. Das erste Element des Kommandos ([lindex] ... 0) ist definitionsgem�� der Name. ---- '''Beispiel: Zahlengenerator''' Diese Prozedur gibt bei jedem Aufruf eine andere Zahl zur�ck, aufsteigend 1,2,3,.... Bei jedem Aufruf definiert sie sich neu, wobei der Body unver�ndert bleibt (sie "schaut in sich selbst", aber der Defaultwert f�r den optionalen Parameter ''seed'' inkrementiert wird. proc intgen {{seed 0}} { proc intgen "{seed [incr seed]}" [info body intgen] set seed } ---- '''Beispiel: Konstanthalter''' Die Variable ''name'' wird auf den Wert ''value'' gesetzt, und zwar im Skopus des Aufrufers ([uplevel] 1). Dann wird ebenfalls dort ein Variablentrace gesetzt, der bei schreibenden Zugriffen auf ''name'' den urspr�nglichen Wert zur�cksetzt: proc const {name value} { uplevel 1 [list set $name $value] uplevel 1 [list trace var $name w "set $name [list $value];#" ] } % const Pi 3.14159 % set Pi 4 3.14159 ---- '''Beispiel: �berladen eines Kommandos''' Jedes Tcl-Kommando, auch das elementarste [set], kann �berladen werden, um z.B. Zusatzfunktionalit�t hinzuzuf�gen (etwa auf ''stdout'' die Aktivit�t anzuzeigen). Dazu wird das Original zun�chst umbenannt, um es nicht zu verlieren: rename set _set proc set {name args} { if [info level]>1 {puts -nonewline [info level -1]:} puts [info level 0] uplevel 1 _set $name $args } So entsteht mit wenig Aufwand Debug-Funktionalit�t, die sich wieder ausschalten l��t mit rename set _; rename _set set; rename _ "" ;# l�schen ---- '''Beispiel: Klagloser Incrementor''' Das Tcl-Kommando [incr] leistet schnelle Integer-Addition bzw. Subtraktion, vorausgesetzt die Variable existiert bereits. Damit sie im anderen Fall automatisch angelegt wird (wie bei ''awk'' �blich), faktorisieren wir die sonst erforderliche Existenzpr�fung an diese proc aus: proc inc {varname {amount 1}} { upvar 1 $varname var if {![info exist var]} {set var 0} incr var $amount } Hier wird die lokale Variable ''var'' an den in ''varname'' �bergebenen Namen einer (m�glichen) Variablen (kann auch Array-Element sein) im '''Skopus des Aufrufers''' gebunden. Damit kann ''var'' gepr�ft, intialisiert, inkrementiert werden.. und f�r den Aufrufer ist das gleiche mit ''$varname'' geschehen. ---- '''Beispiel: Zeichenh�ufigkeit''' proc charFreq string { foreach char [split $string ""] { inc t($char) } foreach {char count} [array get t] { lappend pairs [list $char $count] } lsort -integer -decreasing -index 1 $pairs } Der ''string'' wird in Einzelzeichen gesplittet, �ber die iteriert und ein Element in dem tempor�ren Array ''t'' klaglos inkrementiert wird. Der Inhalt des Arrays, eine ungeordnete flache Liste von alternierend Zeichen und Anzahl, wird in eine Liste von Paaren gewandelt, die dann bequem nach absteigender H�ufigkeit sortiert werden kann. ''String'' kann Megabytes lang und nat�rlich auch koreanisch, griechisch oder arabisch sein... % charFreq Tennessee {e 4} {n 2} {s 2} {T 1} ---- '''Beispiel: Fehlertolerantes Lesen einer Textdatei''' proc readfile {filename} { if ![catch {open $filename} fp] { set res [read $fp [file size $filename]] close $fp } else {set res {}} return $res } Wenn das �ffnen der Datei ''filename'' gelingt (der ''[catch]'' nicht anschl�gt), wird sie in ganzer L�nge in die Variable ''res'' gelesen und zur�ckgegeben. Andernfalls wird ein leerer String zur�ckgegeben. M�gliche Anwendung als Zeilenz�hler: proc wc-l filename {llength [split [readfile $filename] \n] } ---- '''Beispiel: Textausgabe in Datei, mit Sicherheitskopie''' proc text2file {text filename} { if [file exists $filename] { file rename -force $filename $filename.bak } set fp [open $filename w] puts $fp $text close $fp } Existiert eine Datei namens ''filename'', so wird sie in ''(filename).bak'' umbenannt. Dann wird eine neue Datei des Namens zum Schreiben ge�ffnet, der �bergebene String (kann beliebig lang sein) mit abschlie�endem Zeilenvorschub hineingeschrieben, und die Datei geschlossen. ---- '''Beispiel: Internet-Dateiherunterlader''' package require http http::config -proxyhost proxy -proxyport 80 puts [http::data [http::geturl [lindex $argv 0]]] Verwendet wird das mit Tcl mitgelieferte package ''http''. Mit ''http::config'' wird der hier �bliche Proxy-Rechner eingestellt. Die bei Aufruf des Scripts angegebene URL wird geladen und ihr Datenanteil (der eigentliche Inhalt) auf ''stdout'' ausgegeben, kann jedoch in eine Datei umgeleitet werden: $ tclsh ~/tcl/wwwget.tcl www.bahn.de/img/agb.gif > t.gif ---- '''Beispiel: ein kleines Zeichenprogramm''' proc doodle {w {color black}} { bind $w <1> [list doodle'start %W %x %y $color] bind $w {doodle'move %W %x %y} } proc doodle'start {w x y color} { set ::_id [$w create line $x $y $x $y -fill $color] } proc doodle'move {w x y} { eval $w coords $::_id [concat [$w coords $::_id] $x $y] } pack [canvas .c] doodle .c F�r den Canvas werden Bindungen f�r "Mausklick links" (neue Linie beginnen, deren Anfang und Ende zusammenfallen: ''doodle'start'') und "Mausbewegung mit gedr�ckter linker Taste" (aktuelle Linie, durch globale Variable ''::_id'' zug�nglich, zum Zielort der Bewegung verl�ngern: ''doodle'move'') definiert. ---- '''Beispiel: Digitaluhr in 6 Zeilen''' proc every {ms body} { eval $body after $ms [list every $ms $body] } pack [label .clock -textvar time] every 1000 {set ::time [clock format [clock sec] -format %H:%M:%S]} Zun�chst wird ein einfacher, aber wiederverwendbarer Timer, ''every'', definiert, der den �bergebenen ''body'' (Tcl-Kommandos) ausf�hrt und anschlie�end veranla�t, da� er nach ''ms'' Millisekunden erneut aufgerufen wird. Dies ist keine Rekursion! UI: Ein ''[Label]'' wird angelegt, an die Textvariable ''time'' gebunden, und in das Hauptfenster "gepackt". Mit ''every 1000 ...'' wird der Wert von ''time'' sek�ndlich auf die aktuelle Zeit gebracht und automatisch im Label angezeigt. ---- '''Beispiel: Analoguhr in etwas mehr Zeilen''' proc drawhands w { $w delete hands set secSinceMidnight [expr {[clock seconds] - [clock scan 00:00:00]}] foreach divisor {60 3600 43200} length {45 40 30} width {1 3 7} { set angle [expr {$secSinceMidnight * 6.283185 / $divisor}] set x [expr {50 + $length * sin($angle)}] set y [expr {50 - $length * cos($angle)}] $w create line 50 50 $x $y -width $width -tags hands } } pack [canvas .c -width 100 -height 100 -bg white] every 1000 {drawhands .c} Ein [canvas] (Zeichenfl�che) mit 100*100 Pixeln Gr��e wird angelegt. Jede Sekunde werden die Uhrzeiger weggeworfen und als drei ''line''-Items neu gezeichnet, wobei der Zielpunkt des Zeigers �ber Winkelfunktionen bestimmt wird. Man beachte die Mehr-Listen-Form von [foreach], die in drei Durchl�ufen die drei Iterator-Variablen aus den konstanten Listen belegt. Der Timer ''every'' aus der Digitaluhr wird wiederverwendet. ---- '''Beispiel: eine umschaltbare Uhr''' Zuletzt eine Uhr, die durch Mausklick von analog zu Digital und zur�ck wechselt. Dazu ein wiederverwendbarer Umschalter, der das sichtbare von zwei Widgets unsichtbar ([pack] forget) und das andere sichtbar macht: proc toggle {w1 w2} { if [winfo ismapped $w2] { foreach {w2 w1} [list $w1 $w2] break ;# swap } pack forget $w1 pack $w2 } Man beachte das Idiom zum Vertauschen zweier Variablenwerte (w1,w2) mit [foreach]. Schlie�lich legen wir die beiden Uhren an und setzen sie in Gang, "packen" zun�chst die Analoguhr und binden den linken Mausklick (<1>) im Hauptfenster (.) an die Umschaltung: canvas .analog -width 100 -height 100 -bg white every 1000 {drawhands .analog} label .digital -textvar time -font {Courier 24} every 1000 {set ::time [clock format [clock sec] -format %H:%M:%S]} pack .analog bind . <1> {toggle .analog .digital} ---- Diskussion: [JCW] - I'd like to make a few comments: * When you say ''everything is a string'', please do mention that this is only at the conceptual level. Internally, things run at the speed of typed data. * The first example, Mittelwert/average, is of course very clever, but are you sure you want to bring this across as first example? How about ''pack [[button -text "Hello world"]]''? * No mention of how trivial networking is? TclHttpd? AOL? But above all... it's great to see Tcl summarized this way! ---- [RS] (1) fixed. (2) Added procedural counter-example. (3) Know too little... ;-)