To jest stara wersja strony!
Bartłomiej Wojcieszek G1SST
Parser, zwany także analizatorem składniowym, pozwala na analize danych wejściowych i określenie czy są zgodne z daną gramatyką. Zazwyczaj parsery są wykorzystywane przy przetwarzaniu danych zrozumialych dla czlowieka w dane najbardziej wygodne dla komputera.
Biblioteka Spirit pozwala tworzyć własne złożone parsery lub wykorzytywać te najprostsze wbudowane w bibliotekę. Szablony wyrażeń zastosowanych w bibliotece pozwalają na zapis w notcacji bardzo podobnej do EBNF (Extended Backus-Normal Form):
Gramatyka EBNF:
group ::= '(' expression ')' factor ::= integer | group term ::= factor (('*' factor) | ('/' factor))* expression ::= term (('+' term) | ('-' term))*
I ten sam parser zapisany w C++ z pomocą biblioteki Spirit:
group = '(' >> expression >> ')'; factor = integer | group; term = factor >> *(('*' >> factor) | ('/' >> factor)); expression = term >> *(('+' >> term) | ('-' >> term));
Aby wykorzystywać w swoich programach bibliotekę spirit, należy dołączyć odpowiednie nagłówki do naszego pliku:
#include <boost/spirit/core.hpp> #include <boost/spirit/actor/push_back_actor.hpp>
Załóżmy,że chcemy aby nasz program akceptował tylko takie dane wejsciowe, że pierwszym elementem jest liczba rzeczywista, a następnym znak alfabetyczny. Tworzymy parser:
real_p>>alpha_p
Aby go wykorzystać, należy skorzystać z funkcji parse:
bool parseInput(String str){ return parse(str,real_p>>alpha_p,space_p); }
Funkcja parse zwraca true jeżeli parsowanie przebiegło pomyślnie. Przyjmuje trzy argumenty:
Wyrażenia parsujące tworzy się za pomocą wbudowanych w bibliotekę prymitywów oraz operatorów. Kilka podstawowych prymitywów (więcej można znaleźć w dokumentacji):
ch_p('X') //parsuje char podany jako argument range_p('a','z') //parsuje pojedynczy char z podanego zakresu str_p("Hello") //parsuje podany string anychar_p //dowolny znak alnum_p //znak alfanumeryczny alpha_p //znak alfabetu blank_p //spacje i tabulacje lower_p //male litery print_p //znaki drukowane punct_p //znaki punktuacyjne space_p //spacja tab,nowa linia upper_p //duze litery digit_p //cyfra int_p //int real_p //liczba rzeczywista
I operatory:
a | b //a lub b a & b //a i b a - b //a ale nie b a ^ b //a lub b ale nie oba (XOR) a>>b //a i b w sekwencji a&&b //jw a||b //a lub b w sekwencji *a //a zero lub wiecej razy +a //a raz lub wiecej razy !a //a zero lub jeden raz a%b //lista a oddzielonych przez b
Dodatkowo parsery numeryczna możemy bardziej sprecyzować. Oto szablon takiego parsera:
template < typename T = unsigned, //bazowy typ parsera, default jest unsigned int Radix = 10, //podstawa systemu, czyli dziesiętny szesnastkowy itp. unsigned MinDigits = 1, //minimalna liczba cyfr int MaxDigits = -1> //maksymalna liczba cyfr (-1 oznacza niograniczona) struct uint_parser { /*...*/ }; //zatem jeżeli chcemy sparsować liczbę szesnastkową, nasz parser będzie wyglądał następująco: uint_parser<unsigned, 16, 1, -1> const //lub to samo osiągniemy korzystając z wbudowanego parsera hex_p //chcielibyśmy sparsować liczbe odzdzieloną przecinkami, taką jak: 1,234,567,891 uint_parser<unsigned, 10, 1, 3> uint3_p; // parsuje od 1 do 3 cyfr,czyli pierwsza część uint_parser<unsigned, 10, 3, 3> uint3_3_p; // dokładnie 3 cyfry ts_num_p = (uint3_p >> *(',' >> uint3_3_p)); // dowolna liczba oddzielana co 3 cyfry przecinkami
Załóżmy, że chcemy wczytać ze standardowego wyjscia do wektora ciąg liczb oddzielonych przecinkami (oczywiście przecinki nas nie interesują, tylko liczby). Czyli n a wejsciu ma się pojawić liczba, nastepnie dowolna ilość razy przecinek i kolejna liczba. Zatem w kodzie c++ wygląda to następująco:
bool parse_numbers(char const* str, vector<double>& v) { return parse(str, ( real_p[push_back_a(v)] >> *(',' >> real_p[push_back_a(v)]) ) , space_p).full; }
Wywołana metoda full
zwraca true jeżeli wszystkie dane zostały poprawnie sparsowane.
A co to za dziwne nawiasy kwadratowe za wyrażeniem? Jest to akcja semantyczna (ang. Semantic Action). Sam parser sprawdza tylko czy dane wyrażenie pasuje do wzorca, ale nic więcej z nim nie robi. Z pomocą przychodzą akcje semantyczne. Mogą być one dodane w dowolnym miejscu. Funkcja jest wywoływana w momencie, gdy parsowanie przebiegło pomyślnie. Jak to się robi?:
//jeżeli P to nasz parser a F to funkcja w c++ to aby "podłączyć" funkcję do parsera, należy to zrobić w następujący sposób: P[&F] //jeżeli natomiast F jest funktorem: P[F] //dodatkowo argument fukncji musi się zgadzać z elementem parsowanym: void Foo(double d); real_p[&Foo]