Semantyka i Weryfikacja Programów - Laboratorium 6

Podobne dokumenty
Analiza leksykalna 1. Teoria kompilacji. Dr inż. Janusz Majewski Katedra Informatyki

Generatory analizatorów

Języki programowania zasady ich tworzenia

Metody Kompilacji Wykład 1 Wstęp

Zadanie analizy leksykalnej

Analiza leksykalna 1. Języki formalne i automaty. Dr inż. Janusz Majewski Katedra Informatyki

Metody Kompilacji Wykład 3

Plan wykładu. Kompilatory. Literatura. Translatory. Literatura Translatory. Paweł J. Matuszyk

Programowanie w języku Python. Grażyna Koba

Semantyka i Weryfikacja Programów - Laboratorium 3

KONSTRUKCJA KOMPILATORÓW

Programowanie w języku C++ Grażyna Koba

Programowanie Strukturalne i Obiektowe Słownik podstawowych pojęć 1 z 5 Opracował Jan T. Biernat

Temat 1: Podstawowe pojęcia: program, kompilacja, kod

Gramatyki atrybutywne

Języki formalne i gramatyki

Programowanie I. O czym będziemy mówili. Plan wykładu nieco dokładniej. Plan wykładu z lotu ptaka. Podstawy programowania w językach. Uwaga!

Matematyczne Podstawy Informatyki

Matematyczne Podstawy Informatyki

Metody Kompilacji Wykład 8 Analiza Syntaktyczna cd. Włodzimierz Bielecki WI ZUT

1 Podstawy c++ w pigułce.

Podstawy programowania. Wykład Funkcje. Krzysztof Banaś Podstawy programowania 1

Program We Kompilator Wy Źródłowy

Programowanie komputerów

Podstawy Kompilatorów

Podstawy i języki programowania

10. Translacja sterowana składnią i YACC

Zadanie 2: Arytmetyka symboli

JIP. Analiza składni, gramatyki

Elementy języków programowania

SYLABUS DOTYCZY CYKLU KSZTAŁCENIA Bieżący sylabus w semestrze zimowym roku 2016/17

Programowanie. programowania. Klasa 3 Lekcja 9 PASCAL & C++

Java EE produkcja oprogramowania

Podstawowe elementy proceduralne w C++ Program i wyjście. Zmienne i arytmetyka. Wskaźniki i tablice. Testy i pętle. Funkcje.

Metody Kompilacji Wykład 7 Analiza Syntaktyczna

INFORMATYKA, TECHNOLOGIA INFORMACYJNA ORAZ INFORMATYKA W LOGISTYCE

Zadanie nr 3: Sprawdzanie testu z arytmetyki

Włączenie analizy leksykalnej do analizy składniowej jest nietrudne; po co więc jest wydzielona?

Języki formalne i automaty Ćwiczenia 1

Wybrane narzędzia do tworzenia analizatorów leksykalnych i składniowych w C/C++ by Kapitol Team

Laboratorium nr 4: Arytmetyka liczb zespolonych

Efektywna analiza składniowa GBK

Pracownia Informatyczna Instytut Technologii Mechanicznej Wydział Inżynierii Mechanicznej i Mechatroniki. Podstawy Informatyki i algorytmizacji

Wykład 5. Jan Pustelnik

L E X. Generator analizatorów leksykalnych

Analizator syntaktyczny

Wprowadzenie do analizy składniowej. Bartosz Bogacki.

1 Podstawy c++ w pigułce.

utworz tworzącą w pamięci dynamicznej tablicę dwuwymiarową liczb rzeczywistych, a następnie zerującą jej wszystkie elementy,

Wskaźniki a tablice Wskaźniki i tablice są ze sobą w języku C++ ściśle związane. Aby się o tym przekonać wykonajmy cwiczenie.

Część XVII C++ Funkcje. Funkcja bezargumentowa Najprostszym przypadkiem funkcji jest jej wersja bezargumentowa. Spójrzmy na przykład.

Interpreter - EasyCompile

Wstęp do programowania. Wykład 1

ForPascal Interpreter języka Pascal

Zmienne, stałe i operatory

Analiza leksykalna i generator LEX

Podstawy programowania. 1. Operacje arytmetyczne Operacja arytmetyczna jest opisywana za pomocą znaku operacji i jednego lub dwóch wyrażeń.

Podstawy Informatyki Języki programowania c.d.

Metody Kompilacji Wykład 13

Podstawy programowania skrót z wykładów:

Analiza semantyczna. Gramatyka atrybutywna

Wprowadzenie: języki, symbole, alfabety, łańcuchy Języki formalne i automaty. Literatura

Język programowania zbiór reguł określających, które ciągi symboli tworzą program komputerowy oraz jakie obliczenia opisuje ten program.

METODY I JĘZYKI PROGRAMOWANIA PROGRAMOWANIE STRUKTURALNE. Wykład 02

Generator LLgen. Wojciech Complak Generator LLgen - charakterystyka. Generator LLgen -składnia specyfikacji

Typy, klasy typów, składnie w funkcji

Wprowadzenie do kompilatorów

Języki formalne i automaty Ćwiczenia 6

Dr inż. Grażyna KRUPIŃSKA. D-10 pokój 227 WYKŁAD 7 WSTĘP DO INFORMATYKI

Podstawy Kompilatorów

Ćwiczenie 1. Przygotowanie środowiska JAVA

Programowanie obiektowe

GRAMATYKI BEZKONTEKSTOWE

Język programowania DELPHI / Andrzej Marciniak. Poznań, Spis treści

Układy VLSI Bramki 1.0

Obiektowa implementacja parsera klasy LL(1)

Wprowadzenie. Organizacja pracy i środowisko programistyczne. Mirosław Ochodek

Java Podstawy. Michał Bereta

Generator YACC: gramatyki niejednoznaczne

LABORATORIUM 3 ALGORYTMY OBLICZENIOWE W ELEKTRONICE I TELEKOMUNIKACJI. Wprowadzenie do środowiska Matlab

Wstęp do informatyki- wykład 2

Instrukcja do ćwiczenia P4 Analiza semantyczna i generowanie kodu Język: Ada

Informatyka I. Typy danych. Operacje arytmetyczne. Konwersje typów. Zmienne. Wczytywanie danych z klawiatury. dr hab. inż. Andrzej Czerepicki

Laboratorium nr 5: Mnożenie wektorów i macierzy

2.2. Gramatyki, wyprowadzenia, hierarchia Chomsky'ego

Technologie informacyjne - wykład 12 -

Tablice (jedno i wielowymiarowe), łańcuchy znaków

Nazwa wariantu modułu (opcjonalnie): Laboratorium programowania w języku C++

Jeśli chcesz łatwo i szybko opanować podstawy C++, sięgnij po tę książkę.

Mikrokontroler ATmega32. Język symboliczny

Ćwiczenia nr 11. Translatory. Wprowadzenie teoretyczne

Wybrane narzędzia do tworzenia analizatorów leksykalnych i składniowych w C/C++ Narzędzia, zastosowanie oraz próby rankingu.

Algorytm. a programowanie -

Wybrane narzędzia do tworzenia analizatorów leksykalnych i składniowych w Javie

Gramatyki (1-2) Definiowanie języków programowania. Piotr Chrząstowski-Wachjtel

Programowanie C++ Wykład 2 - podstawy języka C++ dr inż. Jakub Możaryn. Warszawa, Instytut Automatyki i Robotyki

Myśl w języku Python! : nauka programowania / Allen B. Downey. Gliwice, cop Spis treści

Algorytmy od problemu do wyniku

Delphi Laboratorium 3

Ćwiczenie 1. Wprowadzenie do programu Octave

Transkrypt:

Semantyka i Weryfikacja Programów - Laboratorium 6 Analizator leksykalny i składniowy - kalkulator programowalny Cel. Przedstawienie zasad budowy i działania narzędzi do tworzenia kompilatorów języków wysokiego poziomu na przykładzie analizatora wyrażeń arytmetycznych kalkulatora programowalnego.. Wprowadzenie Na definicję języka programowania składają się następujące elementy: struktura leksykalna syntaktyka (stałe arytmetyczne, słowa kluczowe, identyfikatory, operatory), opisywana przez wyrażenia regularne (regular expression), gramatyka struktura wyrażeń, deklaracji, definicji, programu..., opisywana przez gramatykę bezkontekstową (context-free grammar), sposób obliczania wyrażeń i wykonywania instrukcji.. Program najpierw jest sprawdzany pod względem leksykalnym i składniowym, a następnie tworzy się kod wykonywalny lub pośredni zależny od typu procesora i środowiska operacyjnego. Tym ostatnim zagadnieniem zajmuje się semantyka operacyjna. Przykład - kalkulator. Jako przykład posłuży dalej program kalkulatora arytmetycznego wykonującego dodawanie, odejmowanie, mnożenie i dzielenie w dziedzinie liczb całkowitych. Dopuszcza się też stosowanie nawiasów. Wyrażenie arytmetyczne podaje się w postaci łańcucha, np.: 2+3*4 Podobne zagadnienie było prezentowane na dwóch poprzednich laboratoriach. Narzędziem realizacyjnym był język ML. Wyrażenie podawane było w postaci symbolicznej, co stanowiło pewną niedogodność. Teraz zostanie opracowany w pełni funkcjonalny program translatora. Przekształcenia wyrażenia podanego w postaci tekstowej przebiegają w dwóch etapach: lekser (zwany skanerem) wyodrębnia jednostki leksykalne zwane tokenami: tu są to stałe całkowite, nawiasy i operatory, parser (analizator składniowy) buduje z pobieranych tokenów drzewo syntaktyczne pokazane na rys. 1. + * 2 3 4 Rys. 1. Drzewo syntaktyczne wyrażenia 2+3*4

Na podstawie drzewa syntaktycznego kompilator generuje kod wykonywalny np. w pliku *.exe. Możliwa jest także praca w trybie interpretera, jak to pokazano na rys. 2. Odbywa się to podobnie jak przy pracy z interpreterem MosML. Przykład c.d. W podanym wyżej przykładzie na podstawie drzewa syntaktycznego pokazanego na rys. 1 tworzona jest reprezentacja wyrażenia zapisana w odwrotnej notacji polskiej 3,4,*,2,+ wykonywana za pomocą prostego programu interpretera. wejście program analizator leksykalny LEXER jednostki leksykalne analizator składniowy PARSER drzewo syntaktyczne interpreter Rys. 2. Zasada tłumaczenia i wykonywania programu wyjście Analizatory leksykalne i składniowe (Lexer i Parser) są budowane dla wielu języków programowania. Powstały programy narzędziowe do ich tworzenia dostępne w językach C, JAVA, ML i innych. Rysunek 3 przedstawia zasadę przetwarzania plików za pomocą narzędzi dostarczanych w pakiecie MosML. definicje jednostek leksykalnych *.lex mosmllex analizator leksykalny *.sml treść programu, dane interpreter definicja składni *.grm mosmlyac analizator składniowy *.sml wyniki Rys. 3. Zasada tworzenia interpretera języka programowania w pakiecie MosML pracującego w trybie interaktywnym Użytkownik przygotowuje pliki źródłowe z definicjami jednostek leksykalnych i składni (zwykle są identyczne dla różnych implementacji języka i zgodne z odpowiednią normą ANSI). Po ich przetworzeniu przez programy mosmllex.exe i mosmyac.exe otrzymuje się analizatory w plikach z rozszerzeniem *.sml. Pliki te są można dołączyć w trybie interaktywnym (interpretera) pakietu MosML wywołując zawarte w nich funkcje i wykonując zależne od implementacji akcje semantyczne.. Szczegóły takiego postępowania podano niżej. Wyrażenia regularne (regular expressions) Jednostki leksykalne wygodnie jest definiować jako wyrażenia regularne. Typowe elementy w językach wysokiego poziomu można nieformalnie zdefiniować jako: liczba całkowita ciąg cyfr o dowolnej długości, może być poprzedzona znakiem - identyfikator ciąg liter i cyfr rozpoczynający się od litery

słowo kluczowe podobnie jak identyfikator, podawane w postaci zbioru skończonego Do budowania wyrażeń regularnych stosuje się specjalnej notacji z symbolami i operatorami podanymi w tablicy 1. Tabl. 1. Symbole stosowane w definiowaniu jednostek leksykalnych symbol opis przykład 'a' znak a, (, : łańcuch konkretny łańcuch while, let, ::, >= [ a b c ] [ a - z 0 9 ] jedna z wymienionych liter litera lub cyfra z zakresu + symbol występuje co najmniej raz [0-9]+ liczba całkowita bez znaku? opcjonalne wystąpienie [-+]?[0-9]+ liczba ze znakiem (opcja) * występuje wielokrotnie lub wcale eof, eol koniec pliku, linii Dla każdego wyrażenia regularnego istnieje automat skończenie stanowy, co pozwala zautomatyzować proces tworzenia skanera na podstawie definicji. Przykład kalkulator c.d. Definicje dla generatora jednostek leksykalnych są następujące (symbole reprezentujące jednostki leksykalne PLUS, MINUS, TIMES, itd. zostały zdefiniowane w przedstawionym dalej analizatorze składniowym): { val intofstring = valof o Int.fromString } rule Token = parse [` ` `\t`] { Token lexbuf } (* pomin odstępy *) [`\n` ] { EOL } (* koniec linii *) eof { EOF } (* koniec pliku *) [`0`-`9`]+ { INT(intOfString (getlexeme lexbuf)) } `+` { PLUS } (* zwroc symbolicznie PLUS `-` { MINUS } `*` { TIMES } `(` { LPAREN } (* lewostronny nawias *) `)` { RPAREN } `~` { UMINUS } _ { raise Fail "illegal symbol" } Z lewej strony podano zbiór definicji wyrażeń regularnych: odstępy, znak nowej linii i końca pliku, liczbę całkowitą i operatory. W nawiasach klamrowych podano wartość symboliczną zwracaną przez skaner po rozpoznaniu wyrażenia. Należy tu wyjaśnić ważniejsze elementy związane z językiem ML. Token: fn: lexbuf -> token - nazwa funkcji głównej utworzonej na podstawie podanych definicji, to ona zwraca kolejne rozpoznane w tekście jednostki leksykalne, czyli tokeny. Specjalne typy lexbuf i token zdefiniowano w pliku Lexing.sig w katologu lib pakietu MosML.

W pierwszym wariancie funkcja Token napotykając znak odstępu przechodzi do dalszej analizy nic nie zwracając. W drugim i trzecim rozpoznawane są znaki końca linii i pliku. W czwartym (liczba całkowita) dana typu lexbuf przekształcana jest najpierw do typu string przez funkcję biblioteczną getlexeme, a następnie do typu int przez intofstring zdefiniowaną następująco: val intofstring = valof o Int.fromString Składnia wyrażeń Składają się na nią: symbole terminalne: identyfikatory, stałe, czyli tokeny generowane przez lexer, symbole nieterminalne, służące do denotacji klas, symbol startowy, który jest nieterminalny reguły lub inaczej produkcje do definicji symboli. Zilustruje to przykład definicji gramatyki kalkulatora: Main ::= Expr EOL Expr::= INT LPAREN Expr RPAREN Expr PLUS Expr Expr MINUS Expr Expr TIMES Expr UMINUS Expr Symbolem startowym i zarazem terminalnym jest tu Main, elementy terminalne to wcześniej zdefiniowane jednostki leksykalne (liczby, operatory nawiasy). Symbolem nieterminalnym opisującym budowę wyrażenia jest Expr. Przyjęto, że program zawarty jest w łańcuchu lub wprowadzany z klawiatury. Składa się on z wyrażenia Expr i znaku końca linii. Definicja wyrażenia Expr jest rekurencyjna. Może to być liczba całkowita, wyrażenie w nawiasach, operacja dodawania, odejmowania, mnożenia lub dzielenia albo zmiany znaku. Przy definiowaniu gramatyki istotne jest podanie łączności i priorytetu operatorów. Realizacja w pakiecie MosML. Na podstawie powyższych definicji zapisanych odpowiednio w pliku źródłowym zwykle z rozszerzeniem *.grm analizator składniowy generowany jest automatycznie przez program mosmlyac.exe. Format pliku źródłowego podzielonego na sekcje znakami %{, }% i %% jest następujący: %{ (* nagłówek funkcje w języku ML*) }% deklaracje tokenów i symboli %% reguły produkcji dla symboli %% Deklaracje podawane są po jednej w linii. Zaczynają się od znaku %. Mogą one mieć postać: %token symbol...symbol deklaracja tokenów (symboli terminalnych) bezargumentowych. %token <type> symbol...symbol deklaracja symboli jw., ale o podanym typie, przykładem jest deklaracja: %token <int> INT, w której symbol INT, użyty w deklaracji jednostek leksykalnych zwraca liczbę typu int, %start symbol symbol (może ich być kilka) startowy gramatyki,

%left symbol symbol (zwykle operator) lewostronnie łączny, %right symbol prawostronnie łączny, %nonassoc niełączny (zwykle jednoargumentowy), Kolejność definicji symboli decyduje o ich priorytecie. Reguły mają postać podobną do notacji BNF: albo: terminal_symbol: symbol...symbol { (* akcja semantyczna *)} symbol...symbol { (* akcja semantyczna *)}... nonterminal_symbol: symbol...symbol { (* akcja semantyczna *) symbol...symbol { (* akcja semantyczna *)}... Akcja semantyczna to wyrażenie w języku ML. Stosuje się w nim umowne oznaczenie symboli występujących w deklaracji reguły, co zostanie wyjaśnione na przykładzie poniżej. Definicja składni kalkulatora jest zatem następująca: %token <int> INT /* symbol z podanym typem */ %token PLUS MINUS TIMES UMINUS /* operatory */ %token LPAREN RPAREN /* nawiasy */ %token EOL EOF /* koniec linii, pliku */ %left PLUS MINUS /* najnizszy priorytet */ %left TIMES /* średni priorytet */ %nonassoc UMINUS /* najwyzszy priorytet */ %type <int> Expr /* symbol Expr jest typu int */ %start Main /* punkt startowy */ %type <int> Main /* typ symbolu */ %% Main: Expr EOL { $1 } Expr: INT { $1 } LPAREN Expr RPAREN { $2 } Expr PLUS Expr { $1 + $3 } Expr MINUS Expr { $1 - $3 } Expr TIMES Expr { $1 * $3 } UMINUS Expr { ~ $2 } W pierwszej części pliku podano deklaracje tokenów, które są zwracane przez analizator leksykalny. Prawie wszystkie są bez podanego typu. Dla tokenu INT podano typ int. Analizator leksykalny wyodrębniwszy liczbę całkowitą zwróci symbolicznie INT oraz

wartość arytmetyczną liczby. W języku ML tokeny są to konstruktory bezargumentowe lub z argumentem (tutaj int). W drugiej części podano typy symboli oraz symbol terminalny (startowy) gramatyki. Po znakach %% znajduje się definicja gramatyki z podanymi akcjami semantycznymi jako wyrażenia w języku ML. Użyto tu specjalnego symbolu $ z podanym numerem. Np $1 reprezentuje wartość pierwszego symbolu w danej produkcji (linii definicji). W definicji INT { $1 } wartością wyrażenia jest liczba typu int (dla symbolu INT zdefiniowano wcześniej typ). W kolejnej definicji Expr PLUS Expr { $1 + $3 } sumy wyrażeń symbole Expr (oznaczone dalej $1 i $3, bo są pierwszym i trzecim elementem definicji) są typu int i w akcji semantycznej kalkulatora należy je dodać. Praca w trybie interaktywnym. Programy pakietu MosML powstały do pracy w trybie tekstowym. Dla wygody pracy w systemie Windows przygotowano katalog o nazwie calc z następującą zawartością: mosmllex.exe, mosmlyac.exe programy narzędziowe przekopiowane z katalogu /bin pakietu MosML Lexer.lex, Parser.grm definicje jednostek leksykalnych i składniowych calc.sml plik interpretera (opisany dalej) yacc.bat, lex.bat pliki wsadowe systemu DOS do kompilacji źródeł. Kompilacja. Celem uruchomienia interpretera należy: Utworzyć plik Parser.sml za pomocą programu mosmlyac.exe uruchamiając plik wsadowy yacc.bat. Utworzyć plik Lexer.sml kompilując plik Lexer.lex za pomocą lex.bat (wywołującej program mosmllex.exe). Uruchomić VisualML. Otworzyć plik calc.sml. Do wykorzystania powstałych analizatorów napisano następujący plik główny interpretera calc.sml. load "BasicIO" (* dołączenie bibliotek *) load "Nonstdio" load "Lexing" load "Parsing" load "Int" use "Parser.sml" (* plik analizatora składniowego *) use "Lexer.sml" (* plik analizatora leksykalnego *) (*definicja wyrażenia ze znakiem końca linii *) val wyraz = " (2*3+4)-(7*2)\n" (* utworzenie danej typu lexbuf z łańcucha *) val lexbuf = Lexing.createLexerString wyraz (* string -> lexbuf *) (* wywołując wielokrotnie funkcję Token otrzymujemy kolejne rozpoznane jednostki leksykalne *)

Token lexbuf Token lexbuf Token lexbuf (* itd. *) (* ponowne utworzenie danej lexbuf *) val lexbuf = Lexing.createLexerString wyraz (* string -> lexbuf *) (* automatyczne obliczenie wyrażenia *) val result:int = Main Token lexbuf Wyżej podano komentarze. Na uwagę zasługuje szczególnie ostatnia linia. Pojawia się tu funkcja Main zawarta w pliku Parser.sml utworzona na podstawie definicji symbolu startowego w pliku Parser.sml. Funkcja Token z pliku Lexer.sml powstała na podstawie definicji rule Token =... z pliku Lexer.lex. Przebieg ćwiczenia 1. Pobierz ze strony przedmiotu katalog calc z zawartymi w nim plikami. 2. Wykonaj kompilację plików źródłowych za pomocą plików wsadowych yacc.bat i lex.bat. 3. Uruchom środowisko VisualML. 4. Wczytaj plik calc.sml. 5. Wywołując wielokrotnie funkcję Token sprawdź, czy jednostki leksykalne pobierane są prawidłowo. 6. Policz inne wyrażenie arytmetyczne. Zadania do samodzielnego wykonania 1. Wprowadź do programu kalkulatora operator dzielenia. Wskazówka: najpierw zdefiniuj token DIV w pliku parser.grm, następnie zapisz dla niego akcję semantyczną oraz zdefiniuj w pliku lexer.lex operator dzielenia. 2. Zmodyfikuj zabezpiecz funkcję dzielenia przed możliwością wystąpienia błędu stosując konstrukcję handle. 3. Zmodyfikuj program kalkulatora tak, by wykonywał obliczenia na liczbach rzeczywistych. Tworzenie pliku wykonywalnego Wyrażenia można przetwarzać nie tylko w trybie interaktywnym, ale także za pomocą programu wykonywalnego otrzymanego za pomocą kompilatora języka ML mosmlc. W tym przypadku należy przygotować pliki źródłowe zastępując instrukcje load i use przez open, czyli dołączając moduły wstępnie skompilowane. Plik z definicjami jednostek leksykalnych lexer.lex jest następujący: { open Parser open Nonstdio val intofstring = valof o Int.fromString } rule Token = parse [` ` `\t`] { Token lexbuf } (* skip blanks *)

... dotychczasowe deklaracje W pliku definicji składni parser.grm żadne zmiany nie są konieczne. Główny plik calc.sml po modyfikacji ma postać: open BasicIO open Lexing open Int open Lexer open Nonstdio open Parsing open Parser val wyraz = " (2*3+4)-(7*2)\n" val lexbuf = Lexing.createLexerString wyraz (* string -> lexbuf *) val result:int = Main Lexer.Token lexbuf val _ = print (Int.toString result) Wywołując funkcje, należy podawać podobnie jak dla modułów bibliotecznych w którym module występują. Program wyprowadza wynik obliczeń na monitor. Przebieg kompilacji. Polecenia wykonywane w trybie MSDOS, np. w środowisku programu Norton Commander w sposób podany w tabeli. polecenie mosmlyac parser.grm mosmllex lexer.lex mosmlc c parser.sig mosmlc c parser.sml mosmlc c lexer.sml mosmlc calc.sml rezultat parser.sig parser.sml lexer.sml parser.ui parser.uo lexer.ui lexer.uo mosmlout.exe Plik mosmlout.exe (nazwę można zmienić) jest plikiem wykonywalnym programem nie wymagającym interpretera MosML. Polecenia do samodzielnego wykonania 1. Zdefiniować jednostki leksykalne: adresy stron www, adresy kont email, rejestracje samochodowe. 2. 3. Przekształć plik calc.sml tak, by wyrażenia były pobierane z pliku wykorzystanie funkcji bibliotecznych 4. Utwórz kalkulator logiczny ze stałymi t i f i operatorami jak w języku C. 5. Wprowadź instrukcje umożliwiające diagnostykę błędów syntaktycznych w podawanym wyrażeniu.