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á chybaG_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 jakoG_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 jiGScannergeneruje 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ápis025.36je v pořádku, konstanty začínající jako oktálníGScannerpo 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).
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);