Compilatore Programma sorgente Compilatore Programma ‘oggetto’ Statistiche Informazioni debugger Listing Messaggi errore Cross reference
Source lang. Compilatore Target lang. Obiettivo : emettere velocemente codice per una macchina target, corretto ed efficiente Velocemente veloce generazione di codice veloce (retargetting) Corretto preservare la semantica del linguaggio sorgente Efficiente in esecuzione
Soluzione : dividire il compilatore in due blocchi principali source program Front End Back End Analisi Sintesi target program Rappresentazione intermedia Isolare le dipendenze dal linguaggio sorgente nel Front End (analisi lessicale, sintattica e semantica) Isolare nel Back End le dipendenze dalla macchina target (generazione ed ottomizzazione del codice)
Analizzatore Lessicale Analizzatore Sintattico Preparazione alla Generazione del codice Generatore di codice TABELLETABELLE Analisi Sintesi L’Analisi Semantica è dispersa in tutte le fasi
Problema degli M x N traduttori CPascalFortranBasic i Z8000 Cobol CPascalFortranBasicCobol i Z8000 Linguaggio intermedio m = 5 n = 3 5 * 3 = 15 traduttori m + n = = 8 traduttori
Esempio di traduzione IA = B + C * DELTA + DELTA * C si riconoscono le diverse unità del testo si classificano le diverse unità del testo si crea la tabella dei simboli si trasforma il linguaggio sorgente in un linguaggio di tokens (id,P 1 ) = (id,P 2 ) + (id,P 3 ) * (id,P 4 ) + (id,P 4 ) * (id,P 3 ) B IA C DELTA P 2 P 1 P 3 P 4 Tabella dei simboli
(id,P 1 ) = (id,P 2 ) + (id,P 3 ) * (id,P 4 ) + (id,P 4 ) * (id,P 3 ) ALBERO SINTATTICO
Albero derivante dalla grammatica: id = + | - | * | / | id | cost | ( ) Anche per l’analisi semantica e per la traduzione si utilizza l’albero sintattico
Lessico Insieme delle parole chiave del linguaggio, degli identificatori, delle costanti e dei commenti IF ‘I’ ‘F’ i simboli lessicali IF, DO, WHILE, … DO ‘D’ ‘O’ appaiono come terminali [ | ] * lettera cifra lettera Commento stringa_senza_graffa_chiusa Stringa_senza_graffa_chiusa ( - ‘ ’) *
Analisi lessicale Produzioni Regole di produzione Codici Costante ( 0 | 1 | ….|9 ) [0 | 1 | ….|9 ] * 2 Identif. ( A | B | …| Z) [ 0 | 1 | …| 9 | A | B|…Z]* 1 Espon. ‘*’ ‘*’ 6 Più ‘+’ 3 Per ‘*’ 7
Analisi lessicale Automa a stati finiti q0q0 q1q1 q2q2 q3q3 q4q4 þ 0,1,..9 þ A, B,..Z 0,1,…9 A,B,…Z + - = þ * * þ
Grafi sintattici * / cost var )( Espr ( | + | - ) term (( + | - ) term ) * Fatt cost | var | (espr) espr Term fatt (( * | / ) fatt ) * fatt term
ANALISI SINTATTICA Grammatiche ambigue (dannose perchè non è chiaro il significato della frase) Metodi per togliere l’ambiguità: Cambio della grammatica Aggiunta di regole per disambiguare
Esempio if then else if then if E1 then S1 else if E2 then S2 else S3 if then else E1 S1 if then else E2 S2 S3
Esempio di ambiguità If E1 then if E2 then S1 else S2 (ambigua perchè ci sono 2 possibili alberi sintattici) 1) if then else E1 S2 if then E2 S1
Esempio di ambiguità 2) If then E1 if then else E1 S1 S2
Esempio di soluzione: “closest match” (si lega l’else all’if-then annidato più vicino) if E1 then if E2 then S1 else S2 if then E1 if else E2 S1 S2
1. E - - > E + T 2. E - - > T 3. T - - > T * F 4. T - - > F 5. F - - > (E) 6. F - - > id id + id * id E E + T T T * F F F id id id
Derivazione sinistra E - - > E + T - - > T + T - - > F + T - - > id + T - - > id + T * F - - > E E E E E E + T E + T E + T E + T E + T T T T T T * F F F F id id
> id + F * F - - > id + id * F - - > id + id * id E E E E + T E + T E + T T T * F T T * F T T * F F F F F id F id id id id id id Derivazione sinistra
ANALISI SINTATTICA verifica se X appartiene a L(G) TOP DOWN Il parser inizia l’analisi dal nodo radice corrispondente allo “start symbol” e ricorsivamente discende sui sottoalberi tracciando l’intero albero sintattico BOTTOM UP Il parser parte dai nodi foglia (terminali) e si costruiscono i sottoalberi dal basso verso l’alto fine ad arrivare all’intero albero sintattico
ANALISI SINTATTICA DETERMINISTICA Ad ogni passo dell’analisi esiste sempre una sola scelta possibile NON DETERMINISTICA E’ possibile arrivare a situazioni in cui più scelte sono possibili. Si procede per tentativi ed in caso di insuccesso occorre tornare sui propri passi e tentare strade alternative
BACKTRACKING Ritornare al punto in cui è stata compiuta l’ultima scelta che aveva alternative valide possibili, disfare tutte le azioni compiute fino a quel punto (cancellare l’albero tracciato fino a quel punto) e continuare scegliendo una delle rimanenti alternative. Nei casi pratici ciò è usualmente non accettabile perchè associate all’analisi sintattica ci sono altre azioni “semantiche” (aggiornamento delle tabelle dei simboli ecc.) ed è troppo costoso farle più di una volta. Si fanno perciò solo analisi deterministiche.
Metodo ricorsivo discendente Ad ogni non terminale è associato un sottoprogramma eventualmente ricorsivo. S - - > c A d ; A - - > a b; A - - > a Procedure A(); begin if scan() = ‘a’ then begin if scan() = ‘b’ then return (true) end; else return(false); end; Procedure S(); begin if scan() = ‘c’ then begin if A() then if scan() = ‘d’ then return(true); end; return(false); end;
Analisi ricorsivo discendente Impedita dalla ricorsione a sinistra: A - - > A α E - - > E + T E - - > T T - - > T * F T - - > F F - - > (E) F - - > id Procedure E(); begin if E() then …… Va in loop
Trasformazione della grammatica E - - > E + T E - - > T E’ = = > E’ - - > + T E’ E - - > T E’ - - > ε T - - > T * F T - - > F T’ = = > T’ - - > * F T’ T - - > F T’ - - > ε F - - > (E) = = > F - - > id
Programma di analisi procedure E(); begin T(); EPRIME(); end; Procedure EPRIME(); begin if scan() = ‘+’ then begin T(); EPRIME(), end; procedure T(); begin F(); TPRIME(); end; Procedure TPRIME(); begin if scan() = ‘*’ then begin F(); TPRIME(); end;
Programma di analisi Procedure F(); begin if scan() = id then return (‘OK’) else if scan() = ‘(‘ then begin E(); if scan() = ‘)’ then ERROR(); end; else ERROR(); end;
Altri impedimenti Prefissi comuni nelle parti destre di diverse produzioni per lo stesso non terminale - - > if then else - - > if then - - > else - - > ε
Regola generale per la fattorizzazione sinistra A - - > α β A - - > α A’ diventano A’ - - > β A - - > α γ A’ - - > γ Analisi ricorsivo discendente: Facile scrittura delle procedure dell’analizzatore sintattico Possibile solo se il linguaggio implementativo permette la ricorsione ed essa è realizzata in modo efficiente
Analisi TOP -DOWN Top Down deterministico si dice PREDITTIVO se e solo se: per ogni non terminale si può prevedere la corretta riduzione. Il Parser, per decidere come procedere, usa il non terminale corrente ed il prossimo token (LOOK AHEAD = prospezione) Se la prospezione ha lunghezza pari ad 1, l’analisi si dice LL(1) (Look ahead, Left to right) Il Pascal è LL(1).
Analisi BOTTOM UP Analisi ascendente a spostamento e riduzione : LR(k) Partendo dalla stringa da analizzare, si procede da sinistra a destra e quando si trova una sottostringa corrispondente ad una parte destra di una produzione si fa una riduzione. S > S # a c b # S - - > a S b S S # S - - > c S 0
Analisi BOTTOM UP Si usano le stesse regole della grammatica ( produzioni) ma in senso riduttivo, cioè da destra a sinistra. Si chiama parte riducibile (HANDLE) la porzione di una stringa, composta da terminali e non terminali, che è parte destra di una regola di produzione e può essere ridotta al non terminale della parte sinistra della regola stessa.
Esempio di analisi BOTTOM UP S - - > a A B e A - - > A b c | b B - - > d 1.a b b c d e(A - - > b) 2. a A b c d e(A - - > A b c) 3. a A d e(B - - > d) 4. a A B e(S - - > a A B e) 5. S Nel passaggio 2-3 si poteva scegliere la regola A - - > b ma …. la frase a A b c d e non è riducibile perchè da S non e’ possibile derivare a A A c d e, cioè S -/ - > a A A c d e PROBLEMA CHIAVE E’ LA DETERMINAZIONE DELLA PARTE RIDUCIBILE (HANDLE)
ANALISI ASCENDENTE LR(k) A SPOSTAMENTO E RIDUZIONE repeat leggi simbolo dal buffer di input sposta simbolo al top dello stack while top dello stack <> handle aggiungi simbolo allo stack endwhile riduci stringa al top dello stack elimina simboli ridotti dallo stack aggiungi al top dello stack il simbolo ridotto until top dello stack = simbolo distintivo or buffer di input = vuoto if top dello stack = simbolo distintivo and buffer di input = vuoto then exit(‘successo’) else exit (‘errore’) endif
Esempio (1) E - - > E + T(2)E - - > T (3)T - - > T * F (4)T - - > F (5)F - - > (E) (6)F - - > id id + id * id stack input azione # id + id * id # sposta 2.# id + id * id # riduce con (6) 3.# F + id * id # riduce con (4) 4.# T + id * id # riduce con (2) 5.# E + id * id # sposta 6.# E + id * id # sposta 7.# E + id * id # riduce con (6) 8.# E + F * id # riduce con (4) 9.# E + T * id # sposta 10.# E + T * id # sposta 11.# E + T * id # riduce con (6) 12.# E + T * F # riduce con (3) 13.# E + T # riduce con (1) 14.# E # accetta
ANALISI SINTATTICA GUIDATA DA TABELLE LR: Analizzatore sintattico governato da tabella che, in funzione del prossimo simbolo in input e del contenuto al top dello stack, esegue una mossa di spostamento o di riduzione. Una grammatica è detta LR se la tabella non ha elementi che indicano contemporaneamente spostamenti e riduzioni. Esistono strumenti che verificano automaticamente se la grammatica è LR e ne costruiscono la tabella dell’analizzatore sintattico ascendente. Es. LEX, YACC in UNIX
Schema di un parser LR guidato da tabella a 1 …. ………a n ………... a k # input S m.. output. S 0 # tabella stack azione goto LR parser
AZIONEGOTO STATOid+*()#ETF 0S 5S S 6accept 2r2S 7r2 3r4 4S 5S r6 6S 5S 493 7S 5S 410 8S 6S 11 9r1S 7r r3 r5 r3 r5 r3 r5
STACKINPUT (1)0id* + # (2)0id5* + # (3)0F3*id+ # (4)0T2*id+ # (5)0T2*7id+ # (6)0T“*7id5+ # (7)0T2*7F10+id# (8)0T2+id# (9)0E1+id# (10)0E1+6id# (11)0E1+6id5# (12)0E1+6F3# (13)0E1+6T9# (14)0E1#
Algoritmo di base per parser LR(1) token = next_token() Loop s = top_stack if action[s,token] = “shift s i ” then push token push s i token = next_token() else if action[s,token] =“reduce A - - >β” then pop 2* length(β ) symbols s = top stack push A push result of goto[s,A] else if action[s,token] =“accept” then return else error endloop
TRATTAMENTO DEGLI ERRORI -rilevamento dell’errore e diagnosi -rimedio all’errore -ripresa dell’analisi -Criteri di merito: -segnalare l’errore al più presto possibile -saltare meno testo possibile prima di riprendere l’analisi Gli analizzatori LL(1) e LR(1) rilevano un errore quandoil token corrente non può essere parte di un programma corretto (grammatica); Trattamento: -Fermarsi al primo errore -Fare error recovery, cioè superare l’errore e riprendere l’analisi
Rimedio agli errori Diverse tecniche: Cancellazione (metodo del “panico”) –Salta i simboli fino a che è possibile la ripresa dell’analisi; ricerca di un simbolo sicuro in grado di far sincronizzare il parser. E’ la forma più semplice di error recovery Sostituzione –Cerca di sostituire un simbolo con un altro (ipoteticamente più probabile); continua con il simbolo successivo a quello errato Inserimento –Cerca di inserire simboli che possono permettere la continuazione dell’analisi
ANALISI SEMANTICA Verifica che ogni elemento del programma (operatori ed operandi) sia usato nel contesto in cui può apparire in base alla specifiche del linguaggio (restrizioni rispetto alla grammatica libera dal contesto) Controlli di tipo statico (alla compilazione :compile time): Controllo sulla congruenza tra operandi di tipo diverso Controllo sul flusso del programma (errata uscita o entrata in strutture di controllo) Controllo di unicità ( ridefinizione delle variabili, labels di “case ripetute, ecc.) Le strutture di lavoro per l’analisi semantica sono: La tavola dei simboli L’albero sintattico generato dall’analizzatore sintattico
Esempio di analisi semantica x := y dichiarata? visibile? variabile? variabile? funzione? costante? tipo? tipo? # parametri? tipo? assegnabile? tipo? compatibile? in range? valore?
Gestione dello “scope” var a : real; var b : integer; a:= a + b; var a: integer, a:= a + b;
Visibilità delle variabili (struttura delle tabelle dei simboli) a ( real)indirizzo b (integer)indirizzo a (integer)indirizzo
CODICI (LINGUAGGI) INTERMEDI 1)Alberi sintattici a:= b + c > := a + b c 2)Statements a 3 indirizzi a := b op c Quadruple oparg1arg2ris +bct1 :=t1a
CODICI (LINGUAGGI) INTERMEDI 3)Statements a 2 indirizzi triple 4)Notazione polacca prefissa := a + b c postfissa a b c + := oparg1arg2 +bc(0) :=a(0)(1)
Quadruple A := - B * ( C + D) T1 := - B T2 := C + D T3 := T1 * T2 A := T3 OPARG1ARG2RIS (0)MinusB-T1 (1)+CDT2 (2)*T1T2T3 (3):=T3-A
Triple A := - B * ( C + D) A[I] := B A := B[I] OPARG1ARG2 (0)minusB- (1)+CD (2)*(0)(1) (3):=(2)A OPARG1ARG2 (0)[]AI (1):=B(0) OPARG!ARG2 (0)[]BI (1):=(0)A
Notazione polacca postfissa (eseguibile o interpretabile direttamente) L’operatore segue gli operandi x + y ----->x y + x + y * z----->x y z * + (a + b) * (x + y)----->a b + x y + *
Interpretazione di una forma polacca postfissa Var γ : pila di valori (interi) cursimb : elemento dell’espressione val : valore (intero) Begin repeat leggi pross. elemento in cursimb if cursimb = operando then push(γ, cursimb) else pop operandi di cursimb ed applicalo ad essi cursimb = val:=cursimb(op1,…., opn); push (γ, val) endif until fine espressione scrivi (top (γ)) End
Valutazione della espressione (3 + 2) * (7 – 3) > * pilainput * * 5 4 * 20
Linguaggio intermedio per istruzioni di controllo if a < b then a:=1 else b:= 1; ge abl1 assign1-a ramo then jpl2-- defl1-- ramo else assign1-b defl2--
Produzione codice intermedio: diretta da sintassi produzioniazione di traduzione S - - > id := ES.ptr = mknode(ASSIGN, BINARY, mknode(NAME, id), E.ptr) E - - > E1 + E2E.ptr = mknode(ADD, E1.ptr, E2.ptr) E - - > E1 * E2E.ptr = mknode(MUL, E1.ptr, E2.ptr) E - - > - E1E.ptr = mknode(MINUS, E1.ptr) E - - > (E1)E.ptr = E1.ptr E - - > idE.ptr = mknode(NAME, id)
Esempio: semplice calcolatrice produzioniazioni semantiche S - - > Eprint(E_val) E - - > T + - EE_val = T_val +- E_val E - - > TE_val = T_val T - - > F */ TT_val = F_val */T_val T - - > FT_val = F_val F - - > (E)F_val = E_val F - - > numF_val = num.value
Generazione del codice INPUT al generatore di codice: formato intermedio – albero sintattico –istruzioni a 3 indirizzi –notazione polacca OUTPUT del generatore di codice: 1.linguaggio macchina assoluto 2.linguaggio macchina rilocabile 3.linguaggio assembler sorgente 2. Maggiore flessibilità: compilazione separata + link a librerie 3. Maggiore semplicità: allocazione della memoria, risoluzione dei salti, gestione del formato rilocabile lasciata all’assemblatore
Tecniche di generazione del codice 1. Macro Espansione 2. “Ad hoc” 3. Pattern matching 2. Famiglia di algoritmi ritagliati sul tipo di architettura target: prive di presupposti formali e/o di possibilità di automatizzazione
Macro espansione Assumendo come input delle quadruple e come output assembler per un processore dotato di registri. ADD ($1, $2, $3) isMOV$1, R0 ADD$2, R0 MOVR0, $3ASSIGN($1, nil, $3) endisMOV$1, R0 MUL ($1, $2, $3)MOVR0, $3 isMOV$1, R0end MUL$2, R0 MOVR0, $3) end
Esempio di macro espansione Istruzione sorgente:a:= b + c * d Produce le seguenti quadruple e codice assembler: MOVc, R0 *cdt1MULd, R0 +bt1t2MOVR0, t1 :=t2-aMOVb, R0 ADDt1, R0 MOVR0, t2 MOVt2, R0 MOVR0, a
Pattern matching si usano patterns srutturati ad albero che descrivono istruzioni e forme intermedie ad albero; si selezionano le istruzioni scomponendo l’input in singoli patterns; si procede top-down con tecnica simile all’analisi sintattica. L’algoritmo è simile all’analisi guidata da sintassi: ogni volta che si riconosce un pattern lo si riscrive (come se fosse una regola sintattica) e come azione corrispondente si emette codice.
Esempio di pattern matching A := B + C * D := A + * B C D Libreria di patterns mem < - - :=[mov reg, mem] mem reg reg < - - +[add mem, reg] mem reg reg < - - *[mov mem1, reg mem1 mem2 mul mem2, reg] Seleziona il pattern 1 Chiede match sul figlio destro Seleziona pattern 2 Chiede match sul figlio destro Seleziona pattern 3 Emissione codice 3 Riscrittira del’albero Emissione codice 2 Riscrittura dell’albero Emissione codice