1 / 48

Technika kompilacji

Technika kompilacji. Analiza leksykalna. Adam Piotrowski. Plan wykładu. Analizator leksykalny Wyrażenia regularne Przykład analizatora leksykalnego implementowanego w języku C Generator analizatorów leksykalnych – flex. Elementy kompilatora. Zadania:

dunn
Download Presentation

Technika kompilacji

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Technika kompilacji Analiza leksykalna Adam Piotrowski

  2. Plan wykładu Analizator leksykalny Wyrażenia regularne Przykład analizatora leksykalnego implementowanego w języku C Generator analizatorów leksykalnych – flex

  3. Elementy kompilatora • Zadania: • czytanie znaków z wejścia i produkcja sekwencji symboli leksykalnych dla analizatora składniowego • eliminacja komentarzy i białych znaków • informowanie o aktualnym numerze analizowanego wiersza • rozwijanie makr preprocesora • uzupełnianie tablicy symboli Program źródłowy Analizator leksykalny Analizator składniowy Analizator semantyczny Obsługa błędów Tablica symboli Gen. kodu pośredniego Optymalizator kodu Generator kodu Program wynikowy

  4. Analiza leksykalna • Powody rozdzielenia analizy leksykalnej i składniowej • prostota projektowania • poprawienie wydajności • zwiększenie przenośności kompilatora • Opisywane za pomocą prostych gramatyk lub wyrażeń regularnych (automaty stanowe)

  5. Analiza leksykalna • Większość symboli leksykalnych należy do jednej z grup: • nazwy (identyfikatory) • słowa zarezerwowane (ustalony podzbiór zbioru nazw) • liczbycałkowite • liczbyrzeczywiste • łańcuchyznakowe • operatory: • addytywne (+, -), • unarne (+, -), • multiplikatywne (*, /) • relacyjne (<, >, =, <=, >=, <>) • logiczne (and, or, not) • przypisania (:=) • ogranicznikijednoznakowe: ; , [ ] ( ) . • ogranicznikidwuznakowe: (* , *), +=, ++ itd.

  6. Analizator leksykalny Leksemy: Symbole leksykalne: identyfikator op_przypisania op_add op_mul identyfikator liczba identyfikator • identyfikator: • zbiór zaczynający się od litery lub podkreślenia, po którym następuje dowolnie długi ciąg liter, cyfr, podkreśleń • op_add: • operator dodawania lub odejmowania • op_mul: • operatory mnożenia i dzielenia • op_przypisania: • operator składający się ze znaku „:” po którym następuje znak „=„ pozycja := poczatek + tempo * 60

  7. Symbole leksykalne, wzorce i leksemy Analizator leksykalny czyta kolejne znaki programu źródłowego i wydziela jednostki leksykalne, zwane symbolami leksykalnymi (tokenami). Leksem to podstawowa jednostka leksykalna na jaką jest dzielony tekst kodu źródłowego. Wzorzec to reguła opisująca postać leksemu który można zaliczyć do danego symbolu leksykalnego.

  8. Przykłady intfun() { int x, y, z; x = y + z - 1.5; return x * 2; }

  9. Atrybuty symboli leksykalnych ? identyfikator int z intfun() { int x, y, z; x = y + z - 1.5; return x * 2; } return y fun x Jeżeli znaki z wejścia pasują do więcej niż jednego wzorca, analizator leksykalny musi mieć dodatkową informację dla dalszych faz kompilacji o konkretnym dopasowanym leksemie. W praktyce symbole leksykalne mają jeden atrybut – wskaźnik pozycji w tablicy symboli.

  10. Analizator leksykalny i jego rola w kompilatorze Symbol leksykalny Analizator leksykalny Analizator składniowy Program źródłowy Analizator składniowy Daj następny symbol leksykalny Tablica symboli

  11. Błędy leksykalne • Błędy wykrywane na etapie analizy leksykalnej: • Niepoprawny ciąg symboli użyty jako identyfikator np.: 123moja_zienna w C/C++ • Użycie nie dozwolonego znaku np.: moja@zmienna w C/C++ • Strategie zachowania po napotkaniu błędu: • Tryb paniki – kasowanie kolejnych znaków, do napotkania pierwszego poprawnego • Wstawienie brakującego znaku • Wymiana złego znaku na poprawny • Zamiana miejscami dwóch sąsiednich znaków

  12. Specyfikacja symboli leksykalnych identyfikator  [a-zA-Z_][a-zA-Z0-9]* Pierwszy znak analizowanego ciągu musi być dużą lub małą literą po której następuje zero lub więcej liter lub cyfr • Wzorce zapisujemy jako wyrażenia regularne

  13. Definicje regularne litera  A|B|C|…|Z|a|b|c|…|z cyfra  0|1|2|…|9 id  litera (litera | cyfra)* Wyrażeniom regularnym można nadać nazwy i używać tych nazw tak jak symboli:

  14. Skróty notacyjne Co najmniej jedno wystąpienie (+) – jednoargumentowy operator +, oznaczający co najmniej jedno wystąpienie poprzedzającego znaku lub wyrażenia regularnego Co najwyżej jedno wystąpienie (?) – jednoargumentowy operator oznaczający zero lub jedno wystąpienie poprzedzającego znaku lub wyrażenia regularnego Dowolna ilość wystąpień (*) – jednoargumentowy operator oznaczający zero lub więcej wystąpień poprzedzającego znaku lub wyrażenia regularnego Klasy znaków – notacja [abc] oznacza skrótowy zapis wyrażenia regularnego a|b|c

  15. Zbiory nieregularne Nie wszystkie języki dają się opisać wyrażeniami regularnymi, np.: wyrażenia regularne nie mogą być użyte do opisu zrównoważonych nawiasów. Wyrażenia regularne mogą jedynie opisać ustaloną liczbę powtórzeń albo dowolną liczbę powtórzeń danej konstrukcji.

  16. Implementacja analizatora leksykalnego Napisanie analizatora w wybranym języku programowania Wykorzystanie jednego z istniejących generatorów analizatorów leksykalnych

  17. Przykład 2+3*5  2 3 5 * + (2+3)*5  2 3 + 5 * ((2+7)/3+(14-3)*4)/2  2 7 + 3 / 14 3 - 4 * + 2 / • Omawiając poszczególne etapy translacji będziemy analizowali kompilator tłumaczący wyrażenia w notacji infiksowej na postfiksową (Odwrotna Notacja Polska), w której operatory występują po swoich argumentach np.: • Zapis ten pozwala na całkowitą rezygnację z użycia nawiasów w wyrażeniach, jako że jednoznacznie określa kolejność wykonywanych działań.

  18. Przykład • Zadania analizatora leksykalnego • Usuwanie znaków odstępu i komentarzy • Analiza stałych • Analizator leksykalny zamienia ciąg symboli w sekwencję par <symbol leksykalny, atrybut leksykalny> np.: 21 + 28 * 29 <liczba, 21> <+, > <liczba, 28> <*, > <liczba, 29> • Rozpoznawanie identyfikatorów i słów kluczowych • Dla identyfikatorów i słów kluczowych atrybut leksykalny oznacza indeks w tablicy symboli licznik = licznik + przyrost <id, 1> <=, > <id, 1> <+, > <id, 2>

  19. Interfejs analizatora leksykalnego Czytaj znak Przekaż symbol leksykalny i jego atrybuty wejście Analizator leksykalny Analizator składniowy Zwróć znak Zwraca symbol leksykalny wywołującemu Używa getchar() do wczytania znaku lexan() analizator leksykalny Zwraca znak c przy użyciu ungetc(c, stdin) Ustawia wartość atrybutu w zmiennej globalnej tokenval

  20. Analizator leksykalny w C intlineno = 1; inttokenval = NONE; intlexan () { int t; while (1) { t = getchar (); if (t == ' ' || t == '\t'); else if (t == '\n') lineno++; else if (isdigit (t)) { tokenval = t – '0'; t = getchar(); while (isdigit(t)) { tokenval = tokenval*10 + t – '0'; t = getchar(); } ungetc (t, stdin); return NUM; } else { tokenval = NONE; return t; } } }

  21. Analizator leksykalny w C intlineno = 1; inttokenval = NONE; intlexan () { int t; while (1) { t = getchar (); if (t == ' ' || t == '\t'); else if (t == '\n') lineno++; else if (isdigit (t)) { tokenval = t – '0'; t = getchar(); while (isdigit(t)) { tokenval = tokenval*10 + t – '0'; t = getchar(); } ungetc (t, stdin); return NUM; } else { tokenval = NONE; return t; } } } Numer aktualnie analizowanego wiersza

  22. Analizator leksykalny w C intlineno = 1; inttokenval = NONE; intlexan () { int t; while (1) { t = getchar (); if (t == ' ' || t == '\t'); else if (t == '\n') lineno++; else if (isdigit (t)) { tokenval = t – '0'; t = getchar(); while (isdigit(t)) { tokenval = tokenval*10 + t – '0'; t = getchar(); } ungetc (t, stdin); return NUM; } else { tokenval = NONE; return t; } } } Atrybut symbolu leksykalnego

  23. Analizator leksykalny w C intlineno = 1; inttokenval = NONE; intlexan () { int t; while (1) { t = getchar (); if (t == ' ' || t == '\t'); else if (t == '\n') lineno++; else if (isdigit (t)) { tokenval = t – '0'; t = getchar(); while (isdigit(t)) { tokenval = tokenval*10 + t – '0'; t = getchar(); } ungetc (t, stdin); return NUM; } else { tokenval = NONE; return t; } } } Pobierz jeden znak

  24. Analizator leksykalny w C intlineno = 1; inttokenval = NONE; intlexan () { int t; while (1) { t = getchar (); if (t == ' ' || t == '\t'); else if (t == '\n') lineno++; else if (isdigit (t)) { tokenval = t – '0'; t = getchar(); while (isdigit(t)) { tokenval = tokenval*10 + t – '0'; t = getchar(); } ungetc (t, stdin); return NUM; } else { tokenval = NONE; return t; } } } Jeżeli t jest białym znakiem, pomiń znak

  25. Analizator leksykalny w C intlineno = 1; inttokenval = NONE; intlexan () { int t; while (1) { t = getchar (); if (t == ' ' || t == '\t'); else if (t == '\n') lineno++; else if (isdigit (t)) { tokenval = t – '0'; t = getchar(); while (isdigit(t)) { tokenval = tokenval*10 + t – '0'; t = getchar(); } ungetc (t, stdin); return NUM; } else { tokenval = NONE; return t; } } } Jeżeli t jest znakiem nowej linii, zwiększ numer aktualnie analizowanego wiersza

  26. Analizator leksykalny w C intlineno = 1; inttokenval = NONE; intlexan () { int t; while (1) { t = getchar (); if (t == ' ' || t == '\t'); else if (t == '\n') lineno++; else if (isdigit (t)) { tokenval = t – '0'; t = getchar(); while (isdigit(t)) { tokenval = tokenval*10 + t – '0'; t = getchar(); } ungetc (t, stdin); return NUM; } else { tokenval = NONE; return t; } } } Jeżeli t jest liczbą, zamień napis na wartość typu całkowitego

  27. Analizator leksykalny w C intlineno = 1; inttokenval = NONE; intlexan () { int t; while (1) { t = getchar (); if (t == ' ' || t == '\t'); else if (t == '\n') lineno++; else if (isdigit (t)) { tokenval = t – '0'; t = getchar(); while (isdigit(t)) { tokenval = tokenval*10 + t – '0'; t = getchar(); } ungetc (t, stdin); return NUM; } else { tokenval = NONE; return t; } } } Wczytany znak jest nieznanym symbolem

  28. Tablica symboli int insert(const char* s, int t); /* zwraca indeks w tablicy symboli dla nowego leksemu s i tokenu t */ int lookup(const char* s); /* zwraca indeks wpisu dla leksemu s lub 0 gdy nie znaleziono */ insert("div", DIV); insert("mod", MOD); Tablica symboli jest wykorzystywana do przechowywania zmiennych oraz słów kluczowych Jest inicjalizowana poprzez wstawienie do niej słów kluczowych:

  29. Analizator leksykalny z tablicą symboli intlexan () { int t; while (1) { t = getchar (); if (t == ' ' || t == '\t'); elseif (t == '\n') lineno++; elseif (isdigit (t)) { ungetc (t, stdin); scanf ("%d", &tokenval); return NUM; } elseif (isalpha (t)) { int p, b = 0; while (isalnum (t)) { lexbuf[b] = t; t = getchar (); b++; if (b >= BSIZE) error ("compilererror"); } lexbuf[b] = EOS; if (t != EOF) ungetc (t, stdin); p = lookup (lexbuf); if (p == 0) p = insert (lexbuf, ID); tokenval = p; return symtable[p].token; } else if (t == EOF) return DONE; else { tokenval = NONE; return t; } } }

  30. Analizator leksykalny z tablicą symboli intlexan () { int t; while (1) { t = getchar (); if (t == ' ' || t == '\t'); elseif (t == '\n') lineno++; elseif (isdigit (t)) { ungetc (t, stdin); scanf ("%d", &tokenval); return NUM; } elseif (isalpha (t)) { int p, b = 0; while (isalnum (t)) { lexbuf[b] = t; t = getchar (); b++; if (b >= BSIZE) error ("compilererror"); } lexbuf[b] = EOS; if (t != EOF) ungetc (t, stdin); p = lookup (lexbuf); if (p == 0) p = insert (lexbuf, ID); tokenval = p; return symtable[p].token; } else if (t == EOF) return DONE; else { tokenval = NONE; return t; } } } Jeżeli t jest identyfikatorem

  31. Analizator leksykalny z tablicą symboli intlexan () { int t; while (1) { t = getchar (); if (t == ' ' || t == '\t'); elseif (t == '\n') lineno++; elseif (isdigit (t)) { ungetc (t, stdin); scanf ("%d", &tokenval); return NUM; } elseif (isalpha (t)) { int p, b = 0; while (isalnum (t)) { lexbuf[b] = t; t = getchar (); b++; if (b >= BSIZE) error ("compilererror"); } lexbuf[b] = EOS; if (t != EOF) ungetc (t, stdin); p = lookup (lexbuf); if (p == 0) p = insert (lexbuf, ID); tokenval = p; return symtable[p].token; } else if (t == EOF) return DONE; else { tokenval = NONE; return t; } } } Jeżeli t jest znakiem końca pliku

  32. Generator analizatorów leksykalnych - flex Program źródłowy w języku flex - lex.l Kompilator flexa lex.yy.c Kompilator C a.out lex.yy.c Sekwencja symboli leksykalnych Strumień wejściowy a.out Generuje kod analizatora na podstawie zadanej specyfikacji Domyślnie analizator jest napisany w języku C Wygenerowany kod źródłowy kompilujemy jako samodzielny program lub moduł programu. yylex() – funkcja wygenerowana przez LEX-a odpowiedzialna za działanie leksera (można ją wykorzystać w innej aplikacji).

  33. Podstawy działania flexa Niedopasowane znaki są przepisywane na wyjście Operacja pusta = reguła składająca się tylko ze wzorca Wzorce zawierające spacje ujęte muszą być w cudzysłów Znaki specjalne poprzedzone muszą być znakiem \ (backslash) Komentarze oznaczamy jak w języku c ( /* */ )

  34. Format pliku konfiguracyjnego Sekcja definicji %% Sekcja reguł przetwarzania, gdzie reguła składa się z dwóch części: wyrażenia regularnego oraz kodu w C %% Sekcja kodu dodatkowego

  35. Specyfikacja dla flexa - przykłady Wzorce %{ int num_lines = 0; num_chars = 0; int_nr = 0; %} %% \n {++num_lines; ++num_chars;} int {++int_nr;} . {++num_chars;} %% intmain() { yylex(); printf( "# of lines = %d, # of chars = %d\n",num_lines, num_chars ); } Operacje

  36. Specyfikacja dla flexa - przykłady sekcja kodu użytkownika - kod bezpośrednio przepisany na początek pliku wyjściowego %{ int num_lines = 0; num_chars = 0; int_nr = 0; %} %% \n {++num_lines; ++num_chars;} int {++int_nr;} . {++num_chars;} %% intmain() { yylex(); printf( "# of lines = %d, # of chars = %d\n",num_lines, num_chars ); } kod bezpośrednio przepisany na koniec pliku wyjściowego

  37. Specyfikacja dla flexa - przykłady #include <stdio.h> signed int foo1(int); int foo2(void); int i; unsigned char c; %{ #include <stdio.h> intyylex(); %} %% ”signedint” {printf(”int”);} ”unsigned char” {printf(”char”);} %% intmain() { return yylex(); } #include <stdio.h> int foo1(int); int foo2(void); int i; char c;

  38. Specyfikacja dla flexa - przykłady %{ #include <math.h> %} DIGIT [0-9] ID [a-z][a-z0-9]* %% {DIGIT}+ { printf( "An integer: %s (%d)\n", yytext,atoi( yytext ) ); } {DIGIT}+"."{DIGIT}* { printf( "A float: %s (%g)\n", yytext, atof( yytext ) ); } if|then|begin|end|procedure|function { printf( "A keyword: %s\n", yytext ); } {ID} { printf( "An identifier: %s\n", yytext ); } "+"|"-"|"*"|"/" {printf( "An operator: %s\n", yytext );} "{"[\^{}}\n]*"}“ [ \t\n]+ . {printf( "Unrecognized character: %s\n", yytext );} %% intmain(intargc, char* argv[] ) { ++argv, --argc; if ( argc > 0 ) yyin = fopen( argv[0], "r" ); else yyin = stdin; yylex(); }

  39. Wyrażenia regularne flexa

  40. Wyrażenia regularne flexa Następujące operatory jezyka flex mają specjalne znaczenie: \” . ^ $ [ ] * + ? { } | / Aby przywrócić im znaczenie znaku (a nie operatora), w wyrażeniach regularnych muszą być poprzedzone znakiem \ (backslash).

  41. Predefiniowane klasy flexa

  42. Wyrażenia regularne, przykłady • Założenia dla wzorca: • - nazwa użytkownika składa się z małych i wielkich liter, cyfr, kropki, podkreślenia i znaku minus, • - nazwa hosta składa się z dwóch do czterech części oddzielonych kropką, • - pierwsza część nazwy hosta składa się z małych i wielkich liter, cyfr, podkreślenia i znaku minus, • pozostałe część nazwy hosta składają się z małych i wielkich liter. • [A-Za-z0-9._-]+@[A-Za-z0-9_-]+(\.[A-Za-z]+){1,3}

  43. Definicje regularne %{ #include <stdio.h> intyylex(); voidon_ID_found(); voidon_NUM_INT_found(); voidon_NUM_FLOAT_found(); %} ID [a-zA-Z_][a-zA-Z0-9_]* NUM_INT [0-9]+ NUM_FLOAT {NUM_INT}”.”{NUM_INT}* %% {ID} {on_ID_found();} {NUM_INT} {on_NUM_INT_found();} {NUM_FLOAT} {on_NUM_FLOAT_found();} %% voidon_ID_found() { /* */ } voidon_NUM_INT_found() { /* */ } voidon_NUM_FLOAT_found() { /* */ }

  44. Zmienne globalne flexa ID [a-zA-Z_][a-zA-Z0-9_]* %% {ID} {printf(”Identyfikator: %s o dlugosci %d znakow”, yytext, yyleng);} . {printf(”Nieznany leksem: %s o dlugosci %d znakow”, yytext, yyleng);} %% int main(int argc, char * argv[]) { if (argc > 1) yyin = fopen( argv[1], "r" ); else yyin = stdin; return yylex(); }

  45. Dopasowywanie ciągów |aaaaaa| > |aaa| Zasada najdłuższego dopasowania. aaaaaa aaa %{ #include <stdio.h> int yywrap(); int yylex(); %} %% aaa {printf(”F”);} a* {printf(”T”);} %% int main() { return yylex(); } T F

  46. Dopasowywanie ciągów |aaa| = |aaa| Zasada wcześniejszego dopasowania. aaa aaa %{ #include <stdio.h> intyywrap(); intyylex(); %} %% aaa {printf(”F”);} a* {printf(”T”);} %% intmain() { return yylex(); } F F

  47. Zwracanie wartości z yylex() %{ #include <stdio.h> intyylex(); #defineKW_begin 300 #defineKW_end 301 #define ID 302 %} %% [Bb][Ee][Gg][Ii][Nn] return KW_begin; [Ee][Nn][Dd] return KW_end; ”(” return '('; ”)” return ')'; ”.” return '.'; [a-zA-Z_]+[a-zA-Z0-9_]* return ID; . %% intmain() { intsymb_leks; while(symb_leks = yylex()) printf(”Znaleziono symbol leksykalny o identyfikatorze %d\n”, symb_leks); return 1; }

  48. Dziękuję za uwagę

More Related