To jest stara wersja strony!
Wersja robocza
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 AWK, grep czy sed) i w interpretowanych językach jak PHP, Perl, 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 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 Boost.Regex, której autorem jest John Maddock.
Oczywiście nie jest to jedyna biblioteka implementująca wyrażenia regularne w języku C++, ale tym razem PCRE, QRegExp (część Qt) i inne rozwiązania zostaną świadomie przemilczane, choć kiedyś na pewno jeszcze o nich wspomnę…
Mamy do zrealizowania mały program, który na wejściu przyjmuje maile, a na wyjściu podaje ich tematy, przy czym ewentualny przedrostek Re: nie należy do wyświetlanego tematu.
Tak mogłoby wyglądać rozwiązanie imperatywne:
std::string line; while (std::getline(std::cin, line)) { if (line.compare(0, 9, "Subject: ") == 0) { std::size_t offset = 9; if (line.compare(offset, 4, "Re: ")) offset += 4; std::cout << line.substr(offset); } }
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:
std::string line; // Asercja ^ jest zbędna gdy wyrażenie jest używane w funkcji boost::regex_match, // ale jej obecność pozwala także na ewentualne zastosowanie funkcji // boost::regex_search. Wyjaśnienie pojawi się w dalszej części... boost::regex pat("^Subject: (Re: )?(.*)"); boost::smatch what; while (std::getline(std::cin, line)) { if (boost::regex_match(line, what, pat)) std::cout << what[2]; }
Rozwiązanie deklaratywne jest lepsze, ponieważ:
przez co jest też łatwiejsze w utrzymaniu.
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…
Do wyboru mamy trzy główne składnie wyrażeń regularnych:
egrep czy awk),grep czy emacs).Omówię pokrótce tylko najważniejsze elementy domyślnej składni.
Zapis ^ Znaczenie
. | dowolny znak
^ | początek linii
$ | koniec linii
( ) | początek i koniec znaczonego grupowania
(?: ) | początek i koniec nieznaczonego grupowania
* | powtórzenie atomu zero lub więcej razy
+ | powtórzenie atomu jeden lub więcej razy
? | powtórzenie atomu zero lub jeden raz
{n} | powtórzenie atomu dokładnie n razy
{n,} | powtórzenie atomu n lub więcej razy
{n,m} | powtórzenie atomu od n do m razy
| | alternatywa
[znaki^odrzuty] | dopuszczalny znaki i niedopuszczalne odrzuty
\d <=> [[:digit:]] | cyfra
\l <=> [[:lower:]] | mała litera
\s <=> [[:space:]] | spacja
\u <=> [[:upper:]] | duża litera
\w <=> [[:word:]] | znak mogący być elementem wyrazu
\D <=> [^[:digit:]] | nie cyfra
\L <=> [^[:lower:]] | nie mała litera
\S <=> [^[:space:]] | nie spacja
\U <=> [^[:upper:]] | nie duża litera
\W <=> [^[:word:]] | nie znak mogący być elementem wyrazu
(?#komentarz) | ignorowany (komentarz)
(?:wzorzec) | "zjada" 0 znaków tylko jeżeli wzorzec się zgadza
(?!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,}?.
Na koniec nie sposób nie wspomnieć o odwołaniach wstecznych (ang. backreferences), które stosujemy poprzez poprzedzenie znakiem backslash (\) numeru grupy znaczonej,
Do skorzystania z dobrodziejstw Boost.Regex niezbędne jest dołączenie pliku nagłówkowego:
#include <boost/regex.hpp>
Wyrażeniu regularnemu odpowiada szablon klasy boost::basic_regex, który parametryzowany jest m.in. typem znaku. Metody tego szablonu to:
bool empty() const; // zwraca true jeżeli w obiekt nie zawiera żadnego poprawnego wyrażenia regularnego 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
Dla wygody, wewnątrz przestrzeni boost zdefiniowane są nasŧepujące typy:
typedef basic_regex<char> regex; typedef basic_regex<wchar_t> wregex;
Ważne innowacje:
| Regex | Spirit | Xpressive | |
|---|---|---|---|
| Dopasowywanie wzorców ad-hoc, języki regularne | tak | tak | tak |
| Ustrukturalizowane parsowanie, gramatyki bezkontekstowe | - | tak | tak |
| Manipulowanie tekstem | tak | tak | tak |
| Akcje semantyczne, manipulowanie stanem programu | - | tak | - |
| Dynamiczność - nowe deklaracje w trakcie działania | tak | - | tak |
| Statyczność - brak nowych deklaracji w trakcie działania | tak | tak | tak |
| Wyczerpujące przeszukiwanie z nawrotami | tak | - | tak |