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 nepsat
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 a doplnit tím 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 které tyto události zpracovávají.
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á periodická aktivace 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 a
návratová hodnota
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 k datovým elementům, které
jsou definovány v sekcích aplikace (proměnné, kanály apod.) a
číst či zapisovat jejich hodnoty. Takové datové elementy
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 definování parametrů procedury a případně
její návratové hodnoty. Parametry a návratová hodnota
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 — tyto instrukce jsou popsány 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í datový element, lhostejno jakého typu. 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í.
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
v definici 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(); (* definice procedury *)
Go(); (* volání procedury *)
Návratová hodnota procedury
Mimo parametrů může procedura definovat i návratovou
hodnotu. V systému Control Web se lze
při psaní výrazů setkat s řadou zabudovaných funkcí, např.
funkce pro výpočet sinu zadaného úhlu sin apod.
Použití takové funkce je snadné — stačí
ve výrazu uvést jméno funkce a do závorek zadat parametr (nebo
parametry). Při vyhodnocování výrazu je funkce zavolána a na
jejím místě se objeví hodnota, kterou funkce vrací (v našem
případě hodnota sinu zadaného úhlu).
Stejně jako zabudované funkce, i procedury mohou vracet
hodnotu. Pokud nějaká procedura hodnotu vrací, je možné ji
volat v rámci jakéhokoliv výrazu. Samozřejmě návratová hodnota
procedury je určitého typu a tak její použití ve výrazu musí
tomuto typu odpovídat, musí být typově korektní.
Proceduru, která žádnou návratovou hodnotu nevrací,
samozřejmě ve výrazu volat nejde. Naopak ale proceduru
vracející hodnotu je možné volat jako samostatný příkaz, její
návratová hodnota bude prostě přehlédnuta.
Pokud procedura vrací hodnotu, je v její definici nutno
uvést typ návratové hodnoty za pravou závorkou uzavírající
seznam parametrů a dvojtečkou:
procedure CheckValue( Value : real ): boolean;
Použití procedur s návratovou hodnotou je jednoduché. Např.
procedura která otestuje zda-li je zadané číslo v intervalu
0..1 bude vypadat takto:
procedure Test( a : real ): boolean;
begin
return (a >= 0) and (a <= 1);
end_procedure;
Proceduru Test je pak možné používat kdekoliv v logickém
výraze (vrací hodnotu typu boolean):
if Test( a ) then (* splněno pokud proměnná a má hodnotu mezi 0 a 1 *)
...
end;
...
Test( 0.5 ); (* syntakticky správné volání, ale bez jakéhokoliv efektu - návratová hodnota přehlédnuta *)
Parametry předávané odkazem
Co se stane, pokud procedura změní hodnotu
parametru? Jestliže jsou parametry vlastně proměnné, lze do
nich uvnitř těla procedury i zapisovat (přiřazovat jim novou
hodnotu). OCL proto zavádí dvě volací konvence, které
odlišují dva druhy chování při přiřazení do parametrů uvnitř
procedury:
Parametry předávané hodnotou se prostě
kopírují do proměnných, které tvoří parametry procedury.
Protože kód procedury pracuje s kopií parametru, může do
nich zapisovat, aniž by se to po návratu z procedury
projevilo. Protože procedura pracuje s lokální kopií, je
možné při volání procedury předat jako parametr i libovolný
výraz daného typu. Výraz je vyhodnocen před vlastním
zavoláním procedury a výsledná hodnota je uložena do
proměnné parametru.
Parametry předávané odkazem pracují jinak.
Uvnitř procedury nevznikne nová proměnná s hodnotou
parametru, ale kód procedury pracuje přímo s proměnnou
předanou jako parametr. To znamená že jakékoliv změny
hodnoty takového parametru se provádí ve skutečnosti přímo
v datovém elementu, který byl na místo parametru uveden při
volání. Z toho plyne i několik omezení — do parametru předávaného odkazem nelze
předat konstantu (procedura by do ní nemohla zapsat
hodnotu) ani výraz, protože výraz nereprezentuje
jediný datový element.
Parametry předávané hodnotou tedy zmizí v těle
procedury a jejich případné změny se navenek neprojeví.
Z hlediska procedury se tedy jedná o parametry pouze
vstupní. Změny v parametrech předávaných odkazem se
po ukončení procedury objeví v předaných datových elementech,
jedná se tedy o parametry vstupní i výstupní.
Způsob předávání parametrů je definován při deklaraci
procedury. Prosté uvedení jména parametru a jeho typu definuje
parametr předávaný hodnotou. Klíčové slovo var
před parametrem značí parametr předávaný odkazem. Například
v deklaraci procedury:
procedure Calculate( var Value : real; Flag : boolean );
je parametr Value předán odkazem, parametr
Flag hodnotou. Na místo prvního parametru není
tedy možné předávat konstantu ani výraz, pouze jednoduchý
datový element. Příklady správného a chybného volání:
Calculate( 2.5, true ); (* chyba, konstantní literál předáván do var parametru *)
Calculate( 2 * Count, flag1 & flag2 ); (* chyba, výraz předáván do var parametru *)
Calculate( Count, ~flag1 ); (* správně, pokud datový element Count není konstanta a je číselného typu *)
Upozornění: Pokud je v proceduře použit některý zpožďující
příkaz pause, yield nebo wait
(tyto příkazy jsou vysvětleny dále), 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.
Pole jako parametry procedur
Protože při předávání parametrů odkazem není předávaná
proměnná kopírována do lokální proměnné, ale pracuje se pouze
s odkazem na tuto proměnnou, je možné předat celé pole jako
var parametr. Pokud má být na místo parametru předáno celé
pole, parametr je uvozen klíčovými slovy array of.
Např. procedura hledající největší prvek pole může mít
hlavičku:
procedure FindMax( a : array of real ): real;
Do takové procedury tedy není možné předat jediný datový
element, ale je nutno jako parametr předat celé pole. Protože
pole mohou být v systému Control Web různě
velká a mohou také začínat libovolným indexem, je možné použít
dvě funkce pro zjištění indexů polí loindex a
hiindex. Obě funkce vyžadují jako parametr
identifikátor pole (nikoliv pouze prvek pole!) a vracejí
nejnižší a nejvyšší index daného pole. S pomocí těchto funkcí
může kód v proceduře bezpečně manipulovat se všemi elementy
v poli. Výše zmíněná procedura FindMax by tedy
mohla být implementována například takto:
procedure FindMax( a : array of real ): real;
var
i : longint;
max : real;
begin
max = a[ loindex( a ) ];
for i = loindex( a ) + 1 to hiindex( a ) do
if a[ i ] > max then
max = a[ i ];
end;
end;
return max;
end_procedure;
Upozornění: Parametry typu array of jsou
automaticky předávány odkazem, i když v jejich definici není
klíčové slovo var. Předat celé pole hodnotou
(kopírovat pole do lokální proměnné) není možné. Jakékoliv
změny v hodnotách prvků pole se tedy provádí přímo v předaném
poli.
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 a návratovou hodnotou.
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.
Upozornění: 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, typy
parametrů a typ návratové hodnoty. Vlastní jména parametrů již
signaturu neovlivňují. Proto procedury
procedure SetColor( color : cardinal );
procedure SetColor( c1 : cardinal );
mají shodnou signaturu.
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á čítající průchody 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é jsou
inicializovány implicitně nebo mají 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 výchozí
hodnotu. 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.
Upozornění: 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.
Zápis kódu procedur
Zápis procedur musí odpovídat následující 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 ] { ; Identifier : TYPE }
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
| switch EXPRESSION of
{ case CONST_EXPRESSION [ , CONST_EXPRESSION ] ;
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
| commit
| send Identifier { , Identifier }
| sound EXPRESSION
| return [ EXPRESSION ]
| Identifier : [ STATEMENT ]
| DataElement = EXPRESSION
| move ArrayDataElement , ArrayDataElement, EXPRESSION
| MethodName ( PARAM_LIST )
| self . MethodName ( PARAM_LIST )
| ObjectName . MethodName ( PARAM_LIST )
| DataElement -> MethodName ( PARAM_LIST )
| (* empty statement *)
(* this rule is valid only in block between 'loop' and 'end' *)
STATEMENT -> exit
| continue
PARAM_LIST = [ PARAMETER ] { , PARAMETER }
PARAMETER = EXPRESSION | DataElement | Array
Každá procedura začíná deklarací hlavičky
procedure JménoProcedury( seznam_parametrů );
procedure JménoProcedury( seznam_parametrů ): typ_návratové_hodnoty;
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_value = 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_value = 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 < 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é { atributy };
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 switch
Příkaz switch rozděluje vykonávání kódu na
základě hodnoty jediného výrazu. Pokud tedy je po řadě
testována hodnota jediného výrazu, jako např. hodnota proměnné
ErrorCode v předchozím příkladě, použití příkazu
switch je jednodušší a jeho vykonání je mnohem
rychlejší než série příkazů if elsif.
switch ErrorCode of
case 0;
ErrorMsg = 'Chyba čtení kanálu';
case 1;
ErrorMsg = 'Chyba zápisu kanálu';
case 2;
ErrorMsg = 'Chyba komunikace';
else
ErrorMsg = 'Neznámá chyba';
end;
Stejně jako u příkazu if je else
větev příkazu switch nepovinná. Pokud testovaný
výraz nenabude žádné z hodnot vyjmenovaných za klíčovými slovy
case a větev else chybí, žádná větev
příkazu switch se neprovede.
Za klíčovým slovem case není nutné uvádět
jedinou hodnotu, ale je možné vyjmenovat více hodnot
oddělených čárkami, např.:
switch InputValue of
case 0, 1, 2, 3;
...
case 10, 100, 1000;
...
end;
Upozornění: Hodnoty za klíčovými slovy case musí
být konstantní (vyčíslitelné v době překladu). Pokud je nutno
výraz porovnat s obecnými výrazy, musí se použít testování
příkazem if elsif.
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 řídicí
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.
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;
Upozornění: Výraz udávající krok za klíčovým slovem by
musí být vždy konstantní (vyčíslitelný v době překladu). To
znamená, že nesmí obsahovat žádné proměnné nebo
kanály.
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; (* while *)
Control1 = true; (* uzavření klapky *)
pause 5; (* čekání na vyprázdnění čerpadla *)
Control2 = false; (* vypnutí čerpadla *)
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 commit
Příkaz commit slouží k vyvolání komunikace
(zápisu a čtení kanálů) bez ztráty časového kroku. Uvedení
příkazu commit má podobný efekt jako např. podmíněný příkaz
obsahující kanál v podmínce — kanály
označené pro čtení jsou přečteny a kanály se zapsanou hodnotou
jsou zapsány do ovladače.
Podobný efekt jako commit má např. příkaz
yield nebo pause, ovšem tyto příkazy
ukončí provádění časového kroku a způsobí pokračování
algoritmu v následujícím kroku. Příkaz commit
pouze vyvolá komunikaci a algoritmus pokračuje v rámci téhož
časového kroku.
Podrobně je funkce příkazu commit popsána
v podkapitole Použití kanálů v procedurách.
Příkaz return
Příkaz return má dvě podoby v závislosti na
tom, jestli daná procedura vrací návratovou hodnotu nebo ne.
Pokud procedura návratovou hodnotu nevrací, způsobí příkaz
return ukončení běhu procedury a návrat do kódu,
který danou proceduru zavolal. Případná další aktivace
procedury způsobí její rozběhnutí opět od počátku.
procedure Divide( a, b : real; var c : real );
begin
if b = 0 then
c = 0;
return;
end;
c = a / b;
end_procedure;
Pokud ale procedura vrací nějakou hodnotu (může být volána
v rámci výrazu), pak za klíčovým slovem return
následuje výraz, jehož hodnota je vrácena jako návratová
hodnota procedury. I v tomto případě příkaz return
způsobí ukončení běhu procedury a návrat do kódu, který danou
proceduru zavolal.
procedure Divide( a, b : real; var c : real ): boolean;
begin
if b = 0 then
return false;
end;
c = a / b;
return true;
end_procedure;
Příkaz send
Za klíčovým slovem send následuje seznam
identifikátorů oddělených čárkami, označující jména přístrojů
v aplikaci (seznam samozřejmě může obsahovat pouze jediný
přístroj). Uvedení tohoto příkazu zajistí aktivování uvedených
přístrojů 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ů. 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.
Příklad:
Panel1Visible = (KeyInput = F1);
Panel2Visible = (KeyInput = F2);
send Panel1, 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 př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.
Upozornění: 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 v plném rozsahu uchovat v proměnné typu integer.
Příklad:
(* číselné 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
(* logické výrazy *)
result = value <= ( a + 1 );
ExitCondition = true;
(* řetězcové výrazy *)
Message = '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 např. ve tvaru
Go();
Toto volání vyvolá proceduru Go stejného
přístroje nebo sekce, který ji volá. Pokud daný přístroj nebo
sekce proceduru Go nemá, nastane při překladu
chyba. Chcete-li vyvolat proceduru jiného přístroje nebo
sekce, musíte před její jméno uvést jméno přístroje (sekce),
který tuto proceduru implementuje. Již dříve bylo řečeno, že
procedury nemohou být implementovány mimo nějaký přístroj nebo
sekci.
InstrumentName.Go();
Proceduru je také možné volat pomocí ukazatelů na
přístroj.
InstrumentPointer->Go();
Pokud ukazatel neukazuje na žádný přístroj nebo na
přístroj, který nemá proceduru Go, dojde k chybě
za běhu aplikace a aplikace bude zastavena.
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();
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ž
s poslední uzavírací komentářovou závorkou.
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.
Vždy (ne)pravdivé výrazy v podmínkách
U řady příkazů jejich syntax předepisuje logický výraz,
který ovlivňuje chování příkazu (např. příkaz while
vyžaduje logický výraz, který určuje zda-li bude smyčka
vykonávána nebo ne). Omezení rozsahu datových typů může
zásadním způsobem ovlivnit splnitelnost (či nesplnitelnost)
podmínkových logických výrazů. Uvažme následující příklad:
var
c : cardinal;
begin
c = 10;
while c >= 0 do
...
c = c - 1;
end;
...
Smyčka while v tomto příkladu bude nekonečná,
protože datový element typu cardinal nemůže
obsahovat číslo menší než 0. Podobně podmínka:
var
i : shortint;
begin
if i > 200 then
...
nebude nikdy splněna, protože největší číslo reprezentované
v datovém elementu typu shortint je 127 a nikdy
nemůže být větší než 200. Podobných příkladů výrazů, které
jsou trvale pravdivé či nepravdivé je celá řada i v dalších
příkazech (repeat until, for to do,
wait, ...).
Překladač je v některých případech (např. pokud je
porovnáván datový element s konstantou) detekovat, že výraz je
vždy pravdivý či nepravdivý a během překladu vypíše varování
(nikoliv však chybu překladu). V řadě případů ale není možné
během překladu neměnnost podmínky určit, protože plyne
z logiky aplikace. Je proto nezbytné na tuto možnost pamatovat
při ladění aplikace a ještě lépe už při jejím návrhu.
Tip: Překladač provádí při spouštění aplikace řadu
optimalizací, které při běžném překlápění či zkušebním
překladu provádět nemůže. Jednou z těchto optimalizací je
vyčíslení konstantních podvýrazů. Pokud tedy je v aplikaci
vždy (ne)splnitelná podmínka skládající se z datového elementu
porovnávaného s konstantním výrazem, varování o trvalém
(ne)splnění podmínky se objeví pouze při spouštění aplikace,
nikoliv při pouhém překlápění.
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 při každé výstupní akci přístroje switch *)
...
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
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í následnou č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. 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 : longcard );
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 — přístroj je
aktivován z jiného modulu
bit 11 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.
Č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.
Upozornění: Algoritmem procedury lze velmi snadno realizovat
nekonečnou (nebo alespoň velmi dlouhou) smyčku. Je nutné si
uvědomit, že takové smyčka blokuje přechod k dalšímu časovému
kroku a celá aplikace tak čeká na její dokončení.
Použití kanálů v procedurách
Pokud nějaký přístroj vyhodnocuje v rámci svého časového kroku
nějaký výraz (např. meter) a tento výraz obsahuje
kanály, pak jádro systému zajistí změření těchto kanálů před
vyhodnocením výrazu (samozřejmě pokud je to možné během času
definovaného parametrem kanálu timeout). Podobná
situace nastane pokud jsou kanály použity v proceduře OnActivate.
Mějme definovány proměnné a1, a2 a
a3 a kanály c1, c2 a c3.
Pak následující část kódu:
a1 = c1;
a2 = c2;
a3 = c3;
bude vykonána až poté, co systém změří hodnoty kanálů c1,
c2 a c3.
Co se ale stane, pokud bude kód vypadat následovně:
if c1 > 0 then
a2 = c2;
else
a3 = c3;
end;
V zápisu procedury se sice vyskytují všechny tři kanály, ale
konstrukce algoritmu vylučuje, že by byly potřebné všechny tři.
Podle hodnoty kanálu c1 bude dále zapotřebí kanál
c2 nebo c3, ale nikdy oba současně.
V tomto případě to vypadá jako nepodstatné diskuse, protože
změření jediného kanálu navíc aplikaci nemůže citelně zdržet.
Ovšem pokud se v jednotlivých větvích budou vyskytovat ne
jednotlivé kanály, ale tisíce či desetitisíce kanálů, může být
násobná doba komunikace vážný problém.
Z těchto důvodů je vykonávání procedur optimalizováno
následujícím způsobem: Pokud je nalezeno větvení v programu, pro
jehož rozhodnutí je zapotřebí přečíst hodnotu nějakého kanálu,
algoritmus určující které kanály bude zapotřebí přečíst se zastaví
a dosud označené kanály se přečtou. To umožní rozhodnutí podmínky
určující která část procedury se bude dále vykonávat. Vyhledávání
potřebných kanálů se dále rozběhne jen ve vybrané větvi
procedury.
Někdy je užitečné uprostřed algoritmu vyvolat komunikaci se
zařízeními, kdy jsou dosud označené kanály přečteny a zapsané
kanály odeslány do zařízení. Jak jsme již popsali, prostý výskyt
nějakého podmíněného příkazu (např. if nebo while)
toto zaručí. Stejně tak zpožďující instrukce (yield,
pause nebo wait) způsobí komunikaci, ale
za cenu přerušení algoritmu a pokračování až v následující
systémovém časovém kroku. Pokud je ale zapotřebí např. zapsat více
hodnot sekvenčně po sobě a přitom neztratit časový krok, je možné
použít příkazu commit. Výskyt tohoto příkazu
v programu nemá vliv na vykonávání algoritmu, pouze způsobí
nucenou komunikaci se zařízením v rámci jediného časového
kroku.
cmd = 'init';
commit; (* řetězec 'init' se zapíše do kanálu *)
cmd = 'start';
commit; (* řetězec 'start' se zapíše do kanálu *)
...
Pokud bychom nepoužili příkazu commit, dva zápisy
po sobě by způsobily přepsání řetězce.
Upozornění: Mezi příkazy yield a commit
je zásadní rozdíl, který budeme demonstrovat na následujících
příkladech. Mějme část kódu: a1 = c1;
commit;
a2 = c1;
commit; Příkaz commit způsobí přečtení kanálu c1.
Přiřazení za commit požaduje opět čtení c1,
ale protože algoritmus pokračuje v témže časovém kroku, systém již
nebude kanál c1 číst bez ohledu na další příkaz
commit, neboť tento kanál již byl
přečten. a1 = c1;
yield;
a2 = c1;
yield; V tomto případě bude kanál c1 skutečně
přečten 2×, protože algoritmus se
rozpadne na více časových kroků. Tato omezení neplatí pro
zápis, který se provádí tolikrát, kolikrát se vyskytuje
v algoritmu.
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é.
Upozornění: 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:
data
var
a : real { init_value = 1 };
end_var;
end_data;
...
instrument
meter m1;
expression = ch1 / a;
var
a : real;
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;
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é.
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.
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.
Až když není datový element nalezen v seznamu lokálních
dat přístroje, je hledán mezi globálními datovými
elementy.
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;
data
channel { driver = drv; init_value = 0 };
a : real { direction = input; driver_index = 1 };
end_channel;
end_data;
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 a není dostupný *)
procedure Go();
var
a : array [0..1] of boolean;
begin
...
if a[ 0 ] then (* a je lokální pole proměnných typu boolean *)
...
end;
...
end_procedure;
end_string_display;
end_instrument;
Všimněte si, že na lokální úrovni (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.
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 |
implementace 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 |
Druhy procedur a jejich volání
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 výskyt nějaké
události.
Možnost deklarace procedur není omezena jen na událostní
procedury, v aplikaci může být definováno libovolné množství
uživatelských procedur, avšak jiné procedury v aplikaci
zodpovídají 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.
Parametry mohou být předávány dvěma způsoby — hodnotou a odkazem. Parametr předaný hodnotou je
vyčíslen a okopírován do proměnné parametru, jeho změny se tedy
mimo proceduru neprojeví. Veškeré změny v parametru předaného
odkazem se projeví přímo v daném datovém elementu. Odkazem tedy
nelze předávat konstanty a výrazy, pouze jednoduché datové
elementy.
Do procedury lze odkazem předat celé pole. Funkce loindex
a hiindex dovolují zjistit dolní a horní indexy
předaného pole.
Procedura může vracet hodnotu. Pak je ji možno volat jako
funkci v rámci výrazu ať již v těle jiné procedury nebo
libovolného jiného výrazu.
V rámci přístroje lze deklarovat lokální datové elementy.
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.
Na lokální úrovni nelze deklarovat jiné druhy datových
elementů než konstanty a proměnné.
Procedura je identifikována svojí signaturou — jménem, typy a pořadím parametrů a typem návratové
hodnoty. 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.
|