Es: Fortran (applicazioni scientifiche, ingegneristiche), Cobol
Transcript
Es: Fortran (applicazioni scientifiche, ingegneristiche), Cobol
LINGUAGGI DI PROGRAMMAZIONE LINGUAGGI DI BASSO LIVELLO Linguaggio macchina Uno per ogni processore o famiglia di processori Linguaggio assembler Versione simbolica di quello macchina. LINGUAGGI DI ALTO LIVELLO Linguaggi imperativi Orientati all’assegnamento di locazioni di memoria o di registri. Es: Fortran (applicazioni scientifiche, ingegneristiche), Cobol (applicazioni gestionali) , Pascal, C , Basic. Linguaggi imperativi orientati agli oggetti Basati sulle “classi”, che sono una evoluzione delle “strutture”; le classi incorporano sia i dati che le funzioni che operano su di essi. Es: Smalltalk, C++, Java. Linguaggi funzionali Basati sulla combinazione di funzioni per ottenerne di più potenti. Es: Lisp, CommonLisp, Scheme. Linguaggi logici Es: Prolog SQL usato in Intelligenza Artificiale orientato al reperimento di informazione nei databases; specifica le proprietà logiche dell’operazione da eseguire e dell’informazione su cui agire. Programmazione imperativa (procedurale, strutturata, orientata agli oggetti) Programmazione dichiarativa (funzionale, logica) PROGRAMMAZIONE VISUALE Orientata alla possibilità di scrivere programmi partendo da oggetti posizionati sullo schermo ai quali sono associate azioni da compiere. Permette di scrivere in modo relativamente semplice programmi orientati agli oggetti; si scelgono alcune scatole, dei collegamenti con I/O appropriato, ecc. Ci sono ambienti di programmazione che consentono l’uso più o meno esteso della programmazione visuale: Visual Basic, Hypercard, SQLWindows, J-Builder. 1 SVILUPPO DI UN PROGRAMMA TRADIZIONALE Redazione Con un editor che visualizza i caratteri stampabili ed interpreta quelli non stampabili; gli editor possono essere sensibili al linguggio di programmazione. Traduzione - Compilatori ma può avvenire anche con: - Interpreti - Compilatori just in time (CJT) - Soluzioni ibride Sorgente pascal Sorgente C Compilatore pascal Sorgente assembler compilatore C assemblatore Files di testo sorgenti non eseguibili COMP/ASSEMB Files binari oggetto non eseguibili LINKING File binario eseguibile LOADING in memoria principale 2 COMPILATORI • Un compilatore è un programma che legge un programma scritto in un linguaggio, detto linguaggio sorgente, e lo traduce in un programma equivalente scritto in un altro linguaggio, detto linguaggio target. Come parte importante del processo di traduzione il compilatore comunica all'utente la presenza di errori nel programma sorgente. • Ci sono migliaia di linguaggi sorgenti e migliaia di linguaggi target, ma i compiti di base che un compilatore deve compiere sono essenzialmente gli stessi. • Concettualmente un compilatore opera in fasi, ciascuna delle quali trasforma il programma sorgente da una rappresentazione in un'altra. • Oltre al compilatore, altri programmi possono essere necessari per creare un programma eseguibile. • Preprocessori. Producono input per i compilatori. Essi possono completare il programma sorgente, ad esempio includendo il contenuto di alcuni files (come in C con #include <stdio.h>), espandere delle abbreviazioni, dette macro, in statements del linguaggio sorgente, permettere costrutti tipo if-statements per specificare condizioni di compilazione, ecc. • Assemblatori. Un compilatore può creare un programma in linguaggio assembler, che successivamente deve essere tradotto da un assemblatore in codice macchina. Altri compilatori possono invece eseguire il compito dell'assemblatore, creando loro stessi codice macchina. • Caricatori. Il processo di linking consiste nel creare un unico programma a partire da diversi files di codice macchina rilocabile; questi files possono essere il risultato di diverse compilazioni ed uno o più possono essere files di librerie di routines fornite dal sistema. Il processo di loading consiste nel prendere codice macchina rilocabile, e caricare istruzioni e dati in memoria all'indirizzo di caricamento desiderato. Spesso un unico programma, chiamato caricatore, esegue la duplice funzione di linking/loading. • Il comando UNIX gcc serve sia per compilare che per legare e caricare i programmi C. Il compilatore genera un modulo oggetto, chiamato object file (nomefile.o), che può essere processato dal linker insieme ad altri object files. Il linker crea un modulo eseguibile il cui nome, se non specificato diversamente, è a.out. 3 FASI DI UN COMPILATORE Tre sono di analisi, tre di sintesi e due di interazione con le altre. source program analizzatore lessicale analizzatore sintattico analyzer gestore della tabella dei simboli analizzatore semantico analyzer generatore del codice intermedio gestore degli errori ottimizzatore del codice generatore del codice generator target program ANALISI LESSICALE Durante questa fase la sequenza di caratteri che costituisce il programma viene letta da sinistra verso destra e raggruppata in sequenze di caratteri (chiamate "token ") che hanno un senso logico globale, come un identificatore, una parola chiave, un simbolo di interpunzione. Es. la sequenza di caratteri nell'istruzione: position = initial + rate * 60 sarebbe raggruppata nei seguenti "tokens" : 1. 2. 3. 4. 5. 6. 7. Identificatore Simbolo di assegnamento Identificatore Segno Identificatore Segno Numero intero position = initial + rate * 60 I caratteri “white” che separano i "token" vengono di solito eliminati durante questa fase. La parte di compilatore che fa questa analisi si chiama scanner e lavora usando tecniche di teoria degli automi finiti. 4 ANALISI SINTATTICA È chiamata anche parsing (e parser si chiame la parte di compilatore che compie questa analisi) e consiste nel raggruppare i tokens in costrutti grammaticali che sono usati dal compilatore per sintetizzare l'output. Di solito i costrutti grammaticali sono rappresentati da un albero dell'analisi, così lo scopo del parser per lo più è quello di costruire il parse tree. Es. Un possibile albero dell'analisi (parse tree) per l'istruzione in esame è: assignment statement = identifier position expression expression + | identifier | initial expression expression | expression * | identifier number | | rate 60 Un'altra comune rappresentazione della struttura sintattica dell'input è data dall'albero della sintassi (sintax tree), che è una forma compressa del parse tree nel quale gli operatori appaiono come nodi interni e gli operandi di un operatore sono i figli del nodo di quell'operatore. Un esempio di sintax tree per l'istruzione precedente è: = + position initial 5 * rate 60 ANALISI SEMANTICA Questa fase usa la struttura gerarchica determinata dalla fase precedente per identificare e controllare gli operatori e gli operandi delle espressioni e delle istruzioni ed identificare eventuali errori semantici nel programma sorgente. Compito principale di questa fase è il controllo dei tipi, cioè il controllo che ogni operatore abbia operandi permessi dalla specifica del linguaggio. Es. Nell'istruzione precedente, se tutti gli identificatori fossero stati dichiarati float, l'approccio sarebbe quello di convertire l'intero 60 in un float ed il compilatore rimpiazzerebbe la costante intera con una float equivalente. = + position * initial rate intofloat 60 GESTIONE DELLA TABELLA DEI SIMBOLI Scopo della gestione della tabella dei simboli è quello di memorizzare tutti gli identificatori usati nel programma sorgente e di collezionare gli attributi di ogni identificatore (la memoria allocata, il tipo, dove è valido nel programma, se è il nome di una procedura ed in tal caso il numero ed i tipi degli argomenti oltre al metodo di passaggio dei parametri ed al tipo del valore ritornato). Quando un identificatore viene individuato nel programma sorgente dall' analizzatore lessicale esso viene messo nella tabella dei simboli; i vari attributi vengono aggiunti via via che vengono collezionati dalle varie fasi. RILEVAMENTO E COMUNICAZIONE DEGLI ERRORI Ogni fase può trovare errori e comunicarli; il processo di compilazione prosegue alla ricerca del maggior numero di errori, senza interrompersi dopo ogni errore. 6 GENERAZIONE DEL CODICE INTERMEDIO Si può pensare alla rappresentazione intermedia come ad un programma per una macchina astratta. La forma intermedia deve essere facile da produrre a facile da tradurre nel linguaggio target. Di forme intermedie ne esistono una grande varietà; quella chiamata "codice a tre indirizzi" è simile ad un linguaggio macchina dove ogni locazione di memoria può agire come un registro. OTTIMIZZAZIONE DEL CODICE Cerca di migliorare il codice intermedio per poter ottenere codice finale più veloce. Nei cosiddetti "compilatori ottimizzanti" una parte significativa del tempo di compilazione è spesa in questa fase. Tuttavia ci sono semplici ottimizzazioni che non rallentano troppo la compilazione e che migliorano sensibilmente il tempo di esecuzione del programma target. GENERAZIONE DEL CODICE È la fase finale che genera il codice target, che di solito è codice macchina rilocabile o codice assembler. Locazioni di memoria sono scelte per ciascuna variabile del programma. Poi le istruzioni intermedie sono tradotte in una sequenza di istruzioni macchina equivalenti. Aspetto cruciale di questa fase è l'assegnazione dei registri. 7 ESEMPIO DI TRADUZIONE DI UNA ISTRUZIONE position = initial + rate * 60 lexical analyzer id1 = id2 + id3 * 60 syntax analyzer = + id1 * id2 id3 60 semantic analyzer SYMBOL TABLE position initial rate .... .... .... = + id1 * id2 id3 intofloat 60 intermediate code generator temp1=intoreal(60) temp2=id3 * temp1 temp3=id2 + temp2 id1=temp3 code optimizer temp=id3 * 60.0 id1=id2+temp code generator MOVF MULF MOVF ADDF MOVF 8 id3, R2 #60.0, R2 id2,R1 R2, R1 R1,id1 COMPILER-COMPILER Regole lessicali SCANNER Grammatica PARSER COMPILER-COMPILER GENERATORE DEL CODICE Routines semantiche I compilatori generati con questa tecnica sono più grossi e compilano più lentamente di quelli scritti a mano, ma il codice generato è più veloce. INTERPRETI Un interprete processa un programma sorgente come un compilatore (esegue analisi lessicale e sintattica ed eventualmente traduce in una forma interna, tipo quadruple od una estensione della forma polacca postfissa) ma lo esegue direttamente, cioè senza tradurre in codice macchina. L’interprete può essere visto come un insieme di funzioni la cui esecuzione è giudata dalla forma interna del programma. La fase di traduzione è più veloce che nei compilatori, ma l’esecuzione è più lenta. È vantaggioso in fase di debugging, poichè si hanno in linea tutte le tabelle. La maggior parte dei linguaggi può essere sia interpretata che compilata. COMPILATORI JUST ON TIME (CJT) Sono un compromesso tra compilatori e interpreti. SOLUZIONI IBRIDE Le applet java, copiate via rete, contengono una compilazione del codice sorgente, chiamato byte-code; esso costituisce codice macchina “generale”, cioè indipendente da specifici processori e/o architetture. Al momento dell’ esecuzione il byte-code viene interpretato o compilato da parte di una JVM (Java Virtual Machine) e poi eseguito. Alcuni calcolatori hanno un processore in grado di eseguire il byte-code direttamente. 9