# Programování a procedury — OCL

Programování se v nejobecnějším slova smyslu zabývá způsoby, jak počítači sdělit algoritmus, který má vykonávat. Od prvopočátků výpočetní techniky, kdy bylo nutno vkládat do paměti počítače přímo jednotlivé instrukce, které pak stroj interpretoval, udělala tato disciplína obrovský pokrok. I když i dnešní počítače v principu nejsou schopny nic více, než jen vykonávat sekvence jednoduchých instrukcí uložených v paměti, způsoby tvorby těchto sekvencí — programů — se dramaticky proměnily.

Pokud z palety přístrojů systému Control Web vyberete zobrazovač hodnoty a v inspektoru mu předepíšete, aby každou sekundu změřil a zobrazil nějakou veličinu, vytvoříte tím program. Z hlediska instrukcí procesoru je to program velmi složitý, zahrnující stovky tisíc instrukcí. Důvod je prostý — samozřejmě neexistuje instrukce pro zobrazení čísla nebo pro vykreslení ručičky měřicího přístroje. Nejsou ani instrukce pro nakreslení přímky (ano, perfekcionisté mohou namítnout, že specializované grafické procesory umí vykreslit přímku jedinou instrukcí, to ale na principu výkladu nic nemění) a dokonce ani pro výpočet funkce sinus, nutné k výpočtu souřadnic konce přímky ze známého úhlu. Procesorové instrukce dokáží jen velmi málo, například přečíst oblast paměti do vnitřního registru procesoru, sečíst dva registry nebo vyhodnotit nějakou podmínku a podle výsledku odskočit do jiné části programu.

Naštěstí vývoj programování již dávno přinesl skvělý vynález — programovací jazyk. Je to způsob zápisu algoritmů na vyšší úrovni, než jsou strojové instrukce procesoru, pomocí symbolických vyjádření. Chcete-li ve vyšším programovacím jazyce naprogramovat sčítání dvou čísel, není nutné znát adresy, na nichž jsou tato čísla uložena v operační paměti, ani není nutné vyhradit pro ně registry procesoru, prostě stačí napsat a + b. Jak je to možné, když procesor rozumí jen svým jednoduchým instrukcím? Do celého procesu je třeba zapojit jiný program — překladač. Překladač převádí zápis algoritmu z programovacího jazyka, většinou srozumitelného člověku, do tvaru strojových instrukcí, kterým rozumí počítač.

Programy tedy lze tvořit pomocí několika prostředků, lišících se úrovní abstrakce, nezávislosti na konkrétním prostředí:

- Strojový kód představuje přímo instrukce pro procesor daného počítače. Jeho použití je velmi pracné a neefektivní, a navíc algoritmus zapsaný pomocí strojových instrukcí pracuje jen na konkrétním typu procesoru a případně i počítače. To jsou hlavní důvody, proč se strojový kód (byť i zapsaný symbolicky v jazyce nazývaném assembler) používá jen naprosto výjimečně, především při implementaci operačních systémů. Na druhé straně je strojový kód nejobecnější vyjadřovací prostředek a lze říci, že pokud něco nelze naprogramovat pomocí strojových instrukcí, pak to nelze naprogramovat vůbec.
- Programovací jazyk je definován množinou pravidel, jimiž se zápis programu, nazývaný zdrojový text, musí řídit (gramatika jazyka) a zdrojový text je pak více či méně nezávislý na prostředí, ve kterém program běží. Překlad zdrojového textu do instrukcí srozumitelných procesoru zajišťuje jiný program — překladač. Programovacích jazyků je celá řada a samy se také liší úrovní abstrakce — od obtížně použitelných jazyků blízkých strojovému kódu (např. jazyk C) po snadněji použitelné modulární jazyky (např. Pascal, Modula-2, Oberon).
- Samostatnou položku si zaslouží programovací jazyky, které jsou mimo samozřejmou schopnost zápisu jakýchkoliv algoritmů schopny komunikovat s jinými komponentami a mohou tak pracovat jako "lepidlo" spojující jednotlivé komponenty. Pro rychlý vývoj aplikací je tato vlastnost velice důležitá, neboť nejefektivněji řeší odvěký problém programování — jak nepsát zbytečně stejný kus programu dvakrát. Klasické jazyky tento problém řeší zavedením knihoven — bloků kódu, který lze k programům připojovat (např. výpočet funkce sinus je typický příklad bloku knihovního kódu, který se v případě potřeby připojí k programu). Knihovny ale mají řadu omezení a až komponenty problém znovupoužitelnosti skutečně řeší. Typickými příklady takových jazyků jsou VisualBasic nebo i OCL (Object Control Language — jazyk řízení objektů) systému Control Web. I když typicky lze komponenty používat i z jiných jazyků, snadnost a přímost práce tím zpravidla utrpí.
- Nástroje rychlého vývoje aplikací představují nejvyšší úroveň abstrakce od principů práce procesoru. Zpravidla umožňují aplikaci "poskládat" z připravených, parametrizovatelných komponent. Právě Control Web je příkladem takového systému.

Motivace zavedení OCL do systému Control Web je tedy zřejmá: zatímco většina aplikací pracuje jako sada předpřipravených komponent, někdy je zapotřebí nástroje sahajícího za hotové komponenty, který dokáže využívat jejich vlastností (a není tedy nutno programovat vykreslování ručičky měřicího přístroje) a přitom může realizovat jakýkoliv algoritmus, doplnit do aplikace potřebnou funkčnost.

## Procedura nebo metoda?

I když se bloky kódu (zápisy algoritmů) v systému Control Web nazývají procedury, přesto se poněkud liší od tzv. procedurálního programování popsaného v řadě klasických učebnic. Podstatný rozdíl tkví především v absenci nějakého "hlavního modulu", který se spustí jako první a který teprve případně volá podprogramy. Proč je tomu tak? Control Web nepracuje klasickým dávkovým způsobem. Control Web je událostmi řízený systém. Neexistuje "hlavní program", namísto toho systém reaguje na události a vyvolává bloky kódu podle daných událostí. Co si ale pod pojmem "událost" představit? U běžných uživatelských aplikací je událost například stisk klávesy nebo pohyb myši. Control Web je ale průmyslový systém reálného času a množina událostí je rozšířená o události generované ovladači vstupně/výstupních zařízení, virtuálními přístroji nebo časovači. Při výskytu dané události je tedy zavolána odpovídající procedura.

Událost se nikdy nevyskytuje "sama o sobě", bez vztahu k nějakému přístroji. Každý stisk klávesy, každý pohyb myši, ale i každá aktivace po uplynutí časového intervalu nebo každá výjimka způsobená ovladačem se týká konkrétního virtuálního přístroje. Z tohoto důvodu jsou i procedury vždy spojeny s konkrétním virtuálním přístrojem. Takový model je velice blízký objektově-orientovanému modelu, v rámci něhož se právě bloky kódu realizující chování objektu nazývají metody objektu. Snad by tedy bylo možné nazývat procedury spíše metodami přístrojů. Avšak objektově-orientované technologie přinášejí komplikace, kterých je lépe se u nástrojů jako je Control Web vyvarovat, a tak zůstáváme u procedur. Jedna věc je ale podstatná — OCL procedury jsou vždy spojeny s nějakým virtuálním přístrojem, nikdy se nevyskytují samostatně.

## Rozhraní procedury — parametry

Jak již bylo řečeno, procedury vykonávají nějaký kód — zápis algoritmu. Algoritmus zpracovává data a nebyl by k užitku, pokud by nenačetl data zvenčí a výsledky opět nevrátil. V podstatě mohou existovat dva způsoby takové výměny dat, které lze samozřejmě kombinovat:

- Procedura může přistupovat na proměnné nebo kanály, které jsou definovány v sekci proměnných a kanálů aplikace nebo i přiváženy z jiných modulů, a číst či zapisovat jejich hodnoty. Takové proměnné a kanály nazýváme globální. Globální datové elementy jsou přístupné z jakékoliv části aplikace, z jakéhokoliv virtuálního přístroje a z jakékoliv procedury.
- Další způsob je definovat parametry procedury. Parametry představují soukromé rozhraní procedury a jejich použití je výhodnější. Hned vysvětlíme proč.

Problémů s přístupem na globální data je několik. Především je nutno vyhradit pro každou nastavovanou proměnnou každé procedury globální proměnnou a jejich počet tak roste. I orientace ve velkém množství globálních proměnných se výrazně ztěžuje. Hrozí problém synchronizace — jedna procedura použije globální proměnnou a během přestávky ve vykonávání kódu (způsobené instrukcemi yield, pause nebo wait — jsou vysvětleny později) jiná procedura její hodnotu přepíše.

Z těchto důvodů může mít každá procedura za svým jménem seznam parametrů — proměnných a jejich typů, např.:

`procedure Calculate( value : real; flag : boolean );`

Na rozdíl od definice globálních proměnných nemají proměnné tvořící parametry procedury inicializační hodnotu — jejich hodnota je vždy definována volajícím kódem. Pokud tedy jakákoliv procedura bude chtít, aby jí procedura Calculate něco spočítala, musí jí předat dvě hodnoty nebo i výrazy, jeden je číslo typu real, další je logická hodnota typu boolean.

Jaká jsou tedy pravidla pro přístup k proměnným, které se objeví jako parametry procedury?

- Parametr se může jmenovat stejně, jako již definovaný globální objekt, lhostejno či konstanta, proměnná nebo kanál. Pak je nutno si uvědomit, že použití takového identifikátoru vždy ukazuje na parametr a globální objekt je zneviditelněn, nelze jej použít. Přitom i pokud existuje např. globální pole kanálů typu real jménem c1 a procedura má definovaný parametr c1 typu boolean, pak v těle procedury lze c1 použít jen jako jednoduchou boolean proměnnou.
- Pokud jsou definovány parametry procedury, tyto parametry jsou viditelné pouze v rámci procedury. Mimo proceduru je nelze použít.
- V parametrech nelze uchovávat žádnou hodnotu, která má "přežít" mezi voláními procedury. Hodnota parametrů je vždy nastavena při jejím vyvolání.

Co se stane, pokud procedura změní hodnotu parametru? Jestliže jsou parametry vlastně proměnné, lze do nich i zapisovat. Vlastní kód procedury ale nemůže ovlivnit, jestli ten, kdo ji volá, přečte změněnou hodnotu zpět. To závisí čistě na kódu, který proceduru volá. Pokud tedy bude výše zmíněná procedura Calculate volána

`InstrumentName.Calculate( a, fl );`

objeví se uvnitř těla procedury v parametru value hodnota elementu a a parametru flag bude hodnota fl. Kód procedury Calculate může měnit hodnoty value i flag, avšak hodnoty a a fl nebudou změněny. Pokud chcete, aby se např. hodnota value promítla po volání Calculate zpět do datového elementu a (může to být proměnná i kanál), je nutno použít při volání znak odkazu &.

`InstrumentName.Calculate( &a, fl );`

Je nutno si uvědomit, že jako parametry při volání lze použít libovolný výraz, který se v rámci volání vyčíslí a do proměnné parametru se zapíše výsledek. Znak odkazu ale lze umístit jen před výraz obsahující pouze jeden datový element — samozřejmě nelze vracet zpět hodnotu do výrazu.

    InstrumentName.Calculate( &x, (x > y) or (x > z) ); (* v pořádku *)
    InstrumentName.Calculate( &(x + y), fl ); (* chyba, výraz nelze předat odkazem *)

Pozor! Pokud je v proceduře použita některá zpožďující instrukce pause, yield nebo wait, jsou z procedury při volání vráceny parametry s hodnotami v době kdy procedura narazila na zpožďující instrukci, nikoliv na konci procedury.

Při zápisu více parametrů stejného typu není nutno uvádět typ každého parametru zvlášť. Můžete parametry oddělit čárkami a typ uvést za dvojtečku za posledním parametrem.

`procedure Check( fl1, fl2 : boolean );`

Oba způsoby lze samozřejmě nezávisle kombinovat.

`procedure TestFlags( fl1, fl2 : boolean; fl3 : boolean );`

Pokud procedura žádné parametry nemá, uvedou se na místo seznamu parametrů prázdné závorky. Prázdné závorky je nutno uvádět i v zápisu procedury i při jejím volání — podle závorek se pozná, že se jedná o proceduru a ne o identifikátor něčeho jiného.

    procedure Go();
    ...
    InstrumentName.Go()

## Signatura procedury

V řadě případů je nutné procedury identifikovat, vzájemně rozlišit. K tomu slouží tzv. signatura (podpis) procedury. Například není možné v rámci jednoho přístroje deklarovat dvě stejné procedury, protože pak by nebylo možno rozhodnout, kterou proceduru volat.

Nejjednodušší způsob, jak definovat signaturu procedury, je prostě prohlásit za signaturu její jméno. Pak by nebylo možné deklarovat dvě procedury stejného jména. Avšak v případě, že potřebujete dvě procedury, které dělají totéž a liší se jen jinými parametry, je škoda muset vymýšlet jiné jméno. Proto jsou v rámci systému Control Web signatury metod tvořeny nejen jménem procedury, ale i jejími parametry. Proto můžete například definovat dvě procedury

    procedure SetColor( color : cardinal );
    procedure SetColor( red, green, blue : shortcard );

Ačkoliv se obě procedury stejně jmenují, každá má jiné parametry a tak není problém je při volání rozlišit. Pokud zavoláte SetColor s jedním parametrem, zavolá se první procedura, pokud se třemi parametry, zavolá se druhá procedura. Pokus o zavolání SetColor s jiným počtem argumentů povede k chybě při překladu.

Pozor! V rámci systému Control Web se provádí automatická konverze číselných typů. V praxi to znamená, že můžete např. do proměnné typu integer přiřadit proměnnou typu real (samozřejmě s nebezpečím ztráty informace, podrobněji popsaným v podkapitole o výrazech) a že do procedury s parametrem typu real můžete předat proměnnou typu cardinal.

Automatická konverze číselných typů je velmi pohodlná, ale má jeden následek, který musíte mít na paměti. Znamená, že signatury procedur se stejným počtem byť rozličných číselných parametrů jsou zaměnitelné a nelze je tedy současně definovat u jednoho přístroje.

    procedure SetColor( color : integer );
    procedure SetColor( color : real ); (* Chyba "procedura redefinována",
    číselné typy jsou zaměnitelné. *)

Jako všechny identifikátory v systému Control Web jsou i jména procedur citlivá na velikost písmen. To znamená, že

    procedure SetColor( color : cardinal );
    procedure setColor( color : cardinal ); (* jméno začíná malým písmenem,
    jedná se o jinou proceduru *)

jsou jiné procedury, lišící se názvem.

Mějte na paměti, že signaturu tvoří jen jméno procedury a typy parametrů. Vlastní jména parametrů již signaturu neovlivňují. Proto procedury

    procedure SetColor( color : cardinal );
    procedure SetColor( c1 : cardinal );

mají shodnou signaturu.

## Nekonečné smyčky a dlouhé algoritmy

Pokud je v algoritmické smyčce (nejenom while, loop apod. ale i ve smyčce vzniklé např. voláním goto) vykonávané v rámci jednoho kroku čtení kanálů, algoritmus bude přerušen, jádro přečte požadované kanály a vyčte frontu událostí. Pokud ale žádné kanály čteny nejsou, algoritmus bude stále setrvávat ve smyčce, potenciálně velmi dlouhou dobu. Během této doby se nedostanou ke slovu jiné přístroje (což je v pořádku, program dělá to, co mu autor předepsal) ale hlavně systém nevyčítá události a aplikace nereaguje na zásahy uživatele.

Aby k těmto jevům nedochácelo, kód každé procedury je vždy po určitém počtu instrukcí přerušen, aby systém mohl vyčíst frontu událostí a reagovat např. na požadavek zastavení aplikace. Počet instrukcí je standardně nastaven na 32768.

Pokud toto přerušení nastane v proceduře OnActivate() aktivované časovačem nebo výjimkou, na jejím běhu se to nijak neprojeví. Po vyčtení fronty kód pokračuje v rámci stejného časového kroku. Pokud je ale procedura volána nějakou jinou procedurou, po vykonání limitního počtu instrukcí se volání vrátí do volající procedury, aniž by kód doběhl do konce. Volaná procedura sice pokračuje v běhu v následujícím systémovém časovém kroku (podobně jako po instrukci yield), ale sémantika algoritmu je změněna.

Tento jev může u záměrně dlouhých smyček a obecně dlouhých algoritmů působit potíže. Proto je v sekci settings parametr procedure_instruction_limit umožňující nastavit limitní počet instrukcí.

## Lokální data procedury

Už víte, že pokud má procedura parametry, lze k těmto parametrům přistupovat jako ke zvláštnímu druhu proměnných. Zvláštnost spočívá v tom, že pokud se parametry jmenují stejně jako globální datové elementy, pak se používají přednostně a globální datové elementy jsou zastíněny, zneviditelněny. Další zvláštností je, že proměnné parametrů jsou viditelné jen zevnitř procedury.

Pro mnoho algoritmů jsou tyto vlastnosti užitečné. Například neexistuje důvod, proč by proměnná určující počet průchodů cyklem měla být viditelná i v jiných procedurách. Navíc pokud se v rámci cyklu vykonávání procedury pozastaví, jiný přístroj může hodnotu proměnné změnit a algoritmus nebude pracovat správně. Určitě by bylo výhodné pro takovou proměnnou vyhradit místo uvnitř procedury, aby na ni jiné přístroje neviděly a aby nezabírala globálně viditelný identifikátor, využitelný i jinak. Proto je v rámci definice procedur možné deklarovat lokální proměnné a konstanty.

Deklarace lokálních proměnných a konstant se musí v zápisu procedury objevit před vlastním kódem procedury (podrobně o způsobu zápisu pojednává následující podkapitola). Lokální proměnné a konstanty můžete používat zcela stejně, jako parametry procedury s výjimkou, že netvoří rozhraní procedury a jejich hodnoty nelze mimo proceduru na rozdíl od parametrů ani nastavovat, ani číst. Stejně jako v případě parametrů lokální proměnné a konstanty stejného jména jako má nějaký globální datový element tento element zneviditelní, zastíní.

Od parametrů se lokální proměnné podstatně liší svou hodnotou na počátku vykonávání procedury. Zatímco hodnoty parametrů nastavuje ten, kdo proceduru volá, lokální proměnné budou mít svou inicializační hodnotu uvedenou v deklaraci.

V řadě případů je ale výhodné, pokud se hodnota lokální proměnné mezi jednotlivými voláními procedury nezapomíná. Například pokud chcete v lokální proměnné uchovávat nějakou hodnotu po celou dobu běhu aplikace, bylo by špatné, kdyby se při každém zavolání procedury tato hodnota nastavila na hodnotu výchozí. Proto lze lokální proměnné deklarovat pomocí dvou klíčových slov — var a static. Zatímco proměnné deklarované za slovem var budou vždy inicializovány na počátku procedury, proměnné deklarované za slovem static budou statické, inicializační hodnota se jim nastaví jen při startu aplikace a poté již stále budou uchovávat naposledy přiřazenou hodnotu.

Pozor! Pokud chcete, aby se hodnota lokální proměnné uchovávala i mezi jednotlivými voláními procedury, musíte ji deklarovat za klíčovým slovem static, nikoliv var.

## Časování procedur

Pokud není stanoveno jinak, celý algoritmus procedury (od počátku do konce) proběhne vždy v jednom časovém kroku, tedy se shodnými naměřenými hodnotami na kanálech. To je nutné si uvědomit zvláště při programování cyklů. Je však možné použít některou ze zpožďujících instrukcí pause, yield nebo wait. Tyto instrukce způsobí pozastavení procedury na určitou dobu, případně do splnění podmínky. V následujícím systémovém časovém kroku (nikoliv tedy časovém kroku přístroje, který proceduru implementuje!) je otestována podmínka běhu, a je-li splněna, procedura pokračuje od místa za podmínkou, ne opět od počátku. V tomto případě už jsou hodnoty čtených kanálů znovu změřeny.

Více podrobností o souvislostech mezi voláním a časováním procedur a zpožďujícími instrukcemi se dočtete v podkapitole "Rekurzivní volání a zpožďující instrukce".

## Zápis kódu procedur

Zápis procedur musí odpovídat následující LL(1) gramatice. Terminální symboly jsou psány malými písmeny, nonterminální symboly velkými písmeny:

    PROCEDURE   -> procedure ( PARAMETERS );
                   { DECLARATION }
                   begin
                     BLOCK
                   end_procedure ;

    PARAMETERS  -> [ name { , name } : TYPE ] { ; name { , name } : TYPE }

    DECLARATION ->   label  LABEL_LIST ;
                   | const  CONST_LIST ;
                   | var    VAR_LIST ;
                   | static VAR_LIST ;

    LABEL_LIST  -> [ Identifier ]
                   { , Identifier }

    CONST_LIST  -> [ Identifier = Value ]
                   { ; Identifier = Value }

    VAR_LIST    -> [ Identifier = TYPE , Value ]
                   { ; Identifier = TYPE , Value }

    TYPE        ->   boolean
                   | shortreal
                   | real
                   | shortint
                   | integer
                   | longint
                   | shortcard
                   | cardinal
                   | longcard
                   | string

    BLOCK       -> [ STATEMENT ] { ; STATEMENT }

    STATEMENT   ->   if EXPRESSION then
                       BLOCK
                     { elsif EXPRESSION then
                       BLOCK }
                     [ else
                       BLOCK ]
                     end
                   | loop
                       BLOCK
                     end
                   | while EXPRESSION do
                       BLOCK
                     end
                   | repeat
                       BLOCK
                     until EXPRESSION
                   | for SimpleNumericVariable = EXPRESSION
                       to EXPRESSION [ by CONST_EXPRESSION ] do
                       BLOCK
                     end
                   | goto Identifier
                   | pause EXPRESSION
                   | yield             (* alias for pause 0 *)
                   | wait EXPRESSION
                   | send Identifier
                   | sound EXPRESSION
                   | stop
                   | Identifier : [ STATEMENT ]
                   | DataElement = EXPRESSION
                   | move ArrayDataElement , ArrayDataElement, EXPRESSION
                   | self . MethodName ( PARAM_LIST )
                   | ObjectName . MethodName ( PARAM_LIST )
                   | (* empty statement *)

    (* this rule is valid only in block between 'loop' and 'end' *)
    STATEMENT   ->   exit
                   | continue

    PARAM_LIST   = [ EXPRESSION | &amp; DataElement ]
                   { , EXPRESSION | &amp; DataElement }

Každá procedura začíná deklarací hlavičky

`procedure JménoProcedury( seznam_parametrů );`

Za hlavičkou následují deklarace bloků oddělené znakem ; (středník). Celkem existuje pět typů bloků:

- Blok label deklaruje návěští použitá v proceduře.
- Blok const deklaruje lokální konstanty.
- Blok var deklaruje lokální inicializované proměnné.
- Blok static deklaruje lokální statické proměnné.
- Blok begin uvozuje zápis kódu procedury.

Bloky deklarací návěští label, konstant const, inicializovaných proměnných var a statických proměnných static mohou být uvedeny v libovolném pořadí a mohou se libovolně opakovat, případně nemusí být uvedeny vůbec. Je ale nutno pamatovat na to, že každý symbol musí být nejprve deklarován a pak teprve použit. Například zápis

    var
      a = real, init_a; (* chyba, symbol init_a není znám *)
    const
      init_a = 1;

je špatný. Konstantu použitou k inicializaci proměnné je nutno definovat dříve.

    const
      init_a = 1;
    var
      a = real, init_a;

Blok definující vlastní kód procedury begin musí být uveden vždy, musí být jen jeden a musí být uveden vždy jako poslední. Tento blok je ukončen vždy klíčovým slovem end_procedure, které také uzavírá celou deklaraci procedury.

### Návěští

Jsou-li v rámci programu použity skokové instrukce goto, je nutno nejprve deklarovat všechny identifikátory použité jako návěští za klíčovým slovem label. Jednotlivé identifikátory jsou odděleny čárkami a celá deklarace návěští je ukončena středníkem. V programu se cíl skoku označí uvedením návěští následovaného dvojtečkou před příkaz, který se má po příkazu skoku vykonat.

Příklad:

    procedure Test();
    label
      Error;
    begin
      if a &lt; b then
        goto Error;
      end;
      ...
    Error:
      sound "ALARM.WAV";
      ...
    end_procedure;

### Deklarace lokálních konstant a proměnných

Jak již bylo uvedeno dříve, konstanty se deklarují za klíčovým slovem const, inicializované proměnné za klíčovým slovem var a statické proměnné za klíčovým slovem static. Zápis lokálních konstant a proměnných je syntakticky identický se zápisem globálních konstant a proměnných.

Konstanty se zapisují ve tvaru

    const
      název_konstanty = hodnota_konstanty;

typ konstanty není uveden, odvozuje se automaticky z její hodnoty.

Proměnné se zapisují ve tvaru

    var
      název_proměnné = typ_proměnné, inicializační_hodnota;

### Tělo procedury

Tělo procedury uvozené klíčovým slovem begin obsahuje jednotlivé příkazy oddělené znakem ; (středník). Tělo procedury je ukončeno klíčovým slovem end_procedure a konec těla procedury je současně i koncem deklarace celé procedury. Příkazy lze rozdělit na dva druhy:

- Řídicí příkazy, které určují vykonávání jiných příkazů, jako např. podmínky nebo cykly a vyvolávají jiné procedury.
- Výkonné příkazy, které ovlivňují stavy proměnných, aktivují jiné přístroje apod.

### Příkaz if

Příkaz if slouží k podmíněnému vykonávání určitých částí programu. Za klíčovým slovem if musí být uveden logický výraz a klíčové slovo then. Následující blok příkazů je vykonáván jen pokud vyhodnocení logického výrazu vrátí hodnotu true. Blok příkazů může být ukončen třemi způsoby:

- Klíčovým slovem end. V případě nesplnění podmínky program pokračuje příkazem za tímto klíčovým slovem.
- Klíčovým slovem else. Za else následuje blok příkazů, který bude vykonán pouze v případě nesplnění podmínky. Tento blok příkazů musí být ukončen klíčovým slovem end.
- Klíčovým slovem elsif. Za tímto slovem následuje podmínka a klíčové slovo then a blok příkazů stejně jako za if.

Tato kombinace umožňuje realizovat výběr jediné varianty v závislosti na různých podmínkách. Platí tedy, že je vykonán blok příkazů za první splněnou podmínkou následující za if nebo elsif a případné zbylé podmínky za elsif nejsou ani testovány. Není-li splněna žádná podmínka, je vykonán blok příkazů za klíčovým slovem else, pokud je přítomno. Není-li na konci podmíněného příkazu else, program pokračuje za klíčovým slovem end.

Příklad:

    if i < 10 then
      i = i + 1;
    end;

    if i < 0 then
      di = 1;
      i = 0;
    else
      di = -1;
      i = 100;
    end;

    if ErrorCode = 0 then
      ErrorMsg = 'Chyba čtení kanálu';
    elsif ErrorCode = 1 then
      ErrorMsg = 'Chyba zápisu kanálu';
    elsif ErrorCode = 2 then
      ErrorMsg = 'Chyba komunikace';
    else
      ErrorMsg = 'Neznámá chyba';
    end;

### Příkaz loop

Příkaz loop realizuje obecné smyčky. Nemá-li být tato smyčka nekonečná, musí být ukončena příkazem exit. Klíčové slovo exit je platné pouze uvnitř cyklu loop. Za klíčovým slovem loop následuje blok příkazů ukončený klíčovým slovem end, který bude cyklicky vykonáván.

Příkaz loop je vhodné použít, pokud podmínka testující opuštění smyčky je vyhodnocována uvnitř bloku příkazů nebo pokud existuje více míst opuštění smyčky. Má-li být ukončovací podmínka na počátku nebo na konci cyklu, je vhodnější použít smyčky while nebo repeat until. Pokud se má cyklus opakovat pro předem známý počet průchodů, je výhodnější použít cyklus for.

Pokud je v sobě zanořených více smyček loop, příkaz exit způsobí opuštění vždy nejvnitřnější smyčky. Uvnitř smyčky loop je možné použít také příkaz continue. Tento příkaz způsobí přenesení řízení na počátek smyčky, tedy přeskočení zbytku příkazů ve smyčce. V případě zanoření smyček se příkaz continue vztahuje na nejvnitřnější smyčku.

Příklad:

    loop
      a = b + c;
      if a > 123 then
        exit;
      end;
      i = i + 1;
      if i > 100 then
        exit;
      end;
      if i < 20 then
        continue;
      end;
      j = i;
    end; (* loop *)

### Příkaz while

Tento příkaz je speciálním případem smyčky. Za klíčovým slovem while následuje logický výraz a klíčové slovo do. Pokud je výsledkem vyhodnocení logického výrazu hodnota true, je vykonáván následující blok příkazů až po klíčové slovo end. Je dobré si uvědomit, že pokud v těle příkazů není příkaz pause nebo wait, neprobíhá v rámci dalších průchodů smyčkou komunikace s vnějšími zařízeními.

Pokud podmínka není ani napoprvé splněna, příkazy uvnitř smyčky nebudou vykonány ani jednou.

Příklad:

    i = 0;
    while i < 10 do
      a = a + c;
      i = i + 1;
    end; (* while *)

### Příkaz repeat until

Klíčové slovo repeat uvozuje začátek bloku příkazů. Blok příkazů je ukončen klíčovým slovem until, za nímž následuje logický výraz. Po provedení bloku příkazů program otestuje podmínku za until a pokud její výsledek je false, začne se opět vykonávat blok příkazů.

I pokud má logický výraz již na počátku hodnotu true, blok příkazů se provede alespoň jednou.

Příklad:

    repeat
      a = a + b;
      i = i + 1;
    until i >= 10;

### Příkaz for

Příkaz cyklu for uvozuje počítaný cyklus. Za klíčovým slovem for následuje zápis číselné proměnné, znak = (rovnítko) a číselný výraz. Poté následuje klíčové slovo to a další číselný výraz následovaný klíčovým slovem do a blokem příkazů těla cyklu ukončeným klíčovým slovem end. Číselná proměnná bude při vstupu do cyklu nastavena na hodnotu danou výrazem za rovnítkem. Poté bude hodnota této proměnné porovnána s hodnotou výrazu za klíčovým slovem to. Pokud bude menší nebo rovna této hodnotě, provede se tělo cyklu až po odpovídající end, řídicí proměnná bude zvětšena o jedničku a porovnání se bude opakovat. Pokud je řídicí proměnná již po přiřazení inicializační hodnoty větší než výraz za to, tělo cyklu se neprovede ani jednou.

Příklad:

    for i = 1 to 10 do
      a[ i ] := i * i;
    end;

    for i = 3 to 2 do
      (* tělo cyklu se neprovede ani jednou *)
    end;

Ne vždy je požadováno, aby se řídicí proměnná zvětšovala o jedničku. Někdy je nutné krok zvětšit nebo i řídidí proměnnou zmenšovat. Za tímto účelem je možno zápis cyklu for doplnit o krok řídicí proměnné. Krok se zapisuje za klíčové slovo by, před klíčové slovo do. Výraz udávající krok musí být vždy konstantní, vyčíslitelný během překladu. To znamená, že nesmí obsahovat žádné proměnné nebo kanály.

Příklad:

    for Alpha = 0 to 360 by 10 do
      x = sin( Alpha / 180 * Pi );
    end;

    for x = 10 to 1 by -1 do
      y = x * 10;
    end;

### Příkaz yield

Jak již bylo řečeno, celé tělo procedury je vykonáno v jediném časovém kroku, tedy se stejnou hodnotou na vstupně/výstupních kanálech. Někdy je ale nutno časový krok ukončit a umožnit systému změřit nové hodnoty vstupně/výstupních kanálů. Příkaz yield ukončí vykonávání procedury v aktuálním časovém kroku a ponechá systému čas na obsluhu všech ostatních přístrojů, které mají být v probíhajícím časovém kroku aktivovány. Vykonávání procedury bude pokračovat v nejbližším dalším systémovém časovém kroku, tedy nikoliv v dalším časovém kroku přístroje, který danou proceduru implementuje.

Příklad:

    while Level < 12.5 do (* čeká na dosažení hladiny *)
      yield; (* ukončení časového kroku a
                umožnění komunikace pro změření nové hodnoty *)
    end;

### Příkaz pause

Tento příkaz slouží k pozastavení činnosti programu na určitou dobu. Za klíčovým slovem pause následuje numerický výraz udávající počet sekund zdržení programu. Narazí-li procedura na příkaz pause, pozastaví vykonávání na zadanou dobu a poté pokračuje dalším příkazem. Příkaz pause 0; sice nepozastaví program, ale poskytne systému čas k případné obsluze dalších přístrojů (stejnou funkci plní příkaz yield).

Příklad:

    while Level < 12.5 do (* čeká na dosažení hladiny *)
      pause 0; (* možno nahradit i příkazem yield *)
    end;
    Control1 = true; (* uzavření klapky *)
    pause 5; (* čekání na vyprázdnění čerpadla *)
    Control2 = false; (* vypnutí čerpadla *)

### Příkaz wait

Tento příkaz pozastaví běh programu do splnění zadané podmínky. Za klíčovým slovem wait následuje logický výraz. Je-li hodnota výrazu true, program pokračuje následujícím příkazem v tomtéž časovém kroku. Pouze je-li hodnota výrazu false, program poskytne systému čas k případné obsluze dalších přístrojů a opět testuje podmínku. Mezitím samozřejmě dojde k případnému přečtení kanálů. Příklad z popisu příkazu pause by tedy bylo možno zapsat efektivněji:

Příklad:

    wait Level >= 12.5; (* čekání na dosažení hladiny *)
    Control1 = true; (* uzavření klapky *)
    pause 5; (* čekání na vyprázdnění čerpadla *)
    Control2 = false; (* vypnutí čerpadla *)

### Příkaz stop

Použití této instrukce je ekvivalentní doběhnutí procedury na konec. Další aktivace procedury způsobí její rozběhnutí opět od počátku.

### Příkaz send

Za klíčovým slovem send následuje identifikátor označující jméno nějakého jiného přístroje v aplikaci. Uvedení tohoto příkazu zajistí aktivování uvedeného přístroje ihned po skončení právě aktuálního časového kroku. Pokud chcete aktivovat stejný přístroj, jež implementuje danou proceduru, můžete vypsat jeho jméno nebo použít zástupného označení self. Příkaz send self; tedy znamená "aktivuj sám sebe v nejbližším dalším systémovém časovém kroku". Toto aktivování je zcela plnohodnotné (provádí se před ním komunikace se vstupně/výstupními zařízeními a aktualizace hodnot kanálů) a prakticky se rovná vložení zvláštního časového kroku pro všechny přístroje uvedené za příkazy send.

Příkaz send má v některých jiných přístrojích ekvivalent v podobě slova receivers následovaného seznamem přístrojů. V programu je k aktivaci více přístrojů různých jmen nutno použít více příkazů send. Pokud má více přístrojů stejné jméno a toto jméno je uvedeno v příkaze send, jsou aktivovány všechny přístroje tohoto jména.

Pozor! Tento příkaz je velice mocný a může ušetřit velké množství výpočetního výkonu. Pokud je např. nějakými proměnnými řízena pouze viditelnost panelů, nemusí být tyto panely vůbec časovány, ale mohou být uvědomeny příkazem send jen při jejich změně.

Příklad:

    Panel1Visible = (KeyInput = F1);
    Panel2Visible = (KeyInput = F2);
    send Panel1;
    send Panel2;

Obdobný postup je samozřejmě možno použít i u přístrojů disponujících příkazem receivers, jako např. multi_switch nebo panel s aktivními obdélníky.

### Příkaz sound

Tento příkaz zajišťuje přehrávání zvukových souborů formátu WAV. Za klíčovým slovem sound je řetězcový výraz vyjadřující jméno souboru obsahujícího navzorkovaný zvuk. Funkčnost tohoto příkazu je samozřejmě podmíněna přítomností podporované zvukové karty v počítači a správnou instalací jejích ovladačů v prostředí Windows. Není-li zvuková podpora správně instalována, příkaz bude ignorován.

Příklad:

    if Alarm = 1 then
      sound 'hori.wav';
    elsif Alarm = 2 then
      sound 'vybuch.wav';
    else
      sound 'klid.wav';
    end;

### Příkaz move

Instrukce move slouží ke vzájemnému přiřazení polí nebo jejich částí. Použití move je vždy rychlejší než přiřazení jednotlivých prvků polí například s použitím programové smyčky. Velmi výrazného urychlení je dosaženo zejména při přenosu pole kanálů do pole proměnných. Při přiřazení prvků pole do proměnných během cyklu je program nucen vždy požádat jádro systému o změření každého prvku kanálu zvlášť, neboť obecně nová hodnota změřeného prvku pole kanálů může ovlivnit index následujícího prvku pole.

Za klíčovým slovem move následuje zápis prvního prvku pole, který má být čten (zdroj), za čárkou je uveden zápis prvního prvku pole, do něhož má být hodnota zapsána (cíl) a za další čárkou je výraz pro počet prvků, které mají být řesunuty.

Příklad:

    move sa[ i ], da[ j ], count;

Tento příkaz přesune celkem count prvků pole sa počínaje prvkem s indexem i do pole da od prvku s indexem j.

### Příkaz přiřazení

Kdekoliv v zápisu algoritmu se může vyskytovat přiřazovací příkaz. Má obecnou podobu datový_element = výraz. Datový element může být proměnná, prvek pole proměnných, výstupní nebo obousměrný kanál nebo pole výstupních nebo obousměrných kanálů. Vpravo od rovnítka je libovolný výraz, který však musí typově odpovídat datovému elementu vlevo od rovnítka. Datovému elementu typu boolean musí odpovídat výraz typu boolean. Datovému elementu typu string musí odpovídat výraz typu string. Pokud je datový element číselný (typu shortint, integer, longint, shortcard, cardinal, longcard, shortreal, real), je možné do něj přiřadit jakýkoliv číselný výraz.

Pozor! Pokud typ datového elementu není schopen pojmout výsledek výrazu, dojde ke ztrátě informace. Při převodu na celá čísla je odřezána desetinná část a při převodu velkých čísel se chyba týká nejvyšších platných míst. Např. číslo nelze uchovat v proměnné typu integer.

Příklad:

    (* numerické výrazy *)
    i = i + 1;
    x[ 12 ] = ( k[ j ] + k[ j + 1 ] ) / ( atan2( theta, ksi ) * 2 );
    a[ i ] = fi * cos( beta ) / PiBy2;
    b[ i + 1 ] = fi * sin( alfa ) / PiBy2

    (* boolean výrazy *)
    result = value <= ( a + 1 );
    ExitCondition = true;

    (* řetězcové výrazy *)
    Message = concat( 'Error: ', ErrorString );
    Title = 'Demonstrační panel';

### Volání procedur

Předešlé příklady se soustředily převážně na to, jak procedury zapsat, jak řídit jejich běh, jak pracovat s lokálními daty a jak do a z procedur předávat parametry. Poslední, co potřebujete znát, je jak procedury vyvolávat. Jedná se vlastně o uzavřený problém, protože procedury se volají zase z jiných procedur a zápis tohoto volání tedy současně patří k popisu vnitřních částí procedury.

Pokud bylo volání procedury v předchozím textu zapsáno, bylo ve tvaru

`InstrumentName.Go();`

Chcete-li vyvolat proceduru, musíte před její jméno uvést jméno přístroje, který tuto proceduru implementuje. Již dříve bylo řečeno, že procedury nemohou být implementovány mimo nějaký přístroj. Pokud voláte proceduru přístroje, který je v jiném modulu, bude před jménem přístroje ještě jméno modulu

`ModuleName.InstrumentName.Go();`

V mnoha případech se kód jedné procedury obrací na kód jiné procedury téhož přístroje. I v takovém případě musí před jméno volané procedury uvést jméno přístroje. To ale není moc pohodlné a navíc to může činit problémy, pokud přístroj přejmenujete nebo překopírujete proceduru do jiného přístroje. Je obtížné rozhodnout, jestli dané volání má být přesměrováno na jiný přístroj nebo ponecháno. Proto bylo do procedur zavedeno nové klíčové slovo self. Toto klíčové slovo vždy v každé proceduře zastupuje přístroj, v němž je procedura implementována.

Příklad:

    meter m1;
      ...
      procedure Go();
      var
        a = real, 0;
      begin
        ...
        m1.SetValue( a );   (* zavolá proceduru SetValue svému přístroji *)
        self.SetValue( a ); (* pracuje stejně jako předchozí volání,
                               ale funguje i po přejmenování přístroje *)
        ...
      end_procedure;
    end_meter;

Při volání procedur je nutné vzít v úvahu několik skutečností:

- Na rozdíl od aktivace přístrojů příkazem send, což prakticky představuje vložení výjimečného časového kroku, následujícího bezprostředně za aktuálním časovým krokem a zahrnujícího komunikaci se vstupně/výstupními zařízeními, volání procedury se vždy děje bezprostředně ve stávajícím časovém kroku a tedy vyhodnocují se vždy právě aktuální hodnoty kanálů. Zavolání procedury nějakému přístroji nezpůsobí přečtení nové hodnoty vyhodnocovaného kanálu ze vstupně/výstupního zařízení, pokud není přečtení vyvoláno nějakým jiným přístrojem vyhodnocovaným v rámci téhož časového kroku.
Volání procedury je blokující operace. To znamená, že vykonávání kódu se přenese z volající procedury do volané procedury a volající procedura bude pokračovat následující instrukcí za zápisem volání až po ukončení volané procedury a návratu z volání.
- Pokud je vykonávání procedury pozastaveno instrukcí pause, yield nebo wait, pak se procedura vrátí z volání do volající procedury a volající procedura pokračuje v běhu bez doběhnutí kódu volané procedury do konce. Přitom takto volaná procedura pokračuje v nejbližším systémovém časovém kroku (po yield), po uplynutí stanovené doby (po pause) nebo po splnění podmínky (po wait) samostatně ve vykonávání svého kódu stejně, jako by byla aktivována např. příkazem send, tedy včetně měření kanálů apod.
- Procedury není možno volat rekurzivně. Procedura tedy nemůže vyvolat ještě jednou sama sebe a to ani přes jiné procedury! Je nutno si uvědomit, že řetězec volání může být velice složitý a k rekurzi může dojít po celé řadě dalších vnořených volání.

### Komentáře

Kdekoliv v zápisu procedur je možno používat komentáře. Komentář je uvozen komentářovou závorkou (* a ukončen opačnou závorkou *). Cokoliv je uvedeno v těchto závorkách je při překladu přeskočeno. Komentářů je tak možno využívat nejen k poznámkám o významu procedur nebo jednotlivých příkazů, ale i k vyřazení částí kódu.

Komentáře v systému Control Web mohou být vnořené. Hloubka zanoření je uchovávána a komentář skončí až když poslední komentářová závorka.

Příklad:

    i = 0;
    (* komentář hloubky 1
    (* hloubka komentářů je 2 *)
    stále ještě platí první komentářová závorka *)
    i = i + 1;

Komentářové závorky lze samozřejmě používat kdekoliv v textovém zápisu aplikace systému Control Web. Vzhledem k duálnímu vývojovému prostředí ale není možné komentáře v grafickém módu zachovat, neboť textová podoba aplikace zcela zaniká. Procedury jsou v tomto ohledu výjimkou a komentáře zachovávají i při přechodech mezi textovým a grafickým módem.

## Událostní a uživatelské procedury

Víte, že každou proceduru je možné volat z jiné procedury. Někdy je ale nutné, aby se alespoň nějaká procedura zavolala jiným mechanizmem, jinak by systém volání nikdy "neobživnul", nikdo by nezačal s prvním zavoláním.

Takový mechanizmus samozřejmě existuje a byl nastíněn již v úvodu této kapitoly. Control Web nemá žádný "hlavní program", namísto toho aktivuje kousky kódu na základě akcí uživatele nebo systémových událostí. I procedury zapadají do tohoto kontextu a mnohé procedury jsou vyvolávány při splnění určitých podmínek — samozřejmě pokud tyto procedury naprogramujete.

Všechny procedury, které naprogramujete v rámci každého přístroje, lze rozdělit na dvě skupiny:

Událostní procedury. Pokud jsou tyto procedury naprogramovány, jsou po výskytu dané události vyvolávány přímo systémem, bez přispění jiné procedury. Uživatelské procedury. O těchto procedurách žádný kód, který je součástí systému Control Web, nic neví a ani je nebude volat. Pokud mají být aktivovány, musí být volány z jiných procedur, ať už uživatelských nebo událostních.

Aby systém Control Web, přesněji konkrétní přístroj tohoto systému, aktivoval (zavolal) událostní proceduru, je nutné, aby ji poznal. K tomu, aby událostní procedura byla virtuálním přístrojem poznána, musí mít signaturu odpovídající dané událostní proceduře konkrétního přístroje (o signaturách procedur bylo psáno výše). Musí tedy mít jméno (pozor na velikost písmen) a parametry odpovídající definici událostní procedury. Pak je přístrojem rozpoznána a volána při vzniku odpovídající události.

Příklad:

    switch sw1;
      ...
      procedure OnOutput( state : boolean );
      begin
      (* tato procedura bude vyvolána vždy, když switch
         zapíše na svůj výstup novou hodnotu *)
      end_procedure;
      ...
    end_switch;

Signatury událostních procedur a podmínky jejich vyvolání jsou popsány u jednotlivých přístrojů. Pokud otevřete nad přístrojem inspektor a kliknete na záložku "Procedury", objeví se seznam všech událostních procedur daného přístroje. Navíc máte možnost doplnit libovolné uživatelské procedury, ale jak již bylo řečeno, o jejich volání se musíte postarat sami.

Existuje skupina událostních procedur, které jsou pro všechny přístroje společné. Neznamená to, že všechny přístroje budou vyvolávat všechny procedury z této skupiny. Např. neviditelné přístroje nebudou vyvolávat procedury pro obsluhu myši nebo klávesnice. Pokud ale má daná procedura pro daný přístroj smysl, bude ji přístroj volat. Podrobněji budou tyto základní událostní procedury popsány po popisu klíčové událostní procedury OnActivate.

### Procedura OnActivate()

OnActivate je výjimečná událostní procedura. První důvod její výjimečnosti spočívá v tom, že je to jediná procedura, volaná vždy při aktivaci přístroje. Prakticky všechny přístroje provádějí svou činnost v rámci své aktivace. Pokud například u přístroje meter definujete výraz, který bude vyčíslen a výsledek přiřazen do nějakého datového elementu, toto vyčíslení, přiřazení a taktéž případné překreslení přístroje meter s novou hodnotou se děje právě v rámci aktivace. V rámci své aktivace přístroj alarm testuje překročení mezních hodnot a přístroj archiver zapisuje data, přístroj draw animuje svou kresbu apod.

Mimo svou aktivaci pracují jen přístroje, které nějak interagují s okolím. Například přístroj control zapíše do datového elementu hodnotu po uživatelském zásahu, aniž by vůbec byl aktivován. Taktéž přístroj httpd, pracující jako WWW server, odešle data po požadavku klienta bez čekání na aktivaci.

Přístroj může být aktivován několika různými způsoby. Příčiny aktivace se velmi liší v aplikacích reálného času a v datově řízených aplikacích.

V aplikacích reálného času může být přístroj aktivován těmito podněty:

- Periodické časování hodnotou nebo časovačem.
- Výjimka způsobená jiným přístrojem (aktivace přístrojem).
- Výjimka způsobená ovladačem (aktivace ovladačem).

V datově řízených aplikacích je periodické časování povoleno jen některým přístrojům, jejichž činnost je závislá na přesném dodržování časového intervalu, např. přístroji archiver nebo pid_regulator. Aktivace výjimkami od přístrojů i ovladačů zůstává zachována. Přibývá ale nový podnět

- Výjimka způsobená změnou dat.

Podrobně se o časování můžete dočíst v kapitole Podrobně o real-time a datově řízených aplikacích v 1.`dílu a v kapitolách Běh datově řízené aplikace a Časování aplikací reálného času ve 2.`dílu.

Jestliže v přístroji deklarujete proceduru odpovídající signatuře

`procedure OnActivate();`

bude tato procedura vyvolána při každé aktivaci, přesněji před každou aktivací přístroje. Procedura OnActivate má tedy možnost testovat a modifikovat datové elementy, ovlivňující činnost přístroje.

Druhý důvod výjimečnosti procedury OnActivate je skutečnost, že tato procedura může odpovídat několika signaturám, i když se jedná o jedinou proceduru. Důvod této výjimky je prostý. V některých situacích může být užitečné znát důvod aktivace procedury. Pak můžete napsat proceduru OnActivate ve tvaru

`procedure OnActivate( ByTimer, ByInstrument, ByDriver, ByData : boolean );`

V této podobě jsou proceduře OnActivate předány čtyři logické parametry určující důvod aktivace. Parametr ByData samozřejmě může být true jen v datově řízených aplikacích. Vlastní kód procedury OnActivate pak může příčinu aktivace testovat a nějakou činnost provádět například jen při periodické aktivaci nebo jen při vyvolání výjimkou z ovladače.

V určitých situacích může dojít k souběhu několika důvodů aktivace. Například nějaký přístroj aktivuje jiný přístroj a v rámci téhož systémového časového kroku nastane i čas pro periodické časování přístroje. Pak je přístroj aktivován jen jednou, logická hodnota true je nastavena do dvou parametrů – ByTimer i ByInstrument.

Systém Control Web ale dokáže přístroji při aktivaci poskytnout ještě mnohem více informací, než jen příčinu aktivace. Není ale možné předávat veškeré údaje jako samostatné parametry. Proto jsou tyto informace předávány v podobě tzv. bitové masky. Bitová maska představuje jedno číslo, jehož jednotlivé bity (dvojkové číslice, nabývající hodnoty buď 0 nebo 1) nesou logické hodnoty podobně jako logické parametry. Pro zjištění, zda patřičný bit daného čísla nastaven lze použít funkci bitget (podrobně o výrazech a funkcích se dočtete v kapitole Datové elementy a výrazy. Pokud tedy například potřebujete nějakou akci v proceduře OnActivate vykonávat jen při aktivaci přístroje z ovladače, můžete proceduru OnActivate zapsat dvěma způsoby:

    procedure OnActivate( ByTimer, ByInstrument, ByDriver, ByData : boolean );
    begin
      if ByDriver then
        (* tento kód se vykoná jen při aktivaci od ovladače *)
      end;
    end_procedure;

    procedure OnActivate( ActivateMode : cardinal );
    begin
      if bitget( ActivateMode, 2 ) = 1 then
        (* tento kód se taktéž vykoná jen při aktivaci od ovladače *)
      end;
    end_procedure;

Ačkoliv oba způsoby zápisu budou pracovat stejně, druhý způsob je přeci jen poněkud komplikovanější, než první způsob. Proč jej tedy zavádět? Protože testováním dalších bitů v parametru ActivateMode můžete zjistit více informací než jen důvod aktivace. Význam jednotlivých bitů je následující:

bit 0 — přístroj je aktivován časovačem bit 1 — přístroj je aktivován jiným přístrojem bit 2 — přístroj je aktivován ovladačem bit 3 — rezervováno, vždy 0 bit 4 — přístroj je aktivován změnou dat (pouze v datově řízených aplikacích bit 5 — přístroj je v časovém skluzu bit 6 — přístroj je nazván "startup" a je právě aktivován jako první přístroj aplikace bit 7 — přístroj je nazván "terminate" a je právě aktivován jako poslední přístroj aplikace bit 8 — aplikace je v časovém skluzu (tedy ne nutně tento přístroj, ale jakýkoliv přístroj v aplikaci) bit 9 — aplikace končí, běží přístroj terminate
- bit 10 až 31 — rezervováno pro budoucí využití

Použijete-li tedy v proceduře OnActivate jediný parametr ActivateMode, můžete například zjistit, kdy je aplikace v časovém skluzu nebo kdy se právě startuje.

Shrnutí:

- Procedura OnActivate je jediná procedura volaná v rámci aktivace přístroje.
- Volání OnActivate nastane vždy před vlastní aktivací přístroje, takže lze měnit datové elementy zpracovávané v rámci vlastní aktivace.
- Procedura OnActivate může mít tři signatury (typy parametrů), vždy to ale je jediná procedura.
V rámci jednoho přístroje nelze definovat více procedur OnActivate, byť s jinými parametry.

### Standardní událostní procedury

Množství a signatury událostních procedur jsou dány jednotlivými přístroji. Přesto existuje skupina událostních procedur shodná v rámci celého systému. Jak již bylo řečeno, neznamená to, že všechny přístroje budou tyto procedury vyvolávat, to závisí na charakteru přístroje.

Proč je nutné tyto procedury popisovat zvlášť? Aby se snadněji předcházelo chybám zaviněným přehlédnutími, mají tyto procedury dvě výjimečné vlastnosti:

- Není možné deklarovat procedury stejného jména jen s jinými parametry, i když v případě ostatních procedur to jde. Toto omezení zabraňuje omylům plynoucím ze špatně uvedených parametrů událostních procedur. Jiné parametry způsobí změnu signatury a taková procedura by nebyla rozpoznána a ani volána jako reakce na událost.
- Dále je zabráněno deklarovat událostní proceduru u přístroje, který na odpovídající událost nereaguje. Není tedy možné deklarovat proceduru, která nebude nikdy vyvolána.

Standardní událostní procedury jsou:

OnActivate() byla popsána v předchozí podkapitole. OnMouseDown( MouseX, MouseY : integer; LeftButton, MiddleButton,
RightButton : boolean ) bude vyvolána při stisku tlačítka myši na ploše přístroje. Parametry MouseX a MouseY obsahují souřadnice kurzoru myši vzhledem k ploše přístroje. Patřičný logický parametr LeftButton, MiddleButton a RightButton obsahuje true pro stisknuté tlačítko myši. OnMouseDoubleClick( MouseX, MouseY : integer; LeftButton,
MiddleButton, RightButton : boolean ) bude vyvolána při dvojitém stisku tlačítka myši (dvojklik) na ploše přístroje. Parametry MouseX a MouseY obsahují souřadnice kurzoru myši vzhledem k ploše přístroje. Patřičný logický parametr LeftButton, MiddleButton a RightButton obsahuje true pro dvojkliknuté tlačítko myši. OnMouseUp( MouseX, MouseY : integer; LeftButton, MiddleButton,
RightButton : boolean ) bude vyvolána při puštění tlačítka myši na ploše přístroje. Parametry MouseX a MouseY obsahují souřadnice kurzoru muši vzhledem k ploše přístroje. Patřičný logický parametr LeftButton, MiddleButton a RightButton obsahuje true pro puštěné tlačítko myši. OnMouseMove( MouseX, MouseY : integer; LeftButton, MiddleButton,
RightButton : boolean ) bude vyvolána při pohybu myši na ploše přístroje. Parametry MouseX a MouseY obsahují souřadnice kurzoru myši vzhledem k ploše přístroje. Logické parametry LeftButton, MiddleButton a RightButton obsahují true pro stisknutá tlačítka myši. OnKeyDown( Character : cardinal ) bude volána, když je přístroj vybrán (přijímá vstup z klávesnice) a byla stisknuta klávesa. ASCII kód klávesy je v parametru Character. OnKeyRepeat( Character : cardinal ) bude volána, když je přístroj vybrán (přijímá vstup z klávesnice) a je držena stisknutá klávesa, takže klávesnice opakuje znak. ASCII kód klávesy je v parametru Character. OnKeyUp( Character : cardinal ) bude volána, když je přístroj vybrán (přijímá vstup z klávesnice) a je puštěna klávesa. ASCII kód klávesy je v parametru Character. OnSelect() bude volána při výběru přístroje (začíná přijímat vstup z klávesnice). OnDeselect() bude volána při ztrátě výběru přístroje (končí příjem vstupu z klávesnice). OnShow() bude volána při zobrazení dříve skrytého přístroje. OnHide() bude volána při skrytí dříve viditelného přístroje. OnNewPosition( RectX, RectY, RectW, RectD : integer ) bude systémem volána při změně pozice a velikosti přístroje. Nové souřadnice jsou předány v parametrech RectX, RectY, RectW a RectD. RectX a RectY je vztaženo k vizuálnímu vlastníkovi přístroje, tedy panelu v němž je přístroj zaregistrován nebo k pozadí. OnWindowMinimize() je-li přístroj v okně, procedura bude volána při minimalizaci onka. OnWindowMaximize() je-li přístroj v okně, procedura bude volána při maximalizaci onka. OnWindowRestore( WasMinimized, WasMaximized : boolean ) procedura bude volána při obnovení velikosti okna. Parametry WasMinimized a WasMaximized nesou informaci, jestli okno bylo dříve minimalizováno nebo maximalizováno. OnWindowClose() bude volána při pokusu o zavření okna. Standardně není možné okna aplikace zavírat, pouze skrývat nebo minimalizovat. Pokud v přístroji deklarujete tuto proceduru, okno stále nelze zavřít, ale je povoleno tlačítko s křížkem v pravém horním rohu titulku okna a po stisku tohoto tlačítka je vyvolána tato procedura. Záleží na kódu této procedury, jak bude na snahu o zavření reagovat – může např. okno skrýt nebo i zastavit celou aplikaci.

## Rekurzivní volání a zpožďující instrukce

Již dříve bylo zdůrazněno, že procedury není možno volat rekurzívně, to znamená, že pokud je některá procedura rozpracována a ještě nedoběhla do konce, nelze ji znovu explicitně vyvolat. Za normálních okolností systém zaručuje, že ke slovu se nedostane jiná procedura, pokud právě běžící procedura nedoběhne. Přesto existuje několik možností, jak může dojít k rekurzivnímu volání, např. procedura volaná z jiné procedury vyvolá opět volající proceduru. Volající procedura však čeká na dokončení volání jiné procedury a je tedy rozpracována. Takové volání bude odmítnuto a do okna zpráv se vypíše varování. Je při tom důležité si uvědomit, že rekurze nemusí být tak prostá, volaná procedura zavolá jinou proceduru, která může zavolat opět jinou proceduru a až na další úrovni může být opět zavolána volající procedura. I taková situace je nepřípustná.

Situace s rekurzivním voláním se ještě podstatně zkomplikuje, pokud se v procedurách použijí zpožďovací instrukce pause, yield nebo wait. Taková instrukce pozastaví vykonávání procedury a vrátí vykonávání opět do volající procedury. Přitom volaná procedura zůstane v rozpracovaném stavu a nemůže být opět zavolána, dokud nedoběhne do konce.

Jedinou výjimkou je implicitně volaná procedura OnActivate, která v takovém případě pokračuje v testování podmínky běhu a případně pokračuje od místa přerušení dále. Pokud tedy procedura OnActivate čeká na nějaké zpožďovací instrukci a nějaký ovladač aktivuje daný přístroj, procedura OnActivate tuto aktivaci "spotřebuje" na testování podmínky dalšího běhu, nikoliv na rozběhnutí od počátku a případné testování důvodu aktivace! Pokud tedy je v proceduře OnActivate použita nějaké zpožďující instrukce a procedura se zároveň rozhoduje podle podmínky aktivace, pak podle časových poměrů může dojít k vynechání ošetření aktivace.

Zvláště časté problémy mohou vznikat při použití zpožďovacích instrukcí v událostních procedurách. Pokud se vykonávání událostní procedury pozastaví a daná událost nastane znovu, opět dojde k rekurzivnímu volání.

Aby se výše popsaným problémům s rekurzivním voláním předešlo, je do OCL zavedena možnost omezení použití zpožďovacích instrukcí:

- Zpožďovací instrukce pause, yield a wait lze použít výhradně v proceduře OnActivate. V žádných jiných událostních nebo uživatelských procedurách tyto instrukce nejsou povoleny.
- Proceduru OnActivate nelze volat explicitně. Může být volána jen implicitně v rámci aktivace přístroje.

Protože v řadě případů může být toto omezení velmi nepříjemné, můžete na sebe převzít zodpovědnost za nevolání procedur rekurzivně a zrušit toto omezení nastavením parametru independent_procedure_execution v sekci settings na true. Tento parametr lze také nastavit v záložce "Datové inspektory — Systém" v integrovaném vývojovém prostředí. V takovém případě můžete používat zpožďující instrukce a volat proceduru OnActivate bez omezení.

## Lokální data přístroje

Výhodnost použití lokálních proměnných a konstant v rámci procedury již byla zmíněna dříve. Jedna z klíčových výhod je v tom, že lokální data jsou viditelná jen zevnitř procedury, "nepletou" se mezi globální, společné proměnné, viditelné ze všech přístrojů.

Pokud ale jedinému přístroji nadefinujete více procedur, které nějakým způsobem spolupracují, mechanizmus lokality proměnných je zneviditelní i mezi procedurami téhož přístroje. Problém je, že procedury jednoho přístroje tak nemohou pracovat se společnými proměnnými, samozřejmě mimo vyhrazení určitých globálních proměnných. Do jisté míry lze tento nedostatek nahradit uschováním potřebných informací v lokálních proměnných zvolené procedury a do ostatních procedur je předávat jako parametry. Mimo nepohodlnost tento postup selže, pokud k takovým proměnným potřebují přistupovat i událostní procedury, jejichž seznam parametrů (signatura) je předem dán.

Proto stejně jako v rámci těla procedury lze v blocích const, var a static deklarovat lokální proměnné a konstanty, lze tyto bloky použít i na vyšší úrovni, v rámci přístroje. Na konstanty a proměnné deklarované v rámci přístroje pak vidí všechny procedury daného přístroje, ale z venku nejsou viditelné.

Na rozdíl od těla procedury, kde blok definující konstanty a proměnné je automaticky ukončen začátkem jiného bloku, v přístroji musí bloky const, var a static končit odpovídajícími klíčovými slovy end_const, end_var a end_static.

Lokálních datových elementů deklarovaných v rámci přístroje lze používat i při zápisu výrazů přístroje. Jsou-li ale použity ve výrazu nebo v proceduře, blok kde jsou tyto elementy deklarovány, musí předcházet jejich použití. Z jiných přístrojů jsou ale takto deklarované proměnné a konstanty neviditelné.

Pozor! Zatímco v rámci těla procedury překladač hlídá pořadí bloků a blok var nelze umístit za blok příkazů uvozený klíčovým slovem begin, v případě těla přístroje tomu tak není. Pokud tedy existuje lokální proměnná přístroje se jménem shodným s globálním datovým elementem, záleží na pořadí deklarací. Například aplikace

    var
      a = real, 1;
    end_var;
    ...

    instrument

      meter m1;
        expression = ch1 / a;
        var
          a = real, 0;
        end_var;
      end_meter;

    end_instrument;

bude pracovat při prvním spuštění dobře, neboť v době překladu výrazy za slovem expression ještě lokální proměnná a neexistuje a ve výrazu se proto pro dělení použije globální proměnná a inicializovaná na 1. Ale po automatickém vygenerování těla přístroje meter (např. po překlopení vývojového prostředí z grafického módu do textového módu) se blok proměnných vždy zapíše jako první

    meter m1;
      var
        a = real, 0;
      end_var;
      expression = ch1 / a;
    end_meter;

a při spuštění se již výraz za expression přeloží s úplně jinou proměnnou, tentokrát lokální. Navíc tato proměnná je inicializována na hodnotu 0 a při dělení způsobí chybu za běhu aplikace. Pokud tedy zapisujete bloky proměnných a konstant v textovém módu, zapisujte je jako první (grafický editor generuje tyto bloky jako první vždy).

Jaký je tedy postup při hledání datového elementu určitého jména? První krok závisí na tom, jestli datový element použit ve výrazu uvnitř těla přístroje (např. expression = a + b; u přístroje meter) nebo ještě uvnitř procedury přístroje. Další dva kroky jsou již stejné.

1. Nejprve se prověří, jestli existuje lokální proměnná nebo parametr procedury daného jména. Parametry a lokální proměnné procedury sdílejí jeden prostor jmen a nelze tedy pojmenovat parametr a lokální proměnnou procedury stejně. Toto pravidlo se samozřejmě uplatní jen pro datové elementy použité uvnitř procedur.
2. Pokud datový element není nalezen uvnitř procedury, zjišťuje se, jestli není deklarován uvnitř přístroje jako lokální proměnná nebo konstanta přístroje.
3. Až když není datový element nalezen v seznamu lokálních dat přístroje, je hledán mezi globálními datovými elementy.
4. Pokud datový element není nalezen ani na globální úrovni, systém nahlásí chybu překladu.

Mějte na paměti, že při překladu jsou vyhodnocována pouze jména datových elementů a jejich atributy (druh a typ) jsou zjišťovány až podle jmen. I pokud se globální a lokální datový element naprosto liší svým druhem i typem, musí se vždy používat ve shodě s nejnižší deklarací. Uvažte následující příklad:

    ...
    driver
      drv = vsource.dll, 'VSOURCE.DMF', 'VSOURCE.PAR';
    end_driver;

    channel
      a = real, 1, drv, input;
    end_channel;

    instrument

      meter m1;
        expression = a; (* a použit jako globální kanál typu real *)
      end_meter;

      string_display s1;
        var
          a = string, '';
        end_var;
        expression = a; (* a použita jako lokální proměnná typu string,
                           globální kanál není viditelný *)

        procedure Go();
        var
          a = array[ 0..1 ] of boolean, false;
        begin
          ...
          if a[ 0 ] then (* a použito jako lokální pole proměnných typu boolean *)
          ...
        end_procedure;

      end_string_display;

    end_instrument;

Všimněte si, že na lokální úrovní (ať již přístroje nebo procedury) lze deklarovat jen konstanty nebo proměnné, nikoliv vstupně/výstupní kanály. Kanály mohou být deklarovány pouze na globální úrovni.

Podobně jako u lokálních proměnných v proceduře, proměnné deklarované v rámci bloku var budou vždy na počátku aktivace přístroje inicializovány, proměnné z bloku static budou inicializovány jen při startu aplikace a poté se jejich obsah bude měnit zápisy, bez ohledu na aktivace (časování) přístroje. Pokud je tedy hodnota přístrojové proměnné za běhu aplikace "záhadně" nastavována na určitou hodnotu, přesvědčte se, zda-li je umístěna v bloku static a ne v bloku var.

## Nativní procedury

Existuje řada vlastností jednotlivých přístrojů, jen obtížně nebo velmi neefektivně reprezentovatelných v podobě vlastností, byť vyjádřených např. jako výraz. Tyto vlastnosti jednotlivých přístrojů proto bývají zpřístupněny jako tzv. nativní procedury.

Poznámka: Ve starších verzích systémů Control Web a Control Panel, které ještě neumožňovaly neomezenou definici procedur spojených s libovolným přístrojem a OCL byl implementován jen v podobě přístroje program, byly nativní procedury jediným typem procedur v systému a byly nazývány OCL metodami.

Definice i implementace nativních procedur je čistě záležitostí patřičné třídy přístroje a naprosto neovlivňuje jeho binární kompatibilitu se zbytkem systému. Kdykoliv je tedy možné rozšiřovat funkčnost přístrojů bez ohrožení systému. Pouze pokud je překládán aplikační program volající nativní proceduru, která není k dispozici (např. při pokusu přeložit novější program za použití starších versí DLL přístrojů, při zrušení nebo změně definice nativní procedury apod.), je ohlášena chyba překladu a program je nutno upravit.

Konvence však dovoluje pouze přidávat další nativní procedury a zachovávat názvy, typy argumentů (signaturu) i funkčnost u již existujících nativních procedur. Při dodržení této konvence je zajištěna vzestupná kompatibilita všech aplikací, to znamená, že novější verze systému Control Web dokáže přeložit všechny starší aplikační programy.

Typickým příkladem použití nativních procedur je ovládání viditelnosti panelu. Každý panel má možnost definování výrazu ovládajícího jeho viditelnost

`visibility = podmínka_viditelnosti;`

Má-li ale panel neustále vyhodnocovat podmínku viditelnosti při periodickém časování nebo po změně některého datového elementu ve výrazu uvedeného, je to zcela nepatřičné plýtvání výkonem. Proto takové panely zpravidla nebývají časovány, ale pouze aktivovány jiným přístrojem, který podmínku viditelnosti mění. Pomocí nativních procedur OCL ale jde zobrazování i skrývání panelů řešit velmi elegantně. Stačí zavolat

    panel1.Hide();
    panel2.Show();

Mnohé vlastnosti jsou dostupné pouze prostřednictvím nativních procedur. Například jednoduché volání

`panel1.MoveTo( 100, 200 );`

přesune přístroj panel1 na souřadnice 100, 200 obrazových bodů.

Nativní procedury se z hlediska volajícího kódu chovají úplně stejně, jako jakékoliv jiné procedury, pouze ve zdrojovém kódu aplikace nejsou nikde deklarovány. Každý přístroj implementuje vlastní sadu nativních procedur. Seznam nativních procedur spolu s jejich popisem můžete v rámci palety přístrojů, popsané v kapitole Integrované vývojové prostředí.

V rámci systému Control Web tedy existují tři kategorie procedur, lišících se některými vlastnostmi:

    kategorie procedur    deklarace uživatelem  volání z procedur  volání systémem
    událostní procedury          ano                  ano                ano
    uživatelské procedury        ano                  ano                ne
    nativní procedury            ne                   ano                ne

## Shrnutí

OCL je mocný nástroj pomáhající všude tam, kde je zapotřebí popsat složitější chování aplikace. Základní rysy OCL jsou:

- OCL je obecný programovací jazyk umožňující zapsat libovolný algoritmus.
- Veškerý kód OCL je soustředěn do procedur. Procedury se nikdy nemohou vyskytovat samostatně, ale vždy jsou spojeny s nějakým přístrojem.
- Neexistuje žádná "hlavní procedura". Control Web je událostmi řízený systém a taktéž procedury jsou primárně vyvolávány jako reakce na přijetí nějaké události.
- Možnost deklarace procedur není omezena jen na událostní procedury, můžete deklarovat libovolné množství uživatelských procedur, avšak zodpovídáte za jejich vyvolávání.
- Přístroje často disponují nativními procedurami, které nelze deklarovat, ale lze je volat.
- V rámci procedur lze deklarovat lokální, odjinud neviditelné datové elementy. Lokální datové elementy zastíní (zneviditelní) globální datové elementy stejného jména (pokud existují).
- Procedury mohou mít vlastní parametry, pracující podobně jako lokální data, ale jejich hodnoty nastavuje volající kód.
- O tom, jestli se změněná hodnota parametru objeví i mimo proceduru, rozhoduje volající kód umístěním znaku & před parametr. Znak & lze umístit pouze před jednoduchý datový element, nikoliv před výraz.
- Lokální datové elementy lze deklarovat i v rámci přístroje. Tyto elementy pak lze použít při zápisu výrazů v přístroji i v procedurách daného přístroje. Záleží však na pořadí deklarací – lokální datové elementy musí být deklarovány před jejich použitím kdekoliv v přístroji. Lokální datové elementy jsou z jiných přístrojů neviditelné.
- Při rozhodování, který datový element stejného jména se má použít, má vždy přednost ten nejlokálnější, tedy proměnná v proceduře má přednost před proměnnou v přístroji a tato má přednost před globální proměnnou nebo kanálem.
- Kanály se mohou deklarovat jen na globální úrovni.
- Procedura je identifikována svojí signaturou – jménem, typy a pořadím parametrů. Všechny číselné typy parametrů jsou kompatibilní (zaměnitelné) a z hlediska signatury splývají.
- V rámci jednoho přístroje nelze deklarovat dvě procedury se stejnými signaturami. Kvůli zamezení problémům spojených s rozlišováním signatur, nelze deklarovat procedury jmenující se stejně jako základní událostní procedury a lišící se pouze parametry, byť mají jinou signaturu.
- Aby byla událostní procedura po výskytu události vyvolána, musí její signatura odpovídat definované signatuře pro danou událostní proceduru.