Metody realizacji języków programowania/MRJP Laboratorium: Różnice pomiędzy wersjami

Z Studia Informatyczne
Przejdź do nawigacjiPrzejdź do wyszukiwania
Mbiskup (dyskusja | edycje)
Mbiskup (dyskusja | edycje)
 
(Nie pokazano 88 wersji utworzonych przez 2 użytkowników)
Linia 1: Linia 1:
Autor: Marek Biskup (mbiskup@mimuw.edu.pl)
= Wstęp =
= Wstęp =


Tematem laboratorium metod realizacji języków programowania jest zaimplementowanie kompletnego kompilatora języka ''Kotek''.
Tematem Laboratorium Metod Realizacji Języków Programowania jest zaimplementowanie kompletnego kompilatora języka ''Kotek''.


= Język kotek =
= Język kotek =


''Kotek'' jest obiektowym językiem programowania, zbliżonym do języków takich jak ''Pascal'', czy ''Java''. Podstawowe cechy ''Kotka'' to:
''Kotek'' jest obiektowym językiem programowania zbliżonym do języków takich jak ''Pascal'' czy ''Java''. Podstawowe cechy ''Kotka'' to:


* obecność zwykłych konstrukcji takich jak pętle '''for''' i '''while''', wyrażeń arytmetycznych, logicznych
* obecność zwykłych konstrukcji takich jak pętle '''for''' i '''while''', wyrażeń arytmetycznych, logicznych,
* silne typowanie - zgodność typów jest sprawdzana w czasie kompilacji
* silne typowanie - zgodność typów jest sprawdzana w czasie kompilacji,
* możliwość deklarowania funkcji
* możliwość deklarowania funkcji,
* obecność struktur danych takich jak tablice i rekordy
* obecność struktur danych takich jak tablice i rekordy,
* możliwość deklarowania klas i tworzenia obiektów
* możliwość deklarowania klas i tworzenia obiektów,
* lokalne deklaracje funkcji i klas
* lokalne deklaracje funkcji i klas,
* dynamiczna alokacja obiektów, rekordów i tablic (na stercie)
* dynamiczna alokacja obiektów, rekordów i tablic (na stercie).


== Gramatyka ''Kotka'' ==


Gramatyka języka jest opisana na stronie [[/Gramatyka Kotka|Gramatyka ''Kotka'']]


Etapy pisania kompilatora
== Semantyka ''Kotka'' ==
# podstawowe konstrukcje: '''for''', '''while''', zmienne tylko typu int i string, proste
operacje arytmetyczne, funkcje globalne, rekurencja (też wzajemna)
# rekordy, tablice,dynamiczna alokacja, zwalnianie jawne ('''delete''')
# lokalne procedury
# klasy deklarowane na poziomie globalnym
# klasy lokalne
# deklaracje zmiennej z podaną wartością do inichalizacji


Możliwe rozszerzenia
Semantyka języka jest opisana na stronie [[/Semantyka Kotka|Semantyka ''Kotka'']]
# zmienne '''double''', '''boolean'''
 
# dodatkowe operatory dla wyrażeń (++, --, +=, operatory bitowe, etc)
= Zadania =
# odśmiecanie zamiast '''delete'''


= Gramatyka języka =
== Zadanie 0: Wybierz narzędzia i platformę docelową ==


== Struktura leksykalna języka Kotek ==
Wybierz platformę docelową kompilatora języka ''Kotek''. Zalecane są platforma ''x86'' i system operacyjny ''Linux'' (kod byłby generowany w asemblerze ''x86'', np. w formacie akceptowanym przez kompilator ''gcc''), bądź ''Maszyna Wirtualna Javy'' (kod generowany w asemblerze JVM, np. w formacie akceptowanym przez asembler ''Jasmin'').


===Identyfikatory===
Następnie wybierz język programowania, w którym będzie pisany sam kompilator. Ten język nie ma żadnego związku z platformą docelową. Zorientuj się, jakie narzędzia są dostępne dla języka, na który się decydujesz.
Identyfikatory to ciągi znaków zaczynające się literą, a zawierające dowolną kombinację liter, cyfr, i znaków  podkreślenia <tt>_</tt>, poza słowami kluczowymi.


===Literały===
* język ''C/C++'': generator analizatorów leksykalnych ''lex'' lub ''flex'', generator parserów ''yacc'' lub ''bison'',
Literały napisowe ''<String>'' mają postać '''"'''<nowiki></nowiki>''x''<nowiki></nowiki>'''"''', gdzie ''x'' jest dowolnym ciągiem znaków z wyjątkiem '''"''', chyba że jest on poprzedzony przez '''\'''.
* język ''Java'': generator analizatorów leksykalnych ''JFlex'', generator parserów ''Coco'', ''CUP'', ''Javacc'', ''ANTLR'',
* język ''Ocaml'': ''ocamllex'', ''ocamlyacc''.


Literały liczbowe ''<Int>'' to niepuste ciągi cyfr.
Niektóre narzędzia wspierają też budowanie drzewa składniowego i jego przekształcanie.


===Zarezerwowane słowa i symbole===
== Zadanie 1: Analizator leksykalny ==
Zbiór zarezerwowanych słów to zbiór terminali z gramatyki. Te słowa kluczowe składają się ze znaków nie będących literami są zwane symbolami. Są one traktowane w inny sposób niż te, które są podobne do identyfikatorów. Reguły leksykalne są takie same jak w językach Haskell, C, i Java, włączając reguły dla najdłuższego dopasowania i konwencje odnośnie białch znaków.


Słowa kluczowe używane w Kotku to :
Napisz analizator leksykalny do kompilatora języka ''Kotek''. Użyj generatora analizatorów leksykalnych (takich jak ''lex'' dla ''C'') generującego kod w języku, w którym będziesz pisał kompilator. Analizator powinien rozpoznawać konstrukcje leksykalne ''Kotka'': identyfikatory, operatory, stałe napisowe, komentarze (jeśli Twój generator analizatorów leksykalnych umożliwia obsługę zagnieżdżonych komentarzy, to powinny one być usuwane już na tym etapie).


<table>
Przetestuj swój analizator leksykalny pisząc program, który zamienia ciąg wejściowy na ciąg leksemów, np. dla wejścia:
<tr>
<td width="20%"><tt>'''array'''</tt> </td>
<td width="20%"><tt>'''class'''</tt> </td>
<td width="20%"><tt>'''delete'''</tt> </td>
</tr>
<tr>
<td><tt>'''do'''</tt> </td>
<td><tt>'''done'''</tt> </td>
<td><tt>'''else'''</tt> </td>
</tr>
<tr>
<td><tt>'''endif'''</tt> </td>
<td><tt>'''extends'''</tt> </td>
<td><tt>'''function'''</tt> </td>
</tr>
<tr>
<td><tt>'''if'''</tt> </td>
<td><tt>'''int'''</tt> </td>
<td><tt>'''new'''</tt> </td>
</tr>
<tr>
<td><tt>'''null'''</tt> </td>
<td><tt>'''of'''</tt> </td>
<td><tt>'''program'''</tt> </td>
</tr>
<tr>
<td><tt>'''read'''</tt> </td>
<td><tt>'''return'''</tt> </td>
<td><tt>'''string'''</tt> </td>
</tr>
<tr>
<td><tt>'''super'''</tt> </td>
<td><tt>'''then'''</tt> </td>
<td><tt>'''this'''</tt> </td>
</tr>
<tr>
<td><tt>'''type'''</tt> </td>
<td><tt>'''var'''</tt> </td>
<td><tt>'''void'''</tt> </td>
</tr>
<tr>
<td><tt>'''while'''</tt> </td>
<td><tt>'''write'''</tt> </td>
<td>
</td>
</tr>
</table>


Symbole używane w Kotku to:
asdf + qwer * != ( "napis\"napis"


<table>
wynikiem działania programu testowego powinno być:
<tr>
<td width="20%"><tt>''';'''</tt></td>
<td width="20%"><tt>'''{'''</tt></td>
<td width="20%"><tt>'''}'''</tt></td>
</tr>


<tr>
IDENTYFIKATOR("asdf") OPERATOR_PLUS IDENTYFIKATOR("qwer") OPERATOR_RAZY
<td><tt>'''='''</tt></td>
OPERATOR_RÓŻNE OPERATOR_LNAWIAS, STRING("napis\"napis")
<td><tt>''','''</tt></td>
<td><tt>''':'''</tt></td>
</tr>


<tr>
lub równoważny zapis.
<td><tt>'''('''</tt></td>
<td><tt>''')'''</tt></td>
<td><tt>''':='''</tt></td>
</tr>


<tr>
== Zadanie 2: Analizator składniowy ==
<td><tt>'''['''</tt></td>
<td><tt>''']'''</tt></td>
<td><tt>'''.'''</tt></td>
</tr>


<tr>
Napisz parser języka ''Kotek'' wykorzystując generator parserów (np. ''yacc'' dla ''C''). Wynikiem analizy składniowej ma być drzewo składni, w którym każdy węzeł odpowiada pewnej produkcji w kodzie źródłowym (lewej stronie), dziećmi węzła są natomiast elementy z prawej strony produkcji. Liśćmi są terminale.
<td><tt>'''-'''</tt></td>
<td><tt>'''+'''</tt></td>
<td><tt>'''!'''</tt></td>
</tr>


<tr>
Dopuszczalne są modyfikacje drzewa składniowego, które upraszczają jego strukturę, a nie gubią żadnych istotnych informacji.  Np. zamiast przechowywania listy deklaracji jako węzła, który zawiera pojedynczą deklarację i drugą listę deklaracji, dopuszczalne (a nawet zalecane) jest używanie standardowych kontenerów języka programowania, w którym kompilator jest pisany.
<td><tt>'''*'''</tt></td>
<td><tt>'''/'''</tt></td>
<td><tt>'''<'''</tt></td>
</tr>


<tr>
Jeśli używasz języka obiektowego, zalecane jest stworzenie oddzielnej klasy dla każdego typu produkcji. Obiekty należy w miarę możliwości i potrzeb powiązać związkami dziedziczenia. Zwykle dobrym pomysłem jest stworzenie oddzielnej klasy dla lewej strony produkcji i podklas dziedziczących z niej dla każdej prawej strony produkcji, w której ta lewa strona występuje. Na przykład możemy mieć klasę ''Instrukcja'', z której dziedziczą m.in. klasy ''Przypisanie'', ''Blok'', ''PustaInstrukcja'' i ''ZłożonaInstrukcja''. Z kolei z klasy ''ZłożonaInstrukcja'' będą dziedziczyć klasy ''InstrukcjaIfElse'', ''InstrukcjaIf'' oraz ''InstrukcjaWhile''.
<td><tt>'''>'''</tt></td>
<td><tt>'''<='''</tt></td>
<td><tt>'''>='''</tt></td>
</tr>


<tr>
Hierarchia klas węzłów drzewa składniowego powinna mieć wspólny korzeń (nazwany np. ''Węzeł''). Ten węzeł będzie zawierał abstrakcyjne metody, które są wymagane w innych węzłach, np. metoda ''gen()'', generująca kod na maszynę docelową, metoda ''sem()'' przeprowadzająca analizę semantyczną (uwaga: jeśli analiza semantyczna będzie robiona w kilku przebiegach, to warto stworzyć więcej metod ''sem'', np. ''sem1'' i ''sem2'').
<td><tt>'''=='''</tt></td>
<td><tt>'''!='''</tt></td>
<td><tt>'''||'''</tt></td>
</tr>


<tr>
Napisz program testujący parser. Program testujący powinien działać jak ''pretty printer'', tzn. wypisywać treść programu na podstawie drzewa składniowego zawartego w pamięci.
<td><tt>'''&&'''</tt></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
</table>


===Komentarze===
=== Zadanie 2.a: Poprawność wyrażeń na etapie parsowania ===
Komentarze jednolinijkowe zaczynają się <tt>'''//'''</tt> i trwają do końca linii.
Komentarze wielolinijkowe są otoczone <tt>'''(*'''</tt> i <tt>'''*)'''</tt>. Komentarze wielolinijkowe mogą być zagnieżdżone.


==Struktura syntaktyczna języka Kotek==
Gramatyka języka dopuszcza pewne wyrażenia, które nie poprawne, np:
Nieterminale otoczone $lt; i &gt. Symbole ::=  (produkcja),  |  (suma)
i &epsilon; (pusta reguła) należą do notacji BNF (uwaga, symbol '''||''' jest z języka Kotek). Wszystkie inne sybmole są terminalami.


<!--        Program -->
  funkcja(param)(param2)
<table>
  zmienna[indeks](param)
<tr>
<td>''<Program>'' </td><td>::=  </td>
<td><tt>'''program'''</tt> <tt>''';'''</tt> ''<Ciało>''  </td>
</tr>
<!--        Ciało  -->
<tr><td>&nbsp;</td><td> </td><td> </td></tr>
<tr><td>''<Ciało>'' </td> <td>::=  </td>
<td>''<ListaDeklaracji>'' ''<Blok>''  </td>
</tr>
<!--        Blok      -->
<tr><td>&nbsp;</td><td> </td><td> </td></tr>
<tr><td>''<Blok>'' </td> <td>::=  </td>
<td><tt>'''{'''</tt> ''<ListaInstrukcji>'' <tt>'''}'''</tt>  </td></tr>
<!--        Lista deklaracji      -->
<tr><td>&nbsp;</td><td> </td><td> </td></tr>
<tr><td>''<ListaDeklaracji>'' </td><td>::=  </td><td> &epsilon;</td></tr>
<tr><td></td><td>|  </td><td>''<Deklaracja>'' ''<ListaDeklaracji>''  </td></tr>
<!-----------------Deklaracja-------------->
<tr><td>&nbsp;</td><td> </td><td> </td></tr>
<tr><td>''<Deklaracja>'' </td><td>::=  </td><td>''<DeklaracjaTypu>''  </td></tr>
<tr><td></td><td>|</td><td>''<DeklaracjaZmiennej>'' </td> </tr>
<tr><td></td><td>|</td><td>''<DeklaracjaFunkcji>''  </td> </tr>
<tr><td></td><td>|</td><td>''<DeklaracjaKlasy>''    </td> </tr>
<!--------------------Deklaracja typu------------->
<tr><td>&nbsp;</td><td> </td><td> </td></tr>
<tr><td>''<DeklaracjaTypu>'' </td> <td>::=  </td>
<td><tt>'''type'''</tt> ''<Ident>'' <tt>'''='''</tt> ''<OpisTypu>''  </td>
</tr>
<!---------------------Opis Typu ----------------->
<tr><td>&nbsp;</td><td> </td><td> </td></tr>
<tr><td>''<OpisTypu>'' </td><td>::=  </td><td>''<Ident>''  </td></tr>
<tr><td></td><td>|</td><td><tt>'''{'''</tt> ''<ListaDeklaracjiZmiennej>'' <tt>'''}'''</tt></td></tr>
<tr><td></td><td>|</td><td><tt>'''array'''</tt> <tt>'''of'''</tt> ''<Typ>''  </td></tr>
<!----------------Lista deklaracji zmiennej ------------->
<tr><td>&nbsp;</td><td> </td><td> </td></tr>
<tr><td>''<ListaDeklaracjiZmiennej>''</td><td>::=</td> <td>''<DeklaracjaZmiennej>''</td></tr>
<tr><td></td><td>|</td><td>''<DeklaracjaZmiennej>'' <tt>''','''</tt> ''<ListaDeklaracjiZmiennej>''  </td></tr>
<!------------------Typ---------------------------------->
<tr><td>&nbsp;</td><td> </td><td> </td></tr>
<tr><td>''<Typ>'' </td><td>::=</td><td>''<Ident>''</td></tr>
<tr><td></td><td>|</td><td><tt>'''string'''</tt>  </td></tr>
<tr><td></td><td>|</td><td><tt>'''int'''</tt>  </td></tr>
<tr><td></td><td>|</td><td><tt>'''void'''</tt>  </td></tr>
<!-----------------Deklaracja zmiennej--------------->
<tr><td>&nbsp;</td><td> </td><td> </td></tr>
<tr><td>''<DeklaracjaZmiennej>'' </td><td>::= </td><td><tt>'''var'''</tt> ''<Ident>'' <tt>''':'''</tt> ''<Typ>''</td></tr>
<!-----------------Deklaracja Funkcji---------------------->
<tr><td>&nbsp;</td><td> </td><td> </td></tr>
<tr><td>''<DeklaracjaFunkcji>'' </td><td>::=  </td><td><tt>'''function'''</tt> ''<Ident>'' <tt>'''('''</tt> ''<DeklaracjaArgumentow>'' <tt>''')'''</tt> <tt>''':'''</tt> ''<Typ>'' ''<Cialo>''  </td></tr>
<!--------------------------Deklaracja Argumentów----------------------->
<tr><td>&nbsp;</td><td> </td><td> </td></tr>
<tr><td>''<DeklaracjaArgumentów>''</td><td>::=</td><td>''<ListaDeklaracjiZmiennej>'' </td></tr>
<tr><td> </td><td>|  </td><td>&epsilon;</td></tr>
<!-----------------Lista instrukcji ------------------------->
<tr><td>&nbsp;</td><td> </td><td> </td></tr>
<tr><td>''<ListaInstrukcji>'' </td><td>::=  </td><td>&epsilon;</td></tr>
<tr><td></td><td>| </td><td>''<Instrukcja>'' ''<ListaInstrukcji>''  </td></tr>
<!------------------Instrukcja-------------------------------->
<tr><td>&nbsp;</td><td> </td><td> </td></tr>
<tr><td>''<Instrukcja>'' </td><td>::=  </td><td>''<Wyrażenie>'' <tt>;</tt>  </td></tr>
<tr><td></td><td>|</td><td>''<ZłożonaInstrukcja>'' <tt>''';'''</tt>  </td></tr>
<tr><td></td><td>|</td><td>''<WyrażeniePostfiksowe>''
          <tt>''':='''</tt> ''<Wyrażenie>'' <tt>''';'''</tt>  </td></tr>
<tr><td></td><td>|</td><td>''<Blok>''  </td></tr>
<tr><td></td><td>|</td><td><tt>'''delete'''</tt> ''<Wyrażenie>'' <tt>''';'''</tt>
        </td></tr>
<tr><td></td><td>|</td><td><tt>;</tt>  </td></tr>
<tr><td></td><td>|</td><td><tt>'''read'''</tt> ''<Ident>'' <tt>''';'''</tt>  </td>
</tr>
<tr><td></td><td>|</td><td><tt>'''write'''</tt> ''<Wyrażenie>'' <tt>
            ''';'''</tt></td></tr>
<tr><td></td><td>|</td><td><tt>'''return'''</tt> ''<Wyrażenie>'' <tt>''';'''</tt>
        </td></tr>
<tr><td></td><td>|  </td><td><tt>'''return'''</tt> <tt>''';'''</tt>  </td></tr>
<!------------------Wyrażenie Podstawowe ------------------->
<tr><td>&nbsp;</td><td> </td><td> </td></tr>
<tr><td>''<WyrazeniePodstawowe>'' </td><td>::=  </td><td>''<Ident>''  </td></tr>
<tr><td></td><td>|</td><td>''<String>''  </td></tr>
<tr><td></td><td>|</td><td>''<Int>''  </td></tr>
<tr><td></td><td>|</td><td><tt>'''('''</tt> ''<Wyrażenie>'' <tt>''')'''</tt>  </td></tr>
<tr><td></td><td>|</td><td><tt>'''this'''</tt> </td></tr>
<tr><td></td><td>|</td><td><tt>'''super'''</tt>  </td></tr>
<tr><td></td><td>|</td><td><tt>'''null'''</tt>  </td></tr>
<!------------------Wyrażenie postfiksowe---------------------->
<tr><td>&nbsp;</td><td> </td><td> </td></tr>
<tr><td>''<WyrazeniePostfiksowe>'' </td><td>::=  </td> <td>''<WyrażeniePostfiksowe>'' <tt>'''['''</tt> ''<Wyrażenie>'' <tt>''']'''</tt></td></tr>
<tr><td></td><td>|  </td> <td>''<WyrażeniePostfiksowe>'' <tt>'''('''</tt> ''<Parametry>'' <tt>''')'''</tt>  </td></tr>
<tr><td></td><td>|  </td><td>''<WyrażeniePostfiksowe>'' <tt>'''.'''</tt> ''<Ident>''</td></tr>
<tr><td></td><td>|  </td><td>''<WyrażeniePodstawowe>''  </td></tr>
<!-----------------------Parametry----------------------------->
<tr><td>&nbsp;</td><td> </td><td> </td></tr>
<tr><td>''<Parametry>'' </td><td>::=  </td><td>&epsilon;</td></tr>
<tr><td> </td><td>|  </td><td>''<ListWyrażenie>''  </td></tr>
<!---------------------Lista wyrażeń--------------------------->
<tr><td>&nbsp;</td><td> </td><td> </td></tr>
<tr><td>''<ListaWyrażeń>'' </td><td>::=  </td><td>''<Wyrażenie>''  </td></tr>
<tr><td></td><td>|  </td><td>''<Wyrażenie>'' <tt>''','''</tt> ''<ListaWyrażeń>''  </td></tr>
<!-------------------- wyrażenie unarne ----------------------->
<tr><td>&nbsp;</td><td> </td><td> </td></tr>
<tr><td>''<WyrażenieUnarne>'' </td><td>::=  </td> <td>''<OperatorUnarny>'' ''<WyrażenieUnarne>''  </td></tr>
<tr><td></td><td>|  </td><td>''<WyrażeniePostfiksowe>''  </td></tr>
<!-----------------------operator unarny ----------------------->
<tr><td>&nbsp;</td><td> </td><td> </td></tr>
<tr><td>''<OperatorUnarny>'' </td><td>::=  </td><td><tt>'''-'''</tt>  </td></tr>
<tr><td></td><td>|  </td><td><tt>'''+'''</tt>  </td></tr>
<tr><td></td><td>|  </td><td><tt>'''!'''</tt>  </td></tr>
<!---------------------- wyrażeniemultiplikatywne -------------------->
<tr><td>&nbsp;</td><td> </td><td> </td></tr>
<tr><td>''<WyrażenieMultiplikatywne>'' </td><td>::=  </td> <td>''<WyrażenieMultiplikatywne>'' ''<OperatorMultiplikatywny>'' ''<WyrażenieUnarne>''  </td></tr>
<tr><td></td><td>|  </td><td>''<WyrażenieUnarne>''  </td></tr>
<!------------------------wyrażenie addytywne------------------------->
<tr><td>&nbsp;</td><td> </td><td> </td></tr>
<tr><td>''<WyrażenieAddytywne>'' </td><td>::=  </td><td>''<WyrażenieAddytywne>''  ''<OperatorAddytywny>'' ''<WyrażenieMultiplikatywne>''  </td></tr>
<tr><td></td><td>|  </td><td>''<WyrażenieMultiplikatywne>''  </td></tr>
<!--------------------operator multiplikatywny-------------------------->
<tr><td>&nbsp;</td><td> </td><td> </td></tr>
<tr><td>''<OperatorMultiplikatywny>'' </td><td>::=  </td><td><tt>'''*'''</tt>  </td></tr>
<tr><td></td><td>|  </td><td><tt>'''/'''</tt>  </td></tr>
<!----------------------operator adytywny------------------------------>
<tr><td>&nbsp;</td><td> </td><td> </td></tr>
<tr><td>''<OperatorAddytywny>'' </td><td>::=  </td><td><tt>'''+'''</tt>  </td></tr>
<tr><td></td><td>|  </td><td><tt>'''-'''</tt>  </td></tr>
<!---------------------wyrażenie porównania--------------------->
<tr><td>&nbsp;</td><td> </td><td> </td></tr>
<tr><td>''<WyrażeniePorównania>'' </td><td>::=  </td><td>''<WyrażenieAddytywne>'' ''<OperatorPorównania>'' ''<WyrażenieAddytywne>''  </td></tr>
<tr><td></td><td>|  </td><td>''<WyrażenieAddytywne>''  </td></tr>
<!--------------------operator porównania-------------------------->
<tr><td>&nbsp;</td><td> </td><td> </td></tr>
<tr><td>''<OperatorPorównania>'' </td><td>::=  </td><td><tt>'''<'''</tt></td></tr>
<tr><td></td><td>|  </td><td><tt>'''>'''</tt>  </td></tr>
<tr><td></td><td>|  </td><td><tt>'''<='''</tt>  </td></tr>
<tr><td></td><td>|  </td><td><tt>'''>='''</tt>  </td></tr>
<tr><td></td><td>|  </td><td><tt>'''=='''</tt>  </td></tr>
<tr><td></td><td>|  </td><td><tt>'''!='''</tt>  </td></tr>
<tr><td>&nbsp;</td><td> </td><td> </td></tr>
<tr><td>''<WyrażenieLogiczne>'' </td><td>::=  </td><td>
''<WyrażeniePorównania>'' ''<OperatorLogiczny>'' ''<WyrażeniePorównania>''</td></tr>
<tr><td></td><td>|  </td><td>''<WyrażeniePorównania>''  </td></tr>
<!--------------------------operator logiczny------------------------>
<tr><td>&nbsp;</td><td> </td><td> </td></tr>
<tr><td>''<OperatorLogiczny>'' </td><td>::=  </td><td><tt>'''||'''</tt></td></tr>
<tr><td></td><td>|  </td><td><tt>'''&&'''</tt></td></tr>
<!---------------------wyrażenie-------------------------->
<tr><td>&nbsp;</td><td> </td><td> </td></tr>
<tr><td>''<Wyrażenie>'' </td><td>::=  </td><td>''<WyrażenieLogiczne>''  </td></tr>
<tr><td></td><td>|</td><td><tt>'''new'''</tt> ''<Typ>''  </td></tr>
<tr><td></td><td>|</td><td><tt>'''new'''</tt> ''<Typ>'' <tt>'''['''</tt> ''<Wyrazenie>'' <tt>''']'''</tt>  </td></tr>
<!------------------złożona instrukcja------------------------>
<tr><td>&nbsp;</td><td> </td><td> </td></tr>
<tr><td>''<ZłożonaInstrukcja>'' </td><td>::=  </td> <td><tt>'''if'''</tt> ''<Wyrażenie>'' <tt>'''then'''</tt> ''<ListaInstrukcji>''  <tt>'''else'''</tt> ''<ListaInstrukcji>'' <tt>'''endif'''</tt>  </td></tr>
<tr><td></td><td>|  </td><td><tt>'''if'''</tt> ''<Wyrażenie>'' <tt>'''then'''</tt>  ''<ListaInstrukcji>'' <tt>'''endif'''</tt>  </td></tr>
<tr><td></td><td>|  </td><td><tt>'''while'''</tt> ''<Wyrażenie>'' <tt>'''do'''</tt>    ''<ListaInstrukcji>'' <tt>'''done'''</tt>  </td></tr>
<tr><td>&nbsp;</td><td> </td><td> </td></tr>
<tr><td>''<DeklaracjaKlasy>'' </td><td>::=  </td><td><tt>'''class'''</tt> ''<Ident>'' <tt>'''extends'''</tt> ''<Ident>'' <tt>'''{'''</tt> ''<ListaDeklaracji>'' <tt>'''}'''</tt>  </td></tr>
<tr><td></td></tr>


</table>
Niepoprawność wynika z tego, że funkcja nie może być wynikiem innej funkcji, funkcja nie może też być przechowywana w tablicy. Czy można zmienić gramatykę tak, aby wykluczała podane wyrażenia? Jakie podejście preferujesz: sprawdzanie poprawności tych wyrażeń na etapie parsera czy analizy semantycznej? Podaj zalety i wady obu metod.


= Semantyka języka =
=== Zadanie 2.b: Poprawność przypisań ===


= Wymagania implementacyjne i ocenianie =
Obecna postać produkcji przypisania:


Na 3:
Instrukcja ::= WyrazeniePostfiksowe ":=" Wyrazenie ";";
Podstawowe konstrukcje, rekordy, tablice, dynamiczna alokacja


Na 4:
dopuszcza pewne błędne przypisania, np.  
Lokalne deklaracje procedur.


Na 5:
"tekst" := 8;
Obiekty, operator '''instanceof''', rzutowanie (por. zadanie FIXME).


Dodatkowo punktowane zadania.
Czy można tak zmienić gramatykę, aby przypisanie zawsze było poprawne? Czy pozwoli to wyeliminować analizę semantyczną tego fragmentu kodu źródłowego?
* Lokalne deklaracje klas,  
* Odśmiecanie zamiast jawnego '''delete'''


= Zadania =
== Zadanie 3: Tablica symboli ==


== Zadanie 1: Inicjalizacja zmiennych ==
Do Twojego kompilatora dopisz kod tworzący tablicę symboli z każdej listy deklaracji. Połącz tablice symboli w graf zgodnie z regułami widoczności nazw. Tablica symboli powinna mieć odniesienie do tablicy symboli nadrzędnej funkcji lub klasy, w której nasza funkcja (lub klasa) została zadeklarowana. W przypadku tablicy symboli klasy dodaj jeszcze odnośniki do tablicy symboli nadklasy. Określ kolejność szukania nazw (najpierw nadklasa, czy nadfunkcja?).


Aby język był bardziej bezpieczny chcielibyśmy wyeliminować możliwość użycia niezainicjalizowanych zmiennych. W tym celu modyfikujemy deklaracje zmiennej tak, aby podanie wartości przy deklaracji było obowiązkowe:
Dla każdej deklaracji określ rozmiar zużytej pamięci, np. w 32 bitowej architekturze x86 zmienne typu '''int''' zużywają 4 bajty, zmienne '''string''' również 4 bajty (napis jest reprezentowany przez wskaźnik do ciągu znaków zakończonego zerem) i tak samo zmienne typu obiektowego, tablicowego i rekordowego. Deklaracje funkcji, rekordów, typów i klas nie zajmują żadnego miejsca (w ''Kotku'' nie są z nimi skojarzone żadne modyfikowalne wartości). Oczywiście rozmiar pamięci zależy od docelowej architektury. Dla każdej kolejnej deklaracji określ jej położenie w pamięci względem pierwszego elementu. Na przykład na wspomnianej wcześniej architekturze dla deklaracji:


  '''var''' x : '''int''' = 5;
  '''var''' x : '''int''';
  '''var''' y : '''int''' = 3*x+5;
  '''var''' y : '''string''';
'''function''' f() : '''void''' {'''return''';}
'''var''' z : '''int''';


a nie
położenie zmiennej ''x'' to 0, zmiennej ''y'' to 4, funkcji ''f'' to 4 (choć ta wartość nie będzie do niczego potrzebna i można ją opuścić), a zmiennej ''z'' to 8.


'''var''' x : '''int''';    // zle! - brak wartości
Tablica symboli powinna udostępniać następujące funkcje:
'''var''' y : '''int''';    // zle! - brak wartości
* znajdowanie identyfikatora - określenie, w której tablicy symboli jest zawarty,
* określanie pozycji identyfikatora w tablicy symboli,
* podawanie całkowitego rozmiaru wszystkich deklaracji z tablicy symboli.


Zastanów się, jakie ograniczenia należy wprowadzić na wyrażenia użyte do inicjalizacji aby język był całkowicie bezpieczny. Na przykład poniższa sekwencja deklaracji prowadzi do błędu:
Rozbuduj ''pretty printer'' z poprzedniego zadania tak, aby obok każdego identyfikatora  wypisywał, z której tablicy symboli identyfikator pochodzi i jakie jest jego przesunięcie w tej tablicy symboli. Nie musisz na razie uwzględniać operatora kropki (odwołanie do pól obiektu). Określ sam czytelny format danych wyjściowych.


'''var''' x : '''int''' = y + 4;
Przetestuj swój kod na trzech przykładach. Przykłady powinny zawierać wszystkie następujące konstrukcje: dziedziczenie, funkcje lokalne, zmienne lokalne, przysłanianie zmiennej, argumenty funkcji, obiekty lokalne wewnątrz funkcji i innych obiektów, rekordy.
'''var''' y : '''int''' = x + 4;


Przemyśl następujące możliwości:
== Zadanie 4: Analiza semantyczna ==
* dopuszczenie tylko wyrażeń stałych
* dopuszczenie jedynie zmiennych zadeklarowanych wcześniej na tym samym poziomie
* dopuszczenie jedynie zmiennych i funkcji zadeklarowanych wcześniej na tym samym poziomie
* dopuszczenie jedynie zmiennych zadeklarowanych wcześniej na tym samym poziomie i wszystkich funkcji z tego poziomu
* dopuszczenie zmiennych zadeklarowanych wcześniej na tym samym poziomie i wszystkich funkcji
* dopuszczenie wszystkich zmiennych zadeklarowanych wcześniej na tym poziomie oraz wszystkich zmiennych i funkcji zakeklarowanych wyżej
* oznaczanie zmiennych niezainicjalizowanych i sprawdzanie bycia zainicjalizowaną przy każdym odwołaniu


Odpowiedz na pytania:
Do swojego kompilatora dopisz kod odpowiedzialny za analizę semantyczną. Głównym zadaniem analizy semantycznej jest określenie typu każdego węzła (niektóre konstrukcje nie mają typu; wtedy typem węzła będzie '''void''') oraz sprawdzanie poprawności typów. Dodatkowo musisz sprawdzać wszystkie inne cechy semantyczne 'Kotka'. Opracowanie kompletnej listy wszystkich rzeczy, które należy sprawdzać jest skomplikowane, więc nie przejmuj się, jeśli o czymś zapomnisz. Zawsze możesz uzupełnić kod analizy semantycznej w czasie pisania generatora kodu. Zajrzyj do zadań z tego rozdziału - być może pomogą Ci lepiej zrozumieć zawiłości semantyki języków programowania.
* Która z możliwości daje największe bezpieczeństwo?
* Która ma duży narzut czasu wykonania?
* Która jest najbardziej elastyczna z punktu widzenia programisty piszącego w Kotku?
* Które z nich pozwalają obejść zabezpieczenia?
* Jakie rozwiązanie zostało przyjęte w języku Java?


Weź pod uwagę jakie zmienne i funkcje są dostępne z wewnątrz funkcji.
Na początku przyjmij, że nie jest dozwolona deklaracja klas wewnątrz innych klas i procedur. Następnie, rozwiązując poniższe podzadania, zastanów się jakie ograniczenia należy wprowadzić, aby definiowanie klas wewnątrz klas i procedur miało sens.


Rozszerz program testowy kompilatora (który po poprzednim zadaniu wypisuje tablicę symboli, z której pochodzi każdy symbol wraz z przesunięciem) o wypisywane typu każdego identyfikatora. Dopisz obsługę rekordów i obiektów (operator kropki).


== Zadanie 2: Klasy wewnątrze procedur ==
=== Zadanie 4.a: Klasy wewnętrzne procedur ===


W tym zadaniu rozważamy jedynie procedury, które nie są metodami klasy ani nie są zawarte z jakiejś metodzie.
W tym zadaniu rozważamy jedynie procedury, które nie są metodami klasy ani nie są zawarte z jakiejś metodzie.


Klasy zadeklarowane lokalnie wewnątrz procedur przydają się do zmniejszenia liczby globalnych nazw oraz do ścisłego określenia zakresu użycia klasy. Udostępnienie takich konstrukcji prowadzi do dodatkowych problemów implementacyjnych, podobnych do problemów z zagnieżdżonymi procedurami.
Klasy zadeklarowane lokalnie wewnątrz procedur przydają się do zmniejszenia liczby globalnych nazw oraz do ścisłego określenia zakresu użycia klasy. Udostępnienie takich konstrukcji prowadzi do dodatkowych problemów implementacyjnych podobnych do problemów z zagnieżdżonymi procedurami.


Pytania:
Pytania:
* Podaj przykład programu, w którym występuje funkcja zwracająca instancję lokalnie zadeklarowanej klasy.
* Podaj przykład programu, w którym występuje funkcja zwracająca instancję lokalnie zadeklarowanej klasy.
* Czy do zewnętrznych zmiennych można odwoływać się za pomocą tablicy display, jak w przypadku procedur?
* Czy do zewnętrznych zmiennych można odwoływać się za pomocą tablicy display, jak w przypadku procedur?
* Język Java dopuszcza korzystanie z lokalnych zmiennych procedury, ale tylko ale zmiennych zadeklarowanych jako final. Dlaczego takie rozwiązanie upraszcza implementację?
* Język ''Java'' dopuszcza korzystanie z lokalnych zmiennych procedury, ale tylko zmiennych zadeklarowanych jako '''final'''. Dlaczego takie rozwiązanie upraszcza implementację?
* Czy dostęp do zmiennych globalnych utrudnia implementację?
* Czy dostęp do zmiennych globalnych utrudnia implementację?
* Jakie jest możliwe rozwiązanie ogólnego przypadku? Czy widzisz związek z językami funkcyjnymi?
* Jakie jest możliwe rozwiązanie ogólnego przypadku? Czy widzisz związek z językami funkcyjnymi?


== Zadanie 3: Klasy wewnątrz klas ==
=== Zadanie 4.b: Klasy wewnątrz klas ===


Kolejnym krokiem do zwiększenia modularności jest dopuszczenie deklaracji klasy wewnątrz klas (na razie nie wewnątrz metody klasy). Taka klasa wewnętrzna miałaby dostęp do metod i zmiennych klasy wewnątrz której jest zadeklarowana.
Kolejnym krokiem do zwiększenia modularności jest dopuszczenie deklaracji klasy wewnątrz klas (na razie nie wewnątrz metody klasy). Taka klasa wewnętrzna miałaby dostęp do metod i zmiennych klasy, wewnątrz której jest zadeklarowana.


* Zaproponuj sposób implementacji dla takich klas.
* Zaproponuj sposób implementacji dla takich klas.
* Czy tworzenie instancji klasy wewnętrznej bez klasy zewnętrznej jest możliwe przy pewnych założeniach?  
* Kiedy jest możliwe utworzenie instancji klasy wewnętrznej bez klasy zewnętrznej?


 
=== Zadanie 4.c: Dowolne, lokalne deklaracje klas ===
== Zadanie 4: Dowolne, lokalne deklaracje klas ==


Zastanów się nad sposobem implementacji dowolnie zagnieżdżonych deklaracji klas. To zadanie jest połączeniem dwóch poprzednich.
Zastanów się nad sposobem implementacji dowolnie zagnieżdżonych deklaracji klas. To zadanie jest połączeniem dwóch poprzednich.


* Jakie informacje musi nieść ze sobą klasa zadeklarowana wewnątrz innej klasy (być może wewnątrz jakiejś metody tamtej klasy lub wewnątrz funkcji zdefiniowanej w metodzie)?
* Jakie informacje musi nieść ze sobą klasa zadeklarowana wewnątrz innej klasy (być może wewnątrz jakiejś metody tamtej klasy lub wewnątrz funkcji zdefiniowanej w metodzie)?
* Jakie informacje musi nieść ze sobą klasa zadeklarowana wewnątrz pewnej funkcji (być może funkcja jest metodą klasy)?
* Jakie informacje musi nieść ze sobą klasa zadeklarowana wewnątrz pewnej funkcji (być może funkcja jest metodą innej klasy)?
 
=== Zadanie 4.d: Inicjalizacja zmiennych ===
 
Aby język był bardziej bezpieczny chcielibyśmy wyeliminować możliwość użycia niezainicjalizowanych zmiennych. W tym celu modyfikujemy deklaracje zmiennej tak, aby podanie wartości przy deklaracji było obowiązkowe:
 
'''var''' x : '''int''' = 5;
'''var''' y : '''int''' = 3*x+5;


== Zadanie 5: Poprawność wyrażeń na etapie parsowania ==
a nie


Grametyka języka dopuszcza pewne wyrażenia, które nie są poprawne, np:
'''var''' x : '''int''';    // zle! - brak wartości
'''var''' y : '''int''';    // zle! - brak wartości


funkcja(param)(param2)
Zastanów się, jakie ograniczenia należy wprowadzić na wyrażenia użyte do inicjalizacji aby język był całkowicie bezpieczny. Na przykład poniższa sekwencja deklaracji prowadzi do błędu:
zmienna[indeks](param)


Niepoprawność wynika z tego, że funkcja nie może być wynikiem innej funkcji, ani funkcja nie może być przechowywana w tablicy. Czy można zmienić gramatykę tak, aby wykluczała podane wyrażenia? Jakie podejście preferujesz: sprawdzanie poprawności tych wyrażeń na etapie parsera, czy analizy semantycznej? Podaj zalety i wady obu metod.
'''var''' x : '''int''' = y + 4;
'''var''' y : '''int''' = x + 4;


== Zadanie 6: Poprawność przypisań ==
Przemyśl następujące możliwości:
* dopuszczenie tylko wyrażeń stałych,
* dopuszczenie jedynie zmiennych zadeklarowanych wcześniej na tym samym poziomie,
* dopuszczenie jedynie zmiennych i funkcji zadeklarowanych wcześniej na tym samym poziomie,
* dopuszczenie jedynie zmiennych zadeklarowanych wcześniej na tym samym poziomie i wszystkich funkcji z tego poziomu,
* dopuszczenie zmiennych zadeklarowanych wcześniej na tym samym poziomie i wszystkich funkcji,
* dopuszczenie wszystkich zmiennych zadeklarowanych wcześniej na tym poziomie oraz wszystkich zmiennych i funkcji zadeklarowanych wyżej,
* oznaczanie zmiennych niezainicjalizowanych i sprawdzanie bycia zainicjalizowaną przy każdym odwołaniu,


Obecna postać produkcji przypisania:
Odpowiedz na pytania:
* Która z możliwości daje największe bezpieczeństwo?
* Która ma duży narzut czasu wykonania?
* Która jest najbardziej elastyczna z punktu widzenia programisty piszącego w ''Kotku''?
* Które z nich pozwalają obejść zabezpieczenia?
* Jakie rozwiązanie zostało przyjęte w języku ''Java''?
 
Weź pod uwagę jakie zmienne i funkcje są dostępne z wewnątrz funkcji.


Instrukcja ::= WyrazeniePostfiksowe ":=" Wyrazenie ";";
== Zadanie 5: Generowanie kodu ==


dopuszcza pewne błędne przypisania, np.  
Napisz ostatni fragment kompilatora - generator kodu. Dla uproszczenia generuj kod od razu na maszynę docelową i pomiń optymalizację. Uprość też maksymalnie alokację rejestrów.  


"tekst" := 8
Możesz sprawdzić jak wygląda przykładowy kod używając istniejącego kompilatora do wygenerowania plików asemblera (np. w ''gcc'' opcja ''-s'' przy kompilacji). Możesz użyć istniejących kompilatorów do wygenerowania kodu dla operacji wejścia/wyjścia ('''read''' i '''write''') oraz dla wygenerowania kodu startu i końca Twojego programu.


Czy można tak zmienić gramatykę, aby przypisanie zawsze było poprawne? Czy pozwoli to wyeliminować analizę sematyczną tego fragmentu kodu źródłowego?
Napisz programy w ''Kotku'' testowe testujące wszystkie konstrukcje języka i sprawdź ich poprawne działanie po skompilowaniu Twoim kompilatorem.


== Zadanie 7: Sprawdzanie poprawności rzutowania ==
=== Zadanie 5.a: Sprawdzanie poprawności rzutowania ===


W językach obiektowych często zachodzi potrzeba rzutowania na inną podklasę. Najprostszą metodą sprawdzania poprawności rzutowania obiektów jest sprawdzenie, czy wskaźnik do tablicy metod wirtulanych wskazuje na tablicę dla szukanego typu. Jeśli nie, to musimy sprawdzić również nadklasę naszego obiektu (wskaźnik do tablicy metod wirtualnych nadklasy możemy przechowywać w samej tablicy metod wirtualnych. Takie sprawdzenie wymaga przejścia po nadklasach obiektu i może być czasochłonne.
W językach obiektowych często zachodzi potrzeba rzutowania na podklasę obiektu. Najprostszą metodą sprawdzania poprawności rzutowania jest sprawdzenie, czy wskaźnik do tablicy metod wirtualnych wskazuje na tablicę dla szukanego typu. Jeśli nie, to musimy sprawdzić również nadklasę naszego obiektu (wskaźnik do tablicy metod wirtualnych nadklasy możemy przechowywać w samej tablicy metod wirtualnych. Takie sprawdzenie wymaga przejścia po nadklasach obiektu i może być czasochłonne.


Zaprojektuj metodę sprawdzającą przynależność obiektu do klasy w stałym czasie.
Zaprojektuj metodę sprawdzającą przynależność obiektu do klasy w stałym czasie.


Wskazówki:
Wskazówki:
* załóż skończoną wysokość hierarchii klas
* załóż skończoną wysokość hierarchii klas,
* rozważ dodanie pewnych informacji do każdej klasy
* rozważ dodanie pewnych informacji do każdej klasy.


== Metody i zmienne statyczne ==
=== Zadanie 5.b: Metody i zmienne statyczne ===


Niektóre języki programowania obiektowego (np. C++, java) dopuszczają statyczne pola klas i metody, a także metody finalne, czyli nie podlegające przedefiniowaniu w podklasach.
Niektóre języki programowania obiektowego (np. ''C++'', ''Java'') dopuszczają statyczne metody i pola klas, a także metody finalne, czyli nie podlegające przedefiniowaniu w podklasach.


Czym może różnić się kod wynikowy dla dostępu do statycznych pól, statycznych metod i finalnych metod od normalnego kodu? Jak takie właściwości pomagają w optymalizacji?
Czym może różnić się kod wynikowy dla dostępu do statycznych pól, statycznych metod i finalnych metod od normalnego kodu? Jak takie właściwości pomagają w optymalizacji?


= Rozszerzenia języka =
== Zadanie 6: Rozszerzenia języka ==


== Inicjalizacja zmiennych ==
Język ''Kotek'' w obecnej postaci jest raczej ubogi. Pożądane jest rozszerzenie go. Poniższe rozszerzenia nie muszą być wykonywane w podanej kolejności.


Zmień deklaracje zmiennych tak, żeby przy deklaracji w procedurze, rekordzie lub klasie (ale nie jako parametru funkcji) trzeba było (obowiązkowo!) podać wartość inicjalizacji zmiennej. Przykład:
=== Zadanie 6.a: Typ logiczny ===


'''var''' x : '''int''' := 50;
Dodaj do języka typ logiczny '''bool'''. Popraw analizę semantyczną tak, aby
rozróżniać typy '''int''' i '''bool'''. Rozszerz gramatykę wyrażeń logicznych tak, aby
a || b || c
a && b && c
a && b || c
były poprawnymi wyrażeniami. Załóż priorytet && nad ||.


=== Zadanie 6.b: Typ zmiennoprzecinkowy ===


Przyjmij, że inicjalizacja tablic i rekordów następuje w momencie ich tworzenia ('''new'''):
Dodaj do języka typy zmiennoprzecinkowe: '''float''' (pojedyncza precyzja) i '''double''' (podwójna precyzja). Zmień analizę semantyczną tak, aby uwzględniała nowe typy. Dodaj operator rzutowania na odpowiednie typy arytmetyczne:


  y = '''new''' '''int'''[3*x] of 5;
  ('''int''') Wyrażenie
  z = '''new''' myRecord { field1 := 4, field2 := "ala ma kota"};
  ('''float''') Wyrażenie
...


Przy inicjalizacji zmiennej dopuszczalne jest użycie zmiennej zadeklarowanej wyżej na tym samym poziomie, wszystkich funkcji na tym poziomie oraz wszstkich zmiennych i funkcji zadeklarowanych na poziomach wyższych.
=== Zadanie 6.c: Dodatkowe operatory ===


== Odśmiecanie ==
Dodaj nowe operatory dla wyrażeń, znane z języka ''C'': '''++''', '''--''', '''+=''' (i pokrewne), operatory bitowe ('''|''', '''&''').
Usuń konstrukcję '''delete''' i zrób zamiast tego automatyczne odśmiecanie.


== Ulepszenie read i write ==
=== Zadanie 6.d: Automatyczne odśmiecanie ===


Język Kotek zawiera bardzo prymitywne mechanizmy wejścia wyjścia. Rozszesz język o:
Zrób automatyczne odśmiecanie niedostępnych obiektów (zamiast instrukcji '''delete'''). Najlepiej użyj gotowego odśmiecacza (ang. '''Garbage Collector'''), choć możesz pokusić się o własną implementację
* możliwość odczytania wartości do pola rekordu lub klasy oraz do elementu tablicy (obecnie gramatyka dopuszcza czytanie tylko zmiennej atomowej)


'''read''' a[5];
=== Zadanie 6.e: Rzutowanie i sprawdzanie typu obiektu ===
'''read''' mojRekord.pole1;
 
* możliwość czytania i pisania wielu wartości na raz


'''read''' a[i], b[i];
Rzutowanie oraz sprawdzanie typu są konstrukcjami często wykorzystywanymi w programowaniu obiektowym.
'''write''' "a[5] = ", a[5];
 
 
== Rzutowanie i sprawdzanie typu obiektu ==
Język nie zawiera rzutowania na typy. Jest to konstrukcja bardzo często wykorzystywana w programowaniu obiektowym, gdy trzeba zrzutować obiekt na typ podlkasy. Do tego jest też potrzebne sprawdzanie, czy obiekt jest elementem danej klasy.


Rozszerz język o konstrukcję rzutowania:
Rozszerz język o konstrukcję rzutowania:
Linia 487: Linia 244:
  obiekt '''instance of''' klasa
  obiekt '''instance of''' klasa


Obie konstrukcje są wyrażeniami. Pierwsza daje typ Podklasa a druga typ int (wartosc 0 lub nie-zero)
Obie konstrukcje są wyrażeniami. Pierwsza daje typ ''Podklasa'' a druga typ '''int''' (wartość 0 lub nie-zero; jeśli do języka został dodany typ '''bool''', to preferowany jest typ '''bool'''). Wartością wyrażenia rzutowania jest zero, jeśli obiekt nie jest instancją klasy na którą rzutowanie jest wykonywane (dynamiczne sprawdzanie typów przy rzutowaniu).


=== Zadanie 6.f: Ulepszenie read i write ===


Język ''Kotek'' zawiera bardzo prymitywne mechanizmy wejścia wyjścia. Rozszerz język o:
* możliwość odczytania wartości do pola rekordu lub klasy oraz do elementu tablicy (obecnie gramatyka dopuszcza czytanie tylko zmiennej atomowej)


== Typ bool ==
'''read''' a[5];
'''read''' mojRekord.pole1;


W języku nie ma typu bool. Dodaj ten typ do języka. Popraw analizę semantyczną tak, aby
* możliwość czytania i pisania wielu wartości na raz
rozróżniać typy int i bool. Rozszeż wyrażenia logiczne tak, aby
a || b || c
a && b && c
a && b || c
były poprawnymi wyrażeniami. Załóż priorytet && nad ||.
 
== Typ zmiennoprzecinkowy ==
 
Dodaj do języka typy zmiennnoprzecinkowe: '''float''' (pojedyncza precyzja) i '''double''' (podwójna precyzja). Zmień analizę semantyczną tak, aby uwzględniała nowe typy. Dodaj operator rzutowania na nowe typy.


'''read''' a[i], b[i];
'''write''' "a[5] = ", a[5];


== Chronione pola klasy ==
=== Zadanie 6.g: Chronione pola klasy ===


Dodaj do języka mechanizm ochrony deklaracji klasowych. Deklaracja będzie rozszerzona o modyfikator '''private''', '''public''' i '''protected''' (obok nazwy typu deklaracji):
Dodaj do języka mechanizm ochrony deklaracji klasowych. Deklaracja wewnątrz klasy będzie rozszerzona o modyfikator '''private''', '''public''' lub '''protected''' (obok nazwy typu deklaracji):


  '''var''' '''private''' x : '''int''';
  '''var''' '''private''' x : '''int''';
Linia 515: Linia 269:
Deklaracje '''public''' mają być widoczne zawsze, '''private''' tylko wewnątrz klasy a '''protected''' wewnątrz klasy i w podklasach.
Deklaracje '''public''' mają być widoczne zawsze, '''private''' tylko wewnątrz klasy a '''protected''' wewnątrz klasy i w podklasach.


== Typy dostępne bez deklaracji ==
=== Zadanie 6.h: Typy złożone dostępne bez deklaracji ===


W obecnej postaci język wymaga deklaracji typów złożonych przed ich użyciem, np.
W obecnej postaci język wymaga deklaracji typów złożonych przed ich użyciem, np.
Linia 530: Linia 284:
  '''var''' x : '''array of''' '''array of''' '''int''';
  '''var''' x : '''array of''' '''array of''' '''int''';


== Scrap material ==
Możesz też pokusić się o implementację anonimowych rekordów deklarowanych przy deklaracji zmiennej.
[[/Scrap | Scrap]]
 
=== Zadanie 6.i: Inicjalizacja zmiennych ===
 
Zmień deklaracje zmiennych tak, żeby przy deklaracji w procedurze, rekordzie lub klasie (ale nie jako parametru funkcji) trzeba było (obowiązkowo!) podać wartość inicjalizacji zmiennej. Przykład:
 
'''var''' x : '''int''' := 50;
 
Przyjmij, że inicjalizacja tablic i rekordów następuje w momencie ich tworzenia ('''new'''):
 
y = '''new''' '''int'''[3*x] '''of''' 5;
z = '''new''' myRecord { field1 := 4, field2 := "ala ma kota"};
 
Przy inicjalizacji zmiennej dopuszczalne jest użycie zmiennej zadeklarowanej wcześniej na tym samym poziomie, wszystkich funkcji na tym poziomie oraz wszystkich zmiennych i funkcji zadeklarowanych na poziomach wyższych.
 
= Wymagania implementacyjne i ocenianie =
 
Aby zaliczyć laboratorium trzeba napisać działający kompilator. Na niższą ocenę nie musi on kompilować wszystkich konstrukcji języka. Implementacja rozszerzeń języka jest dodatkowo punktowana, ale nie wymagana do zaliczenia przedmiotu.
 
{| border="1" align=center cellspacing="0"
! Ocena: 
! Wymagania:
|-
| 5
| Wszystkie podstawowe rzeczy (bez rozszerzeń języka) a dodatkowo operator '''instance of''' i rzutowanie (Zadanie 6.e). W szczególności muszą być zaimplementowane obiekty i lokalne deklaracje procedur (nie muszą być dostępne lokalne deklaracje klas).
|-
| 4
| Bez obiektów. Muszą być dostępne lokalne deklaracje procedur.
|-
| 3
| Bez obiektów i lokalnych deklaracji procedur.
|}

Aktualna wersja na dzień 18:41, 30 wrz 2006

Autor: Marek Biskup (mbiskup@mimuw.edu.pl)

Wstęp

Tematem Laboratorium Metod Realizacji Języków Programowania jest zaimplementowanie kompletnego kompilatora języka Kotek.

Język kotek

Kotek jest obiektowym językiem programowania zbliżonym do języków takich jak Pascal czy Java. Podstawowe cechy Kotka to:

  • obecność zwykłych konstrukcji takich jak pętle for i while, wyrażeń arytmetycznych, logicznych,
  • silne typowanie - zgodność typów jest sprawdzana w czasie kompilacji,
  • możliwość deklarowania funkcji,
  • obecność struktur danych takich jak tablice i rekordy,
  • możliwość deklarowania klas i tworzenia obiektów,
  • lokalne deklaracje funkcji i klas,
  • dynamiczna alokacja obiektów, rekordów i tablic (na stercie).

Gramatyka Kotka

Gramatyka języka jest opisana na stronie Gramatyka Kotka

Semantyka Kotka

Semantyka języka jest opisana na stronie Semantyka Kotka

Zadania

Zadanie 0: Wybierz narzędzia i platformę docelową

Wybierz platformę docelową kompilatora języka Kotek. Zalecane są platforma x86 i system operacyjny Linux (kod byłby generowany w asemblerze x86, np. w formacie akceptowanym przez kompilator gcc), bądź Maszyna Wirtualna Javy (kod generowany w asemblerze JVM, np. w formacie akceptowanym przez asembler Jasmin).

Następnie wybierz język programowania, w którym będzie pisany sam kompilator. Ten język nie ma żadnego związku z platformą docelową. Zorientuj się, jakie narzędzia są dostępne dla języka, na który się decydujesz.

  • język C/C++: generator analizatorów leksykalnych lex lub flex, generator parserów yacc lub bison,
  • język Java: generator analizatorów leksykalnych JFlex, generator parserów Coco, CUP, Javacc, ANTLR,
  • język Ocaml: ocamllex, ocamlyacc.

Niektóre narzędzia wspierają też budowanie drzewa składniowego i jego przekształcanie.

Zadanie 1: Analizator leksykalny

Napisz analizator leksykalny do kompilatora języka Kotek. Użyj generatora analizatorów leksykalnych (takich jak lex dla C) generującego kod w języku, w którym będziesz pisał kompilator. Analizator powinien rozpoznawać konstrukcje leksykalne Kotka: identyfikatory, operatory, stałe napisowe, komentarze (jeśli Twój generator analizatorów leksykalnych umożliwia obsługę zagnieżdżonych komentarzy, to powinny one być usuwane już na tym etapie).

Przetestuj swój analizator leksykalny pisząc program, który zamienia ciąg wejściowy na ciąg leksemów, np. dla wejścia:

asdf + qwer * != ( "napis\"napis"

wynikiem działania programu testowego powinno być:

IDENTYFIKATOR("asdf") OPERATOR_PLUS IDENTYFIKATOR("qwer") OPERATOR_RAZY
OPERATOR_RÓŻNE OPERATOR_LNAWIAS, STRING("napis\"napis")

lub równoważny zapis.

Zadanie 2: Analizator składniowy

Napisz parser języka Kotek wykorzystując generator parserów (np. yacc dla C). Wynikiem analizy składniowej ma być drzewo składni, w którym każdy węzeł odpowiada pewnej produkcji w kodzie źródłowym (lewej stronie), dziećmi węzła są natomiast elementy z prawej strony produkcji. Liśćmi są terminale.

Dopuszczalne są modyfikacje drzewa składniowego, które upraszczają jego strukturę, a nie gubią żadnych istotnych informacji. Np. zamiast przechowywania listy deklaracji jako węzła, który zawiera pojedynczą deklarację i drugą listę deklaracji, dopuszczalne (a nawet zalecane) jest używanie standardowych kontenerów języka programowania, w którym kompilator jest pisany.

Jeśli używasz języka obiektowego, zalecane jest stworzenie oddzielnej klasy dla każdego typu produkcji. Obiekty należy w miarę możliwości i potrzeb powiązać związkami dziedziczenia. Zwykle dobrym pomysłem jest stworzenie oddzielnej klasy dla lewej strony produkcji i podklas dziedziczących z niej dla każdej prawej strony produkcji, w której ta lewa strona występuje. Na przykład możemy mieć klasę Instrukcja, z której dziedziczą m.in. klasy Przypisanie, Blok, PustaInstrukcja i ZłożonaInstrukcja. Z kolei z klasy ZłożonaInstrukcja będą dziedziczyć klasy InstrukcjaIfElse, InstrukcjaIf oraz InstrukcjaWhile.

Hierarchia klas węzłów drzewa składniowego powinna mieć wspólny korzeń (nazwany np. Węzeł). Ten węzeł będzie zawierał abstrakcyjne metody, które są wymagane w innych węzłach, np. metoda gen(), generująca kod na maszynę docelową, metoda sem() przeprowadzająca analizę semantyczną (uwaga: jeśli analiza semantyczna będzie robiona w kilku przebiegach, to warto stworzyć więcej metod sem, np. sem1 i sem2).

Napisz program testujący parser. Program testujący powinien działać jak pretty printer, tzn. wypisywać treść programu na podstawie drzewa składniowego zawartego w pamięci.

Zadanie 2.a: Poprawność wyrażeń na etapie parsowania

Gramatyka języka dopuszcza pewne wyrażenia, które nie są poprawne, np:

funkcja(param)(param2)
zmienna[indeks](param)

Niepoprawność wynika z tego, że funkcja nie może być wynikiem innej funkcji, funkcja nie może też być przechowywana w tablicy. Czy można zmienić gramatykę tak, aby wykluczała podane wyrażenia? Jakie podejście preferujesz: sprawdzanie poprawności tych wyrażeń na etapie parsera czy analizy semantycznej? Podaj zalety i wady obu metod.

Zadanie 2.b: Poprawność przypisań

Obecna postać produkcji przypisania:

Instrukcja ::= WyrazeniePostfiksowe ":=" Wyrazenie ";";

dopuszcza pewne błędne przypisania, np.

"tekst" := 8;

Czy można tak zmienić gramatykę, aby przypisanie zawsze było poprawne? Czy pozwoli to wyeliminować analizę semantyczną tego fragmentu kodu źródłowego?

Zadanie 3: Tablica symboli

Do Twojego kompilatora dopisz kod tworzący tablicę symboli z każdej listy deklaracji. Połącz tablice symboli w graf zgodnie z regułami widoczności nazw. Tablica symboli powinna mieć odniesienie do tablicy symboli nadrzędnej funkcji lub klasy, w której nasza funkcja (lub klasa) została zadeklarowana. W przypadku tablicy symboli klasy dodaj jeszcze odnośniki do tablicy symboli nadklasy. Określ kolejność szukania nazw (najpierw nadklasa, czy nadfunkcja?).

Dla każdej deklaracji określ rozmiar zużytej pamięci, np. w 32 bitowej architekturze x86 zmienne typu int zużywają 4 bajty, zmienne string również 4 bajty (napis jest reprezentowany przez wskaźnik do ciągu znaków zakończonego zerem) i tak samo zmienne typu obiektowego, tablicowego i rekordowego. Deklaracje funkcji, rekordów, typów i klas nie zajmują żadnego miejsca (w Kotku nie są z nimi skojarzone żadne modyfikowalne wartości). Oczywiście rozmiar pamięci zależy od docelowej architektury. Dla każdej kolejnej deklaracji określ jej położenie w pamięci względem pierwszego elementu. Na przykład na wspomnianej wcześniej architekturze dla deklaracji:

var x : int;
var y : string;
function f() : void {return;}
var z : int;

położenie zmiennej x to 0, zmiennej y to 4, funkcji f to 4 (choć ta wartość nie będzie do niczego potrzebna i można ją opuścić), a zmiennej z to 8.

Tablica symboli powinna udostępniać następujące funkcje:

  • znajdowanie identyfikatora - określenie, w której tablicy symboli jest zawarty,
  • określanie pozycji identyfikatora w tablicy symboli,
  • podawanie całkowitego rozmiaru wszystkich deklaracji z tablicy symboli.

Rozbuduj pretty printer z poprzedniego zadania tak, aby obok każdego identyfikatora wypisywał, z której tablicy symboli identyfikator pochodzi i jakie jest jego przesunięcie w tej tablicy symboli. Nie musisz na razie uwzględniać operatora kropki (odwołanie do pól obiektu). Określ sam czytelny format danych wyjściowych.

Przetestuj swój kod na trzech przykładach. Przykłady powinny zawierać wszystkie następujące konstrukcje: dziedziczenie, funkcje lokalne, zmienne lokalne, przysłanianie zmiennej, argumenty funkcji, obiekty lokalne wewnątrz funkcji i innych obiektów, rekordy.

Zadanie 4: Analiza semantyczna

Do swojego kompilatora dopisz kod odpowiedzialny za analizę semantyczną. Głównym zadaniem analizy semantycznej jest określenie typu każdego węzła (niektóre konstrukcje nie mają typu; wtedy typem węzła będzie void) oraz sprawdzanie poprawności typów. Dodatkowo musisz sprawdzać wszystkie inne cechy semantyczne 'Kotka'. Opracowanie kompletnej listy wszystkich rzeczy, które należy sprawdzać jest skomplikowane, więc nie przejmuj się, jeśli o czymś zapomnisz. Zawsze możesz uzupełnić kod analizy semantycznej w czasie pisania generatora kodu. Zajrzyj do zadań z tego rozdziału - być może pomogą Ci lepiej zrozumieć zawiłości semantyki języków programowania.

Na początku przyjmij, że nie jest dozwolona deklaracja klas wewnątrz innych klas i procedur. Następnie, rozwiązując poniższe podzadania, zastanów się jakie ograniczenia należy wprowadzić, aby definiowanie klas wewnątrz klas i procedur miało sens.

Rozszerz program testowy kompilatora (który po poprzednim zadaniu wypisuje tablicę symboli, z której pochodzi każdy symbol wraz z przesunięciem) o wypisywane typu każdego identyfikatora. Dopisz obsługę rekordów i obiektów (operator kropki).

Zadanie 4.a: Klasy wewnętrzne procedur

W tym zadaniu rozważamy jedynie procedury, które nie są metodami klasy ani nie są zawarte z jakiejś metodzie.

Klasy zadeklarowane lokalnie wewnątrz procedur przydają się do zmniejszenia liczby globalnych nazw oraz do ścisłego określenia zakresu użycia klasy. Udostępnienie takich konstrukcji prowadzi do dodatkowych problemów implementacyjnych podobnych do problemów z zagnieżdżonymi procedurami.

Pytania:

  • Podaj przykład programu, w którym występuje funkcja zwracająca instancję lokalnie zadeklarowanej klasy.
  • Czy do zewnętrznych zmiennych można odwoływać się za pomocą tablicy display, jak w przypadku procedur?
  • Język Java dopuszcza korzystanie z lokalnych zmiennych procedury, ale tylko zmiennych zadeklarowanych jako final. Dlaczego takie rozwiązanie upraszcza implementację?
  • Czy dostęp do zmiennych globalnych utrudnia implementację?
  • Jakie jest możliwe rozwiązanie ogólnego przypadku? Czy widzisz związek z językami funkcyjnymi?

Zadanie 4.b: Klasy wewnątrz klas

Kolejnym krokiem do zwiększenia modularności jest dopuszczenie deklaracji klasy wewnątrz klas (na razie nie wewnątrz metody klasy). Taka klasa wewnętrzna miałaby dostęp do metod i zmiennych klasy, wewnątrz której jest zadeklarowana.

  • Zaproponuj sposób implementacji dla takich klas.
  • Kiedy jest możliwe utworzenie instancji klasy wewnętrznej bez klasy zewnętrznej?

Zadanie 4.c: Dowolne, lokalne deklaracje klas

Zastanów się nad sposobem implementacji dowolnie zagnieżdżonych deklaracji klas. To zadanie jest połączeniem dwóch poprzednich.

  • Jakie informacje musi nieść ze sobą klasa zadeklarowana wewnątrz innej klasy (być może wewnątrz jakiejś metody tamtej klasy lub wewnątrz funkcji zdefiniowanej w metodzie)?
  • Jakie informacje musi nieść ze sobą klasa zadeklarowana wewnątrz pewnej funkcji (być może funkcja jest metodą innej klasy)?

Zadanie 4.d: Inicjalizacja zmiennych

Aby język był bardziej bezpieczny chcielibyśmy wyeliminować możliwość użycia niezainicjalizowanych zmiennych. W tym celu modyfikujemy deklaracje zmiennej tak, aby podanie wartości przy deklaracji było obowiązkowe:

var x : int = 5;
var y : int = 3*x+5;

a nie

var x : int;     // zle! - brak wartości
var y : int;     // zle! - brak wartości

Zastanów się, jakie ograniczenia należy wprowadzić na wyrażenia użyte do inicjalizacji aby język był całkowicie bezpieczny. Na przykład poniższa sekwencja deklaracji prowadzi do błędu:

var x : int = y + 4;
var y : int = x + 4;

Przemyśl następujące możliwości:

  • dopuszczenie tylko wyrażeń stałych,
  • dopuszczenie jedynie zmiennych zadeklarowanych wcześniej na tym samym poziomie,
  • dopuszczenie jedynie zmiennych i funkcji zadeklarowanych wcześniej na tym samym poziomie,
  • dopuszczenie jedynie zmiennych zadeklarowanych wcześniej na tym samym poziomie i wszystkich funkcji z tego poziomu,
  • dopuszczenie zmiennych zadeklarowanych wcześniej na tym samym poziomie i wszystkich funkcji,
  • dopuszczenie wszystkich zmiennych zadeklarowanych wcześniej na tym poziomie oraz wszystkich zmiennych i funkcji zadeklarowanych wyżej,
  • oznaczanie zmiennych niezainicjalizowanych i sprawdzanie bycia zainicjalizowaną przy każdym odwołaniu,

Odpowiedz na pytania:

  • Która z możliwości daje największe bezpieczeństwo?
  • Która ma duży narzut czasu wykonania?
  • Która jest najbardziej elastyczna z punktu widzenia programisty piszącego w Kotku?
  • Które z nich pozwalają obejść zabezpieczenia?
  • Jakie rozwiązanie zostało przyjęte w języku Java?

Weź pod uwagę jakie zmienne i funkcje są dostępne z wewnątrz funkcji.

Zadanie 5: Generowanie kodu

Napisz ostatni fragment kompilatora - generator kodu. Dla uproszczenia generuj kod od razu na maszynę docelową i pomiń optymalizację. Uprość też maksymalnie alokację rejestrów.

Możesz sprawdzić jak wygląda przykładowy kod używając istniejącego kompilatora do wygenerowania plików asemblera (np. w gcc opcja -s przy kompilacji). Możesz użyć istniejących kompilatorów do wygenerowania kodu dla operacji wejścia/wyjścia (read i write) oraz dla wygenerowania kodu startu i końca Twojego programu.

Napisz programy w Kotku testowe testujące wszystkie konstrukcje języka i sprawdź ich poprawne działanie po skompilowaniu Twoim kompilatorem.

Zadanie 5.a: Sprawdzanie poprawności rzutowania

W językach obiektowych często zachodzi potrzeba rzutowania na podklasę obiektu. Najprostszą metodą sprawdzania poprawności rzutowania jest sprawdzenie, czy wskaźnik do tablicy metod wirtualnych wskazuje na tablicę dla szukanego typu. Jeśli nie, to musimy sprawdzić również nadklasę naszego obiektu (wskaźnik do tablicy metod wirtualnych nadklasy możemy przechowywać w samej tablicy metod wirtualnych. Takie sprawdzenie wymaga przejścia po nadklasach obiektu i może być czasochłonne.

Zaprojektuj metodę sprawdzającą przynależność obiektu do klasy w stałym czasie.

Wskazówki:

  • załóż skończoną wysokość hierarchii klas,
  • rozważ dodanie pewnych informacji do każdej klasy.

Zadanie 5.b: Metody i zmienne statyczne

Niektóre języki programowania obiektowego (np. C++, Java) dopuszczają statyczne metody i pola klas, a także metody finalne, czyli nie podlegające przedefiniowaniu w podklasach.

Czym może różnić się kod wynikowy dla dostępu do statycznych pól, statycznych metod i finalnych metod od normalnego kodu? Jak takie właściwości pomagają w optymalizacji?

Zadanie 6: Rozszerzenia języka

Język Kotek w obecnej postaci jest raczej ubogi. Pożądane jest rozszerzenie go. Poniższe rozszerzenia nie muszą być wykonywane w podanej kolejności.

Zadanie 6.a: Typ logiczny

Dodaj do języka typ logiczny bool. Popraw analizę semantyczną tak, aby rozróżniać typy int i bool. Rozszerz gramatykę wyrażeń logicznych tak, aby

a || b || c
a && b && c
a && b || c

były poprawnymi wyrażeniami. Załóż priorytet && nad ||.

Zadanie 6.b: Typ zmiennoprzecinkowy

Dodaj do języka typy zmiennoprzecinkowe: float (pojedyncza precyzja) i double (podwójna precyzja). Zmień analizę semantyczną tak, aby uwzględniała nowe typy. Dodaj operator rzutowania na odpowiednie typy arytmetyczne:

(int) Wyrażenie
(float) Wyrażenie
...

Zadanie 6.c: Dodatkowe operatory

Dodaj nowe operatory dla wyrażeń, znane z języka C: ++, --, += (i pokrewne), operatory bitowe (|, &).

Zadanie 6.d: Automatyczne odśmiecanie

Zrób automatyczne odśmiecanie niedostępnych obiektów (zamiast instrukcji delete). Najlepiej użyj gotowego odśmiecacza (ang. Garbage Collector), choć możesz pokusić się o własną implementację

Zadanie 6.e: Rzutowanie i sprawdzanie typu obiektu

Rzutowanie oraz sprawdzanie typu są konstrukcjami często wykorzystywanymi w programowaniu obiektowym.

Rozszerz język o konstrukcję rzutowania:

(Podklasa) mojObiekt

i sprawdzania typu:

obiekt instance of klasa

Obie konstrukcje są wyrażeniami. Pierwsza daje typ Podklasa a druga typ int (wartość 0 lub nie-zero; jeśli do języka został dodany typ bool, to preferowany jest typ bool). Wartością wyrażenia rzutowania jest zero, jeśli obiekt nie jest instancją klasy na którą rzutowanie jest wykonywane (dynamiczne sprawdzanie typów przy rzutowaniu).

Zadanie 6.f: Ulepszenie read i write

Język Kotek zawiera bardzo prymitywne mechanizmy wejścia wyjścia. Rozszerz język o:

  • możliwość odczytania wartości do pola rekordu lub klasy oraz do elementu tablicy (obecnie gramatyka dopuszcza czytanie tylko zmiennej atomowej)
read a[5];
read mojRekord.pole1;
  • możliwość czytania i pisania wielu wartości na raz
read a[i], b[i];
write "a[5] = ", a[5];

Zadanie 6.g: Chronione pola klasy

Dodaj do języka mechanizm ochrony deklaracji klasowych. Deklaracja wewnątrz klasy będzie rozszerzona o modyfikator private, public lub protected (obok nazwy typu deklaracji):

var private x : int;
function public f() : bool;
type protected typ = { var x: int; var y : int };

Deklaracje public mają być widoczne zawsze, private tylko wewnątrz klasy a protected wewnątrz klasy i w podklasach.

Zadanie 6.h: Typy złożone dostępne bez deklaracji

W obecnej postaci język wymaga deklaracji typów złożonych przed ich użyciem, np.

type intArr = array of int;
var x : intArr;

zamiast

var x : array of int;

Rozszerz język o tę nową konstrukcję. Zwróć uwagę, że będzie możliwe napisanie:

var x : array of array of int;

Możesz też pokusić się o implementację anonimowych rekordów deklarowanych przy deklaracji zmiennej.

Zadanie 6.i: Inicjalizacja zmiennych

Zmień deklaracje zmiennych tak, żeby przy deklaracji w procedurze, rekordzie lub klasie (ale nie jako parametru funkcji) trzeba było (obowiązkowo!) podać wartość inicjalizacji zmiennej. Przykład:

var x : int := 50;

Przyjmij, że inicjalizacja tablic i rekordów następuje w momencie ich tworzenia (new):

y = new int[3*x] of 5;
z = new myRecord { field1 := 4, field2 := "ala ma kota"};

Przy inicjalizacji zmiennej dopuszczalne jest użycie zmiennej zadeklarowanej wcześniej na tym samym poziomie, wszystkich funkcji na tym poziomie oraz wszystkich zmiennych i funkcji zadeklarowanych na poziomach wyższych.

Wymagania implementacyjne i ocenianie

Aby zaliczyć laboratorium trzeba napisać działający kompilator. Na niższą ocenę nie musi on kompilować wszystkich konstrukcji języka. Implementacja rozszerzeń języka jest dodatkowo punktowana, ale nie wymagana do zaliczenia przedmiotu.

Ocena: Wymagania:
5 Wszystkie podstawowe rzeczy (bez rozszerzeń języka) a dodatkowo operator instance of i rzutowanie (Zadanie 6.e). W szczególności muszą być zaimplementowane obiekty i lokalne deklaracje procedur (nie muszą być dostępne lokalne deklaracje klas).
4 Bez obiektów. Muszą być dostępne lokalne deklaracje procedur.
3 Bez obiektów i lokalnych deklaracji procedur.