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 jiGScanner
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ápis025.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).
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);