Co už umíme
Nejprve malá rekapitulace. Prozatím jsme si řekli, co to je GScanner a jak funguje, jak se inicializuje pro čtení ze souboru nebo textového bufferu a co všechno můžeme jeho konfigurací ovlivnit. V dnešním dílu se podíváme zblízka na práci s tabulkami symbolů a proces získávání tokenů ze zdroje.
Tabulky symbolů a obory platnosti
Symboly, jak už víme, jsou speciální identifikátory, které chceme od ostatních odlišit, protože pro nás mají zásadní význam. Například u tokenizace zdrojových kódů programovacího jazyka by symbolem typicky byla klíčová slova.
Symboly můžeme sdružovat do logických celků zvaných obory platnosti (scopes). V každém okamžiku má GScanner aktivní právě jeden obor platnosti. Mezi obory se můžeme libovolně přepínat a podle syntaxe čteného zdroje zpřístupňovat nebo naopak znepřístupňovat jednotlivé sady symbolů.
Obory platnosti jsou označovány číselnou konstantou ( guint) a rozhodnete-li se je používat, je vhodné si pro ně zřídit symbolické konstanty ( #define ...).
Jak již jsem psal v minulém dílu, první obor platnosti (scope = 0) může mít speciální význam. GScanner lze nastavit tak, aby tento obor platnosti považoval za univerzální a hledal v něm symboly pokaždé, když neuspěje v aktuálním oboru. (Viz volba scope0_fallback struktury GScannerConfig.)
Symboly se vnitřně ukládají do hash tabulky. Záznam symbolu tvoří jeho identifikátor (řetězec znaků), pointer na hodnotu a označení oboru platnosti (scope_id).
Aktuální obor platnosti se vybírá funkcí
guint g_scanner_set_scope(GScanner *scanner,
guint scope_id);
Po vytvoření nového GScanner u je aktuálním oborem 0.
Pro registraci symbolu ve scanneru se používá volání
void g_scanner_scope_add_symbol(GScanner *scanner,
guint scope_id,
const gchar *symbol,
gpointer value);
…, kde argumentem scanner je GScanner, kterému chceme symbol přidat, scope_id označení oboru platnosti, symbol identifikátor přidávaného symbolu a value hodnota symbolu. Jestliže symbol symbol v oboru platnosti scope_id již existuje, dojde pouze k přiřazení nové hodnoty value.
Opačnou akci, tedy odstranění symbolu z tabulky, provedete pomocí
void g_scanner_scope_remove_symbol(GScanner *scanner,
guint scope_id,
const gchar *symbol);
Opět se musí kromě identifikátoru symbol uvést i číslo oboru platnosti scope_id.
Funkce
gpointer g_scanner_scope_lookup_symbol(GScanner *scanner,
guint scope_id,
const gchar *symbol);
…se používá ke zjištění hodnoty symbol u. Prohledá se výhradně obor platnosti scope_id. V případě neúspěchu vrátí funkce NULL.
Obdobnou činnost provádí i rutina
gpointer g_scanner_lookup_symbol(GScanner *scanner,
const gchar *symbol);
Jejím úkolem je vyhledat v aktuálním oboru platnosti hodnotu symbolu symbol. Je-li nastavena vlastnost scope0_fallback konfiguračního záznamu GScannerConfig, prohledá se v případě neúspěchu i obor platnosti 0. Nemožnost nalezení symbolu indikuje funkce návratovou hodnotou NULL.
Máte-li chuť zavolat nějakou funkci func na každý záznam o symbolu ve zvoleném oboru platnosti scope_id, uvažujte o rutině
void g_scanner_scope_foreach_symbol(GScanner *scanner,
guint scope_id,
GHFunc func,
gpointer user_data);
void (*GHFunc) (gpointer key, gpointer value, gpointer user_data);
Uživatelská funkce func je typu GHFunc (viz hash tabulky) a musí přebírat tři parametry: klíč (řetězec – identifikátor), hodnotu symbolu a uživatelská data user_data.
Pro zpětnou kompatibilitu s předchozími verzemi knihovny GLib (a také pro ušetření psaní na klávesnici) jsou definována makra
#define g_scanner_add_symbol(scanner, symbol, value)
#define g_scanner_remove_symbol(scanner, symbol)
#define g_scanner_foreach_symbol(scanner, func, data)
Pro všechny tři platí, že pracují výhradně s univerzálním oborem platnosti (stejné jako volání předchozích funkcí se scope_id = 0).
Očekáváte-li při inicializaci scanneru, že budete do tabulky symbolů přidávat mnoho záznamů, můžete celý proces optimalizovat co do rychlosti „zmražením“ tabulky symbolů:
void g_scanner_freeze_symbol_table(GScanner *scanner);
Po zavolání této funkce bude přidávání symbolů do tabulky (lépe: obecně jakékoliv operace s tabulkou symbolů) probíhat výrazně rychleji, protože se dočasně odstaví jisté vnitřní mechanismy hash tabulek. Po provedení všech změn ale musíte tabulku „odmrazit“ voláním
void g_scanner_thaw_symbol_table(GScanner *scanner);
…, čímž se veškeré režijní úkony spojené se změnami provedou najednou.
Příklad:
... g_scanner_freeze_symbol_table(scanner); vloz_velmi_mnoho_symbolu(scanner); g_scanner_thaw_symbol_table(scanner); ...
Čtení
Pro čtení tokenů ze vstupního zdroje jsou v GScanner u implementovány dvě funkce:
GTokenType g_scanner_get_next_token(GScanner *scanner); GTokenType g_scanner_peek_next_token(GScanner *scanner);
První z nich načte jeden token a posune ukazovátko aktuální pozice ve zdroji za něj, kdežto tou druhou se jen „podíváte“, jaký je následující token, ale ukazovátko zůstane na svém původním místě.
Volání g_scanner_get_next_token() se dá pro názornost přirovnat k céčkovské funkci getc() a g_scanner_peek_next_token() k posloupnosti příkazů:
c = getc(file); ungetc(c, file);
O každém funkcí g_scanner_get_next_token() právě načteném tokenu jsou ve struktuře GScanner uchovávány čtyři údaje: informace o typu ( token), hodnota tokenu ( value), řádek ( line) a pozice ( position) na řádku.
Přistupovat k nim můžete buď přímo, nebo pomocí k tomu určených funkcí:
GTokenType g_scanner_cur_token(GScanner *scanner); GTokenValue g_scanner_cur_value(GScanner *scanner); guint g_scanner_cur_line(GScanner *scanner); guint g_scanner_cur_position(GScanner *scanner);
V případě hodnot načtených pomocí g_scanner_peek_next_token() je v rozhraní knihovny GLib jistá nesymetričnost, protože se k datům lze dostat jen přímo přes strukturu GScanner – pro ně jsou určeny atributy next_token, next_value, next_line a next_position.
Pokračování příště.