Narzędzia użytkownika

Narzędzia witryny


spirit

To jest stara wersja strony!


Biblioteka Boost Spirit

Bartłomiej Wojcieszek G1SST

Wstęp

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

Jak używać?

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>

Na początek coś prostego

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:

  1. Zakończone nullem const char* dane do sparsowania
  2. Obiekt parsujący
  3. Parser pomijania (mówi na co nie zwracać uwagi w danych wejściowych, w tym przypadku na spacje)

Teraz trochę o gramatyce

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]

Bardziej użyteczny przykład

Wykorzytanie funkcji

O bibliotece Spirit w sieci

spirit.1208322902.txt.gz · ostatnio zmienione: 2008/04/16 07:15 przez bwojcies