Na tomto místě jsou popsány všechny stěžejní unity, které projekt obsahuje.
UnitHlavniOkno
V seznamu v levé části okna (ListBox1
) jsou uvedeny všechny procedury
patřící do projektu. Objekty procedur (exempláře třídy Procedura
)
jsou uloženy ve vlastnosti ListBox1.Items.Objects
.
Když uživatel dvakrát za sebou klikne na název některé z procedur,
vytvoří se nové vlákno ZpracovaniZdrojovehoKodu
, jehož konstruktoru
se předají: objekt třídy Procedura
reprezentující právě
spuštěnou proceduru, plocha, na niž se kreslí (ZImage1
)
a konečně také seznam všech ostatních procedur. To proto, že třída ZpracovaniZdrojovehoKodu
nemá přístup k objektům hlavního okna.
Třída TForm1
dále obstarává nahrávání a ukládání projektu, využívá při tom
metod třídy Procedura
(viz dále).
UnitZpracovaniZdrojovehoKodu
Úmluva: u popisu této unity se slova procedura a parametr objevují se dvěma rozdílnými významy. Jednou se jimi myslí Delphovská procedura a Delphovský parametr, podruhé procedura Želví grafiky či parametr procedury Želví grafiky. Aby byl text srozumitelnější, kdykoliv bude mít některé ze zmíněných slov ten druhý význam, bude podtrženo.
Jádrem celé aplikace je třída ZpracovaniZdrojovehoKodu
. Jak název
napovídá, třída zpracovává zdrojový kód programu vytvořeného v Želví grafice.
Třída ZpracovaniZdrojovehoKodu
je potomkem třídy TThread
, zpracování zdrojového kódu tedy probíhá jako samostatné vlákno. To má dobrý důvod.
Kdyby tomu tak nebylo, při provádění prográmku by byly naprosto vyřazeny všechny
ostatní funkce aplikace Želví grafika. Nefungovala by tlačítka, nešlo by hýbat s hlavním oknem. A to až do doby, než by prográmek skončil. Když pustíme zpracování zdrojového
kódu jako zvláštní vlákno, výše uvedené problémy odpadnou a navíc lze provádění
prográmku kdykoliv ukončit tlačítkem na horní liště.
Zdrojový kód se zpracovává klasickým algoritmem -- rekurzivním sestupem.
Jednotlivé kroky nebudu popisovat do detailu, podrobnosti si zájemce může
sám zjistit z komentářů ve zdrojovém kódu. Metoda, která je volána
ihned po aktivaci vlákna, se nazývá Execute
. Sama však nic
nedělá, jen zavolá metodu pustProceduru
. Ani tato metoda
sama nic nedělá a ihned volá jinou metodu -- spustProceduru
-- pouze
s tím rozdílem, že volání je uzavřeno do bloku try
,
takže tato metoda je schopná ihned ošetřit všechny výjimky, které při
zpracování kódu vzniknou. Vzhledem k tomu, že typů výjimek je velmi mnoho,
má jejich zpracování na starosti více metod. Kód je tak přehlednější.
Za zmínku stojí metoda napoveda
, která otevře editační okénko s procedurou, která byla zpracovávána, když k výjimce došlo. Kurzor
nastaví na poslední zpracovanou pozici. Uživatel tak má možnost dozvědět se,
na kterém místě nastal problém.
Metoda spustProceduru
nejprve zavolá metodu preberParametry
,
která přebere od uživatele parametry (pakliže spuštěná procedura nějaké bere),
tzn. otevře několik oken se sdělením "Tato procedura vyžaduje parametr
XY typu ABC...". Parametry jsou ukládány na zásobník, kde si je později
(viz další odstavec) přebere metoda run
. Metoda preberParametry
se volá poněkud krkolomným způsobem: parametr se metodě předá pomocí globální proměnné
proceduraPrebirajiciParametry
. Důvod je ten, že metoda
preberParametry
pracuje s vizuálními prvky (okny)
a musí být (z mně ne tak zcela jasných důvodů) provedena hlavním vláknem
aplikace, tedy zavolána pomocí metody Synchronize
. Metoda
Synchronize
je však s odpuštěním tak idiotská, že jejím parametrem smí být pouze bezparametrická metoda. Tudíž, chceme-li do preberParametry
nějaký parametr propasírovat, nezbývá než použít globální proměnnou (přesněji řečeno
privátní členskou proměnnou). Takovýchto krkolomných volání uvidíte ve
zdrojovém kódu ještě více. (Pokud tento odstavec nechápete, doporučuji prostudovat
tu část Delphovské nápovědy, která pojednává o multithreadingu.)
Pak na scénu přichází metoda run
. Ta sebere parametry ze zásobníku,
nahází je do proměnných a pustí metodu zpracujSkupinuPrikazu
.
Tato metoda ve smyčce volá metodu nactiPrikaz
, která přečte klíčové slovo,
a nechá rozhodnout metodu zpracujPrikaz
, jakou speciální
metodou má být tento konkrétní příkaz zpracován. Příkazů je mnoho,
proto zmíním jen ty, jejichž zpracování je neobvyklé nebo náročnější na pochopení.
Za prvé, rozeberme si, co se stane, když interpret narazí na volání procedury.
Zpracovávání kódu přejde na metodu zavolejProceduru
. Ta projde
seznam parametrů, které volaná procedura vyžaduje, a na základě jejich typů
(číslo, logické) je ze zdrojového kódu načte a dá na zásobník. Potom na
zásobník návratových pozic uloží aktuální pozici, aktuální pozici nastaví
na začátek volané procedury a rekurzivně zavolá metodu run
.
Coby parametr jí předá volanou proceduru. Po skončení nastaví aktuální pozici
na pozici uvedenou na vrcholu zásobníku.
Za druhé, trochu komplikovanější je přeskakování kusu kódu. To je zapotřebí, jestliže
neplatí podmínka u příkazu pokud
, nebo když se má nulakrát provádět
nějaká smyčka. K tomuto účelu existuje procedura doctiZa
.
Pracuje následnovně: ví, že každý příkaz je zakončen středníkem a středník
nemá ukončovací funkci pouze tehdy, pokud je uzavřen v uvozovkách nebo složených
závorkách (komentář). Tedy, pro interpret není složité najít začátek příkazu.
Pak už jenom stačí pamatovat si, jak hluboko jsme se zanořili do
vložených bloků (narazíme na začátek blokového příkazu, zvětšíme hloubku,
narazíme na příkaz konec
, snížíme hloubku). Jakmile je hloubka
vnoření nulová a narazíme na klíčové slovo ohraničující konstrukci, kvůli níž
kód přeskakujeme, metoda doctiZa
skončí a vrátí, po jaký
příkaz kód přeskočila (např. nebo pokud
, jinak
,
konec
).
ZImage
Unita realizující komponentu TZImage
-- plochu, na niž želvička kreslí.
Pamatuje si svůj stav, tedy aktuální polohu želvičky, směr, kterým je natočená želvička,
dále to, jestli se má želvička vykreslovat a jestli má za sebou zanechávat stopu.
Obsahuje metody pro nakreslení čáry a otočení želvičky, pak také metody pro
změnu vlastností kreslení (tloušťka čáry, barva). Aby komponenta nemusela želvičku
při každém pohybu mazat, obsahuje privátní objekt BM
typu TBitmap
. Když je zavolána procedura kreslící čáru, nekreslí
se přímo na Canvas
této komponenty, ale právě do BM
.
Pak je teprve vykreslen obsah BM
na Canvas
komponenty
a na závěr se na Canvas
nakreslí želvička. Lze to přirovnat
ke skládání obrázku pomocí průsvitek -- na jedné průsvitce je nakresleno pozadí,
na druhé pohybující se objekty.
UnitProcedura
Každá procedura, kterou programátor v jazyce Želví grafiky vytvoří, je objektem
třídy Procedura
.
Ke čtení kódu, který je uložen v objektu třídy Procedura
, slouží
funkce cti
. Ta bere jeden parametr -- index znaku. Počet parametrů
je uložen ve veřejné členské proměnné parametru
, samotné parametry
v poli parametry
. Parametr je struktura o dvou položkách -- názvu
parametru a typu parametru (celý, desetinný, logická hodnota), což je výčtový
typ definovaný v unitě UnitObecnyDatovyTyp
.
Metoda ulozProceduru
je využívána hlavní unitou, když uživatel požádá
o uložení projektu. Hlavní unita pak projde seznam všech procedur, u každé
zavolá metodu ulozProceduru
a předá jí proměnnou typu TextFile
,
procedura ulozProceduru
tak ví, kam má kód zapsat. Obdobné je to se čtením.
Formát souboru je následující:
- název 1. procedury
- seznam parametrů 1. procedury (formát: typ_1._parametru název_1._parametru, typ_2._parametru název_2._parametru, ...)
- počet řádků 1. procedury
- kód 1. procedury
- název 2. procedury
- seznam parametrů 2. procedury
- počet řádků 2. procedury
- kód 2. procedury
...
Každá odrážka ve výše uvedeném schématu odpovídá jednomu řádku souboru, s výjimkou kódu procedury, ten je na tolika řádcích, kolik je v souboru uvedeno. Jednotlivé procedury nejsou nijak odděleny.
Třída Procedura
také obsahuje objekt třídy TForm2
,
tedy editační okno procedury. Když se čte zdrojový text procedury pomocí
příkazu cti
, nečte se přímo z komponenty RichEdit1
--
textové plochy v editačním okně. Místo toho je uživatel
při zavírání editačního okna dotázán, jestli chce proceduru uložit.
Odpoví-li Ano, je text v RichEdit1
zkopírován do členské proměnné
třídy Procedura
s názvem kodProcedury
. Stejně tak
se pomocí procedury ulozParametry
uloží parametry zapsané
v komponentě Edit2
editačního okna do pole parametry
--
členské proměnné třídy Procedura
.
UnitObecnyDatovyTyp
Třída ObecnyDatovyTyp
je tu ze dvou důvodů.
Obecný datový typ obsahuje navíc protected proměnnou typ
,
která značí, o jaký konkrétní typ jde. Potomci obecného datového typu
jsou číselný typ a typ logické proměnné. Číselný typ implementuje všechny
běžné aritmetické operace, upřesňuje výjimky (dojde-li k dělení nulou,
ošetří systémovou výjimku, zároveň však vyvolá novou, typu ODTException),
stará se také o to, aby měl výsledek správný typ, a tak dále.
Objekt číselného typu má dvě privátní proměnné. Jedna slouží k uložení
celého čísla, druhá k uložení desetinného. Vždy je použita právě jedna z nich.
UnitDefiniceVyjimek
Aby se výjimky daly snadno ošetřit, je zaveden jednotný systém jejich ošetřování
pro celou aplikaci Želví grafika. Jsou definovány nové výčtové typy, jejichž názvy
končí na ExceptionCode
-- slouží ke specifikaci, k jaké chybě
konkrétně došlo. Jsou definovány nové výjimkové třídy, každá
pro jiný typ chyby (chyba při zpracování zdrojového kódu, při výpočtu,
práci s proměnnými, běhová chyba). Každá z těchto nových výjimkových tříd
má konstruktor, který bere proměnnou, jejíž typ je některý z nově definovaných
výčtových typů. Navíc každá tato výjimková třída implementuje vlastnost
errType
, která slouží ke zjištění konkrétního typu chyby (vrací
kód nastavený konstruktorem). Všechny výjimky jsou pak ošetřeny na jednom
místě -- v hlavní unitě. Ošetření výjimky znamená zobrazení okna se zprávou
(ta závisí na typu errType
), případně otevření editačního
okna procedury s kurzorem nastaveným na poslední zpracované pozici.
UnitPromenne
Objekt této třídy si pamatuje všechny existující proměnné a poskytuje základní
operace s nimi -- vytvoření, vyhledání (včetně ošetření výjimek) a překrývání.
Je-li zavolána nová procedura Želví grafiky, zavolá se metoda
zvysVysku
, která zvýší výšku zásobníku a tak umožní definovat
proměnnou se jménem, které už bylo použito u jiné proměnné. Hodnota se
překryje. Po skončení procedury se zavolá snizVysku
.
UnitZasobnikyParametruAPozic
Když je zavolána procedura Želví grafiky, interpret přečte její parametry
a uloží je na zásobník parametrů pomocí metody newParam
třídy
Zasobniky
. Potom uloží aktuální pozici (tj. zpracovávanou proceduru a
index posledního zpracovaného znaku zdrojového kódu) na zásobník návratových
pozic pomocí procedury newCall
třídy Zasobniky
.
Pak se nastaví aktuální pozice na začátek volané procedury a rekurzivně
se zavolá metoda třídy ZpracovaniZdrojovehoKodu
, která má na starosti
zpracování procedury -- pustProceduru
. První, co tato metoda
udělá po svém spuštění je, že ze zásobníku parametrů načte parametry, které
přebírá, a uloží je do proměnných. Jakmile skončí, ze zásobníku návratových
pozic se vyjme nejvrchnější prvek, tedy místo, kam se má zpracování vrátit, a
na něj se nastaví aktuální pozice.
Co se týče implementace zásobníků, je použito pole a integerová proměnná
udávající výšku zásobníku, tedy index vrcholu. Při překročení
naximální povolené výšky některého ze zásobníků
(konstanty MAX_PARAM_STACK_DEPTH
a MAX_CALL_STACK_DEPTH
)
je vyvolána výjimka.