Technika kompilacji
Download
1 / 48

Technika kompilacji - PowerPoint PPT Presentation


  • 113 Views
  • Uploaded on

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:

loader
I am the owner, or an agent authorized to act on behalf of the owner, of the copyrighted work described.
capcha
Download Presentation

PowerPoint Slideshow about ' Technika kompilacji' - dunn


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.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.


- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -
Presentation Transcript
Technika kompilacji

Technika kompilacji

Analiza leksykalna

Adam Piotrowski


Plan wyk adu
Plan wykładu

Analizator leksykalny

Wyrażenia regularne

Przykład analizatora leksykalnego implementowanego w języku C

Generator analizatorów leksykalnych – flex


Elementy kompilatora
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


Analiza leksykalna
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)


Analiza leksykalna1
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.


Analizator leksykalny
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


Symbole leksykalne wzorce i leksemy
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.


Przyk ady
Przykłady

intfun()

{

int x, y, z;

x = y + z - 1.5;

return x * 2;

}


Atrybuty symboli leksykalnych
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.


Analizator leksykalny i jego rola w kompilatorze
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


B dy leksykalne
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.: [email protected] 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


Specyfikacja symboli leksykalnych
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


Definicje regularne
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:


Skr ty notacyjne
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


Zbiory nieregularne
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.


Implementacja analizatora leksykalnego
Implementacja analizatora leksykalnego

Napisanie analizatora w wybranym języku programowania

Wykorzystanie jednego z istniejących generatorów analizatorów leksykalnych


Przyk ad
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ń.


Przyk ad1
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>


Interfejs analizatora leksykalnego
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


Analizator leksykalny w c
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;

}

}

}


Analizator leksykalny w c1
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


Analizator leksykalny w c2
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


Analizator leksykalny w c3
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


Analizator leksykalny w c4
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


Analizator leksykalny w c5
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


Analizator leksykalny w c6
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


Analizator leksykalny w c7
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


Tablica symboli
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:


Analizator leksykalny z tablic symboli
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;

}

}

}


Analizator leksykalny z tablic symboli1
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


Analizator leksykalny z tablic symboli2
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


Generator analizator w leksykalnych flex
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).


Podstawy dzia ania flexa
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 ( /* */ )


Format pliku konfiguracyjnego
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


Specyfikacja dla flexa przyk ady
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


Specyfikacja dla flexa przyk ady1
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


Specyfikacja dla flexa przyk ady2
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;


Specyfikacja dla flexa przyk ady3
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();

}



Wyra enia regularne flexa1
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).



Wyra enia regularne przyk ady
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._-][email protected][A-Za-z0-9_-]+(\.[A-Za-z]+){1,3}


Definicje regularne1
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() { /* */ }


Zmienne globalne flexa
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();

}


Dopasowywanie ci g w
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


Dopasowywanie ci g w1
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


Zwracanie warto ci z yylex
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;

}



ad