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

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

Sdílet

Dnešní díl seriálu o programovacím jazyce Forth je věnován popisu slov, která je možné použít pro základní vstupně-výstupní operace (včetně základního formátování výstupu) a pro práci s externími soubory. Také si uvedeme vlastnosti paměťových oblastí TIB a PAD spolu se způsobem jejich využití.

Obsah

1. Paměťové oblasti TIB a PAD
2. Konstantní řetězce
3. Tisk číselných hodnot a řetězců
4. Základ číselné soustavy
5. Načítání řetězců
6. Práce se soubory
7. Obsah dalšího pokračování

1. Paměťové oblasti TIB a PAD

Pro vstupní a výstupní operace se ve Forthu používají dvě paměťové oblasti, které se již tradičně nazývají TIB a PAD. Pochopení významu těchto oblastí nám objasní některé interní činnosti, které se ve Forthu při vstupně/výstupních operacích provádí. Pojďme se tedy podívat tvůrcům tohoto jazyka pod pokličku.

Název paměťové oblasti TIB vychází z anglického sousloví Terminal Input Buffer, a jak již samotný název napovídá, jedná se o oblast, do které se načítají řetězce zadané uživatelem z klávesnice či přečtené z jiného vstupního zařízení. Samotný Forth tuto oblast také používá, zejména pro rozdělení uživatelem zadaného řetězce na jednotlivá slova. TIB je samozřejmě možné přímo adresovat, používat různá specifická slova pro rozpoznání lexikálních prvků (typicky REFILL, v některých implementacích i NUMBER apod.) a dokonce je možné TIB používat jako dočasný úložný prostor pro větší množství dat.

Toto použití sice není doporučováno (viz například standard ANS-Forth), protože se TIB často přemazává během vlastní interpretace, ale zejména v malých vestavěných systémech je možné paměť alokovanou pro TIB prakticky zadarmo využít i pro další účely. V jiných programovacích jazycích takovou možnost většinou nemáme (vzpomeňme například na poněkud tajnůstkářské scanf() v C-čku nebo readln v Pascalu, u kterých není zřejmé, co a kde se alokuje a dealokuje). Obě paměťové oblasti mohou mít buď konstantní velikost, nebo je jejich velikost proměnná v závislosti na ostatních paměťových oblastech (například obou forthských zásobnících a slovníku). V každém případě je možné velikost oblasti TIB zjistit pomocí slova TIB#, které uloží počet právě alokovaných buněk na zásobník operandů.

Paměťová oblast PAD získala svůj název z anglického slova scratch-pad, což lze volně přeložit jako „čmárací blok“. Tuto oblast používá Forth při formátování výstupu, například pro rozdělení čísla na jednotlivé znaky. Podobně jako TIB, je i PAD možné použít pro dočasné uložení hodnot, ale opět záleží na programátorově umu a zkušenostech rozpoznat, kdy bude oblast PAD systémem přemazána – Forth nám při práci neklade prakticky žádné překážky, ale musíme přesně vědět, co děláme…

Některá slova, která budou popsána v dalším textu, využívají jednu z oblastí TIB či PAD. Zejména se jedná o slova používaná pro načítání a výstup řetězců nebo číselných hodnot.

Poznámka: některé dnešní implementace Forthu TIB a PAD nepoužívají v klasickém významu, což může negativně ovlivnit přenositelnost dříve vytvořených programů. Pokud se však na obě oblasti díváme jako na pole, není problém je pomocí slova ALLOT alokovat v operační paměti počítače na nějakou „rozumnou“ velikost:

create PAD 512 allot
create TIB 512 allot

Je samozřejmé, že do takto vytvořených paměťových oblastí (polí) nebude Forth nijak zasahovat.

2. Konstantní řetězce

Slova pro tvorbu konstantních řetězců jsme si uvedli již v předchozích částech tohoto seriálu. Zde si pouze zopakujme, že řetězec lze ve Forthu vytvořit několika různými způsoby. Buď se jedná o pole bytů, kde v prvním byte je uložena délka řetězce, nebo se jedná o řetězec ukončený speciálním znakem (například nulou, podobně jako v C-čku). Třetí možnost představuje řetězec, jehož délka je uložena ve zvláštní proměnné, nezávisle na umístění vlastního řetězce v operační paměti. Při práci s třetím typem řetězce se na zásobník operandů musí uložit jak adresa prvního znaku v řetězci, tak i délka řetězce.

Forth přímo obsahuje slova pro vytvoření konstantního řetězce prvního typu i třetího typu. Řetězec prvního typu, který se v literatuře také nazývá počítaný řetězec (counted string), se vytváří pomocí slovaC" a omezujícího znaku " následujícím způsobem:

C" znaky řetězce"

Po provedení výše uvedené sekvence se v paměti vytvoří konstantní počítaný řetězec a na zásobník operandů se uloží adresa tohoto řetězce. Zásobníkový diagram slova C" je tedy možné zapsat následovně: ( – adresa). Důležité je, že tento typ řetězce NENÍ možné přímo vytisknout pomocí slova TYPE, neboť na zásobníku není uložena délka řetězce, kterou slovo TYPE pro svou korektní činnost vyžaduje. To znamená, že následující zápis je chybný:

C" chybný zápis" type cr

Stejně tak je chybný i zápis:

C"chybný řetězec"

Protože C" je slovo, které musí být od řetězce odděleno alespoň jedním bílým znakem. Při tisku řetězce je nejprve nutné spočítat délku řetězce pomocí slova COUNT, které současně zvýší i adresu řetězce o jednotku, aby při tisku došlo k přeskočení bytu s délkou řetězce. Teprve poté lze řetězec vypsat:

C" korektní zápis" count type cr

Třetí typ řetězce, tj. řetězce, u kterého je délka uložená přímo na zásobníku operandů, se vytvoří pomocí slova S", opět v součinnosti s omezujícím znakem . Slovo se používá následovně (opět pozor na oddělení slova S“ od vlastního řetězce pomocí bílého znaku!):

S" znaky řetězce"

Po provedení tohoto slova je na zásobníku uložena jak počáteční adresa řetězce (jehož první znak leží přímo na této adrese), tak i jeho délka. Zásobníkový diagram tohoto slova tedy vypadá následovně: ( – adresa n), kde n je počet znaků v řetězci. Na takto vytvořený řetězec je ihned možné zavolat slovo TYPE:

S" korektní zápis" type cr

Jak je řetězec na zásobníku uložen, nám ilustruje příklad:

S" pokus" . cr . cr

který vypíše jak délku řetězce, tak i jeho počáteční adresu (ta je samozřejmě systémově závislá). Obě tyto hodnoty se přitom ze zásobníku odstraní, takže řetězec je pro další operace nepoužitelný.

Poznámka: pro nedestruktivní čtení zásobníku je možné použít slovo .S (tečka-S), které vypíše celý obsah zásobníku a případně i celkový počet položek na zásobníku uložených. Konkrétní formát výstupu záleží na použité implementaci Forthu, někdy se například setkáme s odlišnostmi při výpisu prázdného zásobníku. Některé Forthy v tomto případě vypíšou slovo <empty>, některé pouze 0 (nulu) a některé vůbec nic :-) Podobně je při ladění vhodné používat slovo DEPTH, které na zásobník vloží počet položek na zásobníku operandů před voláním tohoto slova.

3. Tisk číselných hodnot a řetězců

Začněme nejprve tiskem řetězců bez specifikace formátu, což je velmi snadné. Pro tento účel se používá výše zmíněné slovo TYPE, které vyžaduje, aby byla na zásobníku operandů uložena adresa prvního znaku v řetězci a délka řetězce. Počítané řetězce je nutné do tohoto formátu převést, například pomocí slova COUNT – viz předchozí kapitolu.

Základy tisku číselných hodnot také známe, jedná se o slovo . (tečka), které vytiskne hodnotu uloženou na vrcholu zásobníku operandů. Při čtení je hodnota ze zásobníku odstraněna, takže se zejména při kontrolních tiscích používá spíše dvojice slov dup ..

V mnoha případech, zejména při tisku jednoduchých tabulek, je zapotřebí provést zarovnání číselných hodnot. K tomuto účelu se používá slovo .R, jehož zásobníkový diagram vypadá následovně: (n1 n2 –). Číselná hodnota n1 je vypsána tak, že je zarovnána doprava, přičemž je použito n2 znaků (číslo je tedy zleva doplněno znakem mezery). Příklad použití tohoto slova při tisku jednoduché tabulky:

1 10 .r cr 10 10 .r cr 100 10 .r cr -10 10 .r cr

dá výsledek:

         1
        10
       100
       -10
 ok

Kromě toho je pro tisk číselných hodnot možné použít i slova .U, U.R a D.R. Slova.U a U.R vytisknou hodnotu z vrcholu zásobníku, která je přitom chápána jako bezznaménková hodnota (unsigned integer). Slovo D.R slouží k tisku doprava zarovnaných hodnot uložených ve dvojitém rozsahu.

Veškerý tisk číselných hodnot je ovlivněn základem číselné soustavy, který nemusí být nutně desítkový. Více se o číselných soustavách dozvíte v následující kapitole.

4. Základ číselné soustavy

Ve Forthu je možné číselné hodnoty zadávat i tisknout v různých číselných soustavách – od binární soustavy po soustavu o základu 36. Nastavená číselná soustava ovlivňuje vlastní interpreter Forthu a zejména pak slova pro vstup a výstup čísel (například slovo .). Číselná soustava se zvolí buď zápisem nové hodnoty do slova/proměnné BASE, nebo použitím některého z následujících slov pro vybrané (nejpoužívanější) číselné soustavy:

  • bin – binární soustava
  • decimal – desítková soustava (je nastavena jako implicitní)
  • oct – osmičková soustava
  • hex – hexadecimální soustava

Pokud některé z výše zmíněných slov ve slovníku Forthu chybí (typicky se jedná o slovo oct), není problém si je dodefinovat:

: bin 2 base ! ;
: decimal 10 base ! ;
: oct 8 base ! ;
: hex 16 base ! ;

Poněkud zvláštní je číselná soustava o základu 36, protože pro vyjádření jedné cifry se musí provést výběr jak ze všech číslic 0–9, tak i z veškerých znaků abecedy A-Z. Z toho důvodu může být někdy výhodné uchovávat krátké řetězce do délky 6 znaků (366 je méně než 232) v celočíselných proměnných.

5. Načítání řetězců

Pro načítání řetězců z klávesnice či jiného nastaveného vstupního zařízení se ve Forthu používá slovo EXPECT. Před použitím tohoto slova se na zásobník operandů musí uložit adresa paměti, do které se načtený řetězec uloží, a také maximální počet znaků, který bude uložen. Použití tohoto slova je tedy bezpečnější než C-čkovské gets(), protože při korektně nastavené délce řetězce nemůže dojít k přepsání cizí paměti.

Alternativní způsob načítání řetězců spočívá v použití slova KEY, pomocí kterého se přečte z klávesnice jeden znak, jehož ASCII kód se uloží na zásobník operandů. Pokud se bude čtení pomocí KEY provádět ve smyčce až do dosažení stanoveného počtu znaků či po dosažení konce dat, je možné simulovat výše uvedené slovo EXPECT.

6. Práce se soubory

V této kapitole si uvedeme slova, která je možné použít pro manipulaci se soubory. Tato slova mohou být vytvořena a používána především na těch platformách, které disponují paměťovými médii, na nichž je vytvořen klasický souborový systém (jedná se tedy o Unixy, různé DOSy, Microsoft Windows aj.). Na platformách, které média se souborovým systémem neobsahují, je většinou výhodnější přímo přistupovat k jednotlivým blokům o velikosti 1024 byte. Princip práce s bloky dat, jež je typická zejména pro vestavěné systémy, bude vysvětlen v následující části tohoto seriálu.

Vzhledem k tomu, že Forth byl implementován na velkém množství vzájemně značně odlišných systémů, byla na různých systémech použita různá slova pro práci se soubory, nebo slova se stejným jménem, ale s jiným chováním. Vzniklý chaos, který trval cca deset let, byl vyřešen až v ANS-Forthu, kde jsou předepsána následující slova, která lze pro práci se soubory použít (všimněte si, že všechna dále zmíněná slova končí na suffix -FILE):

  • CREATE-FILE (adresa_řetězce délka_řetězce fam – handle kód_chyby)
    Pomocí tohoto slova je možné vytvořit a otevřít nový soubor. Před zavoláním slova musí být na zásobníku operandů uloženy tři hodnoty. První hodnota značí adresu řetězce, ve kterém je uloženo jméno vytvářeného souboru. Ve druhé hodnotě je uložena délka řetězce. Třetí hodnota, která je ve skutečnosti uložena na vrcholu zásobníku operandů, obsahuje mód otevření souboru, jež je reprezentován například slovy R/O nebo R/W. Po provedení operace se na zásobník uloží takzvaný handle souboru (číselná hodnota, kterou vrací operační systém) a kód chyby, která při otevírání souboru případně nastane. V případě, že se vytvoření souboru podařilo bez chyby, je navrácena nulová hodnota.
  • DELETE-FILE (adresa_řetězce délka_řetězce – kód_chyby)
    Toto slovo je určeno pro vymazání již existujícího souboru. Podobně jako u předcházejícího slova CREATE-FILE musí být i zde na zásobníku operandů uložena adresa řetězce s názvem souboru a délka tohoto řetězce.
  • OPEN-FILE (adresa_řetězce délka_řetězce fam – handle kód_chyby)
    Toto slovo slouží k otevření souboru pro čtení či pro zápis. Operandy adresa_řetězce a délka_řetězce opět určují řetězec obsahující jméno souboru, v posledním operandu fam je hodnota obsahující mód otevření souboru (R/O…). Po provedení tohoto slova jsou na zásobník operandů uloženy hodnoty stejné jako v případě slovaCREATE-FILE.
  • CLOSE-FILE (handle – kód_chyby)
    Pomocí tohoto slova se uzavře soubor, který je v běžícím systému identifikován svým handlem. Po provedení slova se na zásobník operandů uloží kód chyby nebo nula v případě, že žádná chyba při uzavírání souboru nenastala.
  • READ-FILE (adresa délka handle – kód_chyby)
    Toto slovo slouží k přečtení dat z otevřeného souboru. Před zavoláním tohoto slova musí být na zásobníku operandů uložena adresa, od které se budou zapisovat přečtená data, délka čtené sekvence a handle souboru (otevřeného pro čtení či pro čtení/zápis).
  • WRITE-FILE (adresa délka handle – kód_chyby)
    Zápis po sobě jdoucí sekvence hodnot do souboru, který je určen svým handlem. Po provedení tohoto slova je na vrchol zásobníku vrácen kód chyby.
  • FILE-POSITION (handle – pozice kód_chyby)
    Toto slovo slouží k získání aktuální pozice, kde se provádí čtení nebo naopak zápis do souboru.
  • FILE-SIZE (handle – velikost kód_chyby)
    Po provedení tohoto slova je na vrchol zásobníku operandů vložena délka zpracovávaného souboru.
  • FILE-EXISTS (adresa_řetězce délka_řetězce  – příznak)
    Test, zda soubor se zadaným jménem existuje. Výsledkem testu je hodnota TRUE nebo FALSE.
  • READ-LINE (adresa délka handle – u2 flag kód_chyby)
    Přečtení celého řádku textu (řetězcově orientovaný přístup do souboru).
  • WRITE-LINE (adresa délka handle – kód_chyby)
    Zápis celého řádku textu (opět se jedná o řetězcově orientovaný přístup do souboru).

Výše popsaná slova je možné použít podobným způsobem, jaký je ukázán v následujících sekvencích kódu. Nejprve si zkusme nadefinovat slovo pro vytvoření souboru s konkrétním jménem:

: pokus-se-soubory
    s" test.txt" r/w create-file
    abort" Nepovedlo se vytvořit soubor"
;

V nově vytvořeném slovu pokus-se-soubory je použito slovo ABORT", které provádí dvě činnosti. Nejprve přečte hodnotu ze zásobníku operandů, a pokud je zde uložená hodnota nenulová, zobrazí se text uvedený ihned za tímto slovem (text je ukončený omezujícím znakem ") a celý běh programu se ukončí. Vzhledem k tomu, že návratový kód slova CREATE-FILE je v případě chyby nenulový, bude ABORT velmi jednoduše nahrazovat podmínku současně s výpisem chyby a ukončením běhu programu.

Dále si můžeme všimnout použití slova R/W, kterým je definován mód otevření souborů. Pokud se má soubor otevřít pouze pro čtení, lze použít slovo R/O, otevření pro čtení i zápis dosáhneme slovem R/W a otevření pouze pro zápis se zabezpečí slovem W/O. Tato slova jsou ve skutečnosti konstantami, tj. po jejich uvedení v programu se na zásobník uloží nějaká předem zadaná konstantní hodnota (systémově závislá).

Otevření souboru je stejně snadné jako vytvoření nového souboru:

: pokus-se-soubory
    s" test.txt" r/w open-file
    abort" Nepovedlo se vytvořit soubor"
;

Pro zápis řetězce se jménem souboru je opět použito slovo s" spolu se „zarážkou“ na konci řetězce – ". Místo konstantního řetězce je samozřejmě možné použít i řetězcovou proměnnou, kdy se na zásobník uloží počáteční adresa řetězce.

ict ve školství 24

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

V následující části tohoto seriálu bude uveden zejména způsob práce s blokovými paměťovými médii. Dále se budeme zabývat velmi zajímavou vlastností Forthu – pokročilým formátováním řetězců na výstupu.

Autor článku

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