Základní třídou je mlc::symbol_tables, která reprezentuje symbolové tabulky překladače. Umožnuje uložit a vyhledat tyto symboly a je speciálně vytvořeba pro potřeby jazyka Mlaskal. Tabulka je v sémantických akcích Bisona v akcích Flexe dostupná jako položka tab proměnné ctx
typu mlc::MlaskalCtx, tj. jako ctx->tab
.
Pro propojení lexikální a sémantické analýzy obsahuje 4 základní tabulky symbolů (přesněji metody vracející tyto tabulky):
Tyto tabulky jsou instancí šablony mlaskal::lit_storage<T>, která obsahuje hlavně metodu add(const T&) přidávající záznam do tabulky a vracející ls_id_index (resp. další ls_X_index), což je konstantní iterátor do kontejneru obsahujícího hodnoty v tabulce. Tabulky samy řeší problém duplicitních záznamů (pro stejné hodnoty vrátí stejný iterátor). Tyto "odkzy" jsou iterátory, tedy hodnota se získá operátorem *.
Hlavním úkolem tabulek je však podpora sémantické analýzy, pro které obsahuje výrazně větší podporu. Udržuje nejen tabulky symbolů, ale i funkcí, procedur, proměnných, typů, konstant a návěští. Tabulka sama řeší problémy s duplicitní definicí jednoho symbolu.
Tabulka sama řeší problém lokálních a globalních symbolů. Za tímto účelem obsahuje tyto metody:
Deklarace návěští se uloží metodou mlc::symbol_tables::add_label_entry. Parametry jsou číslo řádky, identifikace návěští (což je číslo), a unikátní identifikace návěští vytvořená funkcí mlc::new_label (tj. jako třetí parametr použijte volátní této funkce).
Na rozdíl od ostatních symbolů identifikovaných identifikátory, labely jsou identifikovány čísly, proto pro jejich vyhledávání slouží jiná metoda a to mlc::symbol_tables::find_label.
Jedním z významých otázek je problematika datových typů. Mlaskal je silně typovaný jazyk a obsahuje konstrukce pro definici nových typů, takže je potřeba je zpracovat a vyhodnotit. Datové typy jsou reprezentovány potomky třídy mlc::abstract_type, při běžné práci ukazatel na tento typ mlc::type_pointer.
Kde se tento ukazatel vezme v případě, že k dispozici je identifikátor? Tabulky obsahují metodu mlc::symbol_tables::find_symbol, která bere identifikátor (samozřejmě jako odkaz do tabulky identifikátorů) a vrací mlc::symbol_pointer, což je ukazatel na mlc::abstract_symbol, což je předek pro všechny třídy odpovídající symbolům v tabulce kromě návěští. Takto získaný ukazatel může ukazovat na libovolný druh symbolu nebo dokonce symbol zadaného jména nemusí existovat vůbec. Vezměme následující kód:
var i:integer; j:i; k:nonexistent;
Ten je zjevně špatně, ale to musí někdo rozpoznat. Existenci symbolu lze otestovat testem na nenulovost ukazatele. Na detekci druhu symbolu existuje metoda mlc::abstract_symbol::kind, která vrací hodnoty z výčtu mlc::symbol_kind, tedy například mlc::SKIND_TYPE.
Když už je jasné, že ukazatel ukazuje na datový typ, lze zavolat jednu z access_X metod mlc::abstract_symbol, v tomto případě access_type, která vrací mlc::type_symbol_reference, což je ukazatel na mlc::type_symbol (jeden z potomků mlc::abstract_symbol). Tato třída je zajímavá tím, že je také potomkem mlc::typed_symbol, který má metodu type , vracející mlc::type_pointer, což je ukazatel na skutečnou reprezentaci datového typu.
Pokud tedy v rámci Bisona máte typ jako třetí parametr a chceme získat mlc::type_pointer, potřebujeme následující kód:
mlc::symbol_pointer sp = ctx->tab->find_symbol($3); if (!sp || (sp->kind() != SKIND_TYPE)) { error(DUERR_NOTTYPE, @3, * $3); } else { mlc::type_symbol_reference tr = sp->access_type(); mlc::type_pointer tp = tr->type(); }
Díky chování mlc::symbol_pointer lze použít i kratší verzi:
mlc::symbol_pointer sp = ctx->tab->find_symbol($3); type_pointer tp = sp->access_typed()->type(); if (sp->kind() != SKIND_TYPE) { error(DUERR_NOTTYPE, @3, * $3); }
Je třeba si povšimnout několika zajímavých kousků. Například vlání kind
na potenciálně nulovém ukazateli sp
. Toto je možné, protože třída mlc::abstract_symbol s tím počítá a kind
vrací v takovém případě mlc::SKIND_UNDEF. Z obdobných důvodů je možné zavolat access_typed
a následně type
na tom samém ukazateli. Do tp
se dostane speciální nedefinovaný datový typ, který lze pak nadále používat kdekoliv, kde je třeba použít nějaký datový typ. Tím samozřejmě vznikne nevalidní záznam v tabulkách, ale z hlediska běhu samotného kompilátoru k probému nedojde a volání funkce error zajistí, že celá kompilace bude označena jako neúspěšná. Tuto možnost lze tedy považovat za regulérní zotavení z chyb při překladu.
Datové typy je však možné i zakládat pomocí mlc::symbol_tables::add_type. Tato metoda má jako parametry číslo řádky, název typu a mlc::type_pointer. Z toho plyne, že tato funkce slouží pro pojmenování již existujícího datového typu. To v Mlaskalu je možné například deklarací type float=real;
Ale zjevně to nestačí, neboť Mlaskal umožňuje i deklarovat nové typy. Pro to existuje několik možností.
První je typ rozsah, který určuje, že datový typ slouží pro ukládaní hodnot z nějakého intervalu celých čísel. Tento typ (přesněji mlc::type_pointer na něj) lze vytvořit pomocí metody mlc::symbol_tables::create_range_type. Parametry jsou dolní a horní mez. V případě neplatného rozsahu (dolní mez je větší než horní mez) je třeba ohlásit chybu mlc::DUERR_BADRANGE.
Obdobě funguje metoda mlc::symbol_tables::create_array_type, která vytváří jednorozměrné pole. Druhým parametrem je mlc::type_pointer na element pole, jehož využití je zjevné. První parametr je také mlc::type_pointer. Důvodem je, že rozsah indexů pole se určuje datovým typem typu rozsah (range) popsaným v předchozím odstavci.
Příklad demonstrující vícerozměrná pole:
array [1..2,3..4] of array [5..6] of integer;
Toto lze vytvořit následujícím kódem (výsledkem je a3
):
mlc::type_pointer v1 = ctx->tab->logical_integer(); mlc::type_pointer i1 = ctx->tab->create_range_type(ctx->tab->ls_int().add(5),ctx->tab->ls_int().add(6)); mlc::type_pointer a1 = ctx->tab->create_array_type(i1,v1); mlc::type_pointer i2 = ctx->tab->create_range_type(ctx->tab->ls_int().add(3),ctx->tab->ls_int().add(4)); mlc::type_pointer a2 = ctx->tab->create_array_type(i2,a1); mlc::type_pointer i3 = ctx->tab->create_range_type(ctx->tab->ls_int().add(1),ctx->tab->ls_int().add(2)); mlc::type_pointer a3 = ctx->tab->create_array_type(i3,a2);
Poslední možností jsou struktury (typ record). K tomu slouží mlc::symbol_tables::create_record_type. Ta má jako parametr (kromě čísla řádky) ukazatel na mlc::field_list. Prázdný mlc::field_list lze vytvořit voláním funkce mlc::create_field_list. Přidání položky se dělá pomocí metody append_field, která má jako první parametr název položky a jako druhý příslušný mlc::type_pointer. Dvě instance mlc::field_list lze spojit pomocí metody append_and_kill, která přopojí k seznamu, na kterém byla zavolána, obsah seznamu předaného jako parametr a zároveň zruší (odalokuje) seznam předaný jako parametr. Kromě těchto metod jsou v mlc::field_list dostupné i tradiční begin a end, které však nejsou pro DÚ4 potřeba.
Zakládání konstant je jednoduché, slouží k tomu 4 metody mlc::symbol_tables:
Všechny obsahují jako první parametr číslo řádku, jako druhý parametr název a třetím parametrem je hodnota. V případě mlc::symbol_tables::add_const_bool je hodnotou přímo bool. V ostatních případech je hodnotou odkaz do tabulky literálů.
Složitějším problémem je zjištění hodnoty konstanty. Základem je opět mlc::symbol_tables::find symbol. Pro konstanty vrací mlc::abstract_symbol::kind hodnotu mlc::SKIND_CONST. Pak mlc::abstract_symbol::access_const vrátí mlc::const_symbol_reference což je ukazatel na mlc::const_symbol. Ten je potomkem již známého mlc::typed_symbol, jehož metoda type vrací typ konstanty. Na mlc::type_pointer je možné zavolat mlc::abstract_type::cat, která vrací kategorii typu, což jsou prvky výčtu mlc::type_category. Například pro celočíselnou konstantu vrátí mlc::TCAT_INT.
Nad takovou konstantou lze pak zavolat mlc::const_symbol::access_int_const, která vrací mlc::int_const_symbol_pointer, což je ukazatel na mlc::int_const_symbol. Tato třídá obsahuje metodu int_value , která vrací hodnotu konstanty (samozřejmě jako odkaz do tabulky).
Následující kód ukazuje, jak získat hodnotu z konstatny typu integer, jejíž název je jako druhý symbol v pravidle Bisona:
int hodnota; mlc::symbol_pointer sp = ctx->tab->find_symbol($2); if ( sp->kind() != SKIND_CONST ) { error( DUERR_NOTCONST, @2, * $2); } if ( sp->access_typed()->type()->cat() == TCAT_INT ) { mlc::ls_int_index ptr = sp->access_const()->access_int_const()->int_value(); hodnota = *ptr; }
Pro přidávání proměnných slouží mlc::symbol_tables::add_var. Jejími argumenty jsou číslo řádky deklarace, název a datový typ. Vyhledávání, které probíhá tradičně pomocí mlc::symbol_tables::find_symbol, není v DU4 potřeba.
Před voláním mlc::symbol_tables::enter je třeba založit funkci nebo proceduru. To se dělá pomocí metod:
Funkce a procedury se liší pouze tím, že funkce má navíc parametr s návratovým typem funkce. Tento typ musí být skalární typ, což znamená předdefinované typy a typ range (lze otestovat pomocí mlc::abstract_type::cat)
Zajímavým parametrem je ještě seznam parametrů. Ten je typu mlc::parameter_list. To je struktura obsahující seznam parametrů. Prázdná struktura vznikne voláním mlc::create_parameter_list. Zajímavé metody těto třídy jsou
ll2
předaný jako argument za již definované parametry a dealokuje ll2
. Metody na přidávání parametrů mají jako první argument název parametru a jako druhý jeho typ. Parametry je třeba do mlc::parameter_list dostat (a hlavně předat do add_fnc a add_proc) v tom pořadí, v jakém jsou deklarovány.
V rámci Bisona by nemělo být potřeba niky dealokovat mlc::parameter_list, protože jejich postupným spojením (kterému se i tak lze vyhnout vhodnou konstrukcí gramatiky) pomocí append_and_kill vznikne nakonec pouze jedna instance, která je předána do mlc::symbol_tables::add_fnc nebo add_proc, které se již postarají o její dealokaci.