Hlavní navigace

GLib: Lexikální scanner (2)

28. 3. 2001
Doba čtení: 6 minut

Sdílet

Hlubší pohled na lexikální scanner knihovny GLib, struktura GScanner, typy tokenů a vytvoření nového scanneru.

Struktura GScanner

Scanner knihovny GLib je do paměti uložen ve struktuře GScanner, jejíž položky jsou uživatelům volně přístupné. Definice datového typu GScanner  zní:

typedef struct _GScanner
{
  /* nepoužité položky */
  gpointer user_data;
  guint max_parse_errors;

  /* počet nalezených chyb
     (inkrementováno funkcí g_scanner_error()) */
  guint parse_errors;

  /* název vstupu (použije se při ohlašování chyb) */
  const gchar *input_name;

  /* další nepoužitá položka */
  gpointer derived_data;

  /* odkaz na konfigurační záznam scanneru */
  GScannerConfig *config;

  /* aktuální token (nastaveno po volání
     funkce g_scanner_get_next_token()) */
  GTokenType token;
  GTokenValue value;
  guint line;
  guint position;

  /* příští token (nastaveno po volání
     funkce g_scanner_peek_next_token()) */
  GTokenType next_token;
  GTokenValue next_value;
  guint next_line;
  guint next_position;

  /* privátní položky (nepoužívat přímo!) */
  GHashTable *symbol_table;
  gint input_fd;
  const gchar *text;
  const gchar *text_end;
  gchar *buffer;
  guint scope_id;

  /* obsluha hlášení funkcí _warn a _error */
  GScannerMsgFunc msg_handler;
} GScanner;

Položky user_data, derived_data a max_parse_errors nejsou mechanismem scanneru využívány, můžete je tedy v případě potřeby použít k uložení vlastních dat.

Položka parse_errors podává informaci o počtu vypsaných chybových hlášení. Její obsah se zvětší o jedničku po každém volání funkce g_scanner_error(). Ta má za úkol, jak se později dovíte, vypisovat právě informaci o chybách.

V řetězci input_name je uložen název vstupu lexikálního scanneru. Každý scanner si totiž můžete libovolně pojmenovat. Zvolené jméno se vypisuje při ohlašování chyb.

Pointer config je ukazatelem na konfigurační záznam scanneru. Strukturou GScannerConfig se ovlivňuje způsob lexikální analýzy a chování GScanner u. Podrobně se jí budeme zabývat později.

Položka msg_handler je pointer na funkci, která má na starosti tisk zpráv o chybách.

Ve struktuře jsou uloženy i záznamy, které GScanner u slouží k uchování vnitřních hodnot. Konkrétně to jsou prvky symbol_table, input_fd, text, text_end, buffer a scope_id. Uživatelé by je raději neměli nikdy používat.

Na konec jsem si nechal dvě skupiny položek: informace o aktuálním a o následujícím tokenu. Údaje o právě zpracovaném tokenu jsou nastaveny vždy po volání funkce g_scanner_get_next_token(). Data o příštím ( next_*) tokenu zjišťuje pro změnu g_scanner_peek_next_token(). O obou rutinách bude ještě řeč, takže nepředbíhejme. O každém tokenu máme k dispozici čtyři údaje: typ tokenu (položka token, popř. next_token), jeho hodnotu ( value), řádek, na kterém se nachází ( line, počítáno od 1) a pozici od začátku řádku ( position, chápáno od 0).

Druhy tokenů

V předchozí deklaraci se vyskytoval datový typ GTokenType. Je to výčtový typ určený k identifikaci druhu zpracovaného tokenu.

typedef enum
{
  G_TOKEN_EOF   =   0,        /* konec souboru         */

  G_TOKEN_LEFT_PAREN  = '(',  /* levá závorka          */
  G_TOKEN_RIGHT_PAREN  = ')', /* pravá závorka         */
  G_TOKEN_LEFT_CURLY  = '{',  /* levá složená závorka  */
  G_TOKEN_RIGHT_CURLY  = '}', /* pravá složená závorka */
  G_TOKEN_LEFT_BRACE  = '[',  /* levá hranatá závorka  */
  G_TOKEN_RIGHT_BRACE  = ']', /* pravá hranatá závorka */
  G_TOKEN_EQUAL_SIGN  = '=',  /* rovnítko              */
  G_TOKEN_COMMA   = ',',      /* čárka                 */

  G_TOKEN_NONE   = 256,       /* nic (prázdný token)   */

  G_TOKEN_ERROR,              /* chyba při lexikální analýze */

  G_TOKEN_CHAR,               /* znak                               */
  G_TOKEN_BINARY,             /* číslo v binárním tvaru             */
  G_TOKEN_OCTAL,              /* číslo v oktálním tvaru             */
  G_TOKEN_INT,                /* integer (číslo v decimálním tvaru) */
  G_TOKEN_HEX,                /* číslo v hexadecimálním tvaru       */
  G_TOKEN_FLOAT,              /* float (racionální číslo)           */
  G_TOKEN_STRING,             /* řetězec                            */

  G_TOKEN_SYMBOL,             /* symbol               */
  G_TOKEN_IDENTIFIER,         /* identifikátor        */
  G_TOKEN_IDENTIFIER_NULL,    /* identifikátor "NULL" */

  G_TOKEN_COMMENT_SINGLE,     /* jednoduchý komentář  */
  G_TOKEN_COMMENT_MULTI,      /* multi komentář       */

  G_TOKEN_LAST                /* konstanta posledního druhu tokenu */
} GTokenType;

Jednotlivé konstanty asi nepotřebují podrobnější popis, neboť jejich názvy mluví samy za sebe. Stručně se zmíním jen o některých z nich:

  • G_TOKEN_EOF  – indikátor přečtení celého vstupu (dosažení konce souboru nebo textového bufferu)
  • G_TOKEN_NONE  – nic, prázdný token (ještě nedošlo k načtení žádného tokenu);
  • G_TOKEN_ERROR  – vrácen po chybě při lexikální analýze (při čtení GScanner u);
  • G_TOKEN_IDENTIFIER  – jakýkoliv identifikátor, který není symbolem;
  • G_TOKEN_IDENTIFIER_NULL  – identifikátor „NULL“ = na vstupu se objevil přímo řetězec NULL (popř. null při case-insensitive zpracovávání);
  • G_TOKEN_COMMENT_SINGLE  – jednoduchý komentář (standardně text mezi # a prvním koncem řádku);
  • G_TOKEN_COMMENT_MULTI  – složený (dvouznakový) komentář (text vymezen céčkařskými komentáři /**/);
  • G_TOKEN_LAST není token v pravém slova smyslu. Jak jistě víte, konstanty výčtových typů si překladač vnitřně „očísluje“ a do přeloženého programu pak místo identifikátorů dosazuje jejich čísla. Konstanta G_TOKEN_LAST byla definována proto, aby programátoři lehce poznali, která číselná hodnota je poslední. Umožňuje to pak např. kontroly typu „ token < G_TOKEN_LAST“ (hodnota proměnné token je v rozmezí platných tokenů) nebo snazší definici vlastních výčtových konstant, a to tak, jak jsme viděli v minulém dílu:

    /* definice výčtových konstant
     * reprezentujících speciální symboly */
    enum {
      SYMBOL_DELKA = G_TOKEN_LAST + 1,
      SYMBOL_VYSKA = G_TOKEN_LAST + 2,
      SYMBOL_SIRKA = G_TOKEN_LAST + 3
    };

    Při takovémto zápisu programu nemůže dojít ke kolizi nových názvů s již zavedenými výčtovými konstantami.

Hodnoty tokenů

Každý přečtený token nese spolu s informací o tom, jakého je typu, také různé doplňkové údaje. Mechanismus, který by na vstupu rozpoznával čísla, ale neprozradil nám, jakou konkrétní hodnotu zjistil, by byl asi k ničemu.

Datový typ GTokenValue je vytvořen k tomu, aby doplňkové informace o tokenech uchovával v rozumné (a příjemné) formě:

typedef union _GTokenValue
{
  gpointer v_symbol;
  gchar  *v_identifier;
  gulong v_binary;
  gulong v_octal;
  gulong v_int;
  gdouble v_float;
  gulong v_hex;
  gchar  *v_string;
  gchar  *v_comment;
  guchar v_char;
  guint  v_error;
} GTokenValue;

Podotýkám, že se jedná o union. Pro přístup k hodnotě tokenu byste proto měli používat vždy jen jednu položku podle typu tokenu. Je-li přečtený token identifikátorem, řetězcem nebo poznámkou, je jeho hodnotou řetězec. Pokud se přečetlo integerové číslo, získáte data typu gulong a podobně.

Vytvoření nového GScanner u a příprava na čtení

Nový GScanner vytvoříte voláním funkce

GScanner* g_scanner_new(GScannerConfig *config_templ);

…kde config_templ je pointer na šablonu s konfigurací scanneru. O konfiguračním záznamu GScannerConfig si povíme příště. Nyní nám stačí vědět, že pokud za tento argument dosadíme NULL, nastaví se GScanner podle předdefinovaných hodnot.

Jakmile máme scanner vytvořený, musíme mu říci, co má zpracovávat. Již minule jsem uvedl, že GScanner umí číst data jak ze souboru, tak z textového bufferu. Po inicializaci scanneru voláním funkce g_scanner_new() proto obvykle následuje volání jedné z následujících dvou rutin:

void g_scanner_input_file(GScanner *scanner,
                          gint input_fd);

…připraví GScanner scanner na čtení ze souboru, jehož deskriptor je input_fd. Soubor musí být pochopitelně otevřen pro čtení.

void g_scanner_input_text(GScanner *scanner,
                          const gchar *text,
                          guint text_len);

…sdělí GScanner scanner u, že jako vstup má použít textový buffer text, jehož délka je  text_len.

CS24_early

Po těchto operacích je scanner plně nastaven a připraven ke čtení…

No a to je pro dnešek z mé strany o scannerech všechno. Příště, jak jsem slíbil, se trochu blíže podíváme na jejich nastavení a poté se konečně budeme moci pustit do výkladu o samotném čtení a zpracovávání vstupního textu.

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.