Hlavní navigace

GLib: Lexikální scanner (6)

26. 4. 2001
Doba čtení: 6 minut

Sdílet

Dnešním dílem dokončíme výklad o propracovaném nástroji knihovny GLib - lexikálním scanneru. Povíme si něco o obsluze chybových stavů, generování chybových hlášení a dalších funkcích.

Druhy chyb

Narazí-li scanner při čtení tokenů na neočekávanou situaci, vygeneruje token G_TOKEN_ERROR, jehož hodnotou je bližší určení, jaká chyba nastala. Druhy chyb jsou shrnuty ve výčtovém typu  GErrorType:

typedef enum
{
  G_ERR_UNKNOWN,
  G_ERR_UNEXP_EOF,
  G_ERR_UNEXP_EOF_IN_STRING,
  G_ERR_UNEXP_EOF_IN_COMMENT,
  G_ERR_NON_DIGIT_IN_CONST,
  G_ERR_DIGIT_RADIX,
  G_ERR_FLOAT_RADIX,
  G_ERR_FLOAT_MALFORMED
} GErrorType;

Jejich význam je následující:

  • G_ERR_UNKNOWN  – neznámá, blíže neurčená chyba
  • G_ERR_UNEXP_EOF  – neočekávaný konec souboru. Scanner potřebuje z nějakého důvodu k vytvoření tokenu více znaků, než je na vstupu k dispozici. Chyba nastane např. pokud je zapnuto rozpoznávání hexadecimálních konstant, a na vstupu je jen „ 0x“.
  • G_ERR_UNEXP_EOF_IN_STRING  – konec souboru nastal uprostřed čtení řetězce. Tato chyba se objeví, pokud na vstupu zapomenete ukončovací uvozovky řetězcové konstanty.
  • G_ERR_UNEXP_EOF_IN_COMMENT  – konec souboru uprostřed komentáře. Podobná chyba jako G_ERR_UNEXP_EOF_IN_STRING. Nastane, pokud zapomenete uzavřít komentář (ať už jednoduchý, nebo multi). Zmíněná chyba je záludná v tom, že ji GScanner generuje i v jednom speciálním případě, kdy uživatel napíše jednoduchý komentář na poslední řádek čteného souboru. Scanner správně rozpozná počátek komentáře ( #), ale marně hledá konec řádku, který by jej uzavřel. Pokud vám tento případ hrozí, měli byste ho nějak ošetřit.
  • G_ERR_NON_DIGIT_IN_CONST  – vyskytl se nečíselný znak v zápisu číselné konstanty. Chyba se generuje, pokud se vám doprostřed nebo na konec čísla vloudí písmeno nebo cokoliv jiného kromě číslic.
  • G_ERR_DIGIT_RADIX  – v číselné konstantě se vyskytla číslice, která nepatří do dané číselné soustavy. Chyba se objeví např. po nalezení znaku „9“ v čísle v oktálním tvaru, písmena „g“ v hexadecimální konstantě apod.

Poslední dvě chyby se týkají problémů s desetinnou tečkou:

  • G_ERR_FLOAT_MALFORMED  – nastane, pokud se ve floatovém čísle desetinná tečka objeví dvakrát, nebo alespoň jednou v exponentu.
  • G_ERR_FLOAT_RADIX  – oznamuje případ, kdy se uživatel pokouší nacpat desetinnou tečku do čísla v binárním nebo hexadecimálním tvaru. (Zápis 025.36 je v pořádku, konstanty začínající jako oktální GScanner po nalezení oddělovače desetinné části interpretuje jako „normální“ float hodnotu:  25.36.)

Na nalezenou chybu můžete reagovat třeba takto:

token = g_scanner_get_next_token(scanner);
if (token == G_TOKEN_ERROR) {
  switch (scanner->value.v_error) {
    case G_ERR_UNKNOWN:
           ...
       break;
    case G_ERR_UNEXP_EOF:
           ...
       break;
    ...
  }
}

Výpis chybových hlášení

Tvůrci GScanner u připravili několik funkcí, které programátorům usnadní vypisování hlášení o chybách při čtení a tokenizaci souborů.

void g_scanner_error(GScanner *scanner,
                     const gchar *format, ...);

…zvýší hodnotu položky parse_errors struktury GScanner o jedničku a vypíše chybové hlášení. Funkce se používá stejně jako printf(). Argument format je formát výstupního řetězce, za ním následují další parametry podle formátu.

Chcete-li vypsat jen varování (bez inkrementace proměnné parse_errors), použijte raději funkci

void g_scanner_warn(GScanner *scanner,
                    const gchar *format, ...);

Jak bude skutečné chybové hlášení vypadat, můžete ovlivnit změnou obsluhy chybových hlášení. Pokud scanneru neřeknete jinak, k tisku chybových hlášení použije privátní funkci g_scanner_msg_handler(), která hlášení ve formátu:

<název scanneru>:<číslo řádku>:[ error:] <hlášení>

směřuje na standardní výstup.

Chcete-li toto chování pozměnit, uložte si do parametru msg_handler záznamu GScanner svou vlastní funkci pro výpis chybových hlášení. Funkce musí být typu:

typedef void (*GScannerMsgFunc) (GScanner *scanner,
                                 gchar *message,
                                 gint error);

Funkce obdrží tři parametry. Argumentem scanner je pointer na GScanner, kterého se volání týká, message je samotný řetězec zprávy a error příznak určující, zda se jedná o chybu ( TRUE), nebo jen varování ( FALSE). GScannerMsgFunc má na starosti jen samotné vypsání chybového hlášení. O inkrementaci atributu parse_errors a podobné věcičky se starat nemusíte.

Mechanizmus GScanner u disponuje také jednou hezkou funkcí, která podá uživateli komplexní a poměrně vyčerpávající informaci o chybě. Je to

void g_scanner_unexp_token(GScanner *scanner,
                           GTokenType expected_token,
                           const gchar *identifier_spec,
                           const gchar *symbol_spec,
                           const gchar *symbol_name,
                           const gchar *message,
                           gint is_error);

Popišme si její argumenty: expected_token je typ očekávaného tokenu, identifier_spec ( symbol_spec) řetězec popisující typ identifikátoru (symbolu) nebo NULL, chcete-li použít standardní řetězec „ identifier“ („ symbol“), symbol_name je název nechtěného symbolu, message dodatečný text, a konečně is_error funguje jako přepínač určující, jedná-li se o chybu ( TRUE), nebo pouze o varovné hlášení ( FALSE). Parametr scanner snad už nemusím komentovat.

Funkce g_scanner_unexp_token() se podívá na aktuální token scanneru a podle něj vytvoří první část svého výstupu. Je-li aktuálním tokenem G_TOKEN_ERROR, funkce vypíše zprávu o typu chyby a skončí. (Např. při G_ERR_UNEXP_EOF_IN_STRING pošle na výstup hlášení „ scanner: unterminated string constant“.)

V opačném případě vypíše stručný popis toho, co bylo přečteno (např. „ character 'A'“) a pak podle atributu expected_token informaci o tom, jaký token jsme čekali.

Pokud řetězec message není NULL nebo prázdný, připojí jej na konec a skončí.

Řetězce identifier_spec a symbol_spec se v algoritmu používají všude tam, kde se hodí slova „ identifier“ nebo „ symbol“. (Např. „ unexpected identifier“ nebo „ ..., expected symbol“.) Necháte-li těmto argumentům hodnotu NULL, budou se na patřičná místa vypisovat právě tyto řetězce. Parametry pozměňte, chcete-li typ symbolu nebo identifikátoru nějak blíže specifikovat. Může se stát, že budete mít záměr uživatele občerstvit podrobnějším chybovým hlášením typu: „ ..., expected previously declared identifier“. Jako parametr identifier_spec proto uvedete  "previously declared identifier".

Řetězec symbol_name se používá při výpisu neočekávaného symbolu. Funkce vytvoří hlášení „ unexpected symbol...“ a pokud je symbol_name různé od NULL, přidá ho mezi jednoduché závorky nakonec. Např. „ unexpected symbol `then', expected...“ apod. (U identifikátorů je podobná vlastnost automatická. Vypisuje se " unexpected identifier `<identifikátor>'..., přičemž podřetězec <identifikátor> se vezme z hodnoty naposledy přečteného ( _get_) tokenu.

Funkce g_scanner_unexp_token() je opravdu dobře udělána. Autoři si vyhráli i s málo častými případy a vhodně je ošetřili. Druhů hlášení, která funkce g_scanner_unexp_token() produkuje, je opravdu mnoho a jistě zde nemá smysl podrobně rozebírat, jak její algoritmus chybovou zprávu sestavuje. Je ale naprogramována zodpovědně, proto se na ni můžete v klidu spolehnout. Na závěr ještě podotknu, že i ona k výpisu používá msg_handler, takže uživatelský chybový výstup bude vždy jednotný.

Veškerá hlášení jsou v angličtině, proto byste se asi také měli držet tohoto jazyka. Zprávy typu:

error: invalid identifikátor "blabla", expected string constant

působí trochu schizofrenicky.

Další funkce

gboolean g_scanner_eof(GScanner *scanner);

…vrátí TRUE, pokud scanner došel až na konec textového bufferu nebo souboru.

void g_scanner_sync_file_offset(GScanner *scanner);

…sesynchronizuje pozici v souboru s místem, na kterém se nachází scanner. Při čtení ze souboru jdou data do GScanner u přes jeho vnitřní buffer. Může se tedy stát, že pozice v souboru neodpovídá místu, kam až scanner data tokenizoval. Touto funkcí si vynutíte seek na patřičnou pozici souboru. Funkce je užitečná zejména v případech, kdy souborový deskriptor používáte i mimo samotný GScanner. g_scanner_sync_file_offset() je automaticky volána z g_scanner_input_file()  i z  g_scanner_input_text().

gint g_scanner_stat_mode(const gchar *filename);

…vrátí atributy souboru filename. Volání funkce je ekvivalentní se systémovým voláním lstat() (popř. stat()) a vrácením položky st_mode (typ souboru, přístupová práva – viz dokumentace k funkci stat(), např. GLIBC – GNU C Library).

root_podpora

Ukončení práce s  GScanner em

Nakonec ještě zmíním jednu klasiku – úklidovou funkci, pomocí níž uvolníte veškerou paměť, kterou GScanner zabírá:

void g_scanner_destroy(GScanner *scanner);

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.