Szablony wyrażeń i Boost Spirit

Podobne dokumenty
Zaawansowane programowanie w C++ (PCP)

IMIĘ i NAZWISKO: Pytania i (przykładowe) Odpowiedzi

Curiously recurring template pattern

Szablony funkcji i szablony klas

Zaawansowane programowanie w języku C++ Funkcje uogólnione - wzorce

Język C++ wykład VIII

Podstawy języka C++ Maciej Trzebiński. Instytut Fizyki Jądrowej Polskiej Akademii Nauk. Praktyki studenckie na LHC IVedycja,2016r.

Automatyczne tworzenie operatora = Integer2& operator=(const Integer& prawy) { zdefiniuje. Integer::operator=(ri);

Automatyczne tworzenie operatora = Integer2& operator=(const Integer& prawy) {

Dariusz Brzeziński. Politechnika Poznańska, Instytut Informatyki

Signals + Threads: Qt vs. Boost

Podczas dziedziczenia obiekt klasy pochodnej może być wskazywany przez wskaźnik typu klasy bazowej.

Jzyk C++ cz 3. Jarosław Gramacki Instytut Informatyki i Elektroniki ( $)*)+' *, - ( ' )*'.' '',*/ *, ','*0) 1 / ) %*+ 2'' 2" ( $%%) )'20 )*0) 1 / )

Szablony. Szablony funkcji

Wprowadzenie do szablonów szablony funkcji

Automatyczne tworzenie operatora = Integer2& operator=(const Integer& prawy) {

Kurs programowania. Wykład 9. Wojciech Macyna. 28 kwiecień 2016

Składnia C++ Programowanie Obiektowe Mateusz Cicheński

Wprowadzenie do szablonów szablony funkcji

Wzorce funkcji (szablony)

Identyfikacje typu na etapie. wykonania (RTTI)

1. Wartość, jaką odczytuje się z obszaru przydzielonego obiektowi to: a) I - wartość b) definicja obiektu c) typ oboektu d) p - wartość

C++ Przeładowanie operatorów i wzorce w klasach

Programowanie i struktury danych

Wykład I. Programowanie II - semestr II Kierunek Informatyka. dr inż. Janusz Słupik. Wydział Matematyki Stosowanej Politechniki Śląskiej

Programowanie Obiektowo Zorientowane w języku c++ Przestrzenie nazw

Wykład 1. Program przedmiotu. Programowanie Obiektowe (język C++) Literatura. Program przedmiotu c.d.:

Zaawansowane programowanie w C++ (PCP)

Dzisiejszy wykład. Wzorce projektowe. Visitor Client-Server Factory Singleton

obiekty funkcyjne - funktory

Programowanie w języku C++

1. Które składowe klasa posiada zawsze, niezależnie od tego czy je zdefiniujemy, czy nie?

EGZAMIN 2 (14 WRZEŚNIA 2015) JĘZYK C++

Operatory na rzecz typu TString

Kurs programowania. Wykład 13. Wojciech Macyna. 14 czerwiec 2017

Obsługa wyjątków. Język C++ WW12

Wykład 1. Program przedmiotu. Programowanie (język C++) Literatura. Program przedmiotu c.d.:

Podstawy języka C++ Maciej Trzebiński. Praktyki studenckie na LHC IFJ PAN. Instytut Fizyki Jądrowej Polskiej Akademii Nauk. M. Trzebiński C++ 1/16

Szablony funkcji i klas (templates)

Zaawansowane programowanie w C++ (PCP)

Wstęp do programowania obiektowego. WYKŁAD 3 Dziedziczenie Pola i funkcje statyczne Funkcje zaprzyjaźnione, this

C++11. C++ 11 wybrane elementy. C++11: referencje do rvalue C++ 11: C++11: referencje do rvalue. C++11: referencje do rvalue. Referencje do rvalue

public: // interfejs private: // implementacja // składowe klasy protected: // póki nie będziemy dziedziczyć, // to pole nas nie interesuje

PARADYGMATY PROGRAMOWANIA Wykład 4

C++ język nie dla ludzi o słabych nerwach. Małgorzata Bieńkowska

Kurs programowania. Wykład 9. Wojciech Macyna

Język C++ wykład VII. uzupełnienie notatek: dr Jerzy Białkowski. Programowanie C/C++ Język C++ wykład VII. dr Jarosław Mederski. Spis.

Abstrakcyjny typ danych

Technologie programowania Wykład 4. Szablony funkcji Notes. Szablony funkcji Notes. Szablony funkcji Notes. Notes. Przemek Błaśkiewicz.

Składnia C++ Programowanie Obiektowe Mateusz Cicheński

Programowanie obiektowe - Przykładowe zadania egzaminacyjne (2005/2006)

1. Pierwszy program. Kompilator ignoruje komentarze; zadaniem komentarza jest bowiem wyjaśnienie programu człowiekowi.

Wprowadzenie do szablonów klas

Programowanie obiektowe w C++ Wykład 12

Wstęp do Programowania 2

Wykład. Materiały bazują częściowo na slajdach Marata Dukhana

Programowanie - wykład 4

Paradygmaty programowania

Kurs programowania. Wykład 3. Wojciech Macyna. 22 marca 2019

Programowanie Komputerów

Kurs programowania. Wykład 1. Wojciech Macyna. 3 marca 2016

Programowanie 2. Język C++. Wykład 3.

C++11. C++ 11 wybrane elementy. C++11: referencje do rvalue C++ 11: C++11: referencje do rvalue. C++11: referencje do rvalue. Referencje do rvalue

C++ - przeciążanie operatorów. C++ - przeciążanie operatorów. C++ - przeciążanie operatorów. C++ - przeciążanie operatorów

Programowanie w C++ Wykład 12. Katarzyna Grzelak. 28 maja K.Grzelak (Wykład 12) Programowanie w C++ 1 / 27

Technologie cyfrowe semestr letni 2018/2019

Wykład I. Programowanie III - semestr III Kierunek Informatyka. dr inż. Janusz Słupik. Wydział Matematyki Stosowanej Politechniki Śląskiej

Funkcje wirtualne. Wskaźniki do klas pochodnych są podstawą dla funkcji wirtualnych i polimorfizmu dynamicznego.

Zaawansowane programowanie w C++ (PCP)

Kurs programowania. Wykład 2. Wojciech Macyna. 17 marca 2016

wykład IV uzupełnienie notatek: dr Jerzy Białkowski Programowanie C/C++ Język C, a C++. wykład IV dr Jarosław Mederski Spis Język C++ - wstęp

Wybrane narzędzia do tworzenia analizatorów leksykalnych i składniowych w C/C++ Narzędzia, zastosowanie oraz próby rankingu.

Zaawansowane programowanie w języku C++ Programowanie obiektowe

EGZAMIN PROGRAMOWANIE II (10 czerwca 2010) pytania i odpowiedzi

Programowanie w C++ Wykład 11. Katarzyna Grzelak. 13 maja K.Grzelak (Wykład 11) Programowanie w C++ 1 / 30

Wprowadzenie w dziedziczenie. Klasa D dziedziczy klasę B: Klasa B klasa bazowa (base class), klasa D klasa pochodna (derived class).

Język C++ wykład VI. uzupełnienie notatek: dr Jerzy Białkowski. Programowanie C/C++ Język C++ wykład VI. dr Jarosław Mederski.

Metody Kompilacji Wykład 3

Instrukcja do pracowni specjalistycznej z przedmiotu. Obiektowe programowanie aplikacji

Programowanie w C++ Wykład 6. Katarzyna Grzelak. kwiecień K.Grzelak (Wykład 6) Programowanie w C++ 1 / 40

Instrukcja laboratoryjna cz.3

Laboratorium 1 Temat: Przygotowanie środowiska programistycznego. Poznanie edytora. Kompilacja i uruchomienie prostych programów przykładowych.

Zaawansowane programowanie w języku C++ Biblioteka standardowa

Programowanie w C++ Wykład 8. Katarzyna Grzelak. 7 maja K.Grzelak (Wykład 8) Programowanie w C++ 1 / 31

Programowanie w C++ Wykład 14. Katarzyna Grzelak. 3 czerwca K.Grzelak (Wykład 14) Programowanie w C++ 1 / 27

Modelowanie numeryczne w fizyce atmosfery Ćwiczenia 3

2. Klasy cz. 2 - Konstruktor kopiujący. Pola tworzone statycznie i dynamicznie - Funkcje zaprzyjaźnione - Składowe statyczne

Programowanie w C++ - wybrane przykłady szablonów Opracowanie: dr hab. Mirosław R. Dudek, prof. UZ

Paradygmaty programowania. Paradygmaty programowania

Wstęp do programowania

Wydajność użycia funktorów z biblioteką STL języka C++

Programowanie obiektowe i C++ dla matematyków

Projektowanie klas c.d. Projektowanie klas przykład

Szablony klas, zastosowanie szablonów w programach

#include <iostream> using namespace std; void ela(int); int main( ); { Funkcja 3. return 0; }

W2 Wprowadzenie do klas C++ Klasa najważniejsze pojęcie C++. To jest mechanizm do tworzenia obiektów. Deklaracje klasy :

Technologie cyfrowe semestr letni 2018/2019

Zajęcia nr 2 Programowanie strukturalne. dr inż. Łukasz Graczykowski mgr inż. Leszek Kosarzewski Wydział Fizyki Politechniki Warszawskiej

STL: implementowanie algorytmów C++: STL. STL: implementowanie algorytmów. STL: implementowanie algorytmów. STL: implementowanie algorytmów

referencje Wykład 2. Programowanie (język C++) Referencje (1) int Num = 50; zdefiniowano zmienną Num (typu int) nadając jej wartość początkową 50.

Transkrypt:

Szablony wyrażeń i Boost Spirit Romuald Juchnowicz-Bierbasz Warsaw C++ Users, 2015

Plan prezentacji Szablony wyrażeń Wprowadzenie Implementacja Właściwości i zastosowania Boost spirit Wprowadzenie Struktura biblioteki Teoria Podstawy Atrybuty parsera Akcje semantyczne Pozostałe funkcjonalności Zalety Źródła

Problem - Biblioteka do algebry liniowej Przykładowa implementacja klasy vector: 1 # include <vector > 2 3 class vector { 4 public : 5 vector ( size_t size ) : _data (size ) {} 6 double & operator []( size_t i) { return _data [i ];} 7 const double & operator []( size_t i) const { return _data [i ];} 8 size_t size (void ) const { return _data.size () ;} 9 private : 10 std :: vector < double > _data ; 11 }; Chcielibyśmy umożliwić zapis następujących wyrażeń: 1 vector v1 (3) ; 2 v1 [0] = 0; v1 [1] = 1; v1 [2] = 2; 3 vector v2 (3) ; 4 v2 [0] = 0; v2 [1] = 1; v2 [2] = 2; 5 vector v3 = v1 + v2 * 2;

Pierwsze podejście - przeciążenie operatorów +, *: 1 class vector { 2 public : 3 vector ( size_t size ) : _data (size ) {} 4 double & operator []( size_t i) { return _data [i ];} 5 double operator []( size_t i) const { return _data [i ];} 6 size_t size (void ) const { return _data.size () ;} 7 vector operator +( const vector & rhv ); 8 vector operator *( double rhv ); 9 private : 10 std :: vector < double > _data ; 11 }; 12 13 // jesli chcemy pozwolic na wyrazenia 2 * v to rowniez : 14 vector operator *( double rhv, const vector & lhv );

Ile razy zostanie wywołany konstruktor w wyrażeniu: 1 vector v3 = v1 + v2 * 2;

Ile razy zostanie wywołany konstruktor w wyrażeniu: 1 vector v3 = v1 + v2 * 2; Przynajmniej 2 razy (przy założeniu RVO): tmp = v2 * 2 v3 = v1 + tmp

Ile razy zostanie wywołany konstruktor w wyrażeniu: 1 vector v3 = v1 + v2 * 2; Przynajmniej 2 razy (przy założeniu RVO): tmp = v2 * 2 v3 = v1 + tmp W przypadku bardziej złożonych wyrażeń odpowiednio więcej (5): 1 vector v = v1 + v2 * 2 + v3 * 3;

A wystarczyłoby jedno wywołanie: 1 vector v3 (3) ; 2 for ( size_t i = 0; i < v3.size (); ++i) 3 v3[i] = v1[i] + v2[i] * 2;

A wystarczyłoby jedno wywołanie: 1 vector v3 (3) ; 2 for ( size_t i = 0; i < v3.size (); ++i) 3 v3[i] = v1[i] + v2[i] * 2; Chcielibyśmy, aby poniższe wyrażenie generowało kod równoważny powyższemu: 1 vector v3 = v1 + v2 * 2

Pierwsza implementacja 1 template < typename E1, typename E2 > 2 class vector_addition { 3 public : 4 vector_addition ( const E1& u, const E2& v) 5 : _u(u), _v(v) 6 { 7 assert (u. size () == v. size ()); 8 } 9 double operator []( size_t i) const { 10 return _u[i] + _v[i]; 11 } 12 size_t size ( void ) const { return _u.size () ;} 13 private : 14 const E1& _u; 15 const E2& _v; 16 }; 17 18 template < typename E> 19 class vector_scaled { 20 public : 21 vector_scaled ( const E& v, double scalar ) 22 : _v(v), _scalar ( scalar ) {} 23 double operator []( size_t i) const { 24 return _v[i] * _scalar ; 25 } 26 size_t size ( void ) const { return _v.size () ;} 27 private : 28 const E& _v; 29 double _scalar ; 30 };

Dodajmy odpowiedni konstruktror do klasy vector: 1 class vector { 2... 3 template < typename E> 4 vector (E const & exp ) { 5 _data. resize (exp.size ()); 6 for ( size_type i = 0; i!= exp.size (); ++i) { 7 _data [i] = exp [i]; 8 } 9 } 10... 11 };

Dodajmy jeszcze operatory, które jednocześnie będę pełniły rolę funkcji tworzących (Creator Functions - analogicznie do, np. std::make pair): 1 template < typename E1, typename E2 > 2 vector_addition <E1, E2 > operator +( const E1& u, const E2& v) 3 { 4 return vector_addition <E1, E2 >(u, v); 5 } 6 7 template < typename E> 8 vector_scaled <E> operator *( const E& v, double scalar ) 9 { 10 return vector_scaled <E >(v, scalar ); 11 } Możemy teraz zapisać: 1 v1 + v2 * 2;

Dodajmy jeszcze operatory, które jednocześnie będę pełniły rolę funkcji tworzących (Creator Functions - analogicznie do, np. std::make pair): 1 template < typename E1, typename E2 > 2 vector_addition <E1, E2 > operator +( const E1& u, const E2& v) 3 { 4 return vector_addition <E1, E2 >(u, v); 5 } 6 7 template < typename E> 8 vector_scaled <E> operator *( const E& v, double scalar ) 9 { 10 return vector_scaled <E >(v, scalar ); 11 } Możemy teraz zapisać: 1 v1 + v2 * 2; Jaki typ będzie miało to wyrażenie?

Do wykrywania typu użyjemy sztuczki zaprezentowanej przez Scotta Mayersa w Effective Modern C++ : 1 template <typename T> 2 class TD; // deklaracja bez definicji 3 4 TD < decltype (v1 + v2 * 2) > etype ; Błąd kompilatora: 1>c:\ users \ romek \ documents \ visual studio 2010\ projects \ spirit \ spirit \main.cpp (69) : error C2079 : etype uses undefined class TD<T> 1> with 1> [ 1> T=vector_addition <vector, vector_scaled <vector >> 1> ] 1>

Drzewo składniowe Nasz typ to: vector addition 1 vector_addition < 2 vector, 3 vector_scaled < 4 vector 5 > 6 > vector vector scaled vector double Czy widać tu podobieństwo do znanego wzorca projektowego?

Diagram klas Tak klasa nie jest zadekarowana vector policy +operator[](index: size_t): double +size(return_value: size_t) vector -_data: std::vector<double> +opertor[](index: size_t): double +size(return_value: size_t) vector_addition +size(return_value: size_t) +opertor[](index: size_t): double E1 E2

Diagram klas - wzorzec kompozyt Component +Operation() +Add(c: Component) +Remove(c: Component) +GetChild(index: int) Leaf +Operation() Composite +Operation() +Add(c: Component) +Remove(c: Component) +GetChild(index: int) +children

Diagram klas - wzorzec kompozyt Component +Operation() +Add(c: Component) +Remove(c: Component) +GetChild(index: int) Leaf +Operation() Composite +Operation() +Add(c: Component) +Remove(c: Component) +GetChild(index: int) +children Dlaczego więc po prostu nie stosować wzorca kompozyt?

Pytanie Are inline virtual member functions ever actually inlined? C++ Super-FAQ

Pytanie Are inline virtual member functions ever actually inlined? Odpowiedź Occasionally... C++ Super-FAQ When the object is referenced via a pointer or a reference, a call to a virtual function cannot be inlined, since the call must be resolved dynamically. Reason: the compiler can t know which actual code to call until run-time (i.e., dynamically), since the code may be from a derived class that was created after the caller was compiled. Therefore the only time an inline virtual call can be inlined is when the compiler knows the exact class of the object which is the target of the virtual function call. This can happen only when the compiler has an actual object rather than a pointer or reference to an object. I.e., either with a local object, a global/static object, or a fully contained object inside a composite.

Szablony wyrażeń pozwalają kompilatorowi na optymalizację kodu poprzez inline owanie metod klas składających się na wyrażenie. Możliwe jest więc aby polecenie: 1 vector v = v1 + v2 * 2; Wygenerowało kod analogiczny do: 1 vector v3 (3) ; 2 for ( size_t i = 0; i < v3.size (); ++i) 3 v3[i] = v1[i] + v2[i] * 2;

Problem - przeciążanie operatorów W naszym przykładzie przeciążyliśmy operatory +, *: 1 template < typename E1, typename E2 > 2 vector_addition <E1, E2 > operator +( const E1& u, const E2& v) 3 { 4 return vector_addition <E1, E2 >(u, v); 5 } 6 7 template < typename E> 8 vector_scaled <E> operator *( const E& v, double scalar ) 9 { 10 return vector_scaled <E >(v, scalar ); 11 } Czy taki kod może powodować problemy?

Przeciążyliśmy operatory dla dowolnych typów.

Przeciążyliśmy operatory dla dowolnych typów. Możemy sobie z tym poradzić dzięki: opakowaniu wszystkiego w przetrzeń nazw, zastosowaniu statycznego polimorfizmu - Curiously recurring template pattern - CRTP

Curiously recurring template pattern Przypomnijmy: 1 // CRTP 2 template <class T> 3 class Base 4 { 5 // metody klasy Base moga miec dostep do metod / atrybutow klasy Derived 6 }; 7 class Derived : public Base <Derived > 8 { 9 //... 10 };

Tworzymy szablonową klasę bazową: 1 template < typename E> 2 class expression { 3 public : 4 size_t size () const { return static_cast <E const & >(* this ).size () ;} 5 double operator []( size_t i) const { 6 return static_cast <E const & >(* this )[i]; 7 } 8 9 operator E &() { return static_cast <E& >(* this ); 10 operator const E &() const { return static_cast <const E& >(* this );} 11 };

Tworzymy szablonową klasę bazową: 1 template < typename E> 2 class expression { 3 public : 4 size_t size () const { return static_cast <E const & >(* this ).size () ;} 5 double operator []( size_t i) const { 6 return static_cast <E const & >(* this )[i]; 7 } 8 9 operator E &() { return static_cast <E& >(* this ); 10 operator const E &() const { return static_cast <const E& >(* this );} 11 }; Klasy vector, vector addition oraz vector scaled dziedziczą z expression: 1 class vector : public expression <vector > { 2 public : 3 vector ( size_t size ) : _data (size ) {} 4 template <typename E> vector ( expression <E> const & exp ); 5 double & operator []( size_t i) { return _data [i ];} 6 double operator []( size_t i) const { return _data [i ];} 7 size_t size (void ) const { return _data.size () ;} 8 private : 9 std :: vector <double > _data ; 10 };

1 template < typename E1, typename E2 > 2 class vector_addition : public expression < vector_addition <E1, E2 > > { 3 public : 4 vector_addition ( const E1& u, const E2& v) : _u(u), _v(v) { 5 assert (u. size () == v. size ()); 6 } 7 double operator []( size_t i) const { return _u[i] + _v[i ];} 8 size_t size (void ) const { return _u.size () ;} 9 private : 10 const E1& _u; 11 const E2& _v; 12 }; 13 14 template < typename E> 15 class vector_scaled : public expression <vector_scaled <E> > { 16 public : 17 vector_scaled ( const E& v, double scalar ) : _v(v), _scalar ( scalar ) {} 18 double operator []( size_t i) const { return _v[i] * _scalar ;} 19 size_t size (void ) const { return _v.size () ;} 20 private : 21 const E& _v; 22 double _scalar ; 23 };

Teraz można operatory zapisać jako: 1 template < typename E1, typename E2 > 2 vector_addition <E1, E2 > operator +( 3 const expression <E1 >& u, const expression <E2 >& v 4 ) 5 { 6 return vector_addition <E1, E2 >(u, v); 7 } 8 9 template < typename E> 10 vector_scaled <E> operator *( const expression <E>& v, double scalar ) 11 { 12 return vector_scaled <E >(v, scalar ); 13 }

Diagram klas Uzyskany digram klas jeszcze bardziej przypomina wzorzec kompozyt: expression E +opertor[](index: size_t): double +size(return_value: size_t) vector -_data: std::vector<double> +opertor[](index: size_t): double +size(return_value: size_t) vector_addition +opertor[](index: size_t): double +size(return_value: size_t) E1 E2

Właściwości i zastosowania szablonów wyrażeń Właściwości: pozwalają wygenerować szybki kod wynikowy przy pomocy intuicyjnej składni, wydłużają kompilację.

Właściwości i zastosowania szablonów wyrażeń Właściwości: pozwalają wygenerować szybki kod wynikowy przy pomocy intuicyjnej składni, wydłużają kompilację. Zastosowania: do tworzenia wbudowanych języków dziedzinowych (domain-specific embedded language - DSEL), np.: nasza przykładowa biblioteka do algebry liniowej, Blitz++ - obliczenia naukowe, Boost Spirit - gramatyki EBNF, implementacja wartościowania leniwego (lazy evaluation), do łatwej generacji funktorów.

Przykładowe rozwiązanie pozwalające na generację funkcji jednoargumentowych: 1 struct variable { 2 double operator () ( double v) { return v;} 3 }; 4 5 struct constant { 6 double c; 7 constant ( double d) : c (d) {} 8 double operator () ( double ) { return c;} 9 }; 10 11 template <class L, class H, class OP> 12 struct binary_expression { 13 L l_; 14 H h_; 15 binary_expression (L l, H h) : l_ (l), h_ (h) {} 16 double operator () ( double d) { return OP :: apply (l_ (d), h_(d));} 17 }; 18 19 struct add { 20 static double apply ( double l, double h) { return l + h;} 21 }; 22 23 template <class E> 24 struct expression { 25 E expr_ ; 26 expression (E e) : expr_ (e) {} 27 double operator () ( double d) { return expr_ (d);} 28 };

Dodajmy jeszcze operatory i definicje typów: 1 template <class A, class B> 2 expression <binary_expression <expression <A>, expression <B>, add > > 3 operator+( expression <A> a, expression <B> b) 4 { 5 typedef binary_expression <expression <A>, expression <B>, add > ExprT ; 6 return expression <ExprT >( ExprT (a,b)); 7 } 8 9 typedef expression <variable > var ; 10 typedef expression <constant > lit ; Możemy teraz napisać: 1 var x(( variable ())); // dlaczego nawiasy? 2 lit l( constant (5.0) ); 3 double result = (x + l + x) (2) ; // 9

Boost spirit Wprowadzenie Problem Parsowanie listy liczb całkowitych: 1, 2, 3, 5, 12,... std::vector<int> Jakie pomysły?

Boost spirit Wprowadzenie Problem Parsowanie listy liczb całkowitych: 1, 2, 3, 5, 12,... std::vector<int> Jakie pomysły? pętla ze scanf lub std::istream::operator>>? boost::regex? readline i boost::split?

Problem A co gdy wejście się trochę komplikuje? 1 + 3, 2 2, 44 33,... std::vector<int>

Problem A co gdy wejście się trochę komplikuje? 1 + 3, 2 2, 44 33,... std::vector<int> W przypadku prostego wejścia wcześniejsze pomysły wystarczają... Ale gdy złożoność rośnie należy użyć rozwiązania: ładniejszego, bardziej skalowalnego, bardziej odpornego na błędy, etc.

Znane narzędzia do generacji parserów: ANTLR Bison Yacc

Znane narzędzia do generacji parserów: ANTLR Bison Yacc Wady: kody pośrednie należy nauczyć się ich stosowania linkowanie bibliotek

Znane narzędzia do generacji parserów: ANTLR Bison Yacc Wady: kody pośrednie należy nauczyć się ich stosowania linkowanie bibliotek Może Boost.Spirit?

Struktura biblioteki Biblioteka Boost Spirit składa się z 3 części: lex - do tworzenia lekserów qi - do tworzenia parserów karma - do formatowania danych ( odwrotność qi) W ramach seminarium dokładniej omówiona zostanie część qi. Boost spirit zbudowany jest dzięki wykorzystaniu szablonów wyrażeń (przy użyciu Boost Proto). Aby w pełni wykorzystać potencjał biblioteki dobrze jest znać inne biblioteki Boost: Fusion, Phoenix.

Struktura biblioteki

qi i karma - ying i yang qi zamienia wejście na struktry danych: 1 2 3 : string [1, 2, 3] : vector karma określa formatowanie struktur danych na wyjściu: [1, 2, 3] : vector 1 2 3 : string

Przepływ danych qi - karma

Gramatyki Boost Spirit operuje na gramatykach bezkontekstowych przy użyciu rozszerzona notacja Backusa-Naura (Extended Backus Naur Form - EBNF): typ 0 - gramatyka kombinatoryczne, typ 1 - gramatyki kontekstowe, typ 2 - gramatyki bezkontekstowe, typ 3 - gramatyki regularne.

Rozszerzona notacja Backusa-Naura Notacja Backusa-Naura cyfra_bez_zera ::= "1" "2" "3" "4" "5" "6" "7" "8" "9" cyfra ::= "0" cyfra_bez_zera liczba_naturalna ::= cyfra_bez_zera, { cyfra } liczba_calkowita ::= "0" [ "-" ], liczba_naturalna Rozszerzona notacja Backusa-Naura cyfra_bez_zera ::= "1" "2" "3" "4" "5" "6" "7" "8" "9" cyfra ::= "0" cyfra_bez_zera liczba_naturalna ::= cyfra_bez_zera cyfra* liczba_calkowita ::= "0" "-"? liczba_naturalna

Qi - podstawy Boost Spirit Qi pozwala na zdefiniowanie parsera przy pomocy notacji EBNF w kodzie C++: cyfra_bez_zera ::= "1" "2" "3" "4" "5" "6" "7" "8" "9" cyfra ::= "0" cyfra_bez_zera liczba_naturalna ::= cyfra_bez_zera cyfra* liczba_całkowita ::= "0" "-"? liczba_naturalna 1 cyfra_bez_zera = char_ ( 1 ) char_ ( 2 ) char_ ( 3 ) 2 char_ ( 4 ) char_ ( 5 ) 3 char_ ( 6 ) char_ ( 7 ) 4 char_ ( 8 ) char_ ( 9 ); 5 cyfra = char_ ( 0 ) cyfra_bez_zera ; 6 liczba_naturalna = cyfra_bez_zera >> * cyfra ; 7 liczba_calkowita = char_ ( 0 ) -char_ ( - ) >> liczba_naturalna ;

Prosty parser - krok 1 Parser liczby całkowitej: int_

Prosty parser - krok 1 Parser liczby całkowitej: int_ Qi posiada wiele innych wbudowanych parserów: double char str

Prosty parser - krok 2 Parser 2 liczb całkowitych: int_ >> int_ Jest to kombinacja dwóch parserów tworząca nowy parser.

Prosty parser - krok 2 Parser 2 liczb całkowitych: int_ >> int_ Jest to kombinacja dwóch parserów tworząca nowy parser. Parser serii liczb całkowitych: *int_

Prosty parser - krok 2 Parser 2 liczb całkowitych: int_ >> int_ Jest to kombinacja dwóch parserów tworząca nowy parser. Parser serii liczb całkowitych: *int_ Parser serii liczb całkowitych oddzielonych przecinkiem: int_ >> *(char_(, ) >> int_)

Prosty parser - cały kod 1 bool parse_numbers ( const std :: string & s) 2 { 3 using qi :: int_ ; 4 using qi :: phrase_parse ; 5 using ascii :: space ; 6 7 std :: string :: iterator first = s. begin (); 8 std :: string :: iterator last = s.end (); 9 10 bool r = phrase_parse ( 11 first, 12 last, 13 int_ >> *(, >> int_ ), 14 space // parser bialych znakow 15 ); 16 if ( first!= last ) // nie udalo sie sparsowac calego wejscia 17 return false ; 18 return r; 19 }

Prosty parser - cały kod 1 bool parse_numbers ( const std :: string & s) 2 { 3 using qi :: int_ ; 4 using qi :: phrase_parse ; 5 using ascii :: space ; 6 7 std :: string :: iterator first = s. begin (); 8 std :: string :: iterator last = s.end (); 9 10 bool r = phrase_parse ( 11 first, 12 last, 13 int_ >> *(, >> int_ ), 14 space // parser bialych znakow 15 ); 16 if ( first!= last ) // nie udalo sie sparsowac calego wejscia 17 return false ; 18 return r; 19 } Pytanie Dlaczego w definicji parsera nie używaliśmy char?

Qi - atrybuty parsera Każdy parser udostępnia atrybut syntezowany - typ zwracany po poprawnym parsowaniu, który reprezentuję sparsowane wejście. Na przykład: int - int double - double char - char str - std::string

Atrybuty złożone A co ze złożeniem parserów? Przykłady: Wyrażenie Typy argumentów Typ rezultatu (a >> b) a: A, b: B tuple<a, B> (a b) a: A, b: B variant<a, B> (a b) a: A, b: A A *a a: A vector<a> -a: a: A optional<a>

Atrybuty złożone - przykłady Atrybutem syntezowanym int >> *(char (, ) >> int ) jest tuple< int, vector< tuple <char, int> > > Ale już wyrażenie: int >> *(lit(, ) >> int ) ma atrybyt syntezowany: vector<int>

Akcje semantyczne Poprzedni parser dawał nam odpowiedź jedynie na pytanie Czy wejście jest poprawnym wyrażeniem w zadanej gramatyce?. To dość mało... Chcemy więcej! Chcemy akcji semantycznych!

Akcje semantyczne Poprzedni parser dawał nam odpowiedź jedynie na pytanie Czy wejście jest poprawnym wyrażeniem w zadanej gramatyce?. To dość mało... Chcemy więcej! Chcemy akcji semantycznych! Niech: P - paser A - akcja Korzystając z operatora indeksu możemy przypisać akcję do parsera. P[A] Akcja A jest wywoływana po poprawnym sparsowaniu wejścia przez parser P.

Akcje semantyczne Akcję możemy zdefiniować używając: wskaźnika do funkcji obiektu funkcyjnego Boost.Bind i wskaźnika do funkcji Boost.Bind i wskaźnika do metody Boost.Lambda Boost.Phoenix

Akcje semantyczne - przykłady 1 namespace qi = boost :: spirit :: qi; 2 // funkcja 3 void print ( int const & i) { 4 std :: cout << i << std :: endl ; 5 } 6 7 // metoda 8 struct writer 9 { 10 void print ( int const & i) const { 11 std :: cout << i << std :: endl ; 12 } 13 }; 14 15 // funktor 16 struct print_action 17 { 18 void operator ()(int const & i, qi :: unused_type, qi :: unused_type ) const { 19 std :: cout << i << std :: endl ; 20 } 21 };

Akcje semantyczne - przykłady Mamy zadane wejście: "{integer}" Chcemy wypisać liczbę znajdującą się w nawiasach: 1 // wskaznik do funkcji 2 parse ( first, last, { >> int_ [& print ] >> } ); 3 4 // funktor 5 parse ( first, last, { >> int_ [ print_action ()] >> } ); 6 7 // Boost. Bind ze wskaznikiem do funkcji 8 parse ( first, last, { >> int_ [ boost :: bind (& print, _1)] >> } ); 9 10 // Boost. Bind ze wskaznikiem do metody 11 writer w; 12 parse ( first, last, { >> int_ [ boost :: bind (& writer :: print, &w, _1)] >> } ); 13 14 // Boost. Lambda 15 parse ( first, last, { >> int_ [ std :: cout << _1 << \n ] >> } );

Przykład - parser liczb zespolonych Parser liczb zespolonych: ( >> double_ >> -(, >> double_) >> ) double_ 1 template <typename Iterator > 2 bool parse_complex ( Iterator first, Iterator last, std :: complex <double >& c) 3 { 4 /* using boost ::... */ 5 6 double rn = 0.0; 7 double in = 0.0; 8 bool r = phrase_parse ( first, last, 9 // Begin grammar 10 ( 11 ( >> double_ [ref (rn) = _1] 12 >> -(, >> double_ [ ref (in) = _1 ]) >> ) 13 double_ [ref (rn) = _1] 14 ), 15 // End grammar 16 space ); 17 18 if (!r first!= last ) // fail if we did not get a full match 19 return false ; 20 c = std :: complex <double >(rn, in); 21 return r; 22 }

Magia Boost Phoenix Zamiast ref z Boost Phoenix: double_[ref(rn) = _1] mogliśmy napisać: 1 struct set_double { 2 double & _d; 3 void operator ()( double const & d, qi :: unused_type, qi :: unused_type ) const 4 { 5 _d = d; 6 } 7 set_double ( double & d) : _d(d) {} 8 }; 9 10... 11 double_ [ set_double (nr)] 12...

Przykład - lista liczb zmiennoprzecinkowych Zamiast: double_ >> *(, >> double_) możemy napisać: double_ %, 1 template <typename Iterator > 2 bool parse_numbers ( Iterator first, Iterator last, std :: vector <double >& v) 3 { 4 /* using... */ 5 6 bool r = phrase_parse ( first, last, 7 8 // Begin grammar 9 ( 10 double_ [ push_back ( ref (v), _1)] %, 11 ) 12, 13 // End grammar 14 15 space ); 16 17 if ( first!= last ) // fail if we did not get a full match 18 return false ; 19 return r; 20 }

Przykład - lista liczb zmiennoprzecinkowych Możemy od razy otrzymać atrybut syntezowany z parsera: 1 template <typename Iterator > 2 bool parse_numbers ( Iterator first, Iterator last, std :: vector <double >& v) 3 { 4 using qi :: double_ ; 5 using qi :: phrase_parse ; 6 using qi :: _1; 7 using ascii :: space ; 8 9 bool r = phrase_parse ( first, last, 10 11 // Begin grammar 12 ( 13 double_ %, 14 ) 15, 16 // End grammar 17 18 space, v); 19 20 if ( first!= last ) // fail if we did not get a full match 21 return false ; 22 return r; 23 }

Pozostałe funkcjonalności definiowanie reguł gramatycznych definiowanie gramatyk struktury jako atrybut syntezowany (Boost.Fusion) atrybuty dziedziczone obsługa błędów

Zalety Boost Spirit korzysta ze składni C++ wyłącznie pliki nagłówkowe bardzo szybka

Źródła I Expression templates http://en.wikipedia.org/wiki/expression_templates More C++ Idioms/Expression-template http://en.wikibooks.org/wiki/more_c%2b%2b_idioms/ Expression-template C++ Expression Templates http://www.angelikalanger.com/articles/cuj/ ExpressionTemplates/ExpressionTemplates.htm Boost Spirit homepage http://boost-spirit.com/home/ C++ Super-FAQ https://isocpp.org/wiki/faq

Źródła II E. Gamma, R. Helm, R. Johnson, J. Vlissides Design Patterns S. Meyers Effective Modern C++ A. Alexandrescu Modern C++ Design