Ś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++, 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 znakami %%: mamy kłopot, gdyż Lex zbyt chętnie używa zmien- definicje nych globalnych. flex pozwala wygenerować klasy %% C++, dla każdego leksera oddzielnie. Użycie metod reguły takich klas nie powoduje efektów ubocznych. Na ko- %% niec jeszcze usprawiedliwienie, w przykładzie nie po- dodatkowy kod kazałem, jak rozwinąć skaner, tak aby obejmował część trzecia jest opcjonalna, a pierwsza musi wy- skanowanie komentarzy. Taki skaner by zajął więcej stępować, ale może być pusta. Reguły są w postaci niż 60 wierszy, czyli dwie strony, a co najważniejsze, jest on dokładnie opisany jako przykład w manualu wzorzec akcja, flex(1), więc przepisywanie go tutaj jest niepotrzeb- gdzie wzorzec jest rozszerzonym wyrażeniem regu- ne. Niemniej, namawiam do zapoznania się z tym larnym (regular expression), zaś akcja jest fragmen- przykładem, gdyż wprowadza on kilka pojęć, takich tem w C, który jest wykonywany jeśli wzorzec jest jak warunki startowe leksera, których w tym prze- spełniony. glądowym tekście nie omawiam. Sprawdźmy jak będzie wyglądać lekser dla na- szego przykładowego małego języka, pomińmy na ra- 4.2. re2c zie skanowanie komentarzy. Moim ulubionym lekserem jest re2c [4]. Również %{ generuje kod w C/C++, niemniej kod jest czystszy – #include "leksdef.h" %} da się przeczytać oczami programisty, w odróżnieniu %% od kodu lex/flex, który jawi się jako kilkaset "for" return LEKSEM_FOR; liczb całkowitych wraz z tajemniczymi indeksami "if" return LEKSEM_IF; przeskakującymi po nich. "else" return LEKSEM_ELSE; Pisząc kod w re2c, musimy zbudować szkie- [0-9]+ return LEKSEM_LICZBA; let programu, taki szkielet dla naszego języka został [A-Za-z_][A-Za-z_0-9]* return LEKSEM_ID; przedstawiony na przykładzie 1. Struktura struct %% Leksem, opisana w wierszu 2 została przeze mnie W pierwszej części widzimy jedną instrukcję wymyślona ad hoc dla potrzeb przykładu. Jedynie #include, drugiej części w zasadzie nie trzeba tłu- wiersze 5–9 są narzucone przez program, gdyż mu- maczyć, jest oczywista. Dla każdego wyrażenia regu- simy jakoś zdefiniować symbole YYCTYPE, YYCURSOR, larnego zwracamy rodzaj leksemu. Możemy ten plik YYLIMIT, YYMARKER oraz YYFILL, które są używane skompilować poleceniem przez generator. Symbole te mogą być zdefiniowane flex przyklad1.lex lokalnie, dlatego nie zaśmiecają przestrzeni nazw. W odróżnieniu od Lexa, re2c nie próbuje czytać Otrzymamy wówczas plik wynikowy o charaktery- standardowego wejścia, tylko wymaga zdefiniowania stycznej nazwie lex.yy.c. Plik ten możemy skom- funkcji YYFILL, dostarczającej znaki alfabetu. W na- pilować, o ile w pliku leksdef.h dostarczymy odpo- szym przykładzie ta funkcja jest pusta, gdyż funkcja wiednich definicji leksemów, na przykład w postaci Leksuj () zakłada, dla prostoty, że całe wejście jest enum {LEKSEM_FOR, LEKSEM_IF, dostarczone jednorazowo w napisie p. W moim od- LEKSEM_ELSE,LEKSEM_LICZBA,LEKSEM_ID}; czuciu jest to zachowanie bardziej poprawne, gdyż Plik wynikowy zawiera funkcję yy lex(), która wy- funkcję dostarczającą znaków ze strumienia buduje woływana w pętli, zwraca kolejne leksemy, zgodnie się banalnie – zwykła funkcja fread() załatwia spra- z akcjami. wę; jeśli zaś nie korzystamy bezpośrednio z funkcji Same leksemy jednak nam nie wystarczą, co plikowych, to re2c nie wymaga, w odróżnieniu od z tego, że wiemy, że lekser natrafił na liczbę, skoro lexa całego wielkiego .

XIII Ogólnopolska Konferencja Polskiej Grupy Użytkowników Systemu TEX 49

Polska Grupa Użytkowników Systemu TEX, 2005 (http://www.GUST.org.pl) Krzysztof Leszczyński

#include "leksdef.h" jego kompilacji. Ceną za to jest znacznie mniejsza struct Leksem { int leksem; char *pozycja;}; prędkość od lekserów w C. int Leksuj(char *p,struct Leksem *l){ char *q; 5. Parsery #define YYCTYPE char #define YYCURSOR p Gramatyki bezkontekstowe dzielą się na wiele pod- #define YYLIMIT p rodzajów. Na szczęście większość języków jest opisy- #define YYMARKER q walna gramatykami typu LR(1) lub LL(1). Dla gra- #define YYFILL(n) matyk LR(1), a konkretniej LALR(1) mamy parsery #define R(leks) {l->leksem=leks; \ Yacc, Bison oraz , dla gramatyk LL* mamy l->pozycja=p; return 0;} do dyspozycji ANTLR [7]. start: /*!re2c 5.1. Yacc i Bison "if" {R(LEKSEM_IF);} "else" {R(LEKSEM_ELSE);} Yacc (Yet Another Compiler-Compiler) jest bar- "for" {R(LEKSEM_FOR);} dzo sędziwym programem napisanym dla systemów [0-9]+ {R(LEKSEM_LICZBA);} UNIX. Jest dostępny z różnymi komercyjnymi sys- [A-Za-z_][A-Za-z_0-9]* {R(LEKSEM_ID);} temami, na przykład z SunOS lub Solarisem. Jego licencja nie pozwala na swobodne rozpowszechnia- "/*" {goto scan_comment;} nie, za to możemy zupełnie swobodnie dysponować */ kodem wygenerowanym przez Yacca. Istnieją dwie goto start; wolne wersje tego programu: byacc, czyli Berkeley Yacc, na licencji public domain oraz Bison na licen- scan_comment: cji GPL. W przypadku używania kodu wygenero- /*!re2c wanego przez Bisona, kod wynikowy albo sam mu- "*/" {goto start;} [\000-\377] {goto scan_comment;} si być na licencji GPL albo musi spełnić dosyć re- */ strykcyjne wymagania. To może się zemścić, nawet } jeśli nie mamy zamiaru pisać oprogramowania ko- mercyjnego z kodem zamkniętym. Oto prawdziwa Przykład 1: Skaner re2c dla przykładowego historia: W połowie lat 90-tych, do produkcji jednej minijęzyka ze stref gry typu AberMUD Northern Lights uży- to Bisona. Wtedy Bison miał bardziej restrykcyjną licencję, mógł być użyty wyłącznie do produkcji wol- Jeśli chcemy pisać nasze parsery w C++, to za- nego oprogramowania. Oczywiście, gracze nie mogli miast tak prostego nagłówka, możemy stworzyć kla- widzieć kodu źródłowego strefy, bo znaliby rozwią- sę i wypełnić odpowiednie miejsca kodu metod pseu- zanie. Sam kod AberMUD-a jest dostępny i otwarty, dokomentarzami /*!re2c...*/ – kod produkowany jedynie strefy są zamknięte z oczywistych powodów. przez re2c jest odporny na C++. Niemniej, jeden z graczy zażądał jego udostępnienia Zwolennicy programowania bez skoków, zapew- powołując się na licencję Bisona. Zrobiła się awan- ne skrzywią się widząc mnogość instrukcji goto. tura, która oparła się o samego RMS-a. Jej efek- W pliku wynikowym, znajdziemy ich jeszcze więcej, tem było paniczne przepisywanie kodu na byacc-a i w dużym lekserze nawet kilkaset. Niestety, leksery miesiącami trwające dysputy czy kod przez pomyłkę mają taką naturę, że chętnie z goto korzystają. Są zarażony GPL, może być uzdrowiony poprzez kom- oczywiście takie, które tego nie robią, ale za to albo pilację innym narzędziem.1 generują kilkadziesiąt razy zagnieżdżone pętle i wa- Plik w Yaccu lub Bisonie wygląda bardzo po- runki, albo pracują na wielkich tablicach i zamiast dobnie jak plik w Lex. Podobnie są trzy strefy goto wykonują de facto pseudokod. Na szczęście owe rozdzielone znakami %%. W przykładzie 2 znajduje skoki są ograniczone do jednej funkcji, więc nie psują się przykład czterodziałaniowego kalkulatora, wraz nam reszty programu. z kolejnością działań. Gramatyka nie wymagałaby tłumaczenia, gdyby nie dziwna reguła 4.3. Inne leksery skladnik: skladnik ’-’ skladnik { $$=$1-$2;} Lekserów jest naprawdę dużo i nie sposób ich choć- widzimy tu rekurencje, która nie wiadomo w któ- by pobieżnie wymienić. W Perlu możemy używać rą stronę ma działać. Czy wyrażenie 1 − 2 − 3 ma modułu Parse::Lex oraz Parse:YYLex, ten ostatni 1 Autor strefy stwierdził, że dyskusja jest bezprzedmioto- jest kompatybilny z Lexem. Reguły możemy usta- wa, bo poprzedni kod (bisonowy) zgubił, nie ma kopii zapa- lać w czasie uruchamiania programu, a nie w czasie sowej i nie jest go w stanie odzyskać.

50 Bachotek, 30 kwietnia – 3 maja 2005

Polska Grupa Użytkowników Systemu TEX, 2005 (http://www.GUST.org.pl) Świat parserów

%{ łamy parser tracimy kontrolę, aż do momentu kie- #define YYSTYPE double dy Bison przeanalizuje cały kod, bądź zgłosi błąd #include syntaktyczny. To może nie być problem w przypad- %} %token NUM ku przetwarzania wsadowego, ale stwarza trudności %left ’-’ ’+’ w przypadku programów interakcyjnych kiedy par- %left ’*’ ’/’ ser ma działać przez cały czas działania programu, %% a program musi mieć sterowanie – choćby po to, że- input: /* puste */ by je oddać funkcji gtk main(). | input wiersz Jeśli nie chcemy stosować wielowątkowości – ; rzecz z parserami generowanymi przez yacca sama wiersz: ’\n’ w sobie niebezpieczna – musimy użyć innego gene- | wyr ’\n’ {printf ("wynik\t%.10g\n", $1);} ratora parserów. ; Lemon[5] jest generatorem parserów gramatyk wyr: ’(’ wyr ’)’ {$$ = $1;} LALR(1), o podejściu zupełnie różnym od rodziny wyr: skladnik {$$ = $1;} Yacc: skladnik: czynnik {$$=$1;} • używa innej gramatyki, która jest bardziej od- skladnik: skladnik ’+’ skladnik { $$=$1+$2;} porna na błędy; skladnik: skladnik ’-’ skladnik { $$=$1-$2;} • parsery są wielobieżne, wielowejściowe (reen- czynnik: NUM { $$ = $1; } czynnik: czynnik ’*’ czynnik {$$ = $1*$2 } trant) oraz bezpieczne w środowisku wielowąt- czynnik: czynnik ’/’ czynnik {$$ = $1*$2 } kowym (thread safe); %% • leksemy użyte w wyrażeniach są starannie nisz- czone, przez co kod nie cieknie, co się niestety Przykład 2: Parser w języku Bison (Yacc) dla nagminnie zdarza parserom. kalkulatora czterodziałaniowego Lemon jest również gruntownie przetestowany przez projekt SQLite [6]. Generator jest dostępny na zasa- − − − być zinterpretowane jako (1 2) 3= 4 czy jako dach public domain, co jest wygodne, gdyż kod wy- − − 1 (2 3) = 2? Na szczęście reguła %left ’+’ ’-’ generowany Lemonem nie podlega żadnym restryk- mówi, że ’-’ wiąże w lewo, czyli tak jakbyśmy się cjom. spodziewali. Podobnie wiąże ’+’, przy czym akurat Na ilustracji 3 znajduje się przykładowy kalku- to nie ma w tym przypadku większego znaczenia. lator czterodziałaniowy. W porównaniu z parserami Gdybyśmy zechcieli wprowadzić operator potęgowa- Bisona widać, że nie stosujemy tajemniczej notacji nia ’^’, który na ogół wiąże w prawo, czyli 2^3^4 34 3 4 $$=$1+$2, tylko mamy zmienne z normalnymi na- oznacza 2 , a nie (2 ) , to musielibyśmy dopisać de- zwami. W dodatku, wszelkie symbole nieterminalne klarację %right ’^’. niosące informację (takie jak czynnik) muszą mieć Ten prosty przykład pokazuje, że co prawda przypisane nazwy, zaś wszelkie symbole terminalne gramatyki w Bisonie wyglądają zrozumiale, to jed- nie niosące informacji – takie jak leksem LEWYNAWIAS nak trzeba uważać na pułapki, na szczęście więk- nie mogą mieć nazw. szość z nich Bison wykryje na etapie kompilacji. Te cechy powodują, że programy w Lemonie są Yacc lubi pracować z programem lex/flex. Nie dużo czytelniejsze od swoich bisonowych odpowied- jest łatwo go przekonać do pracy z innymi lekserami, ników, są też bardziej odporne na błędy i wygodniej- np. re2c lub innym, zrobionym ręcznie. Powodem sze w użyciu. Dlatego, moim zdaniem, nie ma sensu jest fakt, że yacc zakłada, że istnieje funkcja yylex (), używać w nowych projektach parserów bison/yacc. która zwraca leksemy i która umieszcza wartość Po prostu, większe programy są w yaccu mało leksemu w zmiennych yyval i yytext. czytelne, a notacja $liczba jest w przypadku bar- W przypadku Bisona jest lepiej, jeśli umieścimy dziej skomplikowanych wyrażeń po prostu nieczy- deklarację %pure_parser, to nasz parser będzie czy- telna i uderza swoją byleyaccością. sty i nie będzie zależał od żadnych zmiennych global- nych, za to funkcja yylex() będzie dostawać zmienne 5.3. ANTLR – przedstawiciel parserów LL lokalne yyval jako argument przez referencję. ANother Tool For Language Recognition [7] jest ostatnim z omawianych generatorów parserów. Na- 5.2. Lemon pisany został przez prof. Terence’a Parra. W odróż- No właśnie! Bison sam z siebie chce wołać lekser nieniu od poprzednich generatorów, używa grama- tyle razy ile potrzebuje. W momencie kiedy wywo- tyk typu LL, a nie LR. Nie da się ukryć wrażenia,

XIII Ogólnopolska Konferencja Polskiej Grupy Użytkowników Systemu TEX 51

Polska Grupa Użytkowników Systemu TEX, 2005 (http://www.GUST.org.pl) Krzysztof Leszczyński

%token_type {double} %token_destructor {void(0);} %include {#include "toy.h"} input ::= lista_instr . lista_instr ::= . lista_instr ::= instrukcja . lista_instr ::= lista_instr SEMICOLON instrukcja . instrukcja ::= . instrukcja ::= wyr(E). {print("%g\n",E);} %type wyr {double} %type skladnik {double} wyr(T) ::= skladnik(S). { T=S; } wyr(T) ::= wyr(TA) PLUS skladnik(S). { T=TA+S; } wyr(T) ::= wyr(TA) MINUS skladnik(S). { T=TA-S; } %type czynnik {double} skladnik(S) ::= czynnik(C). {S=C;} skladnik(S) ::= skladnik(SA) RAZY czynnik(C). {S=SA*C;} skladnik(S) ::= skladnik(SA) DZIELI czynnik(C). {S=SA/C;} czynnik(C) ::= LICZBA(L). {C=L;} czynnik(C) ::= LEWYNAWIAS wyr(T) PRAWYNAWIAS. {C=T} Przykład 3: Parser w języku Lemon dla kalkulatora czterodziałaniowego

że autor stara się walczyć z LR-ami, a zwłaszcza 6. Podsumowanie z powszechnym ignorowaniem gramatyk LL – skoro Dostępnych generatorów parserów jest dużo, nie nazwał swój produkt nazwą, którą się czyta podob- sposób ich choćby pobieżnie omówić. W ogóle nie nie do anty-LR. zająłem się generatorami z zamkniętym kodem, mię- Jest to bardzo potężne narzędzie. Potrafi pro- dzy innymi dlatego, że nie mogę się oprzeć wraże- dukować parsery w językach: Java, C++, C# oraz niu, że ich wartość na rynku jest raczej marginalna. Python. Inne języki też są dostępne, o ile ktoś zapro- Mam nadzieję, że przekonałem czytelników, że pi- jektuje odpowiedni generator kodu, jest kilka takich sząc swój program lepiej jest skorzystać z ułatwie- dostępnych w sieci. nia jakim jest generator parserów lub lekserów, niż Nie ma sensu deliberować na wyższością AN- pisać własny kod „palcami”. TLR nad poprzednimi parserami, gdyż niektóre ję- Literatura zyki wygodniej się zapisuje w LL, a niektóre LR. Generalnie gramatyka zapisane w LL jest bardziej [1] Ravi Sethi, Jeffrey D. Ullman, Alfred V. Aho iteracyjna niż czysto rekursywna. Na przykład nasz Kompilatory. Reguły, metody i narzędzia, Wy- czterodziałaniowy przykład byśmy zapisali jako dawnictwa Naukowo-Techniczne, Styczeń 2002 [2] John R. Levine, Tony Mason, Doug Brown, Lex wyr: skladnik ((’+’|’-’) skladnik)* & Yacc, O’Reilly & Associates skladnik: czynnik ((’*’|’/’) czynnik)* [3] Strona projektu Flex, http://www.gnu.org/ atom: liczba | (’(’ wyr ’)’) software/flex/ liczba: (’0’..’9’)+ [4] re2c project page, http://re2c.sourceforge. Pierwszy wiersz oznacza, że wyrażenie składa się ze net/ składnika oraz zero lub więcej składników rozdzie- [5] The Lemon Parser, Generator lonych znakami „+” lub „-”. W przypadku definicji http://www.hwaci.com/sw/lemon/ liczby, widzimy że w ANTLR możemy pisać również [6] The SQLite Project, http://www.sqlite.org/ leksery – według identycznego schematu. [7] ANother Tool For Language Recognition, http: Tu trzeba jednak przestrzec, że ANTLR jest na- //www.antlr.org/ rzędziem dużym i trudnym do nauczenia, z pew- nością nie jest dobrym wyborem na jednodniowy projekt, niemniej przy większych zadaniach warto je rozważyć, zwłaszcza, że jest to prawdopodobnie najbardziej starannie rozwijany, testowany i pielę- gnowany generator, który widziałem.

52 Bachotek, 30 kwietnia – 3 maja 2005

Polska Grupa Użytkowników Systemu TEX, 2005 (http://www.GUST.org.pl)