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ě.