Che cos'è un progetto di compilatore? Tipi, strumenti di costruzione, esempio
⚡ Riepilogo intelligente
Le fasi del compilatore descrivono le sei fasi che trasformano il codice sorgente in codice macchina: analisi lessicale, analisi sintattica, analisi semantica, generazione di codice intermedio, ottimizzazione del codice e generazione del codice, supportata dalla gestione della tabella dei simboli e dalla gestione degli errori.
Quali sono le fasi della progettazione del compilatore?
A Compiler Il compilatore opera in varie fasi, e ogni fase trasforma il programma sorgente da una rappresentazione all'altra. Ogni fase riceve input dalla fase precedente e invia il proprio output alla fase successiva del compilatore. Un compilatore è composto da 6 fasi. Ognuna di queste fasi contribuisce alla conversione del linguaggio di alto livello in codice macchina. Le fasi di un compilatore sono:
- Analisi lessicale
- Analisi della sintassi
- Analisi semantica
- Generatore di codice intermedio
- Code ottimizzatore
- Code generatore
Fasi del compilatore
Tutte queste fasi convertono il codice sorgente suddividendolo in token, creando alberi di analisi sintattica e ottimizzando il codice sorgente attraverso diverse fasi.
Fase 1: Analisi lessicale
L'analisi lessicale è la prima fase, in cui il compilatore analizza il codice sorgente. Questo processo può essere eseguito da sinistra a destra, carattere per carattere, e raggruppa i caratteri in token.
In questa fase, il flusso di caratteri proveniente dal programma sorgente viene raggruppato in sequenze significative identificando i token. I token corrispondenti vengono quindi inseriti nella tabella dei simboli e passati alla fase successiva.
Le funzioni principali di questa fase sono:
- Identificare le unità lessicali in un codice sorgente.
- Classifica le unità lessicali in classi come costanti e parole riservate e inseriscile in tabelle diverse. I commenti presenti nel codice sorgente verranno ignorati.
- Individua un token che non fa parte del linguaggio.
Esempio: x = y + 10
| Token | Tipo |
|---|---|
| X | identificatore |
| = | Operatore di assegnazione |
| Y | identificatore |
| + | Operatore di addizione |
| 10 | Numero |
Fase 2: Analisi della sintassi
L'analisi sintattica consiste nello scoprire la struttura del codice. Determina se un testo segue o meno il formato previsto. L'obiettivo principale di questa fase è accertarsi che il codice sorgente scritto dal programmatore sia corretto.
L'analisi sintattica si basa sulle regole dello specifico linguaggio di programmazione, costruendo un albero sintattico a partire dai token. Permette inoltre di determinare la struttura del linguaggio sorgente e la grammatica o sintassi del linguaggio stesso.
Ecco un elenco delle attività svolte in questa fase:
- Ottieni i token dall'analizzatore lessicale.
- Verifica se l'espressione è sintatticamente corretta.
- Segnala tutti gli errori di sintassi.
- Costruisci una struttura gerarchica nota come albero di analisi sintattica.
Esempio
Qualsiasi identificatore/numero è un'espressione. Se x è un identificatore e y+10 è un'espressione, allora x = y+10 è un'istruzione. Si consideri l'albero di analisi sintattica per il seguente esempio:
(a+b)*c
Nell'albero di analisi sintattica:
- Nodo interno: record con un campo operatore e due campi per i figli.
- Foglia: record con 2 o più campi; uno per il token e altre informazioni sul token.
- Assicurarsi che i componenti del programma siano coerenti tra loro in modo significativo.
- Raccoglie informazioni sul tipo e verifica la compatibilità del tipo.
- I controlli sugli operandi sono consentiti dal linguaggio sorgente.
Fase 3: Analisi Semantica
L'analisi semantica verifica la coerenza semantica del codice. Utilizza l'albero della sintassi della fase precedente insieme alla tabella dei simboli per verificare che il codice sorgente fornito sia semanticamente coerente. Controlla inoltre se il codice trasmette un significato appropriato.
L'analizzatore semantico verificherà la presenza di incongruenze di tipo, operandi incompatibili, funzioni chiamate con argomenti non corretti, variabili non dichiarate, ecc.
Le funzioni della fase di analisi semantica sono:
- Consente di memorizzare le informazioni sui tipi raccolte e di salvarle nella tabella dei simboli o nell'albero sintattico.
- Consente di eseguire il controllo dei tipi.
- In caso di mancata corrispondenza dei tipi, qualora non esistano regole di correzione dei tipi esatte che soddisfino l'operazione desiderata, viene visualizzato un errore semantico.
- Raccoglie informazioni sul tipo e verifica la compatibilità del tipo.
- Controlla se il linguaggio sorgente consente gli operandi oppure no.
Esempio
float x = 20.2; float y = x*30;
Nel codice sopra riportato, l'analizzatore semantico effettuerà un cast dell'intero 30 al numero in virgola mobile 30.0 prima della moltiplicazione.
Fase 4: Intermedia Code Generazione
Una volta terminata la fase di analisi semantica, il compilatore genera il codice intermedio per la macchina di destinazione. Esso rappresenta un programma per alcuni valori assoluti.tracmacchina t.
Il codice intermedio si trova tra il linguaggio di alto livello e il linguaggio macchina. Questo codice intermedio deve essere generato in modo tale da facilitarne la traduzione nel codice macchina di destinazione.
Funzioni dell'intermedio Code generazione:
- Dovrebbe essere generato a partire dalla rappresentazione semantica del programma sorgente.
- Contiene i valori calcolati durante il processo di traduzione.
- Consente di tradurre il codice intermedio nella lingua di destinazione.
- Consente di mantenere l'ordine di precedenza della lingua sorgente.
- Contiene il numero corretto di operandi dell'istruzione.
Esempio
Per esempio:
total = count + rate * 5
Il codice intermedio ottenuto con il metodo del codice indirizzo è:
t1 := int_to_float(5)
t2 := rate * t1
t3 := count + t2
total := t3
Fase 5: Code OTTIMIZZAZIONE
La fase successiva consiste nell'ottimizzazione del codice intermedio. Questa fase elimina le righe di codice non necessarie e riorganizza la sequenza delle istruzioni per velocizzare l'esecuzione del programma senza sprecare risorse. L'obiettivo principale di questa fase è migliorare il codice intermedio per generare un codice più veloce e che occupi meno spazio.
Le funzioni principali di questa fase sono:
- Consente di stabilire un compromesso tra velocità di esecuzione e velocità di compilazione.
- Migliora i tempi di esecuzione del programma di destinazione.
- Genera codice semplificato ancora in rappresentazione intermedia.
- Rimozione del codice irraggiungibile ed eliminazione delle variabili inutilizzate.
- Rimozione dal ciclo delle istruzioni che non vengono modificate.
Esempio: Considera il codice seguente:
a = intofloat(10)
b = c * a
d = e + b
f = d
Può diventare:
b = c * 10.0
f = e + b
Fase 6: Code Generazione
Code La generazione del codice è l'ultima fase di un compilatore. Riceve input dalla fase di ottimizzazione del codice e produce il codice pagina o codice oggetto come risultato. L'obiettivo di questa fase è allocare memoria e generare codice macchina rilocabile.
Inoltre, assegna posizioni di memoria per le variabili. Le istruzioni nel codice intermedio vengono convertite in istruzioni macchina. Questa fase converte il codice ottimizzato o intermedio nel linguaggio di destinazione.
Il linguaggio di destinazione è il codice macchina. Pertanto, in questa fase vengono selezionati e allocati anche tutti gli spazi di memoria e i registri. Il codice generato in questa fase viene eseguito per elaborare gli input e generare gli output attesi.
Esempio
a = b + 60.0 potrebbe essere tradotto nei registri come:
MOVF a, R1 MULF #60.0, R2 ADDF R1, R2
Gestione della tabella dei simboli
Una tabella dei simboli contiene un record per ogni identificatore con campi per gli attributi dell'identificatore. Questo componente facilita al compilatore la ricerca del record dell'identificatore e il suo recupero rapido. La tabella dei simboli è utile anche per la gestione degli ambiti. La tabella dei simboli e il gestore degli errori interagiscono con tutte le fasi e la tabella dei simboli viene aggiornata di conseguenza.
Routine di gestione degli errori
Nel processo di progettazione del compilatore, possono verificarsi errori in tutte le fasi indicate di seguito:
- Analizzatore lessicale: Token con errori di ortografia.
- Analizzatore di sintassi: Parentesi mancanti.
- Generatore di codice intermedio: Operandi non corrispondenti per un operatore.
- Code Ottimizzatore: Quando l'istruzione non è raggiungibile.
- Code Generator: Quando la memoria è piena o non sono stati allocati i registri appropriati.
- Tabelle dei simboli: Errore dovuto alla dichiarazione di più identificatori.
Gli errori più comuni sono sequenze di caratteri non valide nella scansione, sequenze di token non valide nel tipo, errori di ambito e errori di analisi sintattica nell'analisi semantica.
L'errore può verificarsi in una qualsiasi delle fasi sopra descritte. Dopo aver individuato gli errori, la fase deve gestirli per poter proseguire con il processo di compilazione. Questi errori devono essere segnalati al gestore degli errori, che si occupa di elaborarli per consentire la prosecuzione del processo di compilazione. Generalmente, gli errori vengono segnalati sotto forma di messaggio.



