Tlmočník 4
V predchádzajúcom príspevku som tlmočníka rozšíril o načítavanie
programu zo štandardného vstupu. Teraz prepíšem niektoré inštrukcie
tak, aby všetky mali práve 3 argumenty. Toto spôsobí, že každá
inštrukcia bude zložená zo 4 celých čísel. Takisto aj dáta
budem ukladať v blokoch 4 celých čísel. Všetky čísla majú dátový
typ int64_t (ako v predchádzajúcej verzii programu). Pre ucelenosť
uvediem všetky súbory nového programu.
Súbor definície.h obsahuje definície konštánt, ktoré využíva viac než jeden programovaný alebo hlavičkový súbor. Obsah tohto súboru je na obrázku 1.
#ifndef _DEFINÍCIE_H #define _DEFINÍCIE_H #define ZÁKLADNÝ_TYP int64_t #define ZT ZÁKLADNÝ_TYP // VEĽKOSŤ_BLOKU je počet //jednotiek typu ZÁKLADNÝ_TYP v // jednom bloku. #define VEĽKOSŤ_BLOKU 4 #define VB VEĽKOSŤ_BLOKU #define POČET_BLOKOV 1000 #define VEĽKOSŤ_PAMÄTE \ (VEĽKOSŤ_BLOKU * POČET_BLOKOV) #define VP VEĽKOSŤ_PAMÄTE // Chybové kódy. #define ŽIADNA_CHYBA 0 #define NEDOSTATOK_PAMÄTE 1 // Indexy blokov. #define BLOK_HODNOTA 2 #endif
Na obrázku vidíme definíciu základného typu, veľkosti bloku, počtu
blokov a veľkosti celej pamäte. Blok je pole celých čísel. Počet čísel
v bloku definuje konštanta POČET_BLOKOV.
Súbor rozpoznanie.h obsahuje funkcie, ktoré rozpoznávajú celé čísla
a bloky. Obsah tohto súboru je na obrázku 2. Obe funkcie
načítaj_kladné_číslo a načítaj_číslo som opísal v predchádzajúcom
príspevku. Druhá funkcia využíva prvú, pričom najprv prečítať prvý
znak a zistí, či ide o znamienko mínus. Ak áno, tak je znamienko
načítaného čísla zmenené na záporné.
#ifndef _ROZPOZNANIE_H_ #define _ROZPOZNANIE_H_ /* V tomto súbore sú funkcie na rozpoznávanie zadaných znakov a blokov. */ #include "definície.h" #include "zjedz.h" ZT načítaj_kladné_číslo(){ // Kladné číslo je konečná // postupnost číslic. ZT číslo = 0; // Max 18 číslic. for (ZT i = 0; i < 18; ++i) { int c = getchar() - '0'; // printf("Znak: %c\n", c); if (c > -1 && c < 10) { číslo *= 10; číslo += c; } else { break; } } return číslo; } ZT načítaj_číslo() { // Pozri prvý znak. Ak je to '-', // tak ide o záporné číslo. int c = getchar(); ZT číslo = 0; if (c == '-') { číslo = načítaj_kladné_číslo(); číslo = -číslo; } else { ungetc(c, stdin); číslo = načítaj_kladné_číslo(); } return číslo; } #endif
Na obrázku 3 je zobrazený súbor
načítaj-program.h, ktorý obsahuje funkciu načítaj_program. Funkciu sme
trochu pozmenili oproti jej implementácii v predchádzajúcej časti
tejto série. Namiesto načítania prvých troch čísiel načíta prvý blok,
ktorý obsahuje uvedené 3 magické čísla. Magické čísla v tejto verzii
reprezentujú počet blokov programu, prvú adresu, na ktorú sa majú
začať bloky načítavať a nakoniec novú hodnotu počítadla blokov.
#ifndef _NAČÍTAJ_PROGRAM_H #define _NAČÍTAJ_PROGRAM_H #include "definície.h" #include "rozpoznanie.h" #include "blok.h" extern ZT pamäť[VP]; extern ZT pb; extern ZT chyba; void načítaj_program() { // Načítaj prvý (špeciálny) blok. ZT prvý_blok[VB]; načítaj_blok(prvý_blok); // Spracuj prvý blok. ZT počet_blokov = prvý_blok[0]; ZT prvá_adresa = prvý_blok[1]; ZT pb_na_konci = prvý_blok[2]; ZT blok[VB]; for (ZT i = 0; i < počet_blokov; ++i) { pb = prvá_adresa + i; if (pb + VB < VP) { načítaj_blok(blok); for (ZT j = 0; j < VB; ++j) { pamäť[VB*pb+j] = blok[j]; } } else { chyba = NEDOSTATOK_PAMÄTE; return; } } pb = pb_na_konci; } #endif
Pokiaľ nie je dostatok voľných blokov v pamäti, potom funkcia nastaví
hodnotu premennej chyba, ktorá je nová globálna premenná, a ktorej
úlohou je signalizovať typ chyby. Obyčajne má hodnotu ŽIADNA_CHYBA. Po
volaní niektorých funkcií môže byť táto premenná zmenená. Ak
programátor chce reagovať na chybu, tak môže pozrieť hodnotu danej
premennej. Toto je dočasné riešenie, ktoré skôr slúži ako ukážka než
seriózna vymoženosť.
Na obrázku 4 je zobrazený obsah
súboru definície-blok.h. Súbor obsahuje pomocné makrá, ktoré pracujú
s blokmi. V tomto prípade ide hlavne o makro I_NA_BLOK, ktoré prevedie
pamäťový index na adresu bloku. Ide o jednoduché indexovanie do
pamäte, ale opakovať danú operáciu v programe mnohokrát metie
čitateľa. Takto stačí ak porozumie jednému makru. Druhé makro
I_NA_BLOK_n vyberie n-té číslo z bloku na indexe i. Tretie makro
I_NA_HODNOTU vyberie hodnotu bloku (hodnota je pozícii BLOK_HODNOTA).
Na obrázku 5 je jeden z nových súborov tejto verzie.
V tomto súbore mám funkcie, ktoré pracujú s blokmi. Príkladom takejto
funkcie je funkcia načítaj_blok, ktorá načíta stanovený počet čísel,
ktorý je daný konštantou VEĽKOSŤ_BLOKU do poľa miesto, ktoré
predstavuje pamäťový blok. Predtým než sa toto stane, funkcia
načítaj_blok zahodí biele znaky z toku vstupných dát pomocou funkcie
zjedz_biele_znaky. Potom zahodí komentár pomocou funkcie
zjedz_komentár. Funkcia zjedz_biele_znaky ako aj zjedz_komentár sú
definované v súbore zjedz.h. Súbor zjedz.h opíšeme o niečo nižšie.
Pokiaľ ďalší znak nie je prázdny znak (biely znak po anglicky,
t. j. medzera alebo nový riadok), potom zjedz_biele_znaky neurobí nič.
Podobne aj funkcia zjedz_komentár nič neurobí, ak ďalší znak nie je
začiatkom komentáru (znak komentáru je /). Táto funkcia zahodí všetko
od znaku / po koniec riadku, čo umožní v programovom súbore uviesť
komentáre.
#ifndef _DEFINÍCIE_BLOK_H #define _DEFINÍCIE_BLOK_H #include "definície.h" // Indexu na adresu bloku. #define I_NA_BLOK(i)\ (&pamäť[VEĽKOSŤ_BLOKU * i]) #define I_NA_BLOK_n(i, n)\ (pamäť[VEĽKOSŤ_BLOKU * i + n]) #define I_NA_HODNOTU(i)\ (pamäť[VEĽKOSŤ_BLOKU * i \ + BLOK_HODNOTA]) #endif
#ifndef _BLOK_H #define _BLOK_H #include "definície.h" #include "definície-blok.h" #include "zjedz.h" extern ZT pamäť[VEĽKOSŤ_PAMÄTE]; void načítaj_blok( ZT miesto[VEĽKOSŤ_BLOKU]){ for(int i=0; i<VEĽKOSŤ_BLOKU;++i) { zjedz_biele_znaky(); zjedz_komentár(); miesto[i] = načítaj_číslo(); } } void vypíš_blok( ZT blok[VEĽKOSŤ_BLOKU]) { for (ZT i = 0; i < VEĽKOSŤ_BLOKU; ++i) { printf("%ld", blok[i]); if (i != VEĽKOSŤ_BLOKU) { printf(" "); } } printf("\n"); } void vypíš_blok_na_indexe( ZT index) { vypíš_blok(I_NA_BLOK(index)); } void vypíš_hodnotu_bloku( ZT blok[VEĽKOSŤ_BLOKU]) { printf("%ld", blok[BLOK_HODNOTA]); } void vypíš_hodnotu_bloku_na_indexe( ZT index) { vypíš_hodnotu_bloku( I_NA_BLOK(index)); } #endif
Zmenil sa aj programový súbor (súbor, v ktorom je uložený program). Jeho obsah je:
9 0 0 0 3 1 4 0 // 0: Skok na blok 4. 5 1 0 0 // 1: Dáta - 0 (pre porovnávanie). 5 1 10 0 // 2: Dáta - počítadlo. 5 1 1 0 // 3: Dáta - krok odčítania. 0 2 0 0 // 4: Výpis počítadla. 3 2 8 0 // 5: Ak počítadlo (2) je 0, skok na 8. 1 2 2 3 // 6: Odpočítaj 1 od počítadla. 3 1 4 0 // 7: Skok na výpis. 2 0 0 0 // 8: Koniec programu.
Všimnime si, že prvý riadok súboru obsahuje špeciálny blok, ktorý sme
už opísali vyššie. Hodnoty 9 0 0 0 znamenajú, že program ma dĺžku 9
blokov, prvý blok sa má zapísať na index 0 v pamäti a tretia nula na
prvom riadku určuje hodnotu počítadla blokov po načítaný programu,
takže v tomto prípade počítadlo blokov je nastavené na hodnotu nula, a
teda tlmočník začne vykonávať prvý blok v súbore, čo je 3 1 4 0.
Každý riadok môže končiť komentárom, ktorý začína znakom / a končí novým riadkom. Pred znakmi / sú bloky, ktoré tlmočník načíta. Všimnime si, že každý blok má 4 čísla, čo predstavuje veľkosť bloku. Túto hodnotu sme definovali v súbore definície.h.
Súbor interpretér4.c obsahuje hlavnú rutinu main. Obsah súboru je na obrázku 6. Musel som vykonať niekoľko zmien, keďže pamäť je v tejto verzii programu rozdelená na bloky, čo sú štvorice čísiel.
Namiesto počítadla programu máme počítadlo bloku pb. Tlmočník prechádza bloky v pamäti, zvyšovaním hodnoty pb. Pozrie hlavičku bloku. Každý blok v pamäti má takúto hlavičku. Je to prvé číslo v bloku. Táto hlavička určuje čo tlmočník urobí ak stretne blok.
Napríklad, ak hlavička bloku s hodnotou 0 predstavuje inštrukciu pre výpis hodnoty bloku. Pojem hodnota bloku má zmysel pre špeciálne bloky, ktoré sú dátové bloky. Tieto bloky majú hlavičku 5.
#include<stdio.h> #include<stdint.h> #include "rozpoznanie.h" #include "načítaj-program.h" #include "definície.h" #include "blok.h" #include "aritmetika.h" #include "skoky.h" // Pamäť programu. ZT pamäť[VEĽKOSŤ_PAMÄTE] = { 4 // blok č. 0: načítaj program }; // Počítadlo blokov. ZT pb = 0; // Chybové kódy. ZT chyba = 0; ZT main() { while (1) { ZT hlavička = pamäť[VEĽKOSŤ_BLOKU * pb]; switch(hlavička) { case 0: // Výpis bloku: [0 index_bloku - -]. ZT index_bloku = pamäť[VEĽKOSŤ_BLOKU * pb + 1]; vypíš_hodnotu_bloku_na_indexe(index_bloku); // Skok na další blok (inštrukciu). pb += 1; break; case 1: odčítaj(); break; case 2: return 0; // Koniec programu. break; case 3: skok_ak_nula(); break; case 4: načítaj_program(); break; case 5: // Dátový blok // (bez interpretácie). pb += 1; break; default: return 1; break; } } }
Niektoré operácie tlmočníka som vyčlenil zo súboru interpretér4.c do samostatných súborov. Ide o operáciu odčítania, ktorú som umiestnil do súboru aritmetika.h. Podobne ako prechádzajúca verzia danej operácie, aj aktuálna verzia odčíta dve čísla a uloží výsledok na stanovené miesto. Obsah súboru aritmetika.h je na obrázku 7.
V tomto prípade argumenty inštrukcie určujú indexy blokov a nie indexy, na ktorých je adresa argumentov a výsledku. a o operáciu podmieneného skoku, ktorú som umiestnil do súboru skoky.h, ktorého obsah je na obrázku 8.
#ifndef _ARITMETIKA_H #define _ARITMETIKA_H #include "definície.h" extern ZT pamäť[VEĽKOSŤ_PAMÄTE]; extern ZT pb; void odčítaj(){ ZT index_výsledku = pamäť[VEĽKOSŤ_BLOKU * pb + 1]; ZT index_argumentu1 = pamäť[VEĽKOSŤ_BLOKU * pb + 2]; ZT index_argumentu2 = pamäť[VEĽKOSŤ_BLOKU * pb + 3]; // Vykonaj operáciu. I_NA_HODNOTU(index_výsledku) = I_NA_HODNOTU(index_argumentu1) - I_NA_HODNOTU(index_argumentu2); pb += 1; } #endif
#ifndef SKOKY_H #define SKOKY_H #include "definície.h" extern ZT pamäť[VEĽKOSŤ_PAMÄTE]; extern ZT pb; void skok_ak_nula(){ // Skok ak nula: // [3 index_testu // cieľový_index -]. ZT index_testu = I_NA_BLOK_n(pb, 1); ZT cieľový_index = I_NA_BLOK_n(pb, 2); ZT testovaná_hodnota = I_NA_HODNOTU(index_testu); if (testovaná_hodnota == 0) { pb = cieľový_index; // Skok. return; } // Prejdi na ďalšiu // inštrukciu bez skoku. pb += 1; } #endif