Compilatori 2005
docente: Fausto Spoto fausto.spoto@univr.it
Il compilatore Kitten versione 0.01
Documentazione della libreria BCEL
Sito da cui scaricare il graphviz per il formato .dot
Il manuale ufficiale della Java Virtual Machine
Making Compiler Design Relevant for Students who will (Most Likely) Never Design a Compiler di Saumya Debray
Lucidi:
Testi d'esame:
24 giugno 2002 postscript,
pdf
3 luglio 2002 postscript,
pdf
6 settembre 2002 postscript,
pdf
20 dicembre 2002 postscript,
pdf
2 aprile 2003 postscript,
pdf
23 giugno 2003 postscript,
pdf
25 luglio 2003 postscript,
pdf
24 settembre 2003 postscript,
pdf
10 ottobre 2003 postscript,
pdf
12 dicembre 2003 postscript,
pdf
26 marzo 2004 postscript,
pdf
2 aprile 2004 postscript,
pdf
21 giugno 2004 postscript,
pdf
5 luglio 2004 postscript,
pdf
21 settembre 2004 postscript,
pdf
1 aprile 2005 postscript,
pdf
20 giugno 2005 postscript,
pdf,
soluzione esercizi 2-3
4 luglio 2005 postscript,
pdf,
soluzione esercizi 2-3
5 settembre 2005 postscript,
pdf
5 ottobre 2005 postscript,
pdf
Lezione 1 (11 gennaio 2005)
introduzione al corso
compilatori, fasi di compilazione.
esempi dei risultati parziali delle fasi di compilazione
esempi di programmi Kitten
Laboratorio 1 (12 gennaio 2005)
installazione di Kitten
esempi di utilizzo e compilazione in Kitten
Lezione 2 (14 gennaio 2005)
espressioni regolari
traduzione in automi a stati finiti
implementazione dell'analisi lessicale di Kitten
Lezione 3 (21 gennaio 2005)
parsing discendente ricorsivo
insiemi NULLABLE, FIRST e FOLLOW
Laboratorio 2 (25 gennaio 2005)
aggiungere al linguaggio Kitten i token
- ++
- --
- switch
- case
- default
Questo si ottiene modificando il file Syntactical/sym.java in modo
da aggiungervi cinque nuovi token, enumerati con una costante diversa da quelle
già usate. Bisogna quindi inserire le espressioni regolari per i
nuovi token dentro Lexical/Kitten.lex e aggiungere la stringa
che va stampata per i nuovi token dentro Lexical/Main_lexical.java.
Infine, compilate con make clean (per cancellare tutto)
seguito da make lexical.
Provate il risultato su un file sorgente
Kitten da voi creato e contenente i nuovi token!
Lezione 4 (26 gennaio 2005)
esercizi sul parsing discendente ricorsivo
gerarchia di grammatiche LL(k)
automi a pila
item LR(0), chiusura di un insieme di item
costruzione dell'automa LR(0)
Lezione 5 (28 gennaio 2005)
costruzione della tabella LR(0)
esercizi di parsing LR(0)
parsing SLR
esercizi di parsing SLR
Lezione 6 (4 febbraio 2005)
parsing LR(0) ed SLR con epsilon-produzioni
item LR(1)
costruzione dell'automa LR(1)
costruzione delle tabella LR(1)
Laboratorio 3 (8 febbraio 2005)
l'analisi sintattica di Kitten: lucidi
aggiunta a Kitten delle produzioni sintattiche per
- post-incremento
- post-decremento
- switch
Questo si ottiene modificando le produzioni grammaticali dentro
Syntactical/Kitten.cup. Si noti che:
- se si stanno aggiungendo token al linguaggio, occorre dichiararli
nella tabella terminal
- se si aggiungono non terminali alla grammatica, tali non terminali
vanno dichiarati nella tabella per i non terminal
- si usi {: RESULT = null; :} come azione semantica delle
nuove produzioni che si aggiungono alla grammatica, tranne se le si
aggiungono ai comandi, per cui si usi
{: RESULT = new Skip(0); :}
Quindi si compili con
make syntactical. Eventuali errori sono scritti dentro al file
Syntactical/Kitten.err.
Si noti che i post-incremento
e post-decremento possono essere sia espressioni che comandi.
La sintassi deve essere scelta in modo che i seguenti sorgenti
siano riconosciuti come legali:
Switch1.kit,
Switch2.kit,
Switch3.kit e
Cifra2.kit.
Invece questi sorgenti devono provocare un syntax error:
Errore1.kit,
Errore2.kit e
Errore3.kit.
Si noti che i case dello switch sono espressioni arbitrarie!
Lezione 7 (9 febbraio 2005)
parsing LALR(1)
esercizi sul parsing LR(1) ed LALR(1)
gerarchia di grammatiche LR(k)
gerarchia complessiva delle grammatiche
parsing con grammatiche ambigue, associatività e precedenze
- il caso dell'if/then/else
- il caso delle espressioni aritmetiche
Lezione 8 (11 febbraio 2005)
esercizi sul parsing LL(1), SLR, LR(0) ed LR(1)
classi di sintassi astratta
generazione dell'albero di sintassi astratta con parsing LL ed LR
visione d'insieme delle classi di sintassi astratta di Kitten
Laboratorio 4 (15 febbraio 2005)
classi di sintassi astratta di Kitten
implementazione delle classi di sintassi astratta per
- post-incremento
- post-decremento
- switch
Occorre creare delle classi di sintassi astratta
dentro la directory Absyn. Fate attenzione
a dove metterle nella gerarchia globale. Post-incremento e post-decremento
possono essere sia comandi che espressioni, e quindi danno
origine a due classi di sintassi astratta ciascuno, sotto Expression
e sotto Command, rispettivamente.
Date a queste classi un metodo fittizio:
public Bytecode translate() {
return null;
}
dove Bytecode sta per Bytecode.Bytecode.
Scrivete delle azioni
semantiche sotto le produzioni di sintassi concreta inserite nel laboratorio
precedente dentro al file Syntactical/Kitten.cup, e dichiarate il
tipo di ritorno di eventuali nuovi non terminali che sono stati introdotti.
Infine compilate con make syntactical.
Compilando i file di esempio
del laboratorio precedente, si dovrebbero generare dei file .dot
che, una volta trasformati in postscript, visualizzano anche
le nuove classi di sintassi astratta.
Lezione 9 (16 febbraio 2005)
approfondimenti sulle classi di sintassi astratta di Kitten
- il predicato blocked sui comandi
- il predicato breaks sui comandi
analisi semantica e type-checking: motivazione e fasi
linguaggi non tipati, debolmente tipati, fortemente tipati
tipi Kitten
fase 1 del type-checking: costruzione dei tipi Kitten dalla loro sintassi astratta
Lucidi sull'analisi semantica
Lezione 10 (18 febbraio 2005)
fase 2 del type-checking:
giudizi e regole
regole per le classi astratte di Kitten
implementazione delle regole di type-checking
controllo dell'accesso alle costanti
controllo della posizione di break e continue
Laboratorio 5 (22 febbraio 2005)
specifica e implementazione del type-checking per
- post-incremento
- post-decremento
- switch
Questo si ottiene in varie fasi:
- Aggiungete un metodo
public void loadClasses(TypeChecker checker) throws Exception
a tutte le classi di sintassi astratta che avete aggiunto a Kitten. Tale metodo
è usato dalla fase 1 del type-checking per fare il buildType
di tutte le classi citate in un pezzo di codice. In pratica, dovrete
semplicemente richiamarlo ricorsivamente sui componenti della vostra sintassi
astratta. Guardate è fatto, per esempio, dentro
Absyn/IfThenElse.java.
- Scrivete delle regole di controllo dei tipi per le nuove classi
di sintassi astratta che avete aggiunto a Kitten. Si permetta
a post-incremento e post-decremento di lavorare soltanto su interi.
Si noti che invece non
è obbligatorio che il selettore di uno switch sia un intero,
ma che deve esistere una qualche relazione fra il selettore e le condizioni
inserite per ciascun case. Per esempio, se il selettore è
un intero allora nessuna condizione di un case può essere uno
Studente. Questo è un esempio, trovate una regola generale!
- Aggiungete un metodo
public Types.Type typeCheck(TypeChecker checker)
alle classi di sintassi astratta che avete aggiunto per nuove espressioni
e nuovi comandi. Tale metodo è usato nella fase 2 del type-checking per
implementare le regole di controllo
dei tipi identificate sopra
per tali classi astratte. Si ricorda che questo metodo per i comandi deve
sempre terminare con
return super.typeCheck(checker);
mentre per le espressioni deve sempre terminare con
return storeStaticType(...);
- A questo punto dovreste già essere capaci di compilare
Kitten (make semantical) e compilare gli esempi
Switch1.kit,
Switch2.kit,
Switch3.kit e
Cifra2.kit senza alcun errore se non quelli relativi
alla posizione del break. Risolveremo questo problema
nel prossimo laboratorio.
Lezione 11 (23 febbraio 2005)
esercizi sul type-checking
il bytecode Kitten:
- operazioni sullo stack
- operazioni sugli oggetti
- operazioni sugli array
Lucidi sul bytecode Kitten.
Lezione 12 (25 febbraio 2005)
eserici sul bytecode sequenziale
chiamata e ritorno da metodo nel bytecode Kitten
blocchi di base: linkati, condizionali e finali
esercizi sul bytecode Kitten non sequenziale
Laboratorio 6 (1 marzo 2005)
Modifica del type-checking in modo da permettere ai break
di comparire nel corpo dei case.
- Aggiungete una nuova modalità a
Semantical/TypeChecker.java, cioè un suo ulteriore
campo:
public boolean _case;
- Estendete i costruttori di Semantical/TypeChecker.java
in modo da considerare anche questo campo.
- I metodi di clonaggio cloneForParser,
cloneIntoReader,
cloneIntoWriter e
cloneIntoLoop,
che vengono usati per cambiare parser o modalità,
devono copiare questo campo.
- Aggiungete un metodo cloneIntoCase che crea e restituisce
un type-checker identico a this ma in modalità _case.
- Modificate il metodo typeCheck che avete scritto
per lo switch in modo da fare il type-checking dei case
in modalità _case.
- Modificate il metodo typeCheck di Absyn/Break.java in
modo da rendere leciti i break che occorrono dentro il case
di uno switch.
- Eseguite make semantical. La compilazione di
Switch1.kit,
Switch2.kit,
Switch3.kit e
Cifra2.kit
deve avvenire senza la segnalazione di alcun errore.
Lezione 13 (4 marzo 2005)
generazione del codice intermedio per le espressioni: tipo statico
e contesto
generazione del codice intermedio per i leftvalue visti come espressioni
lucidi sulla generazione del codice intermedio
Laboratorio 7 (8 marzo 2005)
implementazione della generazione del codice intermedio per
- post-incremento di variabile
- post-decremento di variabile
visti come espressioni.
Suggerimento:
- Definite dentro Absyn/Lvalue.java due metodi:
public abstract Bytecode translateForPostIncrementAsInteger()
public abstract Bytecode translateForPostDecrementAsInteger()
e instanziateli dentro le due sottoclassi
Absyn/FieldAccess.java e Absyn/ArrayAccess.java in modo
da ritornare un bytecode che calcola, per esempio, sempre la costante 0.
- Instanziate tali metodi anche dentro Absyn/Variable.java, ma
questa volta dovete fargli ritornare un bytecode che fa il post-incremento
o il post-decremento della variabile, oltre a lasciare sullo stack il
valore originale della variabile, cioè quello che essa
aveva prima della modifica. Dovreste riuscire a scrivere tutto usando i
soli bytecode che abbiamo descritto a lezione. Non usate variabili
locali per memorizzare risultati temporanei perché non sapete
quali di esse sono libere e quali utilizzate nel metodo in cui
l'espressione occorre
- Aggiungete alle classi di sintassi astratta che avete definito
per post-incremento
e post-decremento il metodo translate, che chiamerà
uno dei due metodi visti sopra per generare il codice che incrementa
o decrementa il leftvalue, rispettivamente
- Compilate Kitten con make translate
- Dovreste a questo punto essere capaci di compilare
Post.kit e ottenere il diagramma
del bytecode generato dentro
Post.main() [void].dot. Traducetelo in postscript e
visualizzatelo
- Se quello che vedete vi sembra sensato, ricompilate Kitten con
make jbgeneration
- Ricompilate Post.kit, ottenendo un file
Post.class che,
una volta eseguito, dovrebbe stampare i numeri da uno a cento
Lezione 14 (9 marzo 2005)
la generazione del codice intermedio per la scrittura nei leftvalue
la generazione del codice intermedio per i comandi
esercizi sulla generazione del codice intermedio
Lezione 15 (11 marzo 2005)
la generazione del codice intermedio per break e
continue
esercizi sulla generazione del codice intermedio
la generazione del codice oggetto Java bytecode
lucidi sulla generazione del codice oggetto
Java bytecode
Progetto
Continuate il lavoro dei laboratori:
- Realizzate la generazione del codice
intermedio per post-incremento e post-decremento di tutti e tre i casi
dei leftvalue, visti sia come espressioni che come comandi.
Se vi servissero bytecode non visti a lezione, definiteli come sottoclassi
di Bytecode/Bytecode.java, instanziando in tali nuove
sottoclassi il metodo JBGenerate che li traduce in Java
bytecode
- Attenzione perché una Absyn/Variable.java rappresenta
sia una variabile che un leftvalue del tipo this.id dove
this è sottointeso. Dovete distinguere i due casi
nella generazione del codice intermedio
- Realizzate la generazione del codice intermedio per lo switch.
I bytecode visti a lezione dovrebbero essere sufficienti ai vostri
scopi. Il default deve venire eseguito solo se nessuno
dei case è stato eseguito.
- Ricordatevi che un break dentro ai case deve
fare uscire dallo switch, altrimenti si esegue il case
successivo
- I leftvalue di post-incremento e post-decremento, nonché
le espressioni dello switch devono essere valutate
esattamente una volta, mai piú volte!
Menzione speciale della giuria
Un bonus di ben tre punti verrà accordato al gruppo che
consegnerà entro la mezzanotte del 3 aprile un progetto che
compila correttamente il file Gara.kit
generando un file Gara.class più piccolo di quello
dei concorrenti. A parità
di dimensione, vince chi ha consegnato prima.