Esplora E-book
Categorie
Esplora Audiolibri
Categorie
Esplora Riviste
Categorie
Esplora Documenti
Categorie
D2C++ COMPILER
SOMMARIO
1. INTRODUZIONE ................................................................................................................... 5 1.1 STRUMENTI UTILIZZATI ............................................................................................ 6 FLEX ......................................................................................................................... 6 BISON ....................................................................................................................... 8
ANALIZZATORE LESSICALE .......................................................................................... 13 2.1 2.2 IMPLEMENTAZIONE .................................................................................................. 13 ERRORI LESSICALI ..................................................................................................... 17
3.
4.
ANALIZZATORE SEMANTICO E GENERAZIONE DEL CODICE INTERMEDIO ..... 28 4.1 4.2 4.3 GESTIONE DEGLI ERRORI SEMANTICI ................................................................. 28 PARSER-STACK OPTIMIZATION ............................................................................. 31 GENERAZIONE DEL CODICE INTERMEDIO ......................................................... 33
5.
SYMBOL TABLE ................................................................................................................ 36 5.1 5.2 5.3 INTRODUZIONE .......................................................................................................... 36 TIPI DI DATO................................................................................................................ 39 ADD&FIND NELLA SYMBOL TABLE ..................................................................... 43
HT_VARIABLES IMPLEMENTATION ............................................................................ 44 HT_SCOPE IMPLEMENTATION ...................................................................................... 45 HT_CLASS IMPLEMENTATION ...................................................................................... 46 HT_ATTRIBUTE IMPLEMENTATION............................................................................. 47
3
GRAPHICAL USER INTERFACE ...................................................................................... 75 CONCLUSIONI .................................................................................................................... 86 8.1. 8.2. 8.3. PERCH IL LINGUAGGIO D? .................................................................................... 86 PERCH IL C++? .......................................................................................................... 86 QUALI SONO I PRINCIPI DI REALIZZAZIONE DEL TEMA DANNO?............... 86
BIBLIOGRAFIA ........................................................................................................................... 87
1. INTRODUZIONE
Il D-Language [1] un linguaggio moderno. Esso mira a un obiettivo ambizioso: unire le potenzialit dei linguaggi C++ e Java. La sua caratteristica quella di essere un linguaggio con una sintassi C-Like ed una tipizzazione statica. Poich mette a disposizione notevoli funzionalit, esso un linguaggio molto complesso e richiede una certa esperienza da parte del programmatore per poter sfruttare a pieno le potenzialit del linguaggio. Nonostante la tipizzazione statica, il D-Language consente anche lutilizzo di aspetti dinamici, evitando talvolta alcune ridondanze nello specificare i tipi. Tra le caratteristiche principali di questo linguaggio, citiamo il garbage collector, il function overloading, lObject Oriented Programming, Class and Function Templates e molto altro. Nel nostro caso, vista la complessit di certi argomenti che richiederebbero troppo tempo qualora venissero trattati (ma non necessariamente eccessive problematiche risolutive), ci siamo soffermati solo sui costrutti fondamentali del linguaggi di programmazione di alto livello, sui tipi di dati pi comunemente utilizzati e su aspetti base della programmazione orientata agli oggetti; proprio relativamente a questultimo aspetto si spiega la scelta di un linguaggio intermedio di alto livello, come il C++. Di seguito portiamo un piccolo esempio di codice scritto in linguaggio D:
1.1.1 FLEX
Flex [2] [3] si propone come un nuovo tool open source per la generazione automatica di analizzatori lessicali. stato scritto originariamente in C da Vern Paxson nel 1987. Flex sotto molti aspetti simile al generatore di scanner Lex, realizzato da M.E.Lesk e E.Schmidt degli AT&T. Bell Laboratories, che oltre ad avere lo svantaggio di essere un software proprietario, produce delle performance peggiori rispetto a Flex. Per tutti questi motivi si ritenuto opportuno optare per il primo applicativo e non per il secondo. Tale scelta stata anche facilitata dalla possibilit di poter utilizzare Flex in maniera identica a Lex, andandone in questo modo a peggiorare le performance, utilizzando durante la compilazione da terminale il flag l. Il purpose di Flex quello di generare uno scanner, il quale non nientaltro che un programma in grado di riconoscere allinterno di un testo. Per poter effettuare tale generazione Flex necessit di un file dotato del seguente pattern lessicali skeleton: Macro %{ #include Costant definition Scanner Macro }% Basic Definition Pattern Definition Support Procedure %% Token Definitions and Action %% Support Procedure C User Code
In cui il Name rappresenta il nome con cui successivamente si potr fare riferimento al pattern presente in Definition. Ogni Name dovr, in maniera simile al C, iniziare con una lettera o un _ seguito da zero o pi lettere, cifre, _ e -. Tutto ci che viene trovato dal primo spazio non bianco successivo al Name fino alla End Of Line viene considerato come Definition. Example: _Letter [a-zA-Z]*
Lesempio su proposto rappresenta unespressione regolare in grado di riconoscere tutte le lettere maiuscole o minuscole ripetute nel testo zero o pi volte. In Pattern Definition possibile specificare quelle che sono le azioni da eseguire ogni qual volta viene individuata unespressione regolare. Il modello per effettuare tali definizioni il seguente: Pattern Action
In Pattern pu essere specificato un nuovo pattern oppure pu direttamente essere richiamata unespressione regolare definita nella sezione Basic Definition attraverso il suo Name. In Action possono essere definite le operazioni che devono essere effettuate al riconoscimento di un determinato Pattern; nel caso di interazione con un analizzatore sintattico tali operazioni consistono, nella maggior parte dei casi, nel ritorno del token associato a quello specifico pattern. Se viene riscontrato pi di un possibile match con i pattern definiti, viene assunto quello che considera il maggior numero di caratteri. Se il numero di caratteri dovesse essere identico, allora tale lessema viene associato allespressione definita prima allinterno di Flex. Nellultima sezione del file che viene passato a Flex vengono poste le definizioni di tutte le funzioni eventualmente dichiarate nella prima sezione e se lanalizzatore lessicale dovesse essere utilizzato senza lausilio di una parser, si potr scrivere un main che ci permetta di eseguirlo.
Limmagine in Fig 1.2 mostra efficacemente ci che viene effettuato da Flex, il quale si comporta essenzialmente come un compilatore, prendendo in ingresso un file con estensione .l e restituendo in uscita un file di default chiamato lex.yy.c, contenente limplementazione dello scanner nel linguaggio C; in particolare il contentuto di questo file viene esplicato da una funzione denominata yylex() che corrisponde esattamente a quello che si aspetta Bison, che descriviamo nella sezione successiva.
1.1.2 BISON
Per poter realizzare lanalizzatore sintattico, cuore del nostro front-end/traduttore, si optato anche in questo caso per lutilizzo di un tool open source di generazione automatica: Bison. Tale scelta stata fatta per due motivi: 1) La realizzazione di una parser LR hand coded un task molto complesso e la generazione di uno che sia efficiente e perfettamente funzionante potrebbe richiedere anche anni uomo; 2) Bison un tool di generazione automatica che prendendo un file di input simile a quello visto per Flex ci permette di realizzare un parser di tipo LR che sia efficiente e che ci permetta di analizzare linput in un tempo linearmente proporzionale alla sua lunghezza.
Prologue }% Bison Declaration Regole e strutture sintattiche %% Grammar Rules Epilogo e Sezione Routine Ausiliarie %% Epilogue Nella sezione Prologo vengono usualmente inserite tutte le dichiarazioni di variabili globali, dichiarazioni di funzioni e le include dei file le cui funzionalit verranno utilizzate durante lanalisi sintattica. In Bison Declaration vengono definiti tutti i token e perci i simboli terminali che Bison stesso utilizzer allinterno della grammatica. Questo potr essere fatto tramite lausilio della parola chiave token nel seguente modo: %token NomeToken
Il NomeToken sar quello utilizzato allinterno dellanalizzatore lessicale nel momento in cui verr effettuata la return NomeToken associata al riconoscimento di un lessema. In questa sezione potranno essere utilizzate anche altre funzionalit di Bison, tra cui definire lassociativit degli operandi, definire una union che permetta di associare un determinato tipo ad un token e di evidenziare, tramite la keyword %expect, il numero di shift-reduce che la grammatica produce. Nella sezione Regole e Strutture Sintattiche verr inserita la grammatica context free utilizzando la seguente notazione:
9
In cui NonTerminal indica un simbolo non terminale che a sua volta produce simboli terminali e/o non terminali(TerminalNonTerminal). In pi ad ogni produzione potr essere associata unazione semantica tramite la quale riusciamo ad utilizzare un SDT (Syntax Directed Translation Scheme). Possiamo inoltre associare ad ogni produzione dei frammenti di codice scritti in C, racchiusi tra parentesi graffe. Lultima sezione del file .y, Epilogo, contiene la definizione di tutte le procedure dichiarate nel prologo. Tale sezione verr ricopiata per intero nel file generato da Bison parser.tab.c. Permettere lintegrazione tra Flex e Bison molto semplice; baster effettuare una banale include specificando lheader del file .c prodotto da Bison allinterno della sezione Macro di Flex. Questo file .h contiene delle coppie NomeToken NumProgressivoAssociato che vengono utilizzate dallanalizzatore lessicale per capire quale valore deve essere restituito alla funzione yyparse(). Per poter compilare il file sorgente utilizzato per la definizione del parser con Bison, si utilizza analogamente a Flex il seguente comando da terminale: bison NomeFile.y con la possibilit di aggiungere eventuali flag.
10
Figura 1.3 3 esempi che mostrano il principio del Parser-Stack Implementation of Postfix SDTs, evidenziando in verde il caso in cui applicabile lottimizzazione, in rosso il caso contrario.
Per la generazione del codice intermedio, abbiamo scelto un linguaggio di alto livello, il C++, perch ci ha consentito di gestire le classi dal punto di vista degli errori e di poterle riportare comodamente nel linguaggio intermedio in caso di esito positivo; quindi, data la stretta
11
Ulteriori approfondimenti vengono specificati nel capitolo 5, dove introdurremo anche il concetto di Marker Non-Terminal che abbiamo utilizzato.
12
2. ANALIZZATORE LESSICALE
Lanalizzatore lessicale il primo componente fondamentale di un compilatore; esso ha il compito di individuare i lessemi allinterno del file di input, di riconoscerli mediante dei pattern costruiti dal programmatore, e di restituire al parser i relativi token.
2.1 IMPLEMENTAZIONE
Come gi detto nel capitolo precedente, per costruire lanalizzatore lessicale ci siamo serviti di Flex; di seguito mostriamo il codice relativo:
13
In questa prima sezione abbiamo descritto tutte le Basic_Definitions che ci sono servite nel progetto; oltre alle varie keyword, quelle di maggiore importanza sono _ID,
14
15
In questa sezione espletiamo le azioni corrispondenti ai vari token individuati; sostanzialmente, a parte i commenti per i quali non necessaria alcuna operazione se non quella di NON restituire nulla al parser, nella maggior parte dei casi la procedura prevede di: Richiamare una funzione countToken che incrementa il numero di token individuati; Valorizzare la variabile yylval con il contenuto del token presente in yytext; Restituire il token mediantre il comando return;
La variabile rows serve a tenere conto del numero di righe; viene incrementata allinterno dei commenti e ogni volta che si trova un token _EOL.
In questultima sezione defininiamo solo la funzione countToken() che semplicemente incrementa il numero di token. Tale funzione definita allinterno dellanalizzatore lessicale, non
16
17
3. ANALIZZATORE SINTATTICO
La CFG (Context-Free Grammar) implementata riguarda un sottoinsieme del linguaggio D; non stato possibile, soprattutto in termini di tempo, elaborare lintero linguaggio. Per la generazione delle produzioni, abbiamo consultato [7] e [8] con laccortezza di inserire opportune modifiche per evitare la presenza di conflitti di tipo reduce-reduce 1 assolutamente da evitare per il nostro tipo di parser. Ricordiamo inoltre che, trattandosi di un LR parser, abbiamo preferito ricorsioni sinistre (left-recursion) che introducono vantaggi in termini di memoria utilizzata 2.
3.1 IMPLEMENTAZIONE
Ricordiamo che un conflitto reduce-reduce un conflitto secondo cui il parser pu risolvere una stessa stringa mediante due riduzioni diverse; di default viene scelta la prima regola incontrata dal parser. Un conflitto shit-reduce un conflitto secondo cui il parser pu contemporaneamente shiftare o ridurre; di default viene privilegiata loperazione di shift. 2 Le ricorsioni sinistre vanno assolutatmente evitate se si tratta di un LL parser, perch potrebbero introdurre ricorsioni infinite. In Bison, tali ricorsioni, producono delle ottimizzazioni nella gestione della memoria.
18
19
20
21
22
23
24
Le immagini mostrano solo le regole sintattiche; nel codice ovviamente queste sono mischiate alle regole semantiche e di generazione del codice intermedio. Per motivi di ordine e di comprensione, non le abbiamo mostrate contemporaneamente. Come si pu vedere, nella grammatica abbiamo fatto ricorso alluso dei MarkerNonTerminal per poter attuare azioni intermedie (tipicamente legate a operazioni di traduzione) laddove questo avrebbe portato a dei conflitti non risolvibili diversamente. Lultima sezione, a partire da ClassDeclaration, dedicata alla parte relativa alla dichiarazione e definizione delle classi. Questa sezione sicuramente rappresenta la novit del nostro tema danno, avendo gestito anche lereditariet singola, loverloading e loverriding; tuttavia, questi argomenti riguarderanno il capitolo successivo.
25
In questo primo caso viene riportato lerrore sintattico generato nel ramo dello SwitchStatement che pu essere dovuto alla mancanza di ( e/o ) e/o errori nel blocco Expression. La modalit : panic mode.
Sempre attraverso la modalit panic mode viene gestito un eventuale errore allinterno del blocco Expression del ForStatement.
Sia attraverso la modalit panic mode ed error production allinterno del Do-While-Statement vengono gestiti errori sintattici pi dettagliati rispetto a quelli precedenti. In particolare viene individuato il caso in cui manchino separatamente (, ) e ;.
26
BlockStatement rappresenta il blocco di codice di definizione di una funzione, che per la nostra grammatica deve essere necessariamente racchiuso tra le parentesi graffe; quindi, sia attraverso la modalit error production che panic mode, vengono individuati e gestiti separatamente i casi in cui manchi { e }.
Nel blocco di dichiarazione un errore tipico quello di dimenticare il simbolo ;. Per questo, mediante la tecnica panic mode, gestiamo questo tipo di errore. Pi genericamente, con questo tipo di produzione riusciamo anche a gestire altri tipi di errori sintattici.
27
28
29
In Fig. 4.1 nella produzione utilizzata per definire un ereditariet, lazione semantica prevede di ricercare lesistenza della classe padre; in caso contrario verr generato un errore semantico.
In Fig. 4.2 viene mostrata la funzione che viene richiamata ogni volta che si verifica un errore semantico del tipo undeclared, ossia quando un dato viene utilizzato ma non stato dichiarato.
30
In Fig. 4.3 viene mostrata la funzione che viene richiamata ogni volta che si verifica un errore semantico del tipo redefinition of, ossia quando viene dichiarato un dato utilizzando due o piu volte lo stesso nome allinterno dello stesso scope.
In Fig. 4.4 viene mostrata la funzione che viene richiamata ogni volta che si verifica un errore semantico del tipo not a static member, ossia quando si prova ad assegnare un valore allinterno della definizione di una classe che non sia stata dichiarata come static.
op
Result
Result X4 X3 X2 X1
Come ci chiaramente noto, per le operazioni +,/,-,* e % abbiamo bisogno di due operandi, che a loro volta possono essere risultato di altre operazioni. Quindi, per ogni operazione di questo tipo, vengono effettuate due pop() per prelevare gli ultimi due valori, viene svolta loperazione e viene effettuata la push() del risultato 3. Supponiamo di gestire unoperazione di questo tipo: = ((/ ) ) 4 --- il risultato 2 = 15, = 3, = 2, = 4;
1) La prima operazione effettuata push(a) che inserisce il valore 15 nello stack; 2) Successivamente si passa a push(b); 3) A questo punto il parser ha riconsociuto la regola MulExpression _SLASH UnaryExpression e mediante un azione postfix effettua due pop, prelevando a e b, calcola la divisione e inserisce il risultato nello stack mediante push(a/b); 4) La prossima operazione sar pop(c); 5) Il parser riconosce la regola MulExpression _TIMES UnaryExpression, preleva i due valori dallo stack e vi inserisce il risultato; 6) Dopodich viene riconosciuto d e quindi pop(d);
N.B. :Avendo esplicitamente definito lassociativit e la precedenza degli operandi, avendo scritto una grammatica apposita al nostro tipo di parser LR, abbiamo sempre la certezza che i due valori utili si trovino in cima allo stack.
32
Fig. 4.7 Esempio di tecnica di traduzione. In evidenza, la regola di casting esplicito presente nel linguaggio D.
Per poter effettuare la traduzione si utilizza una SDT (Syntax Directed Translation Scheme), ossia dei frammenti di codice, nel nostro caso scritti in C, che vengono associati alle varie
33
Inoltre nel caso in cui, allinterno del file sorgente che si vuole compilare, il programmatore faccia uso dello standard output o dello standard input e quindi di operazioni di I/O classiche, dovr necessariamente importare delle librerie std.stdio per loutput e std.c.stdio per linput. Il front-end in caso di presenza di operazioni di I/O e di assenza dellimport di tali libreria restituir un messaggio di errore allutente (vedi paragrafo relativo agli errori semantici). Le due funzioni implementate sono la writefln per loutput e la scanf per linput. Le due hanno una sintassi molto simile alla printf e alla scanf del C, per tale motivo essendo il C++ retrocompatibile sono state adoperate tali funzioni per implementare a livello di codice intermedio le operazioni di I/O. In fase di generazione dell intermediateCode.cpp viene richiamata una funzione che, utilizzando un approccio di tipo blindly, va ad effettuare una include delle librerie necessarie per le operazioni di interazione con lo stdin e lo stdout in C++. Tale funzione mostrata di seguito:
Ultimo punto degno di nota e forse anche il pi complesso relativo alla generazione del codice intermedio il sistema di ottimizzazione realizzato ed implementato nel front-end. Durante la generazione del codice intermedio, a fronte di un assegnazione, anzich ricopiare eventuali espressioni aritmetiche allinterno del file intermediateCode.cpp, viene scritto solo il risultato
34
35
5. SYMBOL TABLE
5.1 INTRODUZIONE
Lo scopo principale della Symbol Table quello di tenere traccia di tutte le informazioni ricavate dai moduli separati e di poterle riutilizzare quando necessarie, sia nei moduli del front-end; che nei moduli del back-end. Per questo necessario progettare la Symbol Table nel miglior modo possibile, cosi che, ad esempio, il valore di una costante prelevato durante le azioni sintattiche possa essere immediatamente disponibile in fase di scrittura del codice intermedio. Nel nostro caso abbiamo deciso di strutturare le symbol table come hash table ad indirizzamento diretto, utilizzandone una implementazione gi esistente denominata Uthash [9] scritta da Troy Hanson e gi precedentemente utilizzata da altri colleghi in [10] e [11]. In particolare, abbiamo utilizzato 6 diverse symbol table: una per la gestione delle variabili (globali e di ciascuna funzione), una per le funzioni dichiarate (main, etc) una per la definizione delle classi, una per gli attributi , una per i metodi di ciascuna classe e un'altra per la dichiarazione degli oggetti. Per ciascuna symbol table abbiamo definito una chiave primaria composta che identificasse univocamente la specifica entry. Tale stratagemma stato utilizzato per risolvere il problema legato allo scope di validit delle variabili. Invece di generare un'unica symbol table per variabili, attributi, classi, oggetti, metodi e funzioni o di generarne una associata ad ogni scope, si preferito utilizzare le sei su citate indicizzandone laccesso con una chiave composta, la cui morfologia cambia a seconda della symbol table considerata. Le seguente immagini mostrano la struttura di tutte le symbol table utilizzate:
36
37
La particolarit di HT_Class che essa una doppia symbol table, ossia indicizzata sia secondo il nome della classe che secondo lo scope univoco associato a ciascuna classe. Questa doppia indicizzazione stata necessaria nel momento in cui dovevamo ricercare un elemento nella symbol table e avevamo a disposizione solo una delle due chiavi.
Va fatto un chiarimento sul concetto di scope e quindi anche su HT_scope. Per scope intendiamo definire una localit spaziale associata alla dichiarazione delle funzioni, in modo che le variabili dichiarate possano identificarsi allinterno di una zona del codice; evidente che
38
Tale struttura si rivelata molto utile per poter tenere traccia del valore associato ad ogni variabile utilizzata allinterno del sorgente analizzato Il compilatore D non permette il casting implicito durante una assegnazione e questo porta alla generazione di un errore ogni qual volta si ha unespressione del tipo: a=b+c; in cui a un tipo di dato int e b e c sono float. Il traduttore da noi realizzato invece aggira il problema operando un casting implicito(logicamente anche esplicito), generando cos al posto di un errore un warning che avvisa il programmatore della possibile perdita di dati a cui va incontro. Ovviamente se si cercasse di assegnare un char ad un intero, loperazione verrebbe comunque eseguita, ma il risultato non avrebbe alcun senso. Per permettere il funzionamento del tutto, sono state sfruttate due strutture di tipo typeVal: initVal e getVal . La prima stata usata per tener traccia del valore da assegnare al campo value di una variabile memorizzata allinterno della symbol table (il valore se non specificato settato
39
40
41
42
Request
Add entry
Find if_exist
Already Esist
Request
Find if_exist
Yes
No
HT_VARIABLES IMPLEMENTATION
44
HT_SCOPE IMPLEMENTATION
45
HT_CLASS IMPLEMENTATION
46
HT_ATTRIBUTE IMPLEMENTATION
47
HT_METHOD IMPLEMENTATION
48
HT_OBJECT IMPLEMENTATION
49
6. TEST CASES
In questa sezione verranno proposti tutti i casi di test e quindi tutti i principali errori che il nostro front-end per il DLanguage in grado di rilevare e comunicare allutente. Nel caso in cui lutente generi errori non contemplati dalla grammatica, verr generato un messaggio di errore di questo tipo: Fatal Error: this error isnt managed from the grammar specificando la riga in cui tale errore stato generato. Tutti i sorgenti relativi ai casi di test sono presenti alla directory /Test_Case e tramite lapplicativo realizzato potranno essere facilmente caricati ed eseguiti. Nelle successive immagini, relative ai vari test case, si possono notare del piccole differenze in termini di look dellinterfaccia, ci dovuto come spiegato nel capitolo 1.1 allutilizzo di due versioni differenti di Ubuntu.
test_case_hello_world.d.
Dopo aver caricato il file e cliccato sul bottone con etichetta Translate il risultato ottenuto il seguente:
50
Verificato che il sorgente fosse corretto e venisse compilato senza problemi, si passati a testare il front-end sottoponendo lo stesso esempio corredato di errori:
Loutput risultante da tale traduzione stato soddisfacente; ovviamente il compilatore ci ha restituito un errore insensato, ma il parsing comunque non stato interrotto.
Nel secondo caso di test stata eliminata la import che permette di utilizzare la funzione writefln e loutput della compilazione viene riportato di seguito:
51
52
53
proprio in questo primo esempio un po pi complesso che si cercato di testare lefficacia del nostro traduttore. Gli errori introdotti in questo prima verifica sono stati: 1. Mancata inclusione delle librerie per gestire le routine di I/O; 2. Mancata dichiarazione della variabile i utilizzata praticamente in tutto il sorgente;
54
Loutput derivante dalla traduzione stato in grado di individuare la mancata dichiarazione della i ogni qual volta questa veniva utilizzata e in pi stata segnalata la mancata import associata alle funzioni di I/O nel momento in cui queste venivano adoperate. Il numero totale di errori riscontrati stato pari a 22, poich in maniera corretta lutilizzo di i allinterno di ogni for stato considerato tre volte. Proprio come succede spesso nei compilatori, correggendo il codice consultando anche solo il primo errore, nella compilazione seguente spariscono anche gli altri errori.
55
La compilazione stata ripetuta considerando nuovi errori: 1. Eliminazione della parentesi graffa di chiusura del for a riga 16; 2. Eliminazione del return dal main; Nel DLanguage il main deve avere sempre come tipo di ritorno int. Il sorgente proposto il seguente:
56
Gli errori sono stati riconosciuti correttamente, lunica pecca che per viene riscontrata anche nei compilatori pi famosi e affermati la posizione associata ai due errori; questo ovvio poich in questo caso il parser associa lultima parentesi graffa } al for e quindi segnaler lerrore solo per la mancanza della parentesi associata alla funzione main. Discorso analogo pu essere
57
Mandando in pasto al nostro front-end il seguente codice abbiamo ottenuto un Successfully Translated. Il risultato della compilazione mostrato subito dopo il codice sorgente.
58
59
Il codice intermedio stato correttamente generato dal nostro compilatore e il risultato viene mostrato di seguito:
60
Come possiamo notare dallimmagine sovrastante tutti gli errori prima citati sono stati individuati correttamente, sia per numero di riga che per tipo di errore; daltro canto sono presenti errori aggiuntivi che individuano uno StackOverflow, tale errore si verifica a causa dellottimizzazione del codice intermedio (vedi Paragrafo 5.2). Poich per eseguire tale
61
62
Come possiamo vedere, nel codice intermedio (destra) le variabili c (in somma()), a, ris, ris1, tot (prima operazione), ris2 sono state direttamente calcolate, mentre c (in somma(a,b)), j, tot (seconda operazione) dipendono da variabili il cui valore cambia, per questo non possono essere calcolate. Lesito della compilazione nella figura sottostante:
63
Ora allinterno dello stesso codice, effettuiamo una variazione: come facile notare, la variabile a viene utilizzata allinterno del main, ma non stata dichiarata. Ecco il risultato della compilazione:
Il compilatore correttamente evidenzia la mancanza della dichiarazione di a ogni qual volta che questa viene utilizzata.
64
65
Automobile, con tre attributi privati (di default se non dichiarati) e tre metodi pubblici. Allinterno del main richiamiamo i tre metodi. Di seguito mostriamo il codice intermedio generato e il risultato della compilazione:
66
In questo caso mostriamo anche il risultato della compilazione del codice intermedio, il quale stampa correttamente a video la frase sto cambiando marcia. Adesso modifichiamo leggermente il codice,
dichiarando come private anche i metodi accelera() e frena(). Il risultato della compilazione il seguente:
Come previsto, il compilatore ci avverte che i due metodi sono privati, e quindi non sono accessibili.
67
Un ultima modifica per questo esempio la seguente: Nel codice mostrato, proviamo a richiamare la funzione frena(), passando un parametro; ma frena() non prevede alcun parametro; per cui il risultato della compilazione sar il seguente:
68
dichiariamo un oggetto di classe Motore; concettualmente, infatti, un automobile ha un motore; sfruttiamo, della quindi, allinterno gli
classe
automobile,
attributi della classe motore. Il risultato della compilazione e il relativo codice intermedio vengono mostrati nelle figure sottostanti:
69
Anche in questo caso, effettuiamo delle modifiche al codice per mostrare gli errori riconosciuti dal nostro compilatore: Nel codice rappresentato, lattributo vel_max della classe Motore privato, e quindi non pu essere utilizzato allinterno da nessuno, classe se non
della
Motore
stessa. In pi, definiamo un oggetto di una classe che non esiste. Nella pagina seguente possiamo osservare il risultato della compilazione
70
Un altro esempio di errori riconosciuti il seguente: In questo codice invece, viene sbagliata la modalit di accesso alla variabile vel_max; infatti vel_max non direttamente un attributo della classe Automobile e quindi bisogna accedervi
attraverso la classe Motore; in pi, allinterno del main viene dichiarata una variabile b, ma manca il ;.
71
72
In questo caso, oltre ad un accesso negato ad un attributo privato, mancano le due parentesi { e } del main().Lerrore generato di questo tipo: Syntax error: something wrong during Declaration. Maybe does ; miss?. Abbiamo scelto questo caso perch ovviamente anche il nostro compilatore pu sbagliare nel riconoscere un errore in alcune circostanze particolari. In questi casi, infatti, gli errori appropriati avrebbero dovuto riguardare esplicitamente la mancanza di { e }. Purtroppo, la mancanza contemporanea delle due parentesi crea un problema di precisione di questo tipo. Il prossimo caso il seguente:
73
In questo ultimo esempio, ancora una volta dimostriamo il funzionamento delle protezioni delle variabili e metodi (sia allinterno della classe che eredita, che allesterno) e la chiamata di un metodo che non esiste secondo quel passaggio di parametri. Nel capito seguente presenteremo linterfaccia grafica realizzata.
74
75
Nellimmagine sottostante viene mostrato la barra di menu, altres detta MenuBar, presente in alto a sinistra che permette in questordine di creare un nuovo file (New), di aprire un proprio file sorgente (Open), di salvare le modifiche apportate sul file(Save), di salvare il file con nome(Save As), di eseguire il programma(Run) e di aprire un terminale(Open Terminal).
La screenshot successiva mostra i due menu a tendina a cui possibile accedere e dai quali possibile innescare le stesse azioni descritte sopra.
76
I vari messaggi derrore che vengono individuati dai due casi duso: procedura derrore esecuzione e procedura derrore traduzione sono rappresentati nelle immagini sottostanti.
Per meglio inquadrare le operazioni che lutente potr innescare stato utilizzato uno strumento fondamentale dello standard UML 2.0: il diagramma dei casi duso. Nellimmagine sottostante possiamo vedere il diagramma dei casi duso, che come si pu evincere dal titolo, mette in evidenza tutte le operazioni che un utilizzatore dellinterfaccia( rappresentato nellimmagine dallattore Utente) pu effettivamente eseguire.
77
Fig. 7.6 Rappresentazione del diagramma dei casi duso Window Interaction
Fondamentalmente linterfaccia stata modellata in modo tale da ricordare un classico IDE (Integrated Development Environment) , per permettere allutente , in maniera intuitiva e senza aver letto alcun manuale sullapplicativo, di eseguire tutti i task previsti. Come in un qualsiasi IDE stata data la possibilit di caricare, creare e salvare file sorgenti; nel nostro caso i file sorgenti avranno estensione .d. Inoltre se la traduzione e quindi lanalisi del sorgente vanno a buon fine, lutente potr compilare e mandare in esecuzione il file contenente la rappresentazione intermedia, il tutto per due ragioni: 1) 2) Verificare che effettivamente il sorgente sia privo di errori; Mandare in esecuzione il programma per poterne visionare il risultato logico;
In aggiunta alle suddette funzionalit lutente/programmatore potr, in caso di necessit, aprire con la semplice pressione di un bottone un nuovo terminale per poter eseguire le operazioni pi disparate. Nelle pagine seguenti verranno riportati i casi duso presenti nellimmagine in forma testuale per riuscire cos ad avere uno scenario ancora pi completo delle operazioni consentite.
78
Lutente/programmatore potr decidere di caricare il file direttamente da un qualsiasi supporto di memorizzazione di massa. Prima dellapertura lutente dovr scegliere il percorso del file da caricare tramite una comunissima finestra di dialogo per la scelta del path Aprire un sorgente(Open) Descrizione Viene data la possibilit in maniera analoga ad un comune IDE di poter caricare un file sorgente che potr successivamente essere modificato e analizzato Il file sorgente da aprire deve avere estensione .d Il file, se non soggetto a modifiche, potr essere analizzato dal punto di vista lessicale, sintattico e semantico e se lanalisi avviene con successo anche eseguito Cliccare sulla sottovoce Open del menu oppure tramite la pressione del bottone presente nella menu bar in alto a sinistra dellinterfaccia Utente Nessuno
Pre-Condizioni Post-Condizioni
Evento Innescante
79
Viene permesso anche il salvataggio del file che stato soggetto a modifiche. Nel caso in cui la prima volta che viene effettuato il salvataggio per un nuovo programma realizzato dallutente, verr data allo stesso la possibilit di scegliere la directory di salvataggio e il nome del file. Se invece non ci troviamo in tale situazione allora il file sar salvato sulla directory precedentemente fornita. Salvare un sorgente(Save) Descrizione Dopo aver effettuato modifiche allinterno del file sorgente caricato o dopo aver realizzato un nuovo file sorgente direttamente da interfaccia, sar possibile salvare tali modifiche Il file sorgente dovr essere salvato con estensione .d Il file, se non soggetto a modifiche, potr essere analizzato dal punto di vista lessicale, sintattico e semantico e se lanalisi avviene con successo anche eseguito Cliccare sulla sottovoce Save del menu oppure tramite la pressione del bottone presente nella menu bar in alto a sinistra dellinterfaccia Utente Salva File Nessuno Finestra di Salvataggio Nessuno Nessuno
Pre-Condizioni Post-Condizioni
Evento Innescante
Attore Primario Include il caso duso Estende il caso duso Esteso dal caso duso Specializza il caso duso Generalizza il caso duso
Il programmatore tramite una finestra di supporto potr prelevare informazioni di carattere generale sui realizzatori e sullapplicativo. Verranno fornite anche le e-mail dei realizzatori per eventuali domande. Informazioni(Info) Descrizione Lutente per poter ricevere una risposta ad un qualunque quesito potr accedere alla sezione dedicata alle informazioni e prelevare le e-mail o informazioni aggiuntive sui creatori Nessuna
80
Pre-Condizioni
La chiusura dellapplicativo potr essere ottenuta con la pressione della classica x sullangolo della finestra oppure tramite la sottovoce presente allinterno del men a tendina. Uscire(Exit) Descrizione Pre-Condizioni Post-Condizioni Dopo aver terminato tutte le operazioni lutente potr uscire dallapplicativo Il file sorgente dovr essere salvato con estensione .d Il file, se non soggetto a modifiche, potr essere analizzato dal punto di vista lessicale, sintattico e semantico e se lanalisi avviene con successo anche eseguito Tale operazione sar perpetuata tramite la pressione della classica x presente sullangolo sinistro dellinterfaccia oppure utilizzando la sottovoce Exit del men Utente Nessuno Nessuno Nessuno Nessuno Nessuno
Evento Innescante
Attore Primario Include il caso duso Estende il caso duso Esteso dal caso duso Specializza il caso duso Generalizza il caso duso
Per poter raggiungere la piena analogia con i classici IDE, viene permesso allutente, dopo aver tradotto con successo il proprio programma di mandarlo in esecuzione. Dato che il linguaggio utilizzato per la rappresentazione intermedia del nostro compilatore il C++, viene effettuata una ulteriore compilazione con il g++. Questo pu essere utile per riscontrare la presenza di errori non individuati dal nostro traduttore e in questo caso lesecuzione verr bloccata; se invece anche tale compilazione va a buon fine allora il programma realizzato verr eseguito allinterno di una finestra di supporto.
81
Attore Primario Include il caso duso Estende il caso duso Esteso dal caso duso Specializza il caso duso Generalizza il caso duso
La funzionalit di base dellapplicativo e su cui si fonda tutto il progetto proprio la seguente, cio la possibilit di effettuare la traduzione del codice sorgente posto in ingresso dal D al C++, se durante la compilazione non vengono riscontrati errori. Traduzione del codice sorgente(Translate) Descrizione Pre-Condizioni Post-Condizioni Evento Innescante Attore Primario Include il caso duso Estende il caso duso Esteso dal caso duso Specializza il caso duso Generalizza il caso duso Il file sorgente caricato viene analizzato e tradotto Il file sorgente se modificato deve essere stato salvato Nella sezione dedicata al file intermedio apparir il risultato della traduzione Cliccare sulla sottovoce Translate del menu oppure tramite la pressione del bottone con etichetta Translate Utente Nessuno Nessuno Procedura derrore traduzione Nessuno Nessuno
Giusto per aggiungere unulteriore funzionalit allapplicativo viene data la possibilit, tramite la pressione di un semplice pulsante, di poter aprire un terminale grazie al quale i programmatori
82
Pre-Condizioni Post-Condizioni Evento Innescante Attore Primario Include il caso duso Estende il caso duso Esteso dal caso duso Specializza il caso duso Generalizza il caso duso
Questo caso duso viene richiamato nel momento in cui c la necessit di avere una finestra per la selezione della directory di salvataggio e normalmente viene attivato nel momento in cui si sta salvando per la prima volta un nuovo sorgente oppure se si sfruttata la classica funzione Save as. Finestra di Salvataggio (Save File Dialog) Descrizione Nel caso in cui lutente abbia creato un nuovo file, prima di effettuare il salvataggio, viene richiesta tramite interfaccia la directory in cui questo dovr avvenire Sia stato creato un nuovo file Il percorso in cui il file verr salvato verr settato in base al percorso che lutente ha scelto Viene richiamato nel momento in cui il path non settato Utente Nessuno Save Nessuno Nessuno Nessuno
83
Pre-Condizioni Post-Condizioni Evento Innescante Attore Primario Include il caso duso Estende il caso duso Esteso dal caso duso Specializza il caso duso Generalizza il caso duso
Permette di salvare il file in formato testuale nel path memorizzato con estensione .d. Salva File (Save File) Descrizione Pre-Condizioni Post-Condizioni Evento Innescante Attore Primario Include il caso duso Estende il caso duso Esteso dal caso duso Specializza il caso duso Generalizza il caso duso Viene salvato il contenuto della casella di testo presente allinterno del file specificato dal path Il path deve essere settato Il file se non presente viene creato Cliccare sulla sottovoce Save del menu oppure tramite la pressione del bottone Save Utente Nessuno Save Nessuno Nessuno Nessuno
Permette di effettuare il classico Salva con nome presente allinterno di qualsiasi editor di testo. Salvare con nome(Save As) Descrizione Pre-Condizioni Post-Condizioni Evento Innescante Attore Primario Include il caso duso Estende il caso duso Esteso dal caso duso Specializza il caso duso Generalizza il caso duso Se si vuole salvare un sorgente caricato in unaltra directory lo si pu fare scegliendo sempre il percorso da interfaccia Sia stato caricato un file Il file viene creato nella directory specificata Cliccare sulla sottovoce Save As del menu oppure tramite la pressione del bottone Utente Finestra di Salvataggio, Salva File Nessuno Nessuno Nessuno Nessuno
Se si dovesse cercare di effettuare lesecuzione di un file che non stato ancora caricato, lapplicativo mostrer una finestra di errore, segnalando allutente che prima di svolgere tale operazione dovr provvedere a fornire il percorso in cui il file sito.
84
Procedura derrore traduzione(Translation Error) Descrizione Pre-Condizioni Post-Condizioni Evento Innescante Attore Primario Include il caso duso Estende il caso duso Esteso dal caso duso Specializza il caso duso Generalizza il caso duso Viene generata una finestra di errore per comunicare la tipologia dellerrore allutente durante la traduzione Lutente voglia tradurre il codice sorgente Viene mostrata una finestra di errore Non sia stato caricato alcun file Utente Nessuno Traduzione del codice sorgente Nessuno Nessuno Nessuno
85
8. CONCLUSIONI
Poniamoci alcune domande e forniamo delle risposte.
86
BIBLIOGRAFIA
[1] [2] [3] [4] [5] http://www.d-programming-language.org http://flex.sourceforge.net http://www.gnu.org/software/flex/manual/html_node/flex_toc.html http://www.gnu.org/software/bison/manual/index.html Compilers: Prinicples, Techiques, &Tools Alfred V.Aho, Monica S.Lam, Ravi Sethi,
Jeffrey D.Ullman pag 325 paragrafo 5.4.2 [6] [7] [8] [9] [10] Robert I. Pitts rip@cs.bu.edu : Stack - Array Implementation http://dlang.org/module.html http://seatd.mainia.de/grammar.html#Expression Uthash http://uthash.sourceforge.net/index.html - an hash tablle for C structures A2C An ADA-like parser/scanner and C translator - Ruggiero Campese, A.A.
2010/2011 [11] PHP2C Un traduttore da PHP a C - Bavaro Gianvito, Capurso Domenico, Donvito
87