Najdłuższą i najtrudniejszą część kursu mamy już za sobą. Znamy wszystkie rodzaje danych, ale nadal nie potrafimy tworzyć prawdziwych skryptów. Nie powiedzieliśmy bowiem jeszcze o tym, co w każdym języku programowania musi być i jest bardzo istotne. Mowa o instrukcjach sterujących przebiegiem sterowania kodu.
Instrukcja warunkowa pozwala wykonać jakiś fragment kodu pod jakimś warunkiem.
Składnia if:
if {<warunek>} { <kod> } elseif {<warunek>} { <kod> } elseif {<warunek>} { <kod> } else { <kod> }
Bloków elseif
może być tyle, ile chcesz.
Może też nie być wcale i wtedy zostanie samo if
i else
.
Może też zostać samo if
.
Instrukcja warunkowa wykona fragment kodu tylko jeśli podany warunek zostanie spełniony.
W przeciwnym wypadku wykona kod zawarty w else
.
Jako warunek można podać dowolne wyrażenie zwracające ostatecznie wartość logiczną
wg takiej samej składni, jak w poleceniu expr
.
Ważne jest by pamiętać o pisaniu nawiasów klamrowych dokładnie tak jak w przykładzie powyżej. Jeśli spróbujesz przenieść je do osobnych wierszy, powstanie błąd.
Poniższy przykład sprawdza zakres podanej liczby:
Przykład z if:
proc SetLiczba {a_iLiczba} { variable m_iLiczba if {$a_iLiczba < 0} { set m_iLiczba 0 } elseif {$a_iLiczba > 100} { set m_iLiczba 100 } else { set m_iLiczba $a_iLiczba } }
Jeśli chcesz podjąć jedną z wielu akcji w zależności od jakiejś wartości, lepszym wyborem będzie użycie instrukcji wyboru :)
Instrukcja switch
wybiera jedną z dostępnych możliwości dopasowując
podaną wartość do wartości zawartych w jej wnętrzu i w zależności od tego wykonuje
podany kod. Jeśli podana wartość nie pasuje do żadnej sprawdzanej, wykonany zostanie
kod podany jako default
, jeśli taki istnieje (nie trzeba go umieszczać).
Składnia switch:
switch <wartość> { <wartość> { <kod> } <wartość> { <kod> } default { <kod> } }
Przykład ze switch:
switch $iLiczba { 0 { Debug "Wartosc minimalna" } 100 { Debug "Wartosc maksymalna" } default { Debug "Inna wartosc" } }
Pętla pozwala wykonać zawarty w niej fragment kodu wiele razy, np. określoną liczbę razy, dla każdego elementu listy lub najczęściej tyle razy, ile potrzeba by podany warunek sprawdzany przed lub po każdej iteracji pętli zwrócił określoną wartość logiczną. Istnieje kilka rodzajów pętli:
Składnia pętli while:
while {<warunek>} { <kod> }
W każdej iteracji najpierw sprawdzany jest warunek, a następnie wykonywany kod, o ile warunek jest prawdziwy (zwraca 1). Ponieważ warunek sprawdzany jest na początku, istnieje możliwość że będzie on fałszywy już za pierwszym razem i kod zawarty w pętli nie wykona się ani razu.
Przykład pętli while:
set i 0 while {$i < 10} { Debug $i incr i }
Powyższy kod wypisuje kolejne liczby od 0 do 9.
Składnia pętli for:
for {<instrukcja-inicjalizacji>} {<warunek>} {<instrukcja-iteracji>} { <kod> }
<instrukcja-inicjalizacji>
<warunek>
<instrukcja-iteracji>
Najczęściej pętlę tą wykorzystuje się w podany niżej sposób, z użyciem tzw. zmiennej sterującej. Jej wartość jest inicjalizowana w instrukcji inicjalizującej, sprawdzana w warunku, zmieniana (np. inkrementowana) w instrukcji iteracji i wykorzystywana w kodzie.
Przykład pętli for:
for {set i 0} {$i < 10} {incr i} { Debug $i }
Nietrudno się domyślić, że kod ten robi dokładnie to samo, co przykład z poprzedniej pętli.
Składnia pętli foreach:
foreach <zmienna> <lista> { <kod> }
Pętla ta pozwala iterować po kolejnych elementach listy. Dla każdego elementu podanej listy wykonywany jest podany kod, a wartość tego elementu na czas jego wykonania przypisywania jest podanej zmiennej.
Przykład pętli foreach:
set lLista1 { { "Programowanie" "ciekawe" } { "TCL" "prosty" } } foreach lElement $lLista1 { Debug "[lindex $lElement 0] jest [lindex $lElement 1]" }
Pętla foreach
doskonale nadaje się także, oprócz list, do iteracji po
elementach tablicy. Wystarczy otrzymać listę kluczy tablicy poleceniem
array names
.
break
continue
Jak sama nazwa wskazuje, instrukcja ta służy do łapania. Zapewne podczas programowania w TCL już nieraz spotkałeś się z błędami. Niezłapany błąd powoduje przerwanie wykonywania kodu lub, jeśli to był kod wykonywany podczas ładowania bota, całkowity jego wysyp. Teraz nauczymy się, jak błędy można łapać.
Składnia catch:
catch { <kod> }
Podany kod jest wykonywany, ale wystąpienie błędu nie powoduje przerwania dalszego wykonywania skryptu. Instrukcja ta zwraca 0 jeśli podczas wykonywania kodu nie wystąpił błąd lub kod błędu, jeśli błąd wystąpił.
Wyłapywane są wyłącznie błędy podczas wykonywania. Instrukcja ta nic nie pomoże na błędy składniowe języka TCL uniemożliwiające kompilację jego kodu.
Ta część była krótka, więc za to projekt będzie długi :) Przedstawię skrypt do okresowego wyświetlania reklam na wybranym kanale. Analizę tego kodu pozostawiam tobie jako zadanie.
reklama.tcl:
########################################################################### # Autor : Regedit # Data : 2003-08-15 # Opis : Skrypt do wyświetlania przerwy na reklamę ########################################################################### ########################################################################### # CEL # Skrypt ma co jakiś czas wyświetlać na jednym, ustalonym kanale wybraną # wiadomość reklamową. Wyświeli ją tylko jeśli nikt nic na kanale nie mówi # przez conajmniej 10 minut, jednak nie częściej niż raz na godzinę. ########################################################################### # Baza danych # Enkapsuluje funkcjonalność bazy reklam namespace eval db { # --- STAŁE --- # Ścieżka do pliku z bazą set sFileName "database/reklama.db" # --- ZMIENNE --- # Czy baza została zmodyfikowana od ostatniego otwarcia set bModified 0 # Tablica z zawartością bazy # aData # --- PROCEDURY --- # Otwiera bazę proc Begin { } { variable sFileName variable bModified variable aData # Jeśli plik istnieje if { [file exists $sFileName] } { # Otwarcie pliku set hFile [open $sFileName r] # Odczytanie danych z pliku do tablicy while { ![eof $hFile] } { set sKey [gets $hFile] set sValue [gets $hFile] if { ($sKey != "") && ($sValue != "") } { set aData($sKey) $sValue } } # Zamknięcie pliku close $hFile } # Tyle co wczytałem - nie zmodyfikowany set bModified 0 } # Zamyka bazę i jeśli trzeba, zapisuje zmiany proc End { } { variable sFileName variable bModified variable aData # Jeśli dane zostały zmodyfikowane if {$db::bModified} { # Otwarcie pliku set hFile [open $sFileName w] # Zapisanie danych do pliku if { [ array exists aData] } { foreach {sKey sValue} [array get aData] { puts $hFile $sKey puts $hFile $sValue } } # Zamknięcie pliku close $hFile } # Wyczyszczenie tablicy if { [array exists aData] } { array unset aData } } # Zapisuje rekord proc SetRec { a_sKey a_sValue } { variable bModified variable aData set aData($a_sKey) $a_sValue set bModified 1 } # Usuwa rekord proc DelRec { a_sKey } { variable bModified variable aData unset aData($a_sKey) set bModified 1 } # Odczytuje rekord proc GetRec { a_sKey } { variable aData return $aData($a_sKey) } # Zwraca listę kluczy proc GetKeys { } { variable aData return [array names aData] } # Zwraca liczbę rekordów proc GetCount { } { variable aData return [array size aData] } } ########################################################################### # Bindy # Binduje dowolny tekst wypowiedziany na kanale bind pubm - * OnPubm # Przed rehash - by usunąć timer bind evnt - prerehash OnPreRehash # --- STEROWANIE --- # Polecenie główne bind msg n "reklama" OnMain # Polecenie zapisania (dodanie lub modyfikacja) rekordu bind msg n "reklama_set" OnSet # Polecenie odczytania rekordu bind msg n "reklama_get" OnGet # Polecenie usunięcia rekordu bind msg n "reklama_del" OnDel # Polecenie wylistowania kluczy rekordów bind msg n "reklama_list" OnList ########################################################################### # Stałe # Nazwa kanału, na którym ma pracować skrypt wyświetlając reklamy set Channel "#lamerlandia" # Czas krótszy (oczekiwanie na ciszę na kanale) set Time1 10 # Czas dłuższy (przerwa między wyświetleniami reklamy) set Time2 60 ########################################################################### # Zmienne # 1 lub 0 zależnie, czy ktoś coś powiedział na kanale od ostatniego timera set Said 0 # ID timera set TimerID "" ########################################################################### # Procedury # Wysyła priv do autora w celu debugowania proc Debug {aText} { putserv "PRIVMSG Nick :(DEBUG) $aText" } # Wysyła priv w odpowiedzi proc Response { a_sNick a_sText } { puthelp "PRIVMSG $a_sNick :$a_sText" } # Wysyła linijkę reklamy proc Reklamuj { a_sText } { global Channel putserv "PRIVMSG $Channel :$a_sText" } # Wyświetla reklamę proc Go { } { global Channel db::Begin Reklamuj "#################### R E K L A M A ####################" foreach {sRow} [split [db::GetRec [lindex [db::GetKeys] [rand [db::GetCount]]]] "|"] { Reklamuj $sRow } Reklamuj "#######################################################" db::End } # Ustawia timer na podany w parametrze czas proc SetTimer {aCzas} { global TimerID set TimerID [timer $aCzas "OnTimer"] } # Usuwa timer proc KillTimer { } { global TimerID if {$TimerID != ""} { killtimer $TimerID } } # Procedura obsługi zdarzenia timera proc OnTimer { } { global Said Time1 Time2 # jeśli ktoś coś powiedział if {$Said == 1} { SetTimer $Time1 # jeśli nikt nic nie powiedział } else { Go SetTimer $Time2 } set Said 0 } # Procedura reakcji na zdarzenie powiedzenia czegokolwiek na kanale proc OnPubm {aNick aHost aHandle aChannel aText} { global Channel Said if {$aChannel != $Channel} { return 0 } set Said 1 return 0 } # reakcja na zdarzenie przed rehashem proc OnPreRehash {type} { KillTimer } proc OnMain {a_sNick a_sHost a_sHandle a_sText} { Response $a_sNick "*REKLAMA* Dostepne polecenia: reklama | reklama_set <key> <value>\ | reklama_get <key> | reklama_del <key> | reklama_list" } proc OnSet {a_sNick a_sHost a_sHandle a_sText} { db::Begin set lList [split $a_sText] set sKey [lindex $lList 0] set sValue [join [lrange $lList 1 end]] db::SetRec $sKey $sValue db::End Response $a_sNick "Reklama \"$sKey\" : \"$sValue\" dodana" } proc OnGet {a_sNick a_sHost a_sHandle a_sText} { db::Begin Response $a_sNick [db::GetRec $a_sText] db::End } proc OnDel {a_sNick a_sHost a_sHandle a_sText} { db::Begin db::DelRec $a_sText db::End Response $a_sNick "Reklama \"$a_sText\" usunieta" } proc OnList {a_sNick a_sHost a_sHandle a_sText} { db::Begin set bFirst 1 set sResult "" foreach {sKey} [lsort -dictionary [db::GetKeys]] { if {$bFirst} { append sResult "$sKey" set bFirst 0 } else { append sResult " $sKey" } } Response $a_sNick $sResult db::End } ########################################################################### # Kod # Zainicjalizowanie timera SetTimer $Time2 # Zalogowanie załadowania skryptu putlog "reklama... loaded"Adam Sawicki