Różnice między wybraną wersją a wersją aktualną.
Both sides previous revision Previous revision | Next revision Both sides next revision | ||
regex [2008/04/16 02:37] przemoc małe zbliżenie do finalnej wersji |
regex [2008/04/16 16:01] przemoc wersja preprefinalna |
||
---|---|---|---|
Linia 1: | Linia 1: | ||
====== Wyrażenia regularne oczami programisty C++, czyli Boost.Regex ====== | ====== Wyrażenia regularne oczami programisty C++, czyli Boost.Regex ====== | ||
- | __Wersja robocza__ | + | [[przemoc@gmail.com|Przemysław Pawełczyk G1ISI]] |
W życiu każdego programisty przychodzi czas, w którym musi poznać wyrażenia regularne (z różnych przyczyn), a znając już je, używa ich chętnie i bez oporów, przynajmniej w skryptach shellowych (a de facto w programach typu [[wp>AWK]], [[wp>grep]] czy [[wp>sed]]) i w interpretowanych językach jak [[wp>PHP]], [[wp>Perl]], [[wp>Ruby_(programming_language)|Ruby]] oraz innych. | W życiu każdego programisty przychodzi czas, w którym musi poznać wyrażenia regularne (z różnych przyczyn), a znając już je, używa ich chętnie i bez oporów, przynajmniej w skryptach shellowych (a de facto w programach typu [[wp>AWK]], [[wp>grep]] czy [[wp>sed]]) i w interpretowanych językach jak [[wp>PHP]], [[wp>Perl]], [[wp>Ruby_(programming_language)|Ruby]] oraz innych. | ||
- | Ogromne możliwości przetwarzania tekstu, jakie dają nam potocznie nazywane regexpy, można też wykorzystać w językach, w których nie wbudowano obsługi wyrażeń regularnych, czyli np. w języku C++. Nie mówię tu o własnoręcznej implementacji np. niedeterministycznego automatu skończonego Thompsona, choć to też byłoby pewne wyjście. Programiści, z natury jednostki leniwe, często lubią korzystać z gotowych komponentów do budowy własnego oprogramowania. Niestety, brak obsługi wyrażeń regularnych w standardowej bibliotece C++ w standardzie C++98 (ISO/IEC 14882:1998) czy ostatnim C++03 (ISO/IEC 14882:2003). Sytuację poprawia dopiero [[http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1836.pdf|TR1]], który będzie częścią standardu C++0x, a którego ostateczne ujęcie i ratyfikacja nastąpią w najbliższych latach, optymistycznie: już w przyszłym roku. | + | Ogromne możliwości przetwarzania tekstu, jakie dają nam potocznie nazywane regexpy, można też wykorzystać w językach, w których nie wbudowano obsługi wyrażeń regularnych, czyli np. w języku C++. Nie mówię tu o własnoręcznej implementacji np. niedeterministycznego automatu skończonego Thompsona, choć to też byłoby pewne wyjście. Programiści, z natury jednostki dość leniwe, często lubią korzystać z gotowych komponentów do budowy własnego oprogramowania. Niestety, brak obsługi wyrażeń regularnych w standardowej bibliotece C++ w standardzie C++98 (ISO/IEC 14882:1998) czy ostatnim C++03 (ISO/IEC 14882:2003). Sytuację poprawia dopiero [[http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1836.pdf|TR1]], który będzie częścią standardu C++0x, a którego ostateczne ujęcie i ratyfikacja nastąpią w najbliższych latach, optymistycznie: już w przyszłym roku. |
Co więc trafiło do przestrzeni ''std::tr1''? To, co dziś znamy jako biblioteka [[http://www.boost.org/doc/libs/1_35_0/libs/regex/index.html|Boost.Regex]], której autorem jest [[http://ourworld.compuserve.com/homepages/John_Maddock/|John Maddock]]. | Co więc trafiło do przestrzeni ''std::tr1''? To, co dziś znamy jako biblioteka [[http://www.boost.org/doc/libs/1_35_0/libs/regex/index.html|Boost.Regex]], której autorem jest [[http://ourworld.compuserve.com/homepages/John_Maddock/|John Maddock]]. | ||
Linia 30: | Linia 30: | ||
</code> | </code> | ||
- | Korzystając z wyrażeń regularnych, a w naszym przypadku z Boost.Regex, "wyłuskiwanie" tematu jesteśmy w stanie zrealizować deklaratywnie (pomijając istnienie niezbędnej pętli), a więc opisując czego szukamy, a nie jak: | + | Korzystając z wyrażeń regularnych, w naszym przypadku poprzez Boost.Regex, "wyłuskiwanie" tematu jesteśmy w stanie zrealizować deklaratywnie (pomijając istnienie niezbędnej pętli), a więc opisując to, czego szukamy, a nie jak: |
<code cpp> | <code cpp> | ||
std::string line; | std::string line; | ||
Linia 53: | Linia 53: | ||
Wyobraźmy sobie teraz, że mamy maile wygenerowane przez program Outlook Express, który lubi dodawać więcej przedrostków ''Re:'', aniżeli jest to potrzebne w dłuższych konwersacjach. Dodatkowo weźmy pod uwagę, że niektóre OE mogły być w polskiej wersji językowej i dodawały ''Odp:'' zamiast ''Re:''. \\ | Wyobraźmy sobie teraz, że mamy maile wygenerowane przez program Outlook Express, który lubi dodawać więcej przedrostków ''Re:'', aniżeli jest to potrzebne w dłuższych konwersacjach. Dodatkowo weźmy pod uwagę, że niektóre OE mogły być w polskiej wersji językowej i dodawały ''Odp:'' zamiast ''Re:''. \\ | ||
- | Poprawione wyrażenie regularne będzie wówczas wyglądać np. tak "''^Subject: (Re: |Odp: )*(.*)''" i to jedyna zmiana niezbędna w rozwiązaniu deklaratywnym. Czytelnik zapewne z wielkim zapałem samodzielnie usprawni rozwiązanie imperatywne... | + | Poprawione wyrażenie regularne będzie wówczas wyglądać np. tak "''^Subject: (Re: |Odp: )*(.*)''" i jest to jedyna zmiana niezbędna w rozwiązaniu deklaratywnym. Czytelnik zapewne z wielkim zapałem samodzielnie usprawni rozwiązanie imperatywne... |
===== Przypomnienie składni wyrażeń regularnych ===== | ===== Przypomnienie składni wyrażeń regularnych ===== | ||
Linia 60: | Linia 60: | ||
* Perl (domyślna), | * Perl (domyślna), | ||
* POSIX rozszerzona (z odmianami używanymi w ''egrep'' czy ''awk''), | * POSIX rozszerzona (z odmianami używanymi w ''egrep'' czy ''awk''), | ||
- | * POSIX podstawowe (z odmianami używanymi w ''grep'' czy ''emacs''). | + | * POSIX podstawowa (z odmianami używanymi w ''grep'' czy ''emacs''). |
Omówię pokrótce tylko najważniejsze elementy domyślnej składni. | Omówię pokrótce tylko najważniejsze elementy domyślnej składni. | ||
Linia 84: | Linia 84: | ||
\s <=> [[:space:]] | spacja | \s <=> [[:space:]] | spacja | ||
\u <=> [[:upper:]] | duża litera | \u <=> [[:upper:]] | duża litera | ||
- | \w <=> [[:word:]] | znak mogący być elementem wyrazu | + | \w <=> [[:word:]] | znak "słowny" (alfanumeryczny lub _) |
- | \D <=> [^[:digit:]] | nie cyfra | + | \D <=> [^[:digit:]] | nie: cyfra |
- | \L <=> [^[:lower:]] | nie mała litera | + | \L <=> [^[:lower:]] | nie: mała litera |
- | \S <=> [^[:space:]] | nie spacja | + | \S <=> [^[:space:]] | nie: spacja |
- | \U <=> [^[:upper:]] | nie duża litera | + | \U <=> [^[:upper:]] | nie: duża litera |
- | \W <=> [^[:word:]] | nie znak mogący być elementem wyrazu | + | \W <=> [^[:word:]] | nie :znak "słowny" |
(?#komentarz) | ignorowany (komentarz) | (?#komentarz) | ignorowany (komentarz) | ||
(?:wzorzec) | "zjada" 0 znaków tylko jeżeli wzorzec się zgadza | (?:wzorzec) | "zjada" 0 znaków tylko jeżeli wzorzec się zgadza | ||
(?!wzorzec) | "zjada" 0 znaków tylko jeżeli wzorzec nie pasuje | (?!wzorzec) | "zjada" 0 znaków tylko jeżeli wzorzec nie pasuje | ||
- | Wszystkie operatory powtórzenia są domyślnie zachłanne, czyli starają się pokryć jak największy obszar tekstu. Odwrócenie tego zachowania możemy uzyskać poprzez dodanie ''?'' na końcu takiego operatora, uzyskując np. ''*?'' lub ''{n,}?''. | + | Wszystkie operatory powtórzenia są domyślnie zachłanne (ang. //greedy//), czyli starają się pokryć jak największy obszar tekstu. Odwrócenie tego zachowania możemy uzyskać poprzez dodanie ''?'' na końcu takiego operatora, uzyskując np. ''*?'' lub ''{n,}?''. |
- | Na koniec nie sposób nie wspomnieć o odwołaniach wstecznych (ang. //backreferences//), które stosujemy poprzez poprzedzenie znakiem backslash (\) numeru grupy znaczonej, | + | Na koniec nie sposób nie wspomnieć o odwołaniach wstecznych (ang. //backreferences//), które stosujemy poprzez poprzedzenie znakiem backslash (\) numeru grupy znaczonej, dzięki którym możemy jeszcze bardziej złożone wyrażenia regularne budować. Wyrażenie regularne odpowiedzialne za wyszukiwanie powtórzeń w tekście mogłoby wyglądać tak: |
+ | (\w) \1 | ||
===== Zaczynamy zabawę ===== | ===== Zaczynamy zabawę ===== | ||
+ | |||
+ | ==== Niezbędnik ==== | ||
Do skorzystania z dobrodziejstw Boost.Regex niezbędne jest dołączenie pliku nagłówkowego: | Do skorzystania z dobrodziejstw Boost.Regex niezbędne jest dołączenie pliku nagłówkowego: | ||
Linia 104: | Linia 107: | ||
#include <boost/regex.hpp> | #include <boost/regex.hpp> | ||
</code> | </code> | ||
+ | Tak jak w przypadku innych bibliotek Boosta, wszystkie stałe, klasy i funkcje zewnętrzne znajdują się przestrzeni nazw ''boost'' i dla przejrzystości nie będę tego pisał przy przytaczaniu fragmentów definicji podstawowych elementów Boost.Regex. | ||
- | Wyrażeniu regularnemu odpowiada szablon klasy ''boost::basic_regex'', który parametryzowany jest m.in. typem znaku. Metody tego szablonu to: | + | ==== Wyrażenie regularne ==== |
+ | |||
+ | Wyrażeniu regularnemu odpowiada szablon klasy ''boost::basic_regex'', który parametryzowany jest typem znaku i klasą cech tego typu (która domyślnie jest definiowana poprzez szablon ''regex_traits'' parametryzowany wcześniej podanym typem znaku). | ||
+ | <code cpp> | ||
+ | template < class charT, | ||
+ | class traits = regex_traits<charT> > | ||
+ | class basic_regex { /*...*/ }; | ||
+ | </code> | ||
+ | |||
+ | Dla wygody zdefiniowane są nasŧepujące typy: | ||
+ | <code cpp> | ||
+ | typedef basic_regex<char> regex; | ||
+ | typedef basic_regex<wchar_t> wregex; | ||
+ | </code> | ||
+ | |||
+ | Ważniejsze metody tego szablonu to: | ||
<code cpp> | <code cpp> | ||
- | bool empty() const; // zwraca true jeżeli w obiekt nie zawiera żadnego poprawnego wyrażenia regularnego | + | bool empty() const; // zwraca true jeżeli obiekt nie zawiera żadnego poprawnego wyrażenia regularnego |
unsigned mark_count() const; // zwraca liczbę znaczonych podwyrażeń w wyrażeniu regularnym | unsigned mark_count() const; // zwraca liczbę znaczonych podwyrażeń w wyrażeniu regularnym | ||
flag_type flags() const; // zwraca maskę bitową zawierającą flagi trybu przetwarzania wyrażenia regularnego | flag_type flags() const; // zwraca maskę bitową zawierającą flagi trybu przetwarzania wyrażenia regularnego | ||
</code> | </code> | ||
- | Dla wygody, wewnątrz przestrzeni ''boost'' zdefiniowane są nasŧepujące typy: | + | Najważniejszy jest jednak konstruktor: |
<code cpp> | <code cpp> | ||
- | typedef basic_regex<char> regex; | + | explicit basic_regex(const charT* p, flag_type f = regex_constants::normal); |
- | typedef basic_regex<wchar_t> wregex; | + | |
</code> | </code> | ||
+ | który przyjmuje sekwencję znaków zawierającą wyrażenie regularne oraz tryb przetwarzania tego wyrażenia. | ||
+ | W przypadku niepoprawnego wyrażenia regularnego rzucany jest wyjątek ''boost::regex_error'' (dawniej ''boost::bad_expression'' lub ''boost::bad_pattern'', które są teraz aliasami do ''boost::regex_error'' dla wstecznej kompatybilności), chyba że przekazano flagę ''regex_constants::no_except''. | ||
+ | |||
+ | Mamy następujące podstawowe (nieuzależnione od wybranej składni) tryby przetwarzania): | ||
+ | <code cpp> | ||
+ | namespace regex_constants { | ||
+ | typedef implementation-specific-bitmask-type syntax_option_type; | ||
+ | |||
+ | static const syntax_option_type normal; // Domyślna składnia RE języka Perl | ||
+ | static const syntax_option_type ECMAScript = normal; // i jej aliasy... | ||
+ | static const syntax_option_type JavaScript = normal; | ||
+ | static const syntax_option_type JScript = normal; | ||
+ | static const syntax_option_type perl = normal; | ||
+ | static const syntax_option_type basic; // Składnia POSIX podstawowa | ||
+ | static const syntax_option_type sed = basic; // i jej najpopularniejszy alias | ||
+ | static const syntax_option_type grep; // oraz odmiana. | ||
+ | static const syntax_option_type extended; // Składnia POSIX rozszerzona | ||
+ | static const syntax_option_type awk; // z dwiema odmianami. | ||
+ | static const syntax_option_type egrep; | ||
+ | static const syntax_option_type icase; // Ignorowanie wielkości liter | ||
+ | // w dopasowaniach | ||
+ | static const syntax_option_type nosubs; // Zaniechanie tworzenia podwyrażen | ||
+ | // w dopasowaniach | ||
+ | static const syntax_option_type optimize; // Optymalizacja poźniejszego dopasowywania | ||
+ | // nawet kosztem dłuższego tworzenia obiektu. | ||
+ | static const syntax_option_type collate; // Zakresy znaków przekazywane w nawiasach | ||
+ | // kwadratowych powinny uwzględniać lokalizm. | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | Dla wygody powyższe stałe zdefiniowane są także w obrębie szablonu klasy ''basic_regex''. | ||
+ | |||
+ | ==== Dopasowania ==== | ||
+ | |||
+ | Oprócz klasy reprezentującej wyrażenie regularne potrzebujemy także klasy przechowującej znalezione dopasowania w zadanym tekście. Służy do tego szablon klasy ''boost::match_results'' parametryzowany typem iteratora obsługującego sekwencję wejściową: | ||
+ | <code cpp> | ||
+ | template < class BidirectionalIterator, | ||
+ | class Allocator = std::allocator<sub_match<BidirectionalIterator> > | ||
+ | class match_results { /*...*/ }; | ||
+ | </code> | ||
+ | |||
+ | Podobnie jak wcześniej, zdefiniowano następujące typy dla wygodniejszego użycia: | ||
+ | <code cpp> | ||
+ | typedef match_results<const char*> cmatch; | ||
+ | typedef match_results<const wchar_t*> wcmatch; | ||
+ | typedef match_results<string::const_iterator> smatch; | ||
+ | typedef match_results<wstring::const_iterator> wsmatch; | ||
+ | </code> | ||
+ | |||
+ | Dopasowania to tak naprawdę kolekcje poddopasowań ''boost::sub_match'', do których dostęp mamy jedynie poprzez ''operator[]'' klasy ''boost::match_results''. | ||
===== Możliwości Boost.Regex ===== | ===== Możliwości Boost.Regex ===== | ||
+ | |||
+ | Same szablony klas ''boost::basic_regex'' oraz ''boost::match_results'' byłyby nic nie warte bez funkcji, które na nich operują realizując podstawowe przypadki użycia wyrażen regularnych. | ||
+ | |||
+ | Poniżej omówione szablony funkcji parametryzowane są różnymi elementami w zajemności od wersji (pominę dokładne deklaracje tych szablonów), ale zawsze jest to m.in. typ znaku. | ||
==== Dopasowywanie (matching) - regex_match ==== | ==== Dopasowywanie (matching) - regex_match ==== | ||
+ | |||
+ | <code cpp> | ||
+ | // Wersja bez zapisywania odnalezionych dopasowań. | ||
+ | template </* */> | ||
+ | bool regex_match(const basic_string</* */>& s, | ||
+ | const basic_regex</* */>& e, | ||
+ | match_flag_type flags = match_default); | ||
+ | |||
+ | // Wersja zapisująca odnalezione dopasowania w obiekcie klasy match_results. | ||
+ | template </* */> | ||
+ | bool regex_match(const basic_string</* */>& s, | ||
+ | match_results</* */>& m, | ||
+ | const basic_regex</* */>& e, | ||
+ | match_flag_type flags = match_default); | ||
+ | |||
+ | // Wersja główna, wołana także przez powyższe | ||
+ | template <class BidirectionalIterator, class Allocator, class charT, class traits> | ||
+ | bool regex_match(BidirectionalIterator first, BidirectionalIterator last, | ||
+ | match_results<BidirectionalIterator, Allocator>& m, | ||
+ | const basic_regex<charT, traits>& e, | ||
+ | match_flag_type flags = match_default); | ||
+ | </code> | ||
==== Przeszukiwanie (searching) - regex_search ==== | ==== Przeszukiwanie (searching) - regex_search ==== | ||
+ | |||
+ | <code cpp> | ||
+ | // Wersja bez zapisywania odnalezionych dopasowań. | ||
+ | template </* */> | ||
+ | bool regex_search(const basic_string</* */>& s, | ||
+ | const basic_regex</* */>& e, | ||
+ | match_flag_type flags = match_default); | ||
+ | |||
+ | // Wersja zapisująca odnalezione dopasowania w obiekcie klasy match_results. | ||
+ | template </* */> | ||
+ | bool regex_search(const basic_string</* */>& s, | ||
+ | match_results</* */>& m, | ||
+ | const basic_regex</* */>& e, | ||
+ | match_flag_type flags = match_default); | ||
+ | |||
+ | // Wesja główna, wołana także przez powyższze | ||
+ | template <class BidirectionalIterator, class Allocator, class charT, class traits> | ||
+ | bool regex_search(BidirectionalIterator first, | ||
+ | BidirectionalIterator last, | ||
+ | match_results<BidirectionalIterator, Allocator>& m, | ||
+ | const basic_regex<charT, traits>& e, | ||
+ | match_flag_type flags = match_default); | ||
+ | </code> | ||
==== Zastępowanie (replacing) - regex_replace ==== | ==== Zastępowanie (replacing) - regex_replace ==== | ||
+ | |||
+ | <code cpp> | ||
+ | // Wersja wygodna | ||
+ | template </* */> | ||
+ | basic_string</* */> regex_replace(const basic_string</* */>& s, | ||
+ | const basic_regex</* */>& e, | ||
+ | const basic_string</* */>& fmt, | ||
+ | match_flag_type flags = match_default); | ||
+ | |||
+ | // Wersja główna | ||
+ | template <class OutputIterator, class BidirectionalIterator, class traits, class charT> | ||
+ | OutputIterator regex_replace(OutputIterator out, | ||
+ | BidirectionalIterator first, | ||
+ | BidirectionalIterator last, | ||
+ | const basic_regex<charT, traits>& e, | ||
+ | const basic_string<charT>& fmt, | ||
+ | match_flag_type flags = match_default); | ||
+ | </code> | ||
==== Dzielenie (tokenizing) - regex_token_iterator ==== | ==== Dzielenie (tokenizing) - regex_token_iterator ==== | ||
+ | |||
+ | ''boost::regex_token_iterator'' to już nie funkcja a szablon klasy będącej adapterem iteratora. | ||
+ | |||
+ | <code cpp> | ||
+ | template <class BidirectionalIterator, | ||
+ | class charT = iterator_traits<BidirectionalIterator>::value_type, | ||
+ | class traits = regex_traits<charT> > | ||
+ | class regex_token_iterator { | ||
+ | /* ... */ | ||
+ | regex_token_iterator(BidirectionalIterator a, | ||
+ | BidirectionalIterator b, | ||
+ | const basic_regex<charT, traits>& re, | ||
+ | int submatch = 0, | ||
+ | match_flag_type m = match_default); | ||
+ | regex_token_iterator(BidirectionalIterator a, | ||
+ | BidirectionalIterator b, | ||
+ | const basic_regex<charT, traits>& re, | ||
+ | const std::vector<int>& submatches, | ||
+ | match_flag_type m = match_default); | ||
+ | /* ... */ | ||
+ | } | ||
+ | </code> | ||
===== Boost.Xpressive - relatywnie młody konkurent ===== | ===== Boost.Xpressive - relatywnie młody konkurent ===== | ||
Linia 133: | Linia 290: | ||
* obsługa tworzenia (obok dynamicznych, podobnych w zapisie do Boost.Regex) statycznych wyrażeń regularnych, podobnych w zapisie do Boost.Spirit, przez przeciążanie operatorów i zastosowanie techniki zwanej szablonami wyrażeniowymi (ang. //expression templates//) - owa statyczność daje wiele możliwości, m.in. kontrolę poprawności takiego wyrażenia już w czasie kompilacji, | * obsługa tworzenia (obok dynamicznych, podobnych w zapisie do Boost.Regex) statycznych wyrażeń regularnych, podobnych w zapisie do Boost.Spirit, przez przeciążanie operatorów i zastosowanie techniki zwanej szablonami wyrażeniowymi (ang. //expression templates//) - owa statyczność daje wiele możliwości, m.in. kontrolę poprawności takiego wyrażenia już w czasie kompilacji, | ||
* możliwość mieszania statycznych i dynamicznych wyrażeń regularnych, | * możliwość mieszania statycznych i dynamicznych wyrażeń regularnych, | ||
- | * możliwość osadzania wyrażeń regularnych poprzez referencje. | + | * możliwość osadzania wyrażeń regularnych w sobie poprzez referencje. |
===== Kiedy co używać? ===== | ===== Kiedy co używać? ===== | ||
Linia 145: | Linia 302: | ||
^ Statyczność - brak nowych deklaracji w trakcie działania | tak | tak | tak | | ^ Statyczność - brak nowych deklaracji w trakcie działania | tak | tak | tak | | ||
^ Wyczerpujące przeszukiwanie z nawrotami | tak | - | tak | | ^ Wyczerpujące przeszukiwanie z nawrotami | tak | - | tak | | ||
+ | |||
+ | Małego komentarza może wymagać wyczerpujące przeszukiwanie z nawrotami (ang. //exhaustive backtracking//). Chodzi o to, że przymiotnik "wyczerpujące" nie opisuje samego procesu przeszukiwania z nawrotami, które w istocie jest wyczerpujące, a konsekwencję w jego stosowaniu, która jest wymagana od wyrażeń regularnych. Boost.Spirit stosuje backtracking, jednak gdy znajduje jakieś częściowe dopasowanie, to nie "cofa się", żeby możliwe było zrealizowanie reszty reguły, tylko uznaje, że dopasowanie się nie powiodło. Dlatego poniższy przykład nigdy nie zadziała: | ||
+ | <code cpp> | ||
+ | *chlit_p('a') >> chlit_p('a'); | ||
+ | </code> |