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; }