Zajęcia P4. Analizator semantyczny i generator kodu (Pascal) 1. Cel ćwiczeń Celem ćwiczeń jest stworzenie generatora kodu asemblerowego i analizatora semantycznego dla języka będącego podzbiorem języka wysokiego poziomu (C, Turbo Pascal, Ada, Modula). Przy tworzeniu analizatora należy skorzystać ze stworzonego analizatora leksykalnego. Sprawdzenie działania analizatora należy przeprowadzić dla zadanego programu testowego. 2. Informacja o tablicy symboli, innych strukturach danych i funkcjach A. Typ tokenów (węzeł drzewa składniowego) Struktura TOKEN_VARIABLE_TYPE opisuje budowę tokenów oraz zmiennych wykorzystywanych w do opisywania reguł gramatyki. Wartości wybranych pól tokenów są ustawiane przez analizator leksykalny, natomiast wartości pól zmiennych są ustawiane podczas analizy składniowej i semantycznej. Znaczenie poszczególnych pól przedstawia tabela: L.p. Pole Typ Opis 1. i int Wartość całkowita, np. wartość liczby, wyrażenia itp. 2. d double Wartość zmiennoprzecinkowa, np. wartość liczby, wyrażenia itp. nie używana w ćwiczeniu P4/2009 3. s char s[256] Wartość znakowa tokena, np. nazwa zmiennej, funkcji itp. 4. buf char buf[10000] Bufor kodu asemblerowego 5. datatype int 6. datasize int Rozmiar zmiennej w Bajtach 7. consttype int Typ zmiennej lub zwracany przez funkcję. Rozpatrywane typy: DT_VOID, DT_CHAR, DT_INT, DT_FLOAT, DT_DOUBLE, DT_LITERAL_SHORT_PTR Wartość liczbowa symbolu wykorzystywana m.in. w optymalizacji wyrażeń arytm.-logicznych. Możliwe wartości: NOT_A_CONST, INT_CONST, FLOAT_CONST, STRING_CONST, CHARACTER_CONST Dla każdej reguły produkcji postaci A: B C D..; tworzona jest nowa zmienna $$, w której dostępne są pola przedstawione w tabeli. B. Tablica symboli Struktura TSymbol opisująca elementy tablicy symboli została przedstawiona w poniższej tabeli. L.p. Pole Typ Opis 1. name char s[256] Wartość znakowa symbolu, np. nazwa zmiennej, funkcji itp. 2. symboltype int Rodzaj symbolu - ST_VARIABLE, ST_CONST, ST_FUNCTION 3. datatype int Typ zmiennej lub zwracany przez funkcję. Rozpatrywane typy: DT_VOID, DT_CHAR, DT_INT, DT_FLOAT, DT_DOUBLE, DT_LITERAL_SHORT_PTR 4. ival int Wartość całkowita, np. wartość stałej liczbowej itp. 5. fval double Wartość zmiennoprzecinkowa, np. wartość liczby, wyrażenia itp. nie używana w ćwiczeniu P4/2009 6. activblock int Numer aktywnego bloku (0 oznacza kontekst globalny, dodatnia wartość - kolejny poziom zagłębienia) 7. buf char buf[10000] Bufor kodu asemblerowego 8. stackoffset int przesunięcie na stosie lokalnym dla parametrów procedur i zmiennych lokalnych Informacja o tym, czy zmienna globalna ma ustaloną 9. isinitialized int wartość w momencie kompilacji wykorzystywane do optymalizacji wyrażeń arytmetycznych. Nowe symbole dopisywane są do tablicy symboli funkcją ST_push_back( char *name, unsigned int symboltype, unsigned int datatype, int isinitialized, int ival, double fval, int activblock, int stackoffset );
Dostęp do właściwości poszczególnych elementów tablicy jest możliwy funkcjami ST_get*(int idx) oraz ST_is*(int idx) gdzie idx jest indeksem tablicy symboli. C. Funkcje dodatkowe Found(const char *nonterminal, const char *value) wypisanie komunikatu o znalezieniu struktury składniowej Error( const char *msg ) wypisanie komunikatu o błędzie D. Przydatne konstrukcje języka C Czynność Przykład Wypisanie informacji do strumienia fprintf( outfile, $<buf>4 ); wyjściowego fprintf(outfile, "mov eax, [%s]", $<s>1 ); Dodanie danych do bufora <buf> strcat( $<buf>$, "$<buf>1" ) sprintf($<buf>$ + strlen($<buf>$), "%d", $<i>3) Skopiowanie danych do bufora strcpy( $<s>$, "" ); sprintf($<buf>$, "Etykieta%d", licznik++ ); E. Koncepcja rozwiązania i cenne uwagi W pliku parser.y znajduje się kompletna gramatyka do ćwiczenia P4. Gramatyki nie należy zmieniać. Wszelkie zmiany należy wprowadzać pod miejscami oznaczonymi przez komentarz postaci /*... */. Komentarza tego nie należy kasować. Zadanie projektowe należy wykonywać etapami za każdym razem sprawdzając działanie wprowadzonego kodu. 3. Generator kodu (realizowany stopniowo, zgodnie z instrukcjami zapisanymi w pliku). F. Czynności wstępne a) Wgranie własnego pliku lexer.l b) Ściągnięcie ze strony przedmiotu plików parser.y, common.h, common.c i test.* c) Zapisanie informacji o autorze w pliku parser.y. d) Wypisywanie własnego imienia i nazwiska na początku programu (funkcja main() w parser.y). e) Sprawdzenie i ewentualne uzupełnienie pliku lexer.l o brakujące lub zmienione nazwy tokenów, m.in.: CHARACTER_CONST, GE_OP, IDENTIFIER, KW_RETURN, KW_WHILE itp. f) Kompilacja programu z wykorzystaniem pliku Makefile lub za pomocą poleceń (pkt 3). g) Uruchomienie programu i zaobserwowanie wyjścia na konsolę (informacja o analizie leksykalnej i składniowej) oraz wyjścia do pliku test.asm. G. Asemblacja z wykorzystaniem NASM a) nasm f elf64 test.asm (yasm -f elf64 test.asm) b) ld o test test.o H. Deklaracje zmiennych globalnych bez wartości początkowej Uzupełnić kod w następujących miejscach: a) Reguła produkcji VAR Identifier_List ':' Type_Specifier, w części dla deklaracji globalnych (po else) b) Procedura PrintGlobalVariableDeclaration() c) Reguła produkcji Type_Specifier.. Zweryfikować pojawienie się nowych elementów w tablicy symboli (konsola) oraz deklaracji zmiennych globalnych na końcu pliku test.asm. I. Wywoływanie funkcji put*() z bloku głównego programu Uzupełnić kod w następujących regułach produkcji: a) Grammar Compound_Statement b) Compound_Statement.. c) Statement_List..
d) Statement Function_Call e) Function_Call.. f) Params.. g) Actual_Parameter_List.. h) Actual_Parameter.. i) Expr INT_NUMBER j) Expr Identifier k) Expr TEXT_CONST l) Odkomentować kolejne wiersze w pliku testowym Zweryfikować wyświetlenie się tekstu po odkomentowaniu kolejnych instrukcji put*(). J. Podstawianie liczb do zmiennych Uzupełnić kod w następujących następujących regułach produkcji: a) Statement Assignment_Statement b) Assignment_Statement.. c) Expr Expr '-' Expr Zweryfikować wyświetlenie poprawnych wartości zmiennych typu integer. K. Deklaracja i wywołanie funkcji ze zmiennymi lokalnymi Uzupełnić kod w następujących następujących regułach produkcji: a) Function_Definition.. b) Direct_Declarator.. c) Arg_Forms.. d) Parameter_List.. e) Parameter_Declaration.. f) Expr IDENTIFIER Params L. Instrukcja warunkowa if i funkcja silnia M. Pętla while.. do i wypisywanie znaków ASCII 4. Zadania dodatkowe N. Analizator semantyczny a) Użycie niezadeklarowanej zmiennej b) Powtórne zadeklarowanie zmiennej w tym samym bloku aktywacji c) Niezwracanie żadnej wartości w funkcji (brak return w języku C) d) Brak wykorzystania zmiennych lokalnych e) Sprawdzanie zgodności typów f) Brak funkcji main() ostrzeżenie i wygenerowanie pustej (tylko język C) g) Za dużo funkcji main() błąd i pominięcie kodu (tylko język C) h) Próba zwracania wartości przez procedurę (funkcję void) O. Optymalizator a) Usunięcie sekwencji push reg, pop reg b) Optymalizacja słownikowa c) Optymalizacja na drzewie wywodu 5. Przykładowe dane wejściowe i wyniki działania (Pascal) Tab. Fragmenty wygenerowanego pliku asemblerowego NASM dla systemów DOS/Windows L.p. P. Kod wynikowy (asembler DOS NASM) Komentarz ; Program in NASM 32-bit version 1. section.text org 100h Nagłówek programu
global _start _start: call main mov ah, 0 int 16h mov ax, 4c00h int 21h ; sys_exit 2. main: Funkcja main() 3. 4. 5. 6. 7. ; call of function puts push dword 34 mov eax, Literal0 push eax call puts add esp, 8 push eax pop eax... ; End of main program's block ret ; --- put*() functions library ---... section.data align 8 ; Declaration of global 'fromascii' fromascii dd 0 ; Declaration of global 'toascii' toascii dd 0 ; Declaration of global 'i' i dd 0 Tab. Informacja o tablicy symboli L.p. Type Name Block ival fval Stack Is Data No ofs. init. type 0. FUN putc 0 0 0 0 F void 1. FUN puts 0 0 0 0 F void 2. FUN putnl 0 0 0 0 F void 3. FUN puti 0 0 0 0 F void 4. VAR fromascii 0 0 0 - F int 5. VAR toascii 0 0 0 - F int 6. VAR i 0 0 0 - F int 7. VAR n 1 0 0 6 F int 8. FUN silnia 1 0 0 - F int 9. VAR K 1 0 0 - F int Funkcje put*() w bloku głównym programu (5.B) Koniec bloku głównego reprezentowanego przez funkcję main() Procedury wypisujące informacje put*() Początek sekcji danych Zmienne globalne (5.A) Efekt działania skompilowanego programu ASCII.
Tab. Plik testowy w Pascalu L.p. Q. Kod źródłowy (Pascal) Komentarz 1. Program Testowy; Nagłówek programu 2. Var fromascii : Integer; toascii : Integer; Zmienne globalne (5.A) i : Integer; 3. Function silnia( n : Integer ) : Integer Begin if ( n >= 2 ) then silnia := n * silnia( n - 1 ) Funkcja silnia else silnia := 1; End; 4. Begin 5. toascii := 255; Ustawianie wartości zmiennych i := toascii; globalnych 6. fromascii := silnia( 5 ) - 88; Wywoływanie zdefiniowanej funkcji 7. 8. puts( 'ASCII codes in reverse.. ' ); puti( i ); puts( ' downto ' ); puti( fromascii ); putc( 10{CR} ); putc( 13{LF} ); while ( i >= fromascii ) do begin putc( i ); i := i - 1; end; Funkcje put*() w bloku głównym programu (5.B) Wyświetlanie kodów ASCII