4. Instrukcje sterujące

Uwaga! Informacje na tej stronie mają ponad 5 lat. Nadal je udostępniam, ale prawdopodobnie nie odzwierciedlają one mojej aktualnej wiedzy ani przekonań.

4. Instrukcje sterujące

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.

4.1. Instrukcja warunkowa if

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
  }
}

4.2. Instrukcja wyboru switch

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"
  }
}

4.3. Pętle

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:

4.3.1. Pętla while

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.

4.3.2. Pętla for

Składnia pętli for:

for {<instrukcja-inicjalizacji>} {<warunek>} {<instrukcja-iteracji>} {
  <kod>
}
<instrukcja-inicjalizacji>
To instrukcja, która wykonuje się raz, podczas uruchamiania pętli. Działa tak samo, jak gdybyś postawił ją tuż przed pętlą jako osobną instrukcję.
<warunek>
To warunek sprawdzany przed każdą iteracją pętli. Kod wykonywany jest tylko, jeśli jest on spełniony.
<instrukcja-iteracji>
To instrukcja, która wykonuje się po każdej iteracji pętli.

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.

4.3.3. Pętla foreach

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.

4.3.4. Polecenia sterujące pętlami

break
To polecenie przerywa wykonywanie pętli i powoduje natychmiastowe wyjście z niej.
continue
To polecenie powoduje natychmiastowe rozpoczęcie nowej iteracji pętli.

4.4. Instrukcja obsługi błędów catch

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.

4.5. PROJEKT - reklama

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
[Download] [Dropbox] [pub] [Mirror] [Privacy policy]
Copyright © 2004-2021