Programovací jazyk Forth a zásobníkové procesory (11)

22. 3. 2005
Doba čtení: 11 minut

Sdílet

Ilustrační obrázek
Autor: Depositphotos – stori
Ilustrační obrázek
V dnešním pokračování seriálu o programovacím jazyku Forth a zásobníkových procesorech dokončíme část věnovanou vlastnímu popisu Forthu. Popíšeme si slova používaná pro přesuny bloků dat, zbývající slova pro manipulaci s řetězci a také se podíváme, jakým způsobem je možné rozšiřovat či modifikovat samotný překladač a interpretr Forthu.

Obsah

1. Přesuny bloků dat
2. Slova pro práci s řetězci
3. Rozšíření překladače
4. Defining words
5. Adresy vytvořených slov
6. Podmíněný překlad
7. Slova pro konstrukci typu switch-case
8. Rozšíření možností v GForthu
9. Obsah dalšího pokračování

1. Přesuny bloků dat

There is no better environment for figuring out hardware, though. When we're confronted with a peripheral with 50 programmable registers, most probably poorly documented, we've got no choice but to sit down and play with the device. Though it's possible to use any debugger to issue specific port input/output commands, Forth shines as we can try something simple and then enshrine it as a „word“, which gets reused as we learn more about the device. 

Jednou ze základních operací, které se při programování často provádějí, je přesun bloků dat. Tato operace se například objevuje při manipulaci s řetězci, při práci se soubory, s databázemi apod. Vzhledem k tomu, že se tyto operace mnohdy aplikují na velké objemy dat, je nutné, aby doba trvání přesunu byla co nejkratší. Vlastní přesuny jsou proto různým způsobem optimalizovány, mnohdy se k tomuto účelu využívají i speciální instrukce mikroprocesorů.

Forth pro přesuny bloků dat nabízí dokonce tři slova. Tato slova se liší zejména tím, kterým směrem probíhá přenos dat, zda od nejnižší adresy, či naopak od adresy nejvyšší. Ve většině případů nás nemusí směr přenosu dat zajímat, problémy však nastanou tehdy, když se zdrojová a cílová oblast překrývá. Potom již samozřejmě na pořadí adres záleží.

Prvním slovem, které provádí blokový přesun dat, je slovo CMOVE, které má zásobníkový diagram ( adr1 adr2 u –). Před provedením tohoto slova musí být na zásobníku operandů uložena počáteční adresa zdrojové oblasti paměti, počáteční adresa cílové oblasti paměti a počet přenášených bytů. Přenos se provádí od nejnižší adresy směrem k adrese nejvyšší. Pokud se zdrojová a cílová oblast překrývá, je možné toto slovo využít k zaplnění paměti jedním znakem (adresa se liší o jednotku) nebo vzorkem (adresa se liší o větší počet bytů). Samozřejmě, že vyplnění paměti jedním znakem pomocí slova CMOVE je neefektivní, protože se každá paměťová buňka musí zapsat i přečíst. Při běžně pojaté výplni dochází pouze k zápisu.

Dalším slovem určeným pro blokový přesun dat je slovo CMOVE>, jehož zásobníkový diagram je stejný jako u slova předchozího, tj. ( adr1 adr2 u – ). Přenos se v tomto případě provádí od nejvyšší adresy směrem k adrese nejnižší. Toto slovo pochází z ANS-Forthu, v některých starších dialektech se jmenovalo <CMOVE, kde menšítko naznačovalo způsob čítání adres. Naproti tomu se u standardizovaného slova naznačuje, že cílová adresa může být bezpečně vyšší než adresa zdrojová.

Třetím slovem, kterým je možné blokově přenášet data, je slovo MOVE, které má stejný zásobníkový diagram jako obě slova předchozí. Po provedení tohoto slova je od cílové adresy uložena přesná kopie dat začínající na adrese zdrojové, bez ohledu na případný překryv obou oblastí.

2. Slova pro práci s řetězci

Forth is portable. If the implementation rigidly specifies the memory architecture and data types used (and this can be done with essentially no sacrifice in speed), Forth programs can be made 100% compatible among implementations. Programs can be transferred as ASCII files, universally interchangeable across systems. Application data types defined in Forth, using its object creation facilities, automatically gain the portability of the underlying data types.

Práci s řetězci jsme si popsali již v osmé části tohoto seriálu (jednalo se o slova typu STRLEN, COPY$, TYPE apod.), dnes si pouze ve zkrácené formě uvedeme slova, která dříve nebyla popsána a která se při práci s řetězci mohou hodit.

Slovo COMPARE se zásobníkovým diagramem ( adr1 u1 adr2 u2 – n ) provádí porovnání dvou řetězců klasickým způsobem, tj. znak po znaku. V případě, že jsou řetězce shodné, vrátí se nulová hodnota, jinak je na zásobník operandů uložena hodnota –1 v případě, že je první řetězec lexikograficky menší než řetězec druhý, nebo hodnota +1 v případě, že je první řetězec lexikograficky vět­ší.

Slovo BLANK slouží k vymazání všech znaků v řetězci mezerou, tj. znakem, jehož ASCII kód je roven třiceti dvěma. Zásobníkový diagram tohoto slova je ( adr u – ), tj. očekává se adresa začátku řetězce následovaná jeho délkou.

/STRING – změní délku řetězce tak, aby obsahoval právě N znaků. Zásobníkový diagram tohoto slova je ( adr1 u1 n – adr2 u2 ).

-TRAILING – odstraní z řetězce přebytečné mezery na začátku. Používá se zejména při načítání ze standardního vstupu. Toto slovo také intenzivně používá samotný interpretr Forthu.

Slovo PAGE sice nemanipuluje přímo s řetězci, nicméně se v souvislosti s nimi velmi často používá. Je to obdoba příkazu pro smazání obrazovky, s tím, že korektní funkčnost je zaručena pouze na běžných terminálech, nikoli při přesměrování výstupu.

3. Rozšíření překladače

Forth is surprisingly modern. Although it appears to be an artifact of the bygone days of 64K computers and teletype machines, many of its concepts, viewed through contemporary eyes, are remarkably up to date. For example, few languages share its ability to define new fundamental data types, along with methods that operate upon them. The multiple dictionary facility of Forth permits one to create objects that inherit, by default, properties of their parents, and to implement such structures in an efficient manner.

Prozatím jsme si uvedli různé vlastnosti jazyka Forth, které byly jistě zajímavé a neobvyklé, ale neuvedli jsme si jeho největší zvláštnost, která není k vidění prakticky u žádného vyššího programovacího jazyka. Touto zvláštností je přístup k samotnému interpretu a překladači, kdy se zejména překladač dá rozšířit o další slova nebo je možné změnit jeho interní strukturu.

Před stručným nástinem možností rozšiřování překladače si musíme vysvětlit podstatný rozdíl mezi chováním slov při překladu a za běhu. Forth se může nacházet ve dvou stavech: ve stavu překladu slova či slov (compile time) a ve stavu běhu slov/programu (run time). Každé nové slovo může být vytvořeno tak, že se při překladu chová jinak než za běhu. Toho lze dosáhnout pomocí slova DOES>, které ukončuje část překládanou a začíná část běhovou. Vlastní použití vypadá následovně:

: nové_slovo ...překládaná část slova... does> ...běhová část slova... ;

Pro rozšiřování překladače a také pro vytváření strukturovaných proměnných se používají takzvaná defining words, která jsou popsána v následující kapitole.

4. Defining words

Slova, jež se nazývají defining words (žádný přesný překlad do češtiny mě nenapadá, snad „vytvářející slova“), tvoří velmi důležitou součást Forthu. Tato slova totiž při svém provádění (běhu) vytvářejí ve slovníku slova nová nebo alokují paměť v již vytvořeném slově. Mezi defining words patří především již dříve popsaná slova:

variable
constant
create

Příklad slova CONSTANT:

: constant create , does> @ ;

Můžeme si například vytvořit nové slovo STRING, kterým lze deklarovat řetězce o zadané délce:

: string create allot does> + ;

Použití tohoto slova při vytváření řetězce je velmi jednoduché:

30 string pozdrav

5. Adresy vytvořených slov

Jak již víme z předchozích částí tohoto seriálu, je každé nově vytvořené slovo uloženo do slovníku(dicti­onary), který je interně vytvořen jako zásobník nebo jednosměrně vázaný lineární seznam. Kód slova se v interpretru Forthu (tj. v interaktivním režimu) vyvolá pouhým zadáním jeho jména (interně se samozřejmě pracuje s adresou slova, to nás však v tomto případě nemusí trápit). Naopak při vytváření, tj. kompilaci, jiného slova se do jeho těla vkládají adresy později volaných slov. To je běžné chování, které od programovacího jazyka tohoto typu očekáváme. Příklad:

: add + ;        \ vytvoření nového slova
                 \ a uložení do slovníku
10 20 add        \ vyvolání slova v interaktivním režimu
: scitej add ;   \ vytvoření dalšího slova
                 \ v jehož těle je adresa slova add

V některých případech však potřebujeme mít přístup přímo k adrese slova i v interaktivním režimu. Pro tento účel se používá slovo (apostrof), které ve své podstatě „zabraňuje“ spuštění slova; podobně se v LISPu chová speciální forma QUOTE – ". Použití slova si můžeme ukázat na příkladu:

: add + ;        \ vytvoření nového slova
' add . cr       \ výpis adresy slova

Pokud by se v předchozím příkladu na druhém řádku nepoužilo slovo ', došlo by ke spuštění/provedení slova ADD, s uvedeným apostrofem se však pouze prohledá slovník a adresa prvního výskytu slova ADD se uloží na zásobník operandů, odkud je potom načtena a vytištěna slovem . (tečka).

Znalost adresy jednotlivých slov lze použít například pro tvorbu samomodifikujících se programů (fuj) nebo pro jednoduchou práci s takzvanými callback funkcemi (huj). U obou postupů je však nutné zajistit, aby se slovo, jehož adresa je uložená na vrcholu zásobníku operandů, spustilo. K tomu slouží slovo EXECUTE. Ukažme si krátký příklad, ve kterém jsou vytvořena dvě slova pro tisk řetězce, jedna proměnná ADRESA pro uložení adresy jednoho slova a následně slovo TISK, které vytiskne řetězec podle adresy uložené v proměnné ADRESA:

: tisk1 ." Tisknu první řetězec" cr;
: tisk2 ." Tisknu druhý řetězec" cr;

variable adresa

: tisk a @ execute ;

\ zavolání slova, které vytiskne první řetězec
' tisk1 adresa !
tisk

\ zavolání slova, které vytiskne druhý řetězec
' tisk2 adresa !
tisk

Je zřejmé, že způsob úschovy adres slov do proměnných je velmi efektivní, například při programování složitých rozskoků typu switch-case (druhá možnost řešení tohoto problému je uvedena v sedmé kapitole), při práci s výše zmíněnými callback funkcemi, ale i při implementaci jednoduchého rozšíření Forthu o objektově orientované techniky.

6. Podmíněný překlad

Podobně jako v C-čku a C++ je i ve Forthu možné provádět podmíněný překlad, tj. vynechání části celého zdrojového textu z fáze překladu (fáze načtení však probíhá vždy pro celý zdrojový text). Podmíněný překlad se například používá při vytváření přenositelných programů, kdy se definice slov pro různé platformy může lišit (na některých platformách například jádro Forthu již obsahuje optimalizovaná slova pro práci s řetězci, takže je zbytečné je tvořit znovu). Nebo je možné jeden zdrojový kód použít pro vytvoření více verzí stejné aplikace. Při ladění je možné pomocí podmíněného překladu jednoduše a rychle „zneviditelnit“ část zdrojového kódu – použití slov pro začátek a konec komentáře není v tomto případě vhodné.

Pro podmíněný překlad se používají tři slova: [if], [else] a [then]. Slovo [if] vyžaduje, aby se před ním uvedl konstantní výraz, který se vyhodnotí jako logická hodnota. Pokud je tato hodnota rovna TRUE, přeloží se větev mezi slovy [if] a [else], v opačném případě se přeloží větev programu mezi slovy [else] a[then].

Jak je z předchozího odstavce patrné, je podmíněný překlad používán prakticky stejným způsobem jako v C/C++. Podmínky pro překlad se mohou navzájem vnořovat, pouze je zapotřebí dát pozor na to, aby nenastal překryv dvou podmínek. Větev mezi [else] a [then] je možné vynechat, slovo [then] se však samozřejmě musí uvést. Ukažme si jednoduchý příklad:

false [if]
: test ." Testovací slovo" ;
[then]

Ve složitějších případech je vhodné použít konstanty, neboť ty se při načítání ihned překládají do slovníku:

true constant demo

demo [if]
: test ." Testovací slovo" ;
[then]

Forth by neměl provádět kontrolu syntaxe textu uvedeného ve větvi, která je přeskakována. Zadaný text by se tedy měl kompletně ignorovat – z toho vyplývá, že konstrukci [if]-[else]-[then] nelze nahradit klasickou podmínkou if-else-then (podobnou věc mimo jiné tvrdili tvůrci Javy, kteří do její definice nezavedli podmíněný překlad s tím, že se vystačí s podmínkami if (demo) {} else {}, což samozřejmě není ve všech případech možné).

7. Slova pro konstrukci typu switch-case

V ANS-Forthu jsou předepsána slova, která je možné použít pro jazykovou konstrukci typu switch-case známou z prakticky všech imperativních programovacích jazyků (ale najdete ji například i v LISPu). Tuto konstrukci lze složit ze slov CASE, ENDCASE, OF a ENDOF. Slovní popis těchto slov by byl zbytečně složitý, proto si ukážeme jednoduchý příklad použití na slovuden-v-tydnu, které podle hodnoty uložené na vrcholu zásobníku operandů vypíše jméno dne v týdnu (pro jednoduchost není použit vstupní test korektnosti dat):

: den-v-tydnu
    case
        1 of ." pondělí" endof
        2 of ." úterý"   endof
        3 of ." středa"  endof
        4 of ." čtvrtek" endof
        5 of ." pátek" " endof
        6 of ." sobota"  endof
        7 of ." neděle"  endof
    endcase
    cr
;

(výše uvedený příklad by se samozřejmě v reálné aplikaci řešil spíše s použitím tabulek, což je efektivnější)

8. Rozšíření možností v GForthu

Programovací jazyky GNU většinou pouze nekopírují nějaký standard, ale přidávají k implementovaným jazykům další vlastnosti. Jestli je to dobrý, či špatný přístup, záleží zejména na úhlu pohledu (viz například mnohá rozšíření GCC), avšak některá rozšíření jsou opravdu přínosná. Podívejme se tedy, co v této oblasti nabízí GForth, což je GNU verze programovacího jazyku Forth (viz třetí část tohoto seriálu).

Patrně nejzajímavější novou vlastností je, že je možné pojmenovat parametry slov. Zápis názvů parametrů ke jménu slova vypadá podobně jako zápis zásobníkového diagramu daného slova. Prakticky vypadá použití pojmenovaných parametrů následovně:

: max { n1 n2 -- n3 }
    n1 n2 > if
        n1
    else
        n2
    then
;

Ihned za jménem vytvářeného slova se nachází úhlové závorky, v nichž jsou napsány parametry slova (samozřejmě bez specifikace typu). Potom přichází nepovinný komentář, který začíná znaky . Celkově tedy zápis vypadá podobně jako zásobníkový diagram, pouze jsou použity jiné závorky (zásobníkový diagram je zapisován ve formě poznámky v kulatých závorkách).

V čem mohou být pojmenované parametry výhodné? Zejména je možné k parametrům přistupovat přímo, bez nutnosti používat slova jako OVER aROT. Dále se zjednoduší a zpřehlední zápis vlastních programů, protože je ihned patrné, které parametry mají být použity. Nevýhodou je samozřejmě nekompatibilita s ostatními verzemi jazyka Forth a v některých případech i neefektivita použití parametrů.

9. Obsah dalšího pokračování

V následujícím pokračování tohoto seriálu budou popsány některé další programovací jazyky, které ideově vycházejí z Forthu. Uvidíme, že možnosti těchto jazyků, které jsou založeny na postfixové notaci a zprostředkovaně i na dvojici zásobníků, mohou zasahovat i do oblasti grafiky či funkcionálních jazyků.

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.