Analiza leksykalna i generator LEX Wojciech Complak Wojciech.Complak@cs.put.poznan.pl wprowadzenie generator LEX wyrażenia regularne i niejednoznaczności retrakcja definicje regularne atrybuty lewy kontekst (stany) prawy kontekst (podgląd) koniec pliku (funkcja yywrap) białe spacje i komentarze słowa kluczowe odrzucenie dopasowania (REJECT) Plan wykładu 1.05 Analiza leksykalna i generator LEX (2/44) Kompilatory i interpretery Model kompilatora kompilator jest programem, który przetwarza kod napisany w jednym języku (tzw. języku źródłowym) na równoważny kod w drugim języku (tzw. języku wynikowym) interpreter jest programem, który nie generuje programu wynikowego tylko od razu wykonuje instrukcje zawarte w programie źródłowym, interpretery są wykorzystywane do wykonywania języków poleceń oraz języków bardzo wysokiego poziomu takich, jak APL czy Prolog każda kompilacja składa się z dwóch etapów: etapu analizy w której program źródłowy rozkładany jest na części składowe i generowana jest jego reprezentacja pośrednia etapu syntezy w której na podstawie reprezentacji pośredniej generowany jest program wynikowy program źródłowy analiza reprezentacja pośrednia synteza program wynikowy Analiza leksykalna i generator LEX (3/44) Analiza leksykalna i generator LEX (4/44) Model kompilatora Analiza leksykalna - wprowadzenie etap analizy obejmuje: analizę leksykalną analizę składniową analizę semantyczną etap syntezy obejmuje: generację kodu pośredniego optymalizację kodu generację kodu wynikowego analiza synteza program źródłowy analiza leksykalna analiza składniowa analiza semantyczna generacja kodu pośredniego optymalizacja kodu generacja kodu wynikowego program wynikowy analiza leksykalna jest pierwszą fazą kompilatora czyta tekst wejściowy i wyprowadza rozpoznane jednostki leksykalne wydzielenie fazy analizy leksykalnej: upraszcza projekt i utrzymanie poprawia wydajność ułatwia przenoszenie analiza synteza program źródłowy analiza leksykalna analiza składniowa analiza semantyczna generacja kodu pośredniego optymalizacja kodu generacja kodu wynikowego program wynikowy Analiza leksykalna i generator LEX (5/44) Analiza leksykalna i generator LEX (6/44) 1
Analiza leksykalna - wprowadzenie Analiza leksykalna przykład (#1/2) rozpoznawanie ciągów znaków o pewnych własnościach (zbudowanych zgodnie z określonymi regułami) wykorzystywana w różnych narzędziach do przetwarzania tekstu takich jak : edytory strukturalne formatery kodu analizatory i kontrolery statyczne kompilatory krzemowe translatory języków naturalnych interpretery i kompilatory języków formalnych (np. języków programowania) Analiza leksykalna i generator LEX (7/44) dany jest fragment programu w języku C, należy podzielić go na jednostki leksykalne podstawowe pojęcia związane z analizą leksykalną: token leksem wzorzec atrybut(y) przykładowy program w języku C: int sqr(int n) { return n * n; Analiza leksykalna i generator LEX (8/44) Analiza leksykalna przykład (#2/2) Generator LEX (#1/3) token wzorzec (LEX) leksem atrybut KWD_INT int int IDENT [_a-za-z][_a-za-z0-9]* sqr "sqr" '(' \( ( KWD_INT int int IDENT [_a-za-z][_a-za-z0-9]* n "n" ')' \) ) '{' \{ { KWD_RET return return IDENT [_a-za-z][_a-za-z0-9]* n "n" OP_STAR \* * IDENT [_a-za-z][_a-za-z0-9]* n "n" ';' ; ; '' \ Analiza leksykalna i generator LEX (9/44) LEX jest generatorem analizatorów leksykalnych na podstawie zadanej specyfikacji generuje program źródłowy implementujący analizator leksykalny analizator leksykalny jest domyślnie generowany w języku C istnieją wersje LEXa, które mogą generować analizatory w innych językach programowania np.: C++, C#, Pascal, Java, Ada, Eiffel synonimy: analizator leksykalny = skaner analizator składniowy = parser Analiza leksykalna i generator LEX (10/44) Generator LEX (#2/3) Generator LEX (#3/3) na wejście generatora LEX podajemy specyfikację analizatora na wyjściu generatora LEX otrzymujemy implementację analizatora w języku C (funkcja yylex()) specyfikacja analizatora leksykalnego LEX funkcja: yylex() plik: lex.yy.c funkcję yylex() można: wykorzystać jako samodzielny analizator zintegrować z większą aplikacją np. z analizatorem składniowym Analiza leksykalna i generator LEX (11/44) postać specyfikacji dla generatora LEX: Definicje Reguły Podprogramy sekcje Definicji i Podprogramów mogą być puste sekcja Reguł musi zawierać co najmniej jedną regułę każda Reguła to: Wzorzec Akcja(e) w specyfikacjach można używać komentarzy (tak jak w ANSI C: /* komentarz */) Analiza leksykalna i generator LEX (12/44) 2
Podstawowe reguły działania (#1/2) Podstawowe reguły działania (#2/2) niedopasowane znaki są przepisywane na wyjście akcja = instrukcja języka C, akcja pusta = ; przykład: usuwanie nadmiarowych słów kluczowych języka C (register, auto) register ; auto ; wejście wyjście int a(void) { register int c; auto int d; int a(void) { int c; int d; Analiza leksykalna i generator LEX (13/44) wzorce zawierające spacje ujmujemy w znaki: " " przykład: skracanie deklaracji języka C long int long signed int int "long int" printf("long"); "signed int" printf("int"); wejście wyjście int a(void) { signed int c; long int d; int a(void) { int c; long d; Analiza leksykalna i generator LEX (14/44) Zmienne statyczne Zmienne automatyczne zliczanie liczby linii do ciągu ### z wykorzystaniem zmiennej statycznej int NR = 0; \n { printf("\n"); NR ++; ### { printf("###\n%d lines\n", NR); Analiza leksykalna i generator LEX (15/44) zliczanie liczby linii do ciągu ### z wykorzystaniem zmiennej automatycznej int NR = 0; \n { printf("\n"); NR ++; ### { printf("###\n%d line(s)\n", NR); Analiza leksykalna i generator LEX (16/44) Funkcje Wyrażenia regularne zliczanie liczby linii do ciągu ### z użyciem funkcji prototyp definicja funkcji int NR = 0; void incnr(int *); \n { printf("\n"); incnr(&nr); ### { printf("###\n%d lines\n",nr); void incnr(int *n) { (*n)++; Analiza leksykalna i generator LEX (17/44) dowolny znak (z wyjątkiem \n). konkatenacja x*y* początek linii ^x koniec linii x$ operator opcjonalności x? nawiasy (x y)z powtórzenia wzorca x{5 alternatywa x y sekwencja ucieczki \ sekwencje specjalne \a\t\n liczba oktalna \nnn liczba szesnastkowa \xhh zakres powtórzeń wzorca x{2,5 domknięcie zwrotne x* klasa znaków [] domknięcie dodatnie x+ Analiza leksykalna i generator LEX (18/44) 3
Niejednoznaczności (#1/2) Niejednoznaczności (#2/2) ile liter a zostanie wypisanych na wyjście? 1* { printf("a"); zasada najdłuższego dopasowania wejście 1111;1 wyjście? Analiza leksykalna i generator LEX (19/44) jakie litery zostaną wypisane na wyjście? 11 { printf("b"); 1* { printf("a"); wejście 11;1111; wyjście? przy równej długości dopasowania wybierany jest wzorzec występujący wcześniej w specyfikacji Analiza leksykalna i generator LEX (20/44) Retrakcja Definicje regularne rozpoznawanie liczb rzeczywistych w Fortranie 1.E? Q dlaczego to działa? wejście 1.EQ.2 wyjście Card RelOp Card [0-9]+ printf("card "); ".EQ." printf("relop "); [0-9]+".E"[0-9]+ printf("real "); Analiza leksykalna i generator LEX (21/44) usuwanie z programu etykiet skoków definicja użycie ident [_a-za-z][_a-za-z0-9]* {ident: ; Analiza leksykalna i generator LEX (22/44) Atrybuty symboli leksykalnych (#1/2) Atrybuty symboli leksykalnych (#2/2) zmienne wbudowane w LEXa: int yyleng char yytext[] / char *yytext (zależnie od implementacji LEXa) zmienne wbudowane najbezpieczniej jest traktować jak zmienne tylko do odczytu przykład: suma sekwencji liczb wejście 1+ 11+ 8= wyjście 1+ 11+ 8= 20 Analiza leksykalna i generator LEX (23/44) int suma = 0; [0-9]+ { int liczba; sscanf(yytext,"%d",&liczba); ECHO; suma+=liczba; = printf("= %d",suma); Analiza leksykalna i generator LEX (24/44) 4
Lewy kontekst (stany) (#1/3) Lewy kontekst (stany) (#2/3) przykład: analizator usuwający łańcuchy ujęte w znaki cudzysłowu: wejście wyjście #include "defs1.h" #include "defs2.h" int i; #include #include int i; "/; %s qstring <qstring>\" BEGIN 0; <qstring>. ; \" BEGIN qstring; BEGIN stan; = zmiana bieżącego stanu./echo 0 qstring./; "/; Analiza leksykalna i generator LEX (25/44) reguły działają tylko w wymienionym stanie jeżeli przed regułą nie ma nazwy stanu to reguła działa we wszystkich stanach! (uwaga na pozostałe zasady) Analiza leksykalna i generator LEX (26/44) Lewy kontekst (stany) (#3/3) Prawy kontekst (podgląd) (#1/3) stan początkowy (<0>): albo <INITIAL> (uwaga na Flexa!): musi być <INITIAL> %s qstring <qstring>\" BEGIN 0; <qstring>. ; <0>\" BEGIN qstring; %s qstring <qstring>\" BEGIN 0; 0 albo INITIAL <qstring>. ; <INITIAL>\" BEGIN qstring; nazwy stanów nie mogą być słowami kluczowymi języka C jeśli reguła ma działać w określonych stanach należy poprzedzić ją nazwami stanów: <INITIAL,c_state>[^ ] { ECHO; Analiza leksykalna i generator LEX (27/44) przykład: rozpoznawanie symbolu zakresu w Moduli-2 Cardinal [0-9]+ Real [0-9]+"."[0-9]*(E[+\-]?[0-9]+)? {Cardinal { printf("card(%s) ",yytext); {Real { printf("real(%s) ",yytext); ".." { printf("range(%s) ",yytext); "." { printf("dot(%s) ",yytext); "[" { printf("["); "]" { printf("]"); Analiza leksykalna i generator LEX (28/44) Prawy kontekst (podgląd) (#2/3) Prawy kontekst (podgląd) (#3/3) problem: wejście [1..5] oczekiwane wyjście [Card(1) Range(..) Card(5) ] rzeczywiste wyjście [Real(1.) Dot(.) Card(5) ] Analiza leksykalna i generator LEX (29/44) poprawne rozwiązanie z wykorzystaniem operatora podglądu Cardinal [0-9]+ Real [0-9]+"."[0-9]*(E[+-]?][0-9]+)? {Cardinal { printf("card(%s) ",yytext); {Real/[^0-9.] { printf("real(%s) ",yytext); ".." { printf("range(%s) ",yytext); "." { printf("dot(%s) ",yytext); "[" { printf("["); "]" { printf("]"); Analiza leksykalna i generator LEX (30/44) 5
Koniec pliku i funkcja yywrap (#1/2) Koniec pliku i funkcja yywrap (#2/2) przykład: suma sekwencji liczb wejście 1 2 3 4 5 wyjście Suma = 15 funkcja yywrap() jest wywoływana przez yylex() po natrafieniu na koniec pliku int yywrap(void) { /* akcje użytkownika */ return 1; 0 powrót do skanowania wejścia!0 zakończenie skanowania Analiza leksykalna i generator LEX (31/44) int suma = 0; [0-9]+ { int liczba; sscanf(yytext,"%d",&liczba); suma += liczba; " " ; int yywrap(void) { printf("suma = %d\n",suma); return 1; Analiza leksykalna i generator LEX (32/44) Białe spacje Komentarze (#1/5) w większości języków programowania białe spacje służą tylko poprawie czytelności programu (wyjątkami są np. Fortran i AWK) analizator leksykalny je pomija (ukrywa przed analizatorem składniowym) [ \t\n] ; problem: pomijanie niezagnieżdżonych komentarzy języka Pascal rozwiązanie z użyciem wzorca typowy błąd \{.+\ ; wejście x{zm:={podst1 {st ; oczekiwane wyjście x:=1 ; rzeczywiste wyjście x ; Analiza leksykalna i generator LEX (33/44) Analiza leksykalna i generator LEX (34/44) Komentarze (#2/5) Komentarze (#3/5) problem: pomijanie niezagnieżdżonych komentarzy języka Pascal rozwiązanie z użyciem wzorca rozwiązanie poprawne \{[^]+\ ; rozwiązanie z wykorzystaniem wzorców jest nieefektywne (niepotrzebnie gromadzi rozpoznawany tekst w buforze) Analiza leksykalna i generator LEX (35/44) problem: pomijanie niezagnieżdżonych komentarzy języka C (standard ANSI) rozwiązanie z użyciem stanów %s comment <0>"/*" BEGIN comment; <comment>. ; <comment>"*/" BEGIN 0; rozwiązanie z użyciem stanów jest efektywniejsze (szybsze, nie gromadzimy tekstu) i łatwo można je rozbudować o możliwość obsługi zagnieżdżonych komentarzy Analiza leksykalna i generator LEX (36/44) 6
Komentarze (#4/5) Komentarze (#5/5) problem: pomijanie zagnieżdżonych komentarzy języka C (rozszerzenie standardu ANSI) int CommentLevel; %s comment <0>"/*" { CommentLevel = 1; BEGIN comment; <comment>"/*" { CommentLevel++; <comment>. { ; <comment>"*/" { if( (--CommentLevel) == 0)BEGIN 0; Analiza leksykalna i generator LEX (37/44) prawdziwy problem: pomijanie zagnieżdżonych komentarzy dwóch różnych typów w języku Pascal (* i *) oraz { i przykładowe wejście: var x : integer; begin x{zm:={p(*ods*)t1{st; end. rozwiązanie:? Analiza leksykalna i generator LEX (38/44) Rozpoznawanie słów kluczowych (#1/3) Rozpoznawanie słów kluczowych (#2/3) usunąć z programu wszystkie identyfikatory z wyjątkiem słów kluczowych begin i end prototyp rozwiązania ident [a-za-z][a-za-z0-9]* {ident ; [bb][ee][gg][ii][nn] ECHO; [ee][nn][dd] ECHO; Analiza leksykalna i generator LEX (39/44) problem: wejście begin var:=x+y; end. oczekiwane wyjście begin :=+; end. rzeczywiste wyjście :=+;. Analiza leksykalna i generator LEX (40/44) Rozpoznawanie słów kluczowych (#3/3) Odrzucenie dopasowanie REJECT (#1/3) usunąć z programu wszystkie identyfikatory z wyjątkiem słów kluczowych begin i end rozwiązanie poprawne ident [a-za-z][a-za-z0-9]* [bb][ee][gg][ii][nn] ECHO; [ee][nn][dd] ECHO; {ident ; Analiza leksykalna i generator LEX (41/44) program zliczający liczbę wystąpień słów he i she prototyp rozwiązania int she = 0, he = 0; she { she++; he { he++;. \n { ; int yywrap(void) { printf("he (%d) she (%d)",he,she); return 1; Analiza leksykalna i generator LEX (42/44) 7
Odrzucenie dopasowanie REJECT (#2/3) Odrzucenie dopasowanie REJECT (3/3) problem: wejście hesheheshe oczekiwane wyjście he (4) she (2) rzeczywiste wyjście he (2) she (2) Analiza leksykalna i generator LEX (43/44) program zliczający liczbę wystąpień słów he i she poprawne rozwiązanie z użyciem REJECT int she = 0, he = 0; she { she++; REJECT; he { he++;. \n { ; int yywrap(void) { printf("he (%d) she (%d)",he,she); return 1; Analiza leksykalna i generator LEX (44/44) 8