[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... ;-)