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

15. 3. 2005
Doba čtení: 12 minut

Sdílet

V dnešní části seriálu o programovacím jazyce Forth a zásobníkových procesorech si ukážeme způsob práce s paměťovými médii pomocí čtení a zápisu jednotlivých bloků dat. Také si řekneme, jakým způsobem je možné provést pokročilé formátování číselných hodnot na výstupu.

Obsah

1. Práce s datovými médii
2. Čtení a zápis bloků dat
3. Uložení zdrojového kódu programu do bloků (obrazovek)
4. Pokročilé formátování výstupu
5. Výpis telefonních čísel
6. Výpis data a času
7. Obsah dalšího pokračování

1. Práce s datovými médii

Již v prvních dílech tohoto seriálu jsme si řekli, že Forth je nejenom programovací jazyk, ale také ucelené vývojové prostředí a současně i (alespoň v některých implementacích) jednoduchý operační systém. Základním rysem prakticky všech historických i moderních operačních systémů je podpora práce s datovými médii nezávisle na konkrétním typu média a způsobu jeho připojení k výpočetnímu systému, tuto vlastnost ostatně splňoval i historický CP/M. Dnes se tedy budeme zabývat zejména forthovskými slovy, která je možné použít při práci s datovými médii, jakými jsou například pevný disk, disketa či jiné podobné blokové zařízení.

Datová média je možné rozdělit do různých skupin podle mnoha vlastností. Jednou z nejvýznačnějších vlastností je (alespoň pro vývojáře nových aplikací) způsob čtení a zápisu dat. Z tohoto hlediska se média (později nazývaná příznačněji zařízení, ve forthovské literatuře se však stále mluví o médiích) dělí na bloková a znaková. U blokových médií jsou informace seskupovány do větších celků, takzvaných bloků, přičemž každý blok má konstantní délku, která je většinou specifikována v bytech. Naproti tomu u znakového média se informace sice také sdružují, ale pouze do jednotlivých znaků, tj. vždy po osmi bitech (v některých případech je dokonce nejvyšší bit znaku ignorován). Příkladem blokového média je disketa, pevný disk, kompaktní disk apod. Znakovým médiem/zařízením je typicky modem a částečně také různé páskové mechaniky.

Práci se znakovými zařízeními jsme si ve své podstatě vysvětlili už v předchozích částech tohoto seriálu. Pro zápis jednotlivých znaků se používá slovo EMIT (kód znaku, jež se má vypsat, je přečten z vrcholu zásobníku operandů), pro čtení slovo KEY (uložení kódu naposledy přečteného znaku na zásobník operandů). Zápis skupiny znaků (řetězce) zajistí slovo TYPE, načtení celého řetězce slovo EXPECT. V některých implementacích Forthu jsou tato slova rezervována pouze pro přístup ke konzoli (tj. ke klávesnici a znakové obrazovce), v jiných je možné programově provést „přesměrování“ vstupu nebo výstupu na jiné zařízení.

Poněkud složitější je práce s blokovými zařízeními. V dřívějších dobách se k blokovým médiím opravdu přistupovalo přes jednotlivé bloky (viz například starší verze DOSu pro počítače PC, kde se údaje o otevřených souborech uchovávaly ve strukturách nazvaných FCB – File Control Block), dnes je jasně patrná snaha o přístup k blokovým datovým médiím pomocí stejných funkcí jako u médií orientovaných na přesuny jednotlivých znaků.

To je na jednu stranu jistě potěšitelné, neboť se zjednoduší vývoj aplikací, na druhou stranu se mezi vlastní zařízení a aplikaci vkládá další (mnohdy zbytečná) vrstva, jejíž vinou může dojít ke zpomalení čtení a/nebo zápisu. Pokud je Forth skutečně použit jako operační systém (například u vestavěných systémů), probíhá čtení a zápis bloků dat přímo, tj. pomocí komunikace jádra Forthu přímo s daným zařízením. U disků připojených přes rozhraní IDE se používají ATA příkazy, u zařízení na sběrnici SCSI příkazy typické pro tuto sběrnici apod. Věci jako souborový systém nejsou implementovány.

Jelikož byl Forth koncipován také jako operační systém s podporou práce s datovými médii (a multitaskingem – viz předchozí díly seriálu), podporuje také přímo práci s blokovými zařízeními. Blok má ve Forthu velikost přesně 1 KB (1024 byte), což je hodnota zvolená tak, aby byla vhodná jak pro zařízení s malou kapacitou, tak i pro zařízení s kapacitou vyšší. Například sektor diskety má podle použité mechaniky a způsobu organizace stop velikost 128 byte až 1024 byte, velikost sektoru pevného disku se pohybuje v rozmezí jednoho kilobyte a „sektor“ na disku CD-ROM má (při použití samoopravného kódu) velikost 2 KB. Kromě toho se do velikosti jednoho bloku zapisují i takzvané obrazovky (screens) ve Forthu, jejichž použití bude uvedeno v dalších kapitolách.

2. Čtení a zápis bloků dat

Jak jsme si již uvedli v předchozí kapitole, má blok ve Forthu velikost přesně jednoho kilobyte. Veškeré zápisy a čtení probíhají po celých blocích, což od programátora aplikace vyžaduje určitou pozornost. Na druhou stranu je však blokový přístup vhodný pro vytváření databází s pevnou délkou záznamu, kdy je přístup k jednotlivých záznamů (či jejich skupinám) nebo jejich modifikace velmi rychlá a přitom efektivní.

Při práci s blokovým zařízením se nepoužívá klasický souborový systém, jediná úroveň abstrakce nad daným zařízením jsou právě bloky. To sice komplikuje organizaci dat na disku, při správně zvolené metodě organizace je však velmi jednoduché vytvářet různé struktury (například stromy apod.) s tím, že přístup k jednotlivým blokům nahrazuje posun na určitá místa v souborech (známé funkce typu seek).

Načtení bloku z blokového zařízení do paměti je možné provést pomocí slovaBLOCK, jehož zásobníkový diagram je možné zapsat: ( u – addr ), kde u značí číslo bloku zadané celým číslem bez znaménka a addr je počáteční adresa, od které Forth načtený blok uložil.

Pokud se v načteném bloku nachází čitelný text (například část samotného programu napsaného ve Forthu), je možné část bloku či celý blok ukázat na obrazovce. Stačí si vzpomenout na slovo TYPE, které vyžaduje, aby na vrcholu zásobníku operandů byla uložena adresa řetězce následovaná počtem znaků (délkou řetězce). Výpis prvního řádku (64 znaků) z bloku číslo jedna tedy zajistí příkaz:

1 block 64 type

Výpis celého bloku je také snadný, protože blok má pevně danou velikost 1024 bytů, což lze rozepsat na šestnáct řádků po 64 znacích:

1 block 16 64 * type

Opakem slova BLOCK je slovo UPDATE. Toto slovo nastaví u současného bloku příznak modifikace. Při následném ukládání bloků (viz následující odstavec) se postupně projdou všechny bloky načtené do paměti a modifikované bloky se uloží zpět na blokové zařízení, například pevný disk. Zásobníkový diagram slova UPDATE je velmi jednoduchý:( – ).

Po zavolání slova SAVE-BUFFERS se interně projdou všechny bloky a ty z nich, které mají nastaven příznak modifikovanosti (viz slovo UPDATE), jsou uloženy zpět na paměťové zařízení. Použití tohoto slova tedy zhruba odpovídá funkci flush operačního systému.

To je vše, žádné další funkce pro práci s bloky nejsou zapotřebí. Je vidět, že se nemusíme starat o otevírání a zavírání souborů, kontrolu zápisu apod. Vše je díky organizaci dat do bloku velmi jednoduché – komplikace samozřejmě nastávají při výpočtu čísel jednotlivých bloků, tato problematika však již úzce souvisí s charakterem vytvářené aplikace a není ji vhodné řešit obecně.

V následující kapitole bude ukázáno, jakým způsobem je možné organizovat samotný zdrojový kód programu do jednotlivých bloků.

3. Uložení zdrojového kódu programu do bloků (obrazovek)

V tradičních Forthech se zdrojový text kódu programu neukládal do textových souborů (jak je tomu dnes), ale do jednotlivých bloků. V případě práce se zdrojovým textem se většinou nemluví o blocích, ale o takzvaných obrazovkách (screens).

Velikost obrazovky je za jakýchkoli podmínek rovna šestnácti řádkům, kde na každém řádku je uloženo 64 znaků. Celková velikost obrazovky je tedy rovna 64×16 = 1024 znakům/bytům, což přesně odpovídá velikosti jednoho bloku. Všimněte si, že délka řádku je vždy rovna 64 znakům, nezávisle na aktuálním obsahu řádku. Pokud je řádek „prázdný“, je v bloku jednoduše zapsáno 64 mezer (ASCII znaků s kódem 32). Konce řádků tedy není zapotřebí žádným způsobem kódovat.

Jisté je, že tento způsob zápisu je neúsporný, zejména při zápisu programů „vertikálním“ způsobem. I z tohoto důvodu někteří zkušení programátoři zapisují definice nových slov „horizontálním“ způsobem, tj. na jeden řádek – slova tak musejí být krátká, což vede k důsledné faktorizaci kódu (ta ostatně není nikdy na škodu, zvláště ve Forthu).

Pro načtení a zpracování (tj. překlad a interpretaci) celé obrazovky se používá slovo LOAD, jehož zásobníkový diagram je následující: ( u –). Před provedením tohoto slova musí být na zásobník operandů uloženo číslo bloku, který se má načíst a přeložit.

Pro souvislé načítání bloků/obrazovek je vytvořeno slovo s krásným zápisem --> (pomlčka pomlčka nerovnítko), které načte následující blok z disku, tj. po bloku U je načten blokU+1. Pro automatizované načítání sekvence obrazovek je možné využít malého triku: na konci každé obrazovky je uvedeno výše popsané slovo -->, které při interpretaci automaticky způsobí odskok.

Pro výpis obsahu obrazovky na obrazovku :-) (tj. na monitor) lze použít slovo LIST se zásobníkovým diagramem ( U – ), kde u je index požadované obrazovky.

Orientace v číslech obrazovek může být složitá, takže se, především u rozsáhlejších aplikací, na některé z prvních obrazovek uvádějí konstanty, které lze pro indexaci používat. Jednoduchý příklad:

1   constant app-init
90  constant graphics
125 constant sound

app-init load
graphics load
graphics list
sound load

Pro tisk obsahu obrazovek na tiskárnu se používá slovo TRIAD, které způsobí tisk třech obrazovek (index první z nich je uložen na zásobníku operandů). Proč zrovna tři obrazovky? Na tiskárnách formátu A4 je možné vytisknout 60 řádků, což pro čtyři obrazovky nedostačuje (16*4 > 60), ale pro tři obrazovky je tato hodnota ideální (obrazovky mohou být odděleny prázdným řádkem nebo několika řádky). Z tohoto důvodu se mnohdy zdrojový kód organizuje tak, aby důležité části (například začátky modulů) začínaly na začátku tiskové strany.

Při tisku lze také využít slovo INDEX, které vytiskne první řádky z šedesáti po sobě jdoucích obrazovek. Pokud tedy obsahuje každá obrazovka stručný popis (samozřejmě v poznámce), je možné velmi jednoduchým způsobem získat „obsah“ programu. Důležitá jsou zejména odsazení prvních řádků tak, aby se při tisku zdůraznila hierarchie. Implementaci slova INDEX ponechám na čtenáři, je totiž poměrně jednoduchá – stačí použít počítaný cyklus, slovoTYPE a nezapomenout na uložení délky řetězce na zásobník operandů.

Při zápisu zdrojových textů se často používají dvě slova uvozující poznámku. První slovo se zapisuje \ a značí, že se ignoruje zbytek aktuálního řádku (tj. podobně jako v C-čku znaky //). Druhé slovo, které se zapisuje jako \S, způsobí, že se ignoruje text až do konce aktuální obrazovky. Pokud je tedy toto slovo uvedeno na prvním řádku, je celá obrazovka považována za komentář.

4. Pokročilé formátování výstupu

Prozatím jsme si při výpisu číselných hodnot ukázali slova, pomocí kterých je možné tisknout hodnoty zarovnané doleva (slovo .) a doprava (slovo .R). Forth však umožňuje formátovat číselné hodnoty na úrovni jednotlivých znaků, což je vlastnost, kterou neposkytuje žádný jiný vyšší programovací jazyk.

Základními slovy, která se při formátování používají, jsou:

  1. <# – toto slovo označuje začátek formátování/výpisu
  2. #> – opak předchozího slova, tj. konec formátování/výpisu
  3. # – toto slovo se na výstupu nahradí jednou cifrou z převáděné hodnoty
  4. #S – výpis zbytku čísla, tj. všech dosud nevytištěných cifer
  5. HOLD – přímý tisk znaku, jehož kód je uložen na vrcholu zásobníku operandů
  6. [CHAR] – následující znak je převeden na interní kód (většinou ASCII) a posléze uložen na zásobník operandů. Používá se v součinnosti se slovem HOLD.
  7. SIGN – pokud je zpracovávaná hodnota záporná, vypíše se znak – (minus).

Při formátování čísel se bere do úvahy nastavená báze (základ číselné soustavy), protože převod čísla na jednotlivé cifry se děje postupným prováděním operací dělení a modulo, přičemž se zpracovávaná hodnota postupně dělí nastavenou bází. Veškeré výpočetní operace přitom probíhají nad čísly bez znaménka uloženými v dvojitém rozsahu.

Dále si musíme uvědomit, že převod probíhá pozpátku, tj. od cifry v nejnižším řádu až po řád nejvyšší. V desítkové soustavě to znamená, že se nejdříve zpracují jednotky, potom desítky, následně stovky atd.

Ukažme si některá jednoduchá slova určená pro formátování čísel. Začneme prakticky nejjednodušším slovem, které pouze vypíše hodnotu celého čísla v dvojnásobném rozsahu bez znaménka. Slovo nazveme UINT. (ve Forthu je zvykem dávat slovům, která provádějí tisk hodnot, na konec tečku):

: uint. <# #s #> type cr ;

Použití tohoto slova je snadné, pouze nesmíme zapomenout provést převod čísla z jednoduchého rozsahu do rozsahu dvojitého:

123 s>d uint.

Převod čísel z jednoduchého rozsahu je samozřejmě možné provést i uvnitř slova, zamezí se tak vzniku chyb v případě, že programátor na převod zapomene (v tomto případě by se ze zásobníku odstranila ještě jedna položka):

: uint2. s>d <# #s #> type cr ;

Příklad použití:

123 uint2.

5. Výpis telefonních čísel

Ukažme si složitější příklad na formátování výstupu. Máme za úkol vypsat telefonní číslo tak, aby poslední trojčíslí bylo odděleno znakem – (pomlčka). Pokud si uvědomíme skutečnost, že se jednotlivé cifry generují pozpátku, je řešení přímočaré. Trojice slov [char] – hold slouží k výpisu znaku „-“ na zadanou pozici, slovo # vypíše vždy jednu cifru a slovo #S zbytek čísla (obecně nevíme, kolik bude mít telefonní číslo cifer:

: phone. <# # # # [char] - hold #s #> type cr ;

Pro otestování slova phone. je možné zadat příkaz:

12345678 s>d phone.

Výpis:

12345-679

6. Výpis data a času

Dalším nově nadefinovaným slovem je slovo DATE.sloužící k výpisu data, které je uloženo ve formátu ddmmrrrr. Při výpisu data musíme mezi dny, měsíce a roky vložit vhodný oddělovač, v našich končinách je to podle ČSN tečka. Slovo DATE. tedy musí nejprve vypsat čtyři cifry s rokem (výpis se provádí odzadu), potom vypsat oddělovací znak, dvě cifry s měsícem, opět oddělovací znak a posléze jednu nebo dvě cifry s pořadovým číslem dne v měsíci:

: date. <# # # # # [char] . hold # # [char] . hold #s #> type cr ;

Test tohoto slova je stejně snadný jako u předchozího slova PHONE.:

17111989 s>d date.
17.11.1989

Složitější slovo je použito pro výpis času. V tomto případě není čas zadán symbolicky (jako v případě data), ale jako počet sekund od půlnoci, protože takto je možné s časovým údajem provádět různé matematické operace. Při převodu se tedy musí při rozdělování času na jednotlivé cifry měnit číselná báze. Pro jednotky sekund a minut se použije desítková soustava, pro „desítky“ sekund a minut soustava šestková (aby se správně generovaly i vyšší jednotky). Jelikož je tento problém rozsáhlejší, je výhodnější provést jeho rozdělení na více jednoduchých slov.

Slovo BASE6 slouží k přepnutí na šestkovou soustavu, zpět na desítkovou soustavu je možné použít slovo DECIMAL. Slovo OO vypíše dvě cifry, před něž se vloží znak dvojtečky (oddělovač hodin, minut a sekund) a konečně slovo TIME.provede převod jednotek a veškeré formátování:

: base6 6 base ! ;
: OO # base6 # decimal [char] : hold ;
: time. s>d <# OO OO #s #> type cr ;

Příklady použití tohoto slova:

60 time.
0:01:00

120 time.
0:02:00

1210 time.
0:20:10

123456 time.
34:17:36

Jak je z výše uvedených slov DATE. a TIME. patrné, je formátování číselných hodnot ve Forthu poměrně snadné, protože máme k dispozici všechna slova, která se interně používají v samotném interpretru. Zejména možnost měnit základ číselné soustavy v průběhu formátování je v naší civilizaci (která chaoticky používá mnoho číselných soustav) velmi vítaná.

bitcoin_skoleni

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

V dalším pokračování tohoto seriálu dokončíme rozsáhlou část věnovanou programovacímu jazyku Forth a v navazujících dílech si popíšeme související oblasti, zejména problematiku zásobníkových procesorů.

Autor článku

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