Usprawnij pracę z wyrażeniami regularnymi

Ah, dawno nic nie wrzucałem ;). Chciałbym się dziś podzielić dwoma poradami związanymi z regex'ami (wyrażeniami regularnymi). Komentarze w wyrażeniach regularnych Szybkie pytanie: co dopasowuje poniższe wyrażenie regularne?
preg_match('/.*(?=.{6,})(?=.*\d)(?=.*[a-zA-Z]).*/', $something);
Myślę, że nawet wprawionemu w bojach programiście przetworzenie tego wyrażenia zajmie krótką chwilę. W praktyce wyrażenia regularne szybko robią się skomplikowane, ekstremalnie trudne w czytaniu i jeszcze trudniejsze w debugowaniu. Można jednak te problemy rozwiązać za pomocą odrobiny formatowania i dodania komentarzy.
preg_match('/^
            .*              # Match any number of characters...
            (?=.{6,})       # ... AND match at least 6 characters (lookahead) ...
            (?=.*\d)        # ... AND match one digit after any number of characters (lookahead) ...
            (?=.*[a-zA-Z])  # ... AND match one letter after any number of characters (lookahead) ...
            .*              # ... AND allow any number of characters later.
            $/x', $password);
Prawda, że czytelniej? Modyfikator x powoduje, że wszystkie niewyescape'owane białe znaki zostaną zignorowane, a wszystko od znaku # do końca linii zostanie potraktowane jako komentarz i zignorowane. O możliwości komentowania wyrażeń regularnych dowiedziałem się z artykułu Advanced Regular Expression Tips and Techniques. Zachęcam do jego przeczytania - jest tam więcej ciekawych porad, m.in. na temat callbacków czy nadawania nazw grupom. Debugger do regexpów Istnieje świetne narzędzie do analizy i debugowania regexpów - Debuggex. Wyświetla on strukturę wyrażenia graficznie, pozwalając na analizę krok po kroku. Spójrzmy na przykład z poprzedniej części tego posta: Prawdziwa potęga tego narzędzia objawia się przy bardzo złożonych wyrażeniach regularnych, jak na przykład:
^(?:(?:ht|f)tp(?:s?)\:\/\/|~\/|\/)?(?:\w+:\w+@)?(?:(?:[-\w]+\.)+(?:com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum|travel|[a-z]{2}))(?::[\d]{1,5})?(?:(?:(?:\/(?:[-\w~!$+|.,=]|%[a-f\d]{2})+)+|\/)+|\?|#)?(?:(?:\?(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)(?:&(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)*)*(?:#(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)?$
Polecam skopiowanie powyższego wyrażenia do Debuggexa. Z jednej strony widać wyraźnie, jak ten regexp działa i do czego służy; z drugiej strony, sekcja Some random matches pokazuje, jak dziwne rzeczy przejdą przez nie poprawnie. Uwaga o wydajności wyrażeń regularnych Jak zapewne wielu programistów, którzy swą przygodę zaczęli od programowania gier, jestem sceptycznie nastawiony do wszelkich rzeczy pracujących na stringach. W szczególności, regexpy mogą na pierwszy rzut oka wydawać się wysoce niewydajne - ot skomplikowane dopasowanie tekstu zapisane w formie tekstu. Z pewnością napisanie tego w formie normalnego kodu - acz dłuższe - musi być wydajniejsze, prawda? Otóż nie. Wyrażenia regularne są tak na prawdę programem - są (bardzo skondensowanym) opisem maszyny stanów, przez którą dopasowywany tekst stopniowo przechodzi, i jeśli uda się doprowadzić tę maszynę do stanu terminalnego, to dopasowanie jest uznawane za udane. Co to dokładnie znaczy? Polecam przyglądnąć się jeszcze raz rysunkom, które tworzy Debuggex - to dokładnie struktura tejże maszyny stanów. Taki system reprezentowany jest w programie w sposób bardzo wydajny; prawdopodobnie dużo wydajniejszy od naiwnego parsera, którego moglibyśmy napisać w niewielkiej liczbie linii kodu. Co więcej, niektóre języki programowania (jak na przykład Common Lisp lub Java) pozwalają prekompilować wyrażenia regularne - jeżeli spodziewamy się, że dane wyrażenie regularne będzie używane wielokrotnie w czasie działania programu, to możemy zbudować maszynę stanów tylko raz (w przeciwieństwie do tworzenia jej na nowo za każdym razem, gdy chcemy dopasować jakiś napis), a następnie używać jej wszędzie tam, gdzie potrzebujemy korzystać z danego wyrażenia regularnego. Są nawet języki, które kompilują regexpy automatycznie.