Świat Parserów
Total Page:16
File Type:pdf, Size:1020Kb
Świat parserów Krzysztof Leszczyński Polska Grupa Użytkowników Linuxa [email protected] Streszczenie Pisząc oprogramowanie, stykamy się bardzo często z potrzebą analizy języków programowania. Niniejsza praca ma pomóc czytelnikowi zorientować się w gę- stwinie różnych mniej lub bardziej rozbudowanych generatorów parserów. 1. Wprowadzenie Zdecydowana większość używanych przez nas języków mieści się w klasie języków generowanych Języków programowania jest dzisiaj dużo, żeby nie gramatykami bezkontekstowymi. Czym innym jest powiedzieć za dużo. Ankieta, przeprowadzona wśród jednak zdefiniowanie gramatyki, a czym innym napi- koleżanek i kolegów zajmujących się programowa- sanie do niej parsera. A parsery musimy pisać dosyć niem, wykazała, że programiści twierdzą, że posłu- często. W przypadku języków opisu danych struktu- gują się przeciętnie pięcioma językami. Okazuje się, ralnych, pewną nadzieję dawało rozpowszechnienie że jest to przekonanie błędne. Indagowani przeze się języka XML. Nadzieja okazała się złudną, gdyż mnie, przyznają, że co prawda znają (lepiej lub go- XML naprawdę niewiele pomógł. Zamiast analizo- rzej) języki „podstawowe”: C, C++, jeden z P* wać mniej lub bardziej swobodny tekst, musimy ana- (Perl, Python, PHP), SQL, to jeszcze posługują się lizować tekst XML-owy pokrojony na leksemy XML- wieloma innymi językami, o których istnieniu nie owe. Analizy jest niewiele mniej niż w czasach przed- wiedzą; podobnie jak nie wiemy, że prawie cały czas XML-owych. mówimy prozą. Gdybyśmy policzyli liczbę rodzajów plików, które edytujemy, to różnych języków doli- Byłoby najlepiej, gdybyśmy mogli opisać for- czymy się prawdopodobnie co najmniej dwudziestu. malną gramatykę i opisać reguły przetwarzania okre- Postarajmy się wobec tego zdefiniować, co w ogóle ślonych struktur. Do tego właśnie służą generatory uważamy za język programowania. Bardzo niefor- parserów. malna definicja brzmi: językiem nazywamy sposób Przed rozpoczęciem poszukiwania odpowiednie- zapisu pewnego algorytmu, bądź chociażby pewnych go narzędzia należy odpowiedzieć na kilka pytań: danych, w taki sposób, że jest on możliwy do ręcznej 1. W jakim języku (językach) ma być wygenero- edycji przy pomocy edytora tekstowego. Za języki wany docelowy parser. danych (choć nie za języki programowania) uzna- 2. Jak bardzo nam zależy na bezpieczeństwie, w libyśmy także pliki konfiguracyjne niektórych pro- szczególności czy ufamy osobie piszącej skrypt, gramów, na przykład /etc/named.conf. Z drugiej który ma być interpretowany. strony, formatu kompresji ZIP nie uznamy za język programowania, gdyż nie wyobrażam sobie potrzeby 3. Czy zależy nam na prędkości analizy. uczenia się algorytmów i zapisu algorytmu Deflate Odpowiedź na pytanie 2 jest bardzo istotna, w innych celach niż sama analiza algorytmu. szczególnie jeśli językiem docelowym ma być jakiś Za język nie uznamy też formatu /etc/passwd język interpretowany. Na przykład, bardzo dużo pro- lub /etc/shadow, gdyż są to typowi przedstawiciele gramów pisanych w Perlu ma pliki konfiguracyjne plików CSV (comma-separated values) i, oczywiście, pisane również w Perlu, na ogół jako ciąg przypisań. analiza takich plików jest zbyt prosta, żeby stosować Jest to bardzo wygodne pod warunkiem, że wierzy- do nich techniki analizy. my w czystość intencji użytkownika – nikt nie za- broni w pliku konfiguracyjnych uruchomić dowolnie 2. Gramatyki szatańskiego kodu! Dlatego sporo programów, któ- Bardzo często pliki, takie jak wymieniony już /etc/ re działają w trybie użytkownika posługuje się tą named.conf, musimy analizować z poziomu pisa- metodą – klasycznym przykładem jest tu program nych przez nas programów, wówczas napisanie anali- mirror. Z kolei programy uruchamiane w trybie zatora może kosztować nas wiele czasu, a otrzymana daemon, unikają takich metod. Stare wersje progra- procedura nie musi być wolna od błędów. mu SpamAssassin posługiwały się tą metodą. Nowe XIII Ogólnopolska Konferencja Polskiej Grupy Użytkowników Systemu TEX 47 Polska Grupa Użytkowników Systemu TEX, 2005 (http://www.GUST.org.pl) Krzysztof Leszczyński mają własny, nieperlowy opis plików, do tego stop- Na szczęście gramatyki niebezkontekstowe (non-con- nia, że nawet nie pozwalają stosować wyrażeń re- text-free grammars), nie występują często w prak- gularnych, gdyż Perl pozwala w regułach stosować tycznych zastosowaniach i nie będę ich w ogóle roz- dowolny kod – również ten o bardzo kosmatych skut- patrywał. Generatory parserów dla takich grama- kach. tyk są przeważnie drogie w użyciu i, poza bardzo Nawet pomijając kwestie bezpieczeństwa, „pliki specjalnymi przypadkami, nieprzydatne. Jedynym, konfiguracyjne” pisane w tym samym języku, w któ- potwierdzającym regułę, wyjątkiem jest gramatyka rym jest główny program, są czasami zbyt silne dla wyrażeń regularnych pcre, która nie jest, w ogól- zwykłych użytkowników. Przy programach bardziej nym wypadku, bezkontekstowa, a do której istnieją rozpowszechnionych lepiej czasami ograniczyć moż- szybkie parsery. liwości, gdyż potem będzie bardzo trudno wprowa- Parsery działają na leksemach. Leksemem na- dzać zmiany. zywamy najmniejszą niepodzielną część języka. W Na pytanie 3, w przypadku analizatorów krót- popularnych językach programowania, leksemami są kich plików konfiguracyjnych odpowiedzią z pewno- na przykład identyfikatory, liczby całkowite, liczby ścią będzie „nie”. Nie jest dla nas istotne, czy plik rzeczywiste, ale również tak skomplikowane twory się będzie się przetwarzał jedną tysięczną czy jedną jak stałe napisowe. Oczywiście, co jest a co nie jest dziesięciotysięczną sekundy; jeśli używamy analiza- leksemem zależy od konkretnego języka. W językach tora leksykalnego jako części analizatora pakietów C lub Pascal nie istnieje identyfikator 123abc – żad- w routerze, to jego prędkość może mieć dla nas duże na zmienna, ani funkcja nie może mieć takiej nazwy. znaczenie. Wynika to z zasady, że identyfikatory w tych języ- kach nie mogą mieć nazw zaczynających się od cyfry. 3. Gramatyki bezkontekstowe Z drugiej strony, w Lispie identyfikatorem może być Poniższe definicje będą bardzo uproszczone, ale ilość niemal dowolny ciąg znaków. miejsca na ten referat jest dosyć ograniczona, po- 4. Leksery za tym chcemy się skupić na istniejących rozwią- zaniach. Czytelników pragnących zapoznać się z do- Parsery żywią się leksemami, których musimy do- kładnymi informacjami na temat gramatyk, pozwolę starczyć. Leksemy na ogół składamy z jeszcze mniej- sobie odesłać do literatury [1]. szych elementów, nazywanych znakami. Zbiór zna- W skrócie: gramatykę zapisujemy w postaci ków nazywamy alfabetem. Alfabetem może na przy- tzw. reguł produkcji. Każda gramatyka zawiera sym- kład ASCII lub ISO-8859-2, czyli coś co nam się naj- bol startowy (na przykład program) oraz zbiór reguł, bardziej kojarzy z alfabetem. Może też być UTF-8, które pozwalają złożyć program z innych elementów, który się charakteryzuje tym, że znaki nie mają rów- aż do elementów terminalnych, czyli leksemów, ta- nej długości w bitach. Dla dekompresora gunzip al- kich jak liczba albo „+”. Na przykład, jeśli program fabetem będą pojedyncze bity. może być pusty lub zawierać ciąg instrukcji zakoń- Naszym zadaniem jest zbudować leksem z ciągu czonych średnikiem, to możemy napisać takie regu- znaków, według określonych reguł – na ogół według ły: pewnego zbioru wyrażeń regułowych. W przypad- ku prostych reguł możemy sami napisać kod, któ- program ::= . /* program może być pusty */ ry podzieli ciąg znaków na odpowiednie leksemy, /* definicja rekursywna */ ewentualnie możemy się posiłkować biblioteką pcre program ::= program instrukcja . (Perl-compatible regular expression library), dostęp- oczywiście trzeba napisać co to jest instrukcja, jeśli ną obecne każdej dużej dystrybucji Linuxa. instrukcją jest wyrażenie zakończone średnikiem, to Tam gdzie mamy bardziej skomplikowane regu- definicja może być taka ły, lepiej jest użyć prawdziwych lekserów. Do celów instrukcja ::= wyrażenie ’;’ . testowych zdefiniujmy sobie mały C-podobny język. Język będzie się składał z: Gramatykę zawierającą po lewych stronach produk- • liczb całkowitych, zdefiniowanych jako ciągi cji jedynie po jednym elemencie nieterminalnym, cyfr, pomijamy dopuszczalność zakresów, nazywamy gramatyką bezkontekstową. Gramatyka, • identyfikatorów, takich jak w C lub Pascalu, w której byśmy inaczej interpretowali produkcje • znaków operacji: „+”, „-”, „*”, „/”, w zależności od kontekstu, nazywamy niebezkontek- • ciągi „/*ciąg znaków*/” są traktowane jak stową. Przykładem takiej produkcji jest wyrażenie, komentarze i nie generują leksemów. po którego obu stronach stoją wyrażenia identyczne • słowa kluczowe: for, if, else. Słowa kluczowe, wyr1 wyr2 wyr1 ::= ... nie mogą być identyfikatorami. 48 Bachotek, 30 kwietnia – 3 maja 2005 Polska Grupa Użytkowników Systemu TEX, 2005 (http://www.GUST.org.pl) Świat parserów Wyposażeni w powyższe reguły możemy poznać nie wiemy co to za liczba! Wartość leksemu (w po- pierwszego i zarazem najstarszego z przedstawicieli staci tekstu), jest zachowana w zmiennej globalnej, lekserów. zdefiniowanej jako extern char *yytext; 4.1. Lex do której możemy zaglądać pomiędzy wywołaniami Program Lex [2] jest związany z najwcześniejszymi funkcji yy lex(). systemami UNIX, był dostępny już na PDP-11. A co mamy zrobić, jeśli chcemy mieć w jed- Produkuje lekser w języku C. Obecnie głównie nym programie kilka lekserów? W przypadku ory- używamy jego wersji GNU: flex [3]. ginalnego Lexa mamy kłopot i raczej bez przetwa- Plik dla programu (f)lex dzieli się na trzy rzania pliku wynikowego się nie obejdzie, a i wtedy części oddzielone