Ostuni Dario <dario.ostuni@studenti.unimi.it>
(mat. 870321)
Nitti Gianluca <gianluca.nitti@studenti.unimi.it>
(mat. 871108)
Il progetto è gestito con Gradle. Per compilarlo, generando un fat jar (cioè un archivio jar eseguibile contenente tutte le dipendenze), si esegue il comando:
(su Linux / macOS)
./gradlew jar
(su Windows)
gradlew.bat jar
Il jar viene generato in build/libs/compiler-lt2018.jar
. Quindi, ad esempio, per compilare un programma contenuto in un file chiamato programma.lang
, il comando da eseguire è
java -jar build/libs/compiler-lt2018.jar programma.lang
che, se non vi sono errori, genera un eseguibile a.out
nella cartella corrente.
Eseguendo invece
java -jar build/libs/compiler-lt2018.jar -h
si otterrà un'elenco delle opzioni da riga di comando disponibili, che consentono ad esempio di modificare il linker da utilizzare (l'opzione di default è cc
) o di salvare in forma testuale l'assembly LLVM-IR.
-
src/main/jflex/Lexer.jflex
File di specifica JFlex dell'analizzatore lessicale; scansiona il file sorgente in input, producendo token compatibili con il parser generato da CUP (i token sono costituiti da oggeti della classe Symbol, dalle librerie di runtime di CUP). Sono presenti 3 stati:
- Nello stato iniziale (
YYINITIAL
), l'analizzatore lessicale scarta eventuali commenti o ritorni a capo - Nello stato
BODY
, vengono prodoti i token che rappresentano i simboli terminali della grammatica del linguaggio, ad eccezione dei letterali stringa - Lo stato
STRING_LITERAL
è utilizzato per riconoscere i letterali stringa; il carattere"
è interpretato come fine della stringa, mentre ogni altro carattere viene aggiunto alla stringa che si sta costruendo. Utilizzare uno stato dedicato consente di riconoscere correttamente più stringhe nella stessa istruzione/riga; l'implementazione iniziale consisteva in una regola\".*\"
all'interno dello stato BODY; tuttavia in questo modo, ad esempio, in un'istruzioneinput "A:" + input "B:"
,"A:" + input "B:"
veniva erroneamente riconosciuto come un'unico letterale stringa, poichè JFlex effettua il match della stringa più lunga. Per semplificare il lavoro del parser, l'analizzatore lessicale restituisce un'unico token di tipo NEWLINE (separatore delle istruzioni) anche in presenza di più ritorni a capo e/o linee di commento consecutivi. Inoltre, i simboli terminali corrispondenti a letterali di tipo numerico (decimali o esadecimali) non vengono forniti al parser sotto forma di stringa ma direttamente come numeri (oggetti della classeLong
).
- Nello stato iniziale (
-
src/main/cup/Parser.cup
File di specifica CUP contenente la grammatica del linguaggio, utilizzato per generare il parser. Un breve frammento di codice Java inserito con la direttiva
parser code
è utilizzato per migliorare la segnalazione degli errori, effettuando l'override di alcuni metodi della classe padre e sollevando delle eccezioni (definite nel packageerr
) all'interno delle implementazioni. Seguono la dichiarazione dei simboli terminali e nonterminali, delle precedenze ed infine le regole della grammatica. Le azioni semantiche associate ad ogni regola costruiscono degli oggetti del packageast
, che costituiscono i nodi dell'albero di parsing. -
src/main/java/App.java
Classe principale dell'applicazione (contenente il metodo
void main(String[] args)
). Viene utilizzata la libreria esterna Argparse4j per semplificare la gestione degli argomenti da riga di comando. Una volta letti gli argomenti, il programma legge il file sorgente e lo fornisce in input all'analizzatore lessicale, che a sua volta viene utilizzato come input del parser. Prima di fare ciò, tuttavia, alla fine dell'input viene aggiunto un ritorno a capo; ciò è necessario poichè nella grammatica il ritorno a capo è utilizzato come separatore delle istruzioni, quindi se non ve ne fosse almeno uno alla fine del sorgente, il parser non sarebbe in grado di riconoscere l'input. Se l'operazione di parsing ha successo, avremo ottenuto come radice dell'Abstract Syntax Tree un oggetto di tipoast.statements.Stmt
che rappresenta la lista di istruzioni del programma. A questo punto viene estratto l'elenco di variabili utilizzate e passato alla classeCodeGen
, che lo utilizzerà per costruire la symbol table. A questo punto, sempre tramite i metodi della classeCodeGen
, viene generato il codice eseguibile che viene salvato in un file oggetto; infine, viene invocato il linker di sistema per produrre il file eseguibile. -
src/main/java/ast/statements/Stmt.java
Classe astratta che rappresenta un'istruzione all'interno dell'albero sintattico. Dichiara i metodi astratti che devono essere implementati da ogni tipo di istruzione, per costruire l'elenco di variabili e per effettuare la generazione del codice. Le quattro classi seguenti (
OutputStmt
,StmtList
,AssignStmt
eLoopStmt
) estendonoStmt
fornendo ciascuna le implementazioni adeguate di tali metodi. -
src/main/java/ast/statements/OutputStmt.java
Rappresenta un'istruzione
output
onewLine
del linguaggio. L'implementazione del metodo di genrazione del codice genera una chiamata alla funzioneprintf
dilibc
. -
src/main/java/ast/statements/StmtList.java
Sequenza di istruzioni; viene utilizzata per rappresentare un intero programma o il corpo di un ciclo.
-
src/main/java/ast/statements/AssignStmt.java
Rappresenta un'istruzione di assegnamento; un oggetto della classe
AssignStmt
viene costruito dal parser solamente quando un assegnamento è utilizzato come istruzione ed il suo valore non viene utilizzato. Ad esempio, nell'istruzionea = b = c
il primo=
dà origine ad un'istanza diAssignStmt
, mentre il secondo ad un'istanza diAssignExpr
. -
src/main/java/ast/statements/LoopStmt.java
Rappresenta un ciclo. Ha come nodi figli un'
Expr
(condizione) ed unoStmt
(corpo). -
src/main/java/ast/expressions/Expr.java
Classe astratta simile a
Stmt
, ma che rappresenta un'espressione invece che un'istruzione. La principale differenza è che qui il metodocodeGen
restituisce un riferimento alla posizione (ad esempio un registro del processore o un'indirizzo di memoria) in cui, in fase di esecuzione, sarà presente il valore dell'espressione. -
src/main/java/ast/expressions/AssignExpr.java
Espressione di assegnamento; vedere l'esempio per
AssignStmt
. -
src/main/java/ast/expressions/BinaryOpExpr.java
Classe astratta che estende
Expr
e rappresenta un'operazione binaria tra due espressioni (i suoi nodi figli). Definisce un metodo astrattodoBinaryCG
, che, dati i valori dei due operandi (valutati ricorsivamente), deve resitituire il riferimento al risultato dell'operLa symbol table è una mappa associativa dove le chiavi sono i nomi delle variabili e i valori sono puntatori a dove si trovano le variabili in memoriaazione. -
src/main/java/ast/expressions/AddExpr.java
-
src/main/java/ast/expressions/SubExpr.java
-
src/main/java/ast/expressions/MulExpr.java
-
src/main/java/ast/expressions/DivExpr.java
-
src/main/java/ast/expressions/ModExpr.java
Sottoclassi di
BinaryOpExpr
ch implementano le rispettive operazioni. -
src/main/java/ast/expressions/NegExpr.java
Rappresenta la negazione di un'espressione; il metodo di generazione codice calcola il valore di tale espressione e lo nega.
-
src/main/java/ast/expressions/InputExpr.java
Espressione di input; il metodo
codeGen
genera una chiamata alla funzione di sistemaprintf
che stampa il messagio di prompt, seguita da una chiamata ascanf
che legge un numero e ne restituisce il riferimento come valore dell'espressione. -
src/main/java/ast/expressions/NumLiteralExpr.java
Letterale numerico presente nel programma;
codeGen
inserisce il valore come immediato e ne restituisce il riferimento. -
src/main/java/ast/expressions/IdentifierExpr.java
Rappresenta l'utilizzo di un identificatore; viene cercato nella symbol table l'indirizzo di memoria associato alla variabile, e viene generato il codice che carica il valore leggendo da tale indirizzo.
-
src/main/java/ast/expressions/IfExpr.java
Rappresenta un utilizzo dell'operatore condizionale (ternario); ha come nodi figli tre espressioni: la condizione e i due possibili risultati. Viene generato il codice per valutare ognuna di esse, e la logica per scegliere il ramo corretto.
-
src/main/java/cg/CodeGen.java
Classe singleton per generare codice macchina tramite LLVM. La classe (la prima volta che viene instanziato l'oggetto singleton) inizializza LLVM e crea le necessarie strutture. Ne viene poi chiamata una funzione per inizializzarne la symbol table. Offre funzioni per la traduzione dell'AST nella rappresentazione intermedia di LLVM. Infine offre un metodo di finalizzazione per emettere il codice oggetto generato.
-
src/main/java/err/AbstractError.java
Sottoclasse di
java.lang.Exception
che rappresenta un generico errore di compilazione, memorizzandone la posizione (indici di riga e colonna). -
src/main/java/err/ParseError.java
Sottoclasse di
AbstractError
utilizzata per rappresentare errori riportati dall'analizzatore sintattico durante il parsing. -
src/main/java/err/LexError.java
Sottoclasse di
AbstractError
utilizzata per rappresentare errori riportati dall'analizzatore lessicale durante l'individuazione dei token.
La symbol table è una mappa associativa dove le chiavi sono i nomi delle variabili e i valori sono puntatori a dove si trovano le variabili in memoria.