DIPL.-ING. MARTIN WEITZEL, 64380 ROSSDORF, GERMANY

RuSQuickTcl2019-01

Unterlagen-Archiv zum Download (aktualisiert zum Kursende)

http://tbfe.de/data/uploads/rus/wetrain-rus_tcl-master.zip

Abschließender Erfolgstest für den Stoff von Montagvormittag

https://goo.gl/forms/EtJCOXZtMh7XDVHs2

Kursbegleitende Übungen für den Stoff ab Montagnachmittag

Hinweis: die Lösungen finden Sie auf diesem Server unter:

http://tbfe.de/en/uploads/rus/sXX.tcl

(XX ist die Nummer des jeweiligen Schritt, ggf. mit führender Null.)

Schritt 1

Schreiben Sie ein einfaches Tcl Programm das eine "Einmaleins"-Tabelle ausgibt, also

  1  2  3  4  5  6  7  8  9  10
  2  4  6  8 10 12 14 16 18  20
 …
 10 20 30 40 50 60 70 80 90 100

Zum Berechnen des Produkts von zwei Zahlen (z.B. in den Variablen i und j) verwenden Sie

set v [expr $i * $j]

und um das Ergebnis (jetzt in Variable v) mit drei Zeichen Breite rechtsbündig auszugeben verwenden Sie

puts -nonewline [format %3d $v]

Legen Sie die Lösung in einer Datei ab (z.B. mit Namen s01.tcl), die Sie diese anschließend in einer Vivado Tcl-Shell mit dem Kommando

source s01.tcl

ausführen. (Der Dateiname s01.tcl steht für "Schritt 1", in späteren Schritten zählen Sie diese Nummer einfach hoch.)

Insgesamt sollte Ihr "Entwicklungs-Zyklus" so aussehen:

  1. Laden Sie die Datei (im Beispiel s.tcl) in einen Text-Editor
  2. Schreiben Sie die initiale Version des Programms
  3. Speichern Sie die Datei ab ohne den Editor zu verlassen
  4. Führen Sie das Kommando source s.tcl in der einer Tcl-Shell aus (z.B. Vivado)
  5. Wenn das Ergebnis Ihren der Aufgabenstellung entspricht => Fertig
  6. Andernfalls nehmen Sie Änderungen vor => zurück zu 3.

Schritt 2

Machen Sie die Anzahl der Zeilen und Spalten der Tabelle leicht veränderbar indem Sie zwei Variablen (z.B. lines und cols) dafür verwenden. Zum Testen mit unterschiedlichen Größen setzen Sie vorher diese Variablen:

set lines 10; set cols 10
source s02.tcl

(Erwartete Ausgabe wie zuvor)

set lines 4; set cols 5
source s02.tcl

Erwartete Ausgabe:

  1  2  3  4  5
  2  4  6  8 10
  3  6  9 12 15
  4  8 12 16 20

Schritt 3 (gestern übersprungen)

Packen Sie die beiden Schleifen, die bislang direkt durch das source Kommando ausgeführt wurden in ein Unterprogramm (Tcl proc). Um nun auf die globalen Variablen zugreifen zu können müssen Sie diese innerhalb des Unterprogramms sichtbar machen.

proc mtable {} {
    global lines cols
    # zwei geschachtelte for-Schleifen wie zuvor
    # ...

}

Zum Testen müssen Sie nun nach den Änderungen im Quelltext diesen nicht nur mit dem source-Kommando neu laden (was zu einer Neu-Definition des Unterprogramms mtable führt, wenn es niicht schon bekann war, sondern dieses auch aufrufen.

source s03.tcl
set lines 10; set cols 10
mtable

(Prüfen ob Ausgabe den Erwartungen emtspricht)

set lines 4; set cols 5
mtable

(Prüfen ob Ausgabe den Erwartungen emtspricht)

Schritt 4 (gestern übersprungen)

Anstelle globale Variable mit der global-Anweisung bekannt zu machen, können Sie diesen beim Zugriff auch zwei Doppelpunkte voranstellen:


for {set i 1} {$i <= $::lines} {incr i} {

    for {set j 1} {$j <= $::cols} {incr j} {
       # berechnen und ausgeben
       # ...
   }
}

Schritt 5

Übergeben Sie die gewünschte Anzahl der Zeilen und Spalten nun mit Argumenten:

proc mtable {lines cols} {
    # wie bisher (aber Zugriff mit $lines und $cols anstatt $::lines und $::cols)
    # ...
}

source s05.tcl
mtable 10 10

(Prüfen ob Ausgabe den Erwartungen emtspricht)

mtable 4 5

(Prüfen ob Ausgabe den Erwartungen emtspricht)

Schritt 6

Anstelle einer Tabelle des (kleinen) Einmaleins oll nun eine Tabelle der Winkelfunktionen (Sinus, Cosinus, und eventuell Tangens) erzeugt werden. Die Funktion soll von vorneherein mit Parametern geschrieben werden, die z.B. folgende Tests erlauben:

source s06.tcl
ftable 0 15 90 {sin cos tan}
ftable 0 30 360 {sin}
ftable 0 1 10 cos

Damit muss die Funktion (in etwa) so aussehen:

proc ftable {start step end functions} {
    # for {set deg $start} {$deg <= $end} {incr deg $step} {
        # zuerst die Gradzahl ausgeben
        # ...

        set rad … # !!! TBD: Grad in Bogenmass umrechnen
        # innere Schleife ueber alle Funktionsnamem in functions
        foreach f $functions {
            set v [expr ${f}($rad)]
            # eventuell $f ($rad)
            # aber NICHT $f($rad)
            #            ^^^^^^^^-sonst Tcl Array-Syntax!

            # ...
            # Ausgabe z.B. mit Formatspezifikation %10.5f
            #         das sind 10 Stellen insgesamt-^^ ||
            #         davon 5 Nachkommastellen---------+|
            #         eine Gleitpunktzahl---------------+
        }
        # ...

    }
}

Schritt 7 (nicht ausgeführt)

Fügen Sie der Ausgabe der Winkelfunktions-Ausgabe eine Überschrit-Zeile hinzu.

Schritt 8 (nicht ausgeführt)

Passen Sie Ausgabeformate in den beiden Funktionen mtable und ftable dynamisch so an, dass sich für alle Argumente und Wertebereiche "optisch schöne" Ausgaben ergeben, also wählen Sie

  • in der Einmaleins die Spatlenbreite abhängig von der größten vorkommenden Zahl
  • in der Winkelfunkionstabelle die Nachkommastellen abhängig von Wert, also
  • um so weniger je größer der Wert ist,
  • mit Umschaltung auf das wissnschaftliche Format für sehr große und sehr kleine Zahlen,
  • und der Ausgabe +inf und -inf für Werte, die (vermutlich) nahe bei "unendlich" liegen, wie etwa Tangens von 90°.

Schritt 9

Erklären Sie was, die Bit-Operationen in der folgenden Schleife machen:

set val 42
set count 0
while {$val > 0} {
    if {$val & 1} {incr count}
    
set val [expr {$val >> 1}]
}

Betten Sie diese Schleife anschließend in eine Funktion bitcount_init ein, welche bits ermittelt für alle Werte von val im Bereich zwischen 0 und 255 und in ener globalen Array-Variablen bitcount_lookup ablegt.

Der Test könnte so aussehen, dass Sie (wie üblich nach Einlesen der Funtionsdefinition) die Funktion aufrufen und exemplarisch einige Wert aus der erzeugten Tabelle ansehen:

source s09.tcl
unset -nocomplain bitcount_lookup
bitcount_init
set bitcount_lookup(0)   ;# -> erwartet: 0
set bitcount_lookup(127) ;# -> erwartet: 7
set bitcount_lookup(128) ;# -> erwartet: 1
set bitcount_lookup(129) ;# -> erwartet: 2
set bitcount_lookup(130) ;# -> erwartet: 2
set bitcount_lookup(42)  ;# -> erwartet: 3

Schritt 10

Übergeben Sie die obere Grenze für die Tabelleninitialisierung als Argument an die Funktion aus Schritt 9. Für en Fall dass der Wert nicht angegeben wird soll der Defaultwert 255 gelten. (Die untere Grenze soll stets 0 sein).

Die Funktion sollte außerdem bei jedem Aufruf den vorherigen Inhalt von bitcount_lookup wieder löschen. Zeigen Sie dies in den Tests indem Sie die Größe des Arrays kontrollieren.

source s11.tcl
bitcount_init 20
array size bitcount_lookup ;# erwartet 21
set bitcount_lookup(10)    ;# -> erwartet 2
set bitcount_lookup(42)    ;# -> erwartet ERROR
bitcount_init
array size bitcount_lookup 
;# -> erwartet 256
set bitcount_lookup(10)    ;# -> erwartet 2
set bitcount_lookup(42)    ;# -> erwartet 3
set bitcount_lookup(255)   ;# -> erwartet 8

Schritt 11

Ändern Sie die Funktion aus Schritt 10 nun so ab, dass der Name des Arrays in dem das Ergebnis abgelegt werden soll als Parameter übergeben wird, d.h. der Test könnte nun wie folgt aussehen:

source s11.tcl
bitcount_init tl
array size tl ;# -> erwartet: 256

set tl(0)     ;# -> erwartet: 0
set tl(128)   ;# -> erwartet: 1
set tl(42)    ;# -> erwartet: 3

Hinweis: Um die Funktion möglichst "robust" gegen Fehlbenutzung zu machen sollte diese die übergebene Variable zunächst löschen und ggf. als Array der Größe 0 neu anlegen. Sehen Sie einen entsprechenden Test vor, z.B.:

bitcount_init xx -1
array size xx ;# -> erwartet: 0

Schritt 12 (optional)

Ändern Sie die Funktion aus Schritt 11 nun so ab, dass sie die vorher in einem Array abgelegte Tabelle nun über Liste zurückgibt, deren Name der Aufrufer übergibt, d.h. der Test könnte nun wie folgt aussehen:

source s12.tcl
bitcount_init tl 127
llength $tl    ;# -> erwartet: 128

lindex $tl 0   ;# -> erwartet: 0
lindex $tl 127 ;# -> erwartet: 7
lindex $tl 42  ;# -> erwartet: 3

Schritt 13 (optional)

Ändern Sie die Funktion aus Schritt 12 nun so ab, dass sie die Liste als return-Wert zurückliefert, d.h. der Test könnte nun wie folgt aussehen:

source s13.tcl
set tl [bitcount_init]
llength $tl     ;# -> erwartet: 256
lindex $t0 0    ;# -> erwartet: 0
lindex $tl 127  ;# -> erwartet: 7
lindex $tl 128  ;# -> erwartet: 1
lindex $tl 129  ;# -> erwartet: 2
lindex $tl 130  ;# -> erwartet: 2
lindex $tl 42   ;# -> erwartet: 3

Schritt 14 (optional)

Ändern Sie die Funktion aus Schritt 13 nun so ab, dass sie die Lookup-Tabelle über ihren return-Wert als dictionary zurückliefert, d.h. der Test könnte nun wie folgt aussehen:

source s14.tcl
set tl [bitcount_init 20]
dict size $tl     ;# -> erwartet: 21
dict get $tl 0    ;# -> erwartet: 0

dict get $tl 10   ;# -> erwartet: 2
dict exist $tl 10 ;# -> erwartet: 1
dict exist $tl 42 ;# -> erwartet: 0

Schritt 15

Schreiben Sie eine Funktion die eine Liste aller Dateinamen erzeugt, die zu einem bestimmten Muster passen, und diese Namen lexikographisch sortiert ausgibt, gefolgt vom Zeitpunkt der letzten Änderung. Hoer der grobe Rahmen inlusive einiger TBDs (to be done):

proc listfiles {{pattern *}} {
    set file_list [glob $pattern]
    foreach … { ;# TBD: … Schleife mit Platzhaltervariable `f`
                 # ueber SORTIERTE `file_list`
        set mtime …   ;# TBD: letzten Aenderungszeitpunkt
                       #      zur Datei `$f` festellen
        puts "$f: … " ;# TBD: Zeitstempel $mtime in
                       #      Text-Format konvertieren
    }
}

Testen Sie wie üblich durch:

source s15.tcl
listfiles
listfile *.tcl

Schritt 16

Ändern Sie ausgehend vom letzten Schrit das Zeitstempelformat in vierstelliges Jahr, Montat und Tag (durch Minuszeichen getrennt) gefolgt von Stunde, Minute und Sekunde durch Doppelpunkte getrennt (wenn Sie es nicht ohnehin so gemacht hatten).

Schritt 17

Verlagern Sie das Auslesen der Zeitstempel in eine getrennte foreach Schleife vor der Schleife, welche die Ausgabe erzeugt:

set time_stamps [list]
foreach f $file_list {
    lappend time_stamps [file mtime $f]
}

Führen Sie die beiden Listen zusammen mit der folgenden Version der foreach-Schleife:

foreach f $file_list t $time_stamps {
    # TBD: Ausgabe von Dateinamen und formatiertem Zeitstempel
}

Wenn die Dateinamen lexikographish sortiert sein sollen, wo müsste das nun passieren?

Schritt 18 (optional)

Legen Sie die Zeitstempel nicht in einer Liste sondern in einer Hilfslste zuammen mit den Dateinamen ab:

set fn_ts_list [list]
foreach f $file_list {
    lappend fn_ts_list [list $f [file mtime $f]]
}

und passen Sie die foreach-Schleife mit der Ausgabe wo erforderlich an.

Schrtt 19 (optional)

Sehen Sie sich das Kommando lsort speziell im Hinblick auf die Option -index und benutzen Sie diese um die Liste der Dateinamen und Zeitstempel

  • entweder nach dem Namen
  • oder nach dem Zeitstempel

sortiert auszugeben.

Führen Sie für die Sortierung ein weiteres Argument sortby ein mit dem Defaultwert name und testen Sie diese mit der Tcl switch Anweisung um die Sortierung festzulegen. Wenn der Wert mtime ist soll nach dem Zeitpunkt der letzten Änderung sortiert werden.

Schritt 20 (optional)

Stellen Sie die Übergabe der Argumente in der Fuktion aus dem vorhergehenden Schritt um auf eine Schlüsselwort-Syntax. Folgende Aufrufformen sollen unterstützt werden:

listfiles ;# Defaults für pattern und sortby
listfiles -sortby mtime  ;# Default nur für sortby

listfiles -pattern *.tcl ;# Default nur für pattern
listfiles -pattern s??.tcl -sortby mtime ;# keine Defaults
listfiles -mtime *.tcl -pattern s??.tcl  ;# Alternative Form

Eine einfache Form die Argumentliste auszuwerten kann dann so aussehen so aus:

proc listfiles {args} {
    set argval(-pattern) *
    set argval(-sortby) name
    array set argval $args
    …
}

Der Zugriff auf die Argumentwerte kann dann so erfolgen:

  • … $argval(-pattern) …
  • … $argval(-sortby) …

Schritt 21 (optional)

Führen Sie ein weiteres Argument content ein, für welches die Werte no, yes und lnum erlaubt sein sollen. Für yes soll auch der Inhalt der Datei ausgegeben werden und für lnum sollen die Zeilen dabei aufsteigend durchnummeriert werden.

Schritt 22 (optional)

Ergänzen Sie wiederverwendbaren Hilfsfunktion checkargs welche überprüft ob als Argument tatsächlich nur die erlaubten Formen angeben wurden und bei Fehlverwendung in Kurzform die korrekte Benutzung anzeigt.