Hlavní navigace

GLib: Lexikální scanner (5)

19. 4. 2001
Doba čtení: 4 minuty

Sdílet

Dnes se ve výkladu o lexikálním scanneru pohneme zase o kus dál. Probereme si podrobně obory platnosti symbolů a proces čtení tokenů.

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 im­plementová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_nex­t_token() se dá pro názornost přirovnat k céčkovské funkci getc() a g_scanner_peek_nex­t_token() k posloupnosti příkazů:

c = getc(file);
ungetc(c, file);

O každém funkcí g_scanner_get_nex­t_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í:

root_podpora

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_nex­t_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_linenext_position.

Pokračování příště.

Byl pro vás článek přínosný?

Autor článku

Michal Burda vystudoval informatiku a aplikovanou matematiku a nyní pracuje na Ostravské univerzitě jako odborný asistent. Zajímá se o data mining, Javu a Linux.