Parsování textu pomocí knihovny Spirit
Spirit jako jedna z mnoha částí Boost knihoven je objektově orientovaný framework pro parsování textu. Velice snadno je možné pomoci syntaxe jazyka C++ vytvořit parser odpovídající zápisu gramatiky v rozšířené Backus-Naurově formě. Předpokladem pro efektivní používání je alespoň základní znalost šablon jazyka C++ a knihovny STL.
Elementární parsery
Spirit disponuje množstvím elementárních parserů, z kterých je možné pomocí různých operátorů skládat složitější pravidla. Příkladem elementárního parseru je real_p, který rozpozná číslo s plovoucí řádovou čárkou. Nejjednodušší funkční příklad by mohl vypadat např. takto:
#include <iostream>
#include <boost/spirit/core.hpp>
using namespace std;
using namespace boost::spirit;
int main() {
parse_info<const char *> pInfo;
pInfo = parse("ahoj", real_p, space_p);
cout << pInfo.full << endl; // pInfo.full = 0
pInfo = parse("2.34", real_p, space_p);
cout << pInfo.full << endl; // pInfo.full = 1
return 0;
}
Funkce parse
Funkce parse, která provede vlastní parsování, má jako první parametr řetězec s parsovaným textem, druhým parametrem je použitý parser a třetí parametr je tzv. skip parser. Jeho úkolem je určit, na jaké kusy textu bude parser aplikován. Použitý space_p rozpoznává tzv. bílé znaky, v našem příkladě tedy zjišťujeme, zda první slovo je reálné číslo.
Návratovou hodnotou je struktura parse_info, která obsahuje následující proměnné:
- full – true v případě úplné shody, vstupní text byl rozpársován celý
- hit – true v případě částečné shody. Vstupní text nemusel (ale mohl) být použit celý; pokud bysme tedy v našem příkladě parsovali text „2.34 je číslo“, proměnná full bude nastavená na false, hit bude true
- length - počet rozparsovaných znaků; hodnota je platná pouze v případě částečné nebo úplné shody
- stop – iterátor na pozici, po kterou byl vstupní text zpracován
Funkce parse je přetížená, její další prototypy lze nalézt v bohaté dokumentaci Spiritu.
Výběr elementárních parserů
Znakové parsery
| anychar_p | jakýkoliv znak |
| ch_p(char) | zadaný znak |
| chset_p(charset) | znak ze zadané množiny |
| range_p(char1, char2) | znak z uvedeného intervalu (včetně) |
| space_p | „bílý“ znak |
| blank_p | mezera nebo tabulátor |
| print_p | tisknutelný znak |
| graph_p | tisknutelný znak mimo mezeru |
| alpha_p | písmeno |
| digit_p | číslice |
| alnum_p | písmeno nebo číslice |
| lower_p | malé písmeno |
| upper_p | velké písmeno |
| xdigit_p | hexadecimální číslice |
| punct_p | interpunkční znak |
Číselné parsery
| real_p | číslo s plovoucí řádovou čárkou |
| ureal_p | číslo s plovoucí řádovou čárkou bez znaménka |
| strict_real_p | číslo s plovoucí řádovou čárkou, musí obsahovat desetinnou tečku |
| int_p | celé číslo |
| uint_p | celé číslo bez znaménka |
| int_parser<type, base, min, max> | číslo se základem číselné soustavy base s minimálním a maximálním počtem číslic min a max |
Ostatní parsery
| eol_p | jakákoliv kombinace CR, LF |
| end_p | EOF - end of file |
| nothing_p | neodpovídá ničemu, porovnání vždy selže |
| regex_p(regex) | regulární výraz |
| str_p(string) | řetězec |
| str_p(iter1, iter2) | řetězec |
Operátor >>
Tento binární operátor je klíčový pro vytváření složitějších pravidel, umožňuje spojovat elementární parsery. Zatímco jedno reálné číslo jsme rozeznali parserem real_p, na dvě po sobě následující nám poslouží tato konstrukce:
real_p >> real_p
Pokud budeme mít dvě čísla oddělená čárkou, po nahlédnutí do předchozího přehledu elementární parserů snadno sestavíme tento výraz:
real_p >> ch_p(',') >> real_p
Elementární parsery mají operátor >> přetížený tak, že jako parametr kromě dalšího parseru zvládnou přímo znak, a tak předchozí pravidlo můžeme zjednodušit takto:
real_p >> ',' >> real_p
Pokud bysme ovšem chtěli začít parsováním znaku, musíme použít ch_p, protože typ char samozřejmě nemá nadefinovaný operátor >> s parametrem parser. Obdobně, jak funguje toto zjednodušování s parserem ch_p a znaky, můžeme nakládat s parserem str_p a řetězci.
Operátor *
Tento unární operátor zařídí opakování následujícího výrazu 0 až n-krát (n je celé číslo větší než 0). Libovolný počet reálných čísel lze rozparsovat takto:
*real_p
Reálná čísla oddělená čárkami zvládne tento parser:
real_p >> *(ch_p(',') >> real_p)
Výběr ostatních operátorů
| +P | opakování P 1 až n-krát |
| !P | P nebo prázdný řetězec |
| ~P | cokoliv jiného než P |
| P1 % P2 | Jeden nebo více výskytů P1 oddělených P2 |
| P1 – P2 | P1, ale ne P2 |
| P1 | P2 | P1 nebo P2 |
Sémantické akce
V dosud uvedených příkladech byl zadaný text pouze rozpoznáván. Nyní si ukážeme, jak rozpoznaný text také zpracovat. Vše se děje uvedením funkce nebo funktoru do hranatých závorek za parser. Pokud je F následující funkce
void F(double n) {
cout << n << endl;
}
rozpoznání a vypsání reálného čísla zajistí tento parser:
real_p[&F]
Pokud by F byl funktor, zápis by byl následující:
real_p[F]
Spirit má pro svoje účely nadefinované různé generátory funktorů (funkce, které vrací funktor). Jedním z nich je např. push_back_a, pomocí kterého lze přidat hodnotu z parseru na konec zadaného kontejneru. Následující příklad demonstruje parsování reálných čísel oddělených mezerami a uložení jejich hodnot do vektoru proměnných double.
#include <iostream>
#include <vector>
#include <boost/spirit/core.hpp>
using namespace std;
using namespace boost::spirit;
int main() {
string text("56 45.5 -7.45 0.5");
vector<double> dblV;
if (!parse(text.c_str(), *real_p[push_back_a(dblV)], space_p).full) {
cout << "Chyba!" << endl;
return 1;
}
for(vector<double>::iterator i = dblV.begin(); i != dblV.end(); i++)
cout << *i << endl;
return 0;
}