58
Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori Versione 1.2 © 2000 Marco Torchiano 1 1 Laboratorio di Compilatori a.a. 1999/2000 Esercitazioni in aula http://www.polito.it/Ulisse/CORSI/INF/N3070/materiale/ Marco Torchiano Dipartimento di Automatica e Informatica Tel. (011 564) 7081 E-mail: [email protected] 2 Contenuti Analisi lessicale Analisi sintattica Regole semantiche Controllo dei tipi Ambienti di esecuzione Codici intermedi Tecniche per il parsing 3 Analisi lessicale Espressioni regolari Lex Espressioni regolari in Lex Azioni associate alle espressioni regolari La struttura di un programma sorgente Lex Il codice generato da Lex Risoluzione delle ambiguità Dipendenza dal contesto Condizioni iniziali Eliminazione dei commenti Il file prodotto Inclusione di file Meccanismi di chiamata e definizione di simboli 4 Espressioni regolari Costituiscono un metodo semplice ed efficace per descrivere insiemi di stringhe di caratteri. Opportuni operatori consentono di indicare caratteri c’ o anche c classi di caratteri [ a, b, c] o [ a- c] opzionalità exp ? ripetizione (0 o più volte) exp * ripetizione (1 o più volte) exp + alternativa exp1 | exp2 concatenazione exp1 exp2 ambito di operatori ( exp )

Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

  • Upload
    others

  • View
    1

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 1

1

Laboratorio di Compilatoria.a. 1999/2000Esercitazioni in aula

http://www.polito.it/Ulisse/CORSI/INF/N3070/materiale/

Marco TorchianoDipartimento di Automatica e InformaticaTel. (011 564) 7081E-mail: [email protected]

2 Contenuti

☛ Analisi lessicale ☛ Analisi sintattica☛ Regole semantiche☛ Controllo dei tipi☛ Ambienti di esecuzione☛ Codici intermedi☛ Tecniche per il parsing

3 Analisi lessicale

☛ Espressioni regolari☛ Lex

✏ Espressioni regolari in Lex

✏ Azioni associate alle espressioni regolari✏ La struttura di un programma sorgente Lex

✏ Il codice generato da Lex

✏ Risoluzione delle ambiguità✏ Dipendenza dal contesto✏ Condizioni iniziali✏ Eliminazione dei commenti✏ Il file prodotto✏ Inclusione di file✏ Meccanismi di chiamata e definizione di simboli

4 Espressioni regolari

☛ Costituiscono un metodo semplice ed efficace per descrivere insiemi di stringhe di caratteri.

☛ Opportuni operatori consentono di indicare✏ caratteri ‘c’ o anche c

✏ classi di caratteri [a,b,c] o [a-c]

✏ opzionalità exp ?

✏ ripetizione (0 o più volte) exp *

✏ ripetizione (1 o più volte) exp +

✏ alternativa exp1 | exp2

✏ concatenazione exp1 exp2

✏ ambito di operatori ( exp )

Page 2: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 2

5 Esempi di espressioni regolari

☛ numero intero positivo✏ [0-9]+

☛ numero intero positivo senza 0 inziali✏ [1-9][0-9]*

☛ numero intero positivo o negativo✏ (‘+’|’-’)? [0-9]+

☛ numero in virgola mobile✏ (‘+’|’-’)? ( ([0-9]+ ‘.’ [0-9]*) |

([0-9]* ‘.’ [0-9]+) )

gli apici consentono di distinguere un carattere in ingresso (‘+’) da un operatore (+).

6 Esercizi

☛ Scrivere l’espressione regolare che descrive unidentificatore nel linguaggio C.

☛ Scrivere l’espressione regolare di un numero in virgola mobile con eventuale parte esponenziale

☛ Come sopra, eliminando zeri iniziali e finali non significativi.

☛ Scrivere l’espressione regolare di un commento nel linguaggio C.

✏ Il commento può contenere i caratteri ‘*’ e ‘/’ purché non adiacenti.

7 lex - un generatore di analizzatori lessicali

LexLexEspressioniregolari

Espressioniregolari

ProgrammaC

ProgrammaC

☛ Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici (e noiosi), spesso si utilizza un generatore automatico di analizzatori lessicali.

☛ Lex è un generatore che accetta in ingresso un insieme di espressioni regolari e di azioni associate a ciascuna espressione e produce in uscita un programma che riconosce tali espressioni.

8 Espressioni regolari in Lex

☛ Le espressioni regolari descrivono sequenze di caratteri ASCII ed utilizzano un certo numero di operatori:

✏ “ \ [ ] ^ - ? . * + | ( ) $ / { } % < >

☛ Lettere e numeri del testo di ingresso sono descritti mediante se stessi:

✏ l’espressione regolare val1 rappresenta la sequenza ‘v’ ‘a’‘l’ ‘1’ nel testo di ingresso

☛ I caratteri non alfabetici vengono rappresentati inLex racchiudendoli tra doppi apici, per evitare ambiguità con gli operatori:

✏ l’espressione xyz“++” rappresenta la sequenza ‘x’ ‘y’ ‘z’‘+’ ‘+’ nel testo di ingresso.

Page 3: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 3

9

☛ I caratteri non alfabetici possono essere anche descritti facendoli precedere dal carattere \

✏ l’espressione xyz\+\+ rappresenta la sequenza ‘x’ ‘y’ ‘z’‘+’ ‘+’ nel testo di ingresso.

☛ Le classi di caratteri vengono descritte mediante gli operatori []:

✏ l’espressione [0123456789] rappresenta una cifra nel testo di ingresso.

☛ Nel descrivere classi di caratteri, il segno - indica una gamma di caratteri:

✏ l’espressione [0-9] rappresenta una cifra nel testo di ingresso.

Espressioni regolari in Lex...continua...

10

☛ Per includere il carattere - in una classe di caratteri, questo deve essere specificato come primo o ultimo della serie:

✏ l’espressione [-+0-9] rappresenta una cifra o un segno nel testo di ingresso.

☛ Nelle descrizioni di classi di caratteri, il segno ^posto all’inizio indica una gamma di caratteri da escludere:

✏ l’espressione [^0-9] rappresenta un qualunque carattere che non sia una cifra nel testo di ingresso.

☛ L’insieme di tutti i caratteri eccetto il fine riga (new line) viene descritto mediante il simbolo “.”.

Espressioni regolari in Lex...continua...

11

☛ Il carattere di fine riga viene descritto dal simbolo \n.☛ Il carattere di tabulazione viene descritto dal simbolo

\t.☛ L’operatore ? indica l’espressione precedente è

opzionale:✏ ab?c indica sia la sequenza ac che abc.

☛ L’operatore * indica l’espressione precedente può essere ripetuta 0 o più volte:

✏ ab*c indica tutte le sequenze che iniziano per a, terminano per ce hanno all’interno un numero qualsiasi di b.

Espressioni regolari in Lex...continua...

12

☛ L’operatore + indica l’espressione precedente può essere ripetuta 1 o più volte:

✏ ab+c indica tutte le sequenze che iniziano per a, terminano per ce hanno all’interno almeno un b.

☛ L’operatore | indica un’alternativa tra due espressioni:

✏ ab|cd indica sia la sequenza ab che la sequenza cd.

☛ Le parentesi tonde consentono di esprimere la priorità tra operatori:

✏ (ab|cd+)?ef indica sequenze tipo ef, abef, cdddef.

Espressioni regolari in Lex...continua

Page 4: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 4

13 Azioni associate alle espressioni regolari continua...

☛ Ad ogni espressione regolare è associata in Lexun’azione che viene eseguita all’atto del riconoscimento.

☛ Le azioni sono espresse sotto forma di codice C: se tale codice comprende più di una istruzione o occupa più di una linea deve essere racchiuso tra parentesi graffe.

☛ L’azione più semplice consiste nell’ignorare il testo riconosciuto: si esprime un’azione nulla con il carattere ‘;’.

14

☛ Il testo riconosciuto viene accumulato nella variabile yytext, definita come puntatore a caratteri. Operando su tale variabile, si possono definire azioni più complesse.

☛ Il numero di caratteri riconosciuti viene memorizzato nella variabile yyleng, definita come intero.

☛ Esiste un’azione di default che viene eseguita in corrispondenza del testo non descritto da nessuna espressione regolare: il testo non riconosciuto viene ricopiato in uscita, carattere per carattere.

Azioni associate alle espressioni regolari ...continua

15

☛ Un file sorgente per lex è composto di tre sezioni distinte separate dal simbolo ‘%%’.

☛ La prima sezione contiene le definizioni e può essere vuota.

☛ La seconda sezione contiene le regole sotto forma di coppie espressione_regolare azione.

☛ Le azioni devono iniziare sulla stessa riga in cui termina l’espressione regolare e ne sono separate tramite spazi o tabulazioni.

☛ La terza sezione contiene le procedure di cui il programmatore intende servirsi: se è vuota, il separatore ‘%%’ viene omesso.

La struttura di un programma sorgente Lex

16 Definizioni continua...

☛ Per semplificare la gestione di espressioni regolari complesse o ripetitive, è possibile definireidentificatori che designano sotto-espressioni regolari.

☛ Ogni riga della prima sezione il cui primo carattere non sia di spaziatura è una definizione:numero [+-]?[0-9]+

☛ La sotto-espressione così definita può essere utilizzata racchiudendone il nome tra parentesi graffe:{numero} printf(“trovato numero\n”);

Page 5: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 5

17

☛ Le righe che iniziano con spazio o tabulazione sono ricopiate identiche nel file di codice C generato dall’esecuzione di Lex.

☛ Lo stesso avviene per tutti i caratteri compresi tra i delimitatori ‘%{‘ e ‘%}’.

☛ Tutte le righe presenti nella sezione delle procedure (la terza) del programma sorgente sono ricopiate nel file C generato da Lex.

Definizioni ...continua

18 Esercizi

☛ Si scriva un programma LEX che dato in ingresso un programma C ne produca in uscita uno equivalente ma privo dei commenti.

☛ Si modifichi il programma dell’esercizio precedente in modo che riconosca le direttive #include e, quando le incontra, segnali un errore e termini l’analisi. Si tenga conto che:

✏ possono comparire degli spazi o tabulazioni,✏ non interessa verificare la correttezza del path: è un controllo che

dovrebbe essere effettuato ad un livello superiore.

19 Risoluzione delle ambiguità lessicali

☛ Esistono due tipi di ambiguità lessicali:✏ la parte iniziale di una sequenza di caratteri riconosciuta da

un’espressione regolare è riconosciuta anche da una seconda espressione regolare.

✏ La stessa sequenza di caratteri è riconosciuta da due espressioni regolari distinte.

☛ Nel primo caso verrà eseguita l’azione associata all’espressione regolare che ha riconosciuto la sequenza più lunga.

☛ Nel secondo caso sarà eseguita l’azione associata all’espressione regolare dichiarata per prima nel file sorgente di lex.

20 Esempio

☛ Dato il file%%for {return FOR_CMD;}format {return FORMAT_CMD;}[a-z]+ {return GENERIC_ID;}

e la stringa di ingresso “format”, la procedura yylex ritorna il valore FORMAT_CMD, preferendo la seconda regola alla prima - perché descrive una sequenza più lunga, e la seconda regola alla terza -perché definita prima nel file sorgente.

Page 6: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 6

21

☛ Date le regole di risoluzione dell’ambiguità, è necessario definire prima le regole per le parole chiave e poi quelle per gli identificatori.

☛ Il principio di preferenza per le corrispondenze più lunghe può essere pericoloso:’.*’ {return QUOTED_STRING;}

cerca di riconoscere il secondo apice il più lontano possibile: cosi, dato il seguente ingresso’first’ quoted string here, ’second’ here

riconoscerà 36 caratteri invece di 7.☛ Una regola migliore è la seguente:

’[^’\n]+’ {return QUOTED_STRING;}

Risoluzione delle ambiguità lessicali

22 Esercizi

☛ Scrivere l’espressione regolare di una stringa che possa contenere al proprio interno anche apici purché preceduti dal carattere ‘\’.

☛ Modificare la regola precedente affinché ritorni una copia della stringa riconosciuta, dopo aver opportunamente rimosso eventuali ‘\’ di troppo.

☛ Scrivere un programma lex che rimuova i tag da un file in formato HTML sostituendo i tag <P> e <BR> con un a-capo.

23 Dipendenza dal contesto

☛ Può essere necessario limitare la validità di un’espressione regolare all’interno di un determinato contesto.

☛ Esistono meccanismi diversi per specificare la dipendenza dal contesto destro (cioè ciò che segue la sequenza di caratteri che si sta riconoscendo) rispetto alla dipendenza dal contesto sinistro (ciò che la precede).

☛ Fa eccezione la gestione del contesto di inizio e fine riga.

24 Contesto di inizio e fine riga

☛ Il carattere ‘^’ all’inizio di un’espressione regolare indica che la sequenza descritta deve essere posta all’inizio di riga.

☛ Ciò significa che o si è posizionati all’inizio del file di ingresso o che l’ultimo carattere letto è stato un carattere di fine riga.

☛ Il carattere ‘$’ al termine di un’espressione regolare indica che la sequenza descritta deve essere seguita da un carattere di fine riga.

☛ Tale carattere non viene incluso nella sequenza riconosciuta, deve essere riconosciuto da un’altra regola.

Page 7: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 7

25 Dipendenza dal contesto destro

☛ L’operatore binario ‘/’ separa un’espressione regolare dal suo contesto destro.

☛ Pertanto, l’espressioneab/cd

indica la stringa “ab”, ma solo se seguita da “cd”.☛ I caratteri che formano il contesto destro vengono

letti dal file di ingresso ma non introdotti nel testo riconosciuto. A tale scopo viene utilizzato un apposito buffer fornito da Lex.

☛ L’espressione ab$ è equivalente a ab/\n.

26 Dipendenza dal contesto sinistro

☛ È utile poter avere diversi insiemi di regole lessicali da applicare in porzioni diverse del file di ingresso, in genere in funzione di ciò che precede, ovvero del contesto sinistro.

☛ Esistono tre approcci distinti per affrontare il problema:

✏ uso di variabili flag.✏ uso di condizioni iniziali sulle regole o stati.✏ uso congiunto di più analizzatori lessicali.

27 Uso di variabili flag

☛ Per esprimere la dipendenza dal contesto sinistro è possibile utilizzare variabili flag nelle azioni delle regole.

☛ Tali variabili devono essere state precedentemente dichiarate come variabili globali.

☛ La funzione REJECT può essere utilizzata per evitare corrispondenze non volute: redirige lo scanner

✏ alla seconda miglior regola che riconosce lo stesso input, oppure✏ ad una regola che riconosca un prefisso dello stesso input.

28 Esempio

☛ Il seguente programma gestisce pseudo-commenti del tipo // $var+

int flag=0;

%%

“//” {flag=1; /* begin of comment */}

\n {flag=0; /* \n terminates comment */}

“ “ /* ignore blanks*/

\t /* and tabs */

\$[a-zA-Z]+[-+] {

if(flag==1) process(yytext);

else REJECT;}

... other rules

Page 8: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 8

29

☛ Le regole la cui espressione regolare inizia con<state>

sono attive solo quando l’analizzatore si trova nello stato state.

☛ Gli stati possibili devono essere definiti nella sezione delle dichiarazioni mediante la parola-chiave %s.

☛ Lo stato di default è lo stato 0 o INITIAL.☛ Si entra in uno stato quando viene eseguita l’azione

BEGIN(state);

Uso di condizioni iniziali sulle regole (stati inclusivi)

30

☛ Quando uno stato viene attivato, le regole dello stato si aggiungono (or-inclusivo) alle regole base dell’analizzatore.

☛ Tale stato rimane attivo fino a che non se ne attiva un altro. Per tornare alla situazione iniziale si deve eseguire il comando

BEGIN(0); oppure BEGIN(INITIAL);

☛ Una regola può essere preceduta dal nome di più stati, separati da virgola, per indicare che tale regola è attiva in ciascuno di essi.

Uso di condizioni iniziali sulle regole (stati inclusivi) continua

31 Esempio

☛ Il seguente programma gestisce pseudo-commenti del tipo // $var+%s comment

%%

<comment>\$[a-zA-Z]+[-+] {process(yytext);}

“//” {BEGIN(comment);}

\n {BEGIN(INITIAL);}

“ “ /* ignore blanks*/

\t /* and tabs */

... other rules

32 Uso congiunto di più analizzatori lessicali (stati esclusivi)

☛ È possibile raggruppare un insieme di regole all’interno di uno stato esclusivo.

☛ Quando l’analizzatore si trova in uno stato esclusivo:✏ le regole di default risultano disabilitate,✏ sono attive solo le regole esplicitamente attivate nello stato.

☛ In tal modo è possibile specificare dei “mini-analizzatori” che esaminano porzioni particolari del flusso di caratteri in ingresso, quali ad esempio i commenti o le stringhe.

☛ La parola chiave %x introduce uno stato esclusivo.

Page 9: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 9

33

☛ Un possibile approccio all’eliminazione dei commenti del linguaggio C: %%"/*" { int c;

for ( ; ; ) {while ((c = input())!='*' &&

c != EOF ) ;/* eat up text of comment */

if ( c == '*') {while ((c=input())=='*') ;if ( c == '/' ) break;

/* found the end */}if ( c == EOF ) {

error( "EOF in comment" );break;

}}

}

Eliminazione dei commenti

34 Eliminazione dei commenti

☛ Questo è un analizzatore che riconosce e scarta commenti del linguaggio C, mantenendo un conteggio del numero della riga di ingresso.

int line_num = 1;%x comment%%

\n ++line_num;"/*" BEGIN(comment);<comment>[^*\n]* ;<comment>"*"+[^*/\n]* ;<comment>\n ++line_num;<comment>"*"+"/" BEGIN(INITIAL);

... altre regole

35 Esercizi

☛ Si modifichi il programma precedente affinché consenta l’eliminazione di commenti annidati.

☛ Si estenda il programma precedente in modo che gestisca anche possibili pseudo-commenti della forma $var[+-]

☛ Al programma precedente aggiungere l’eliminazione di commenti brevi, introdotti dal simbolo // e terminati dal carattere di fine riga.

36 La struttura del programma generato da Lex☛ Lex produce un programma C, privo di main() il cui

punto di accesso è dato dalla funzione intyylex().

☛ Tale funzione legge dal file yyin e ricopia sul file yyout il testo non riconosciuto.

☛ Se non specificato diversamente nelle azioni (tramite l’istruzione return), tale funzione termina solo quando l’intero file di ingresso è stato analizzato.

☛ Al termine di ogni azione l’automa si ricolloca sullo stato iniziale pronto a riconoscere nuovi simboli.

Page 10: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 10

37 Ingresso e uscita

☛ Per default, i file yyin e yyout sono inizializzatirispettivamente a stdin e stdout.

☛ Il programmatore può alterare questa situazione re-inizializzando tali variabili globali.

38 Il file generato

inizializzazionedel file yyin

(default=stdin)

scansione del file di ingresso

elaborazione del simbolo

azioneeseguereturn

trovatoEOF

yylex()

1 2 fine dell’analisi3

yywrap()

altrifile?

si

no

reinizializzazionedel file yyin

39

☛ Negli analizzatori costruiti tramite il programma flex, per motivi di efficienza, vengono letti blocchi di caratteri dal file di ingresso invece che singoli valori. Questo comportamento può essere modificato alterando la definizione della macro YY_INPUT.

☛ Tale macro è definita come segue:YY_INPUT(buf,result,max_size)

☛ Essa pone nel vettore di caratteri buf un blocco la cui lunghezza è compresa tra 1 e max_size.

☛ La variabile intera result contiene il numero di caratteri letti oppure la costante YY_NULL per indicare la fine del file di ingresso.

Il file prodotto continua

40

☛ Per far avvenire la lettura un carattere alla volta si può utilizzare il codice seguente:

%{#undef YY_INPUT#define YY_INPUT(buf,result,max_size) \

{ \int c = getchar(); \result = (c == EOF) ? YY_NULL : (buf[0] = c, 1); \}

%}

☛ Questo non si deve fare per gli analizzatori scritti conLex, in cui la routine input() richiama direttamente la routine getchar().

Il file prodotto continua

Page 11: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 11

41

☛ Quando l’analizzatore incontra la fine del file di ingresso, chiama la funzione yywrap().

☛ Se essa ritorna il valore 0, Lex assume che il file yyin sia stato re-inizializzato e procede con l’analisi.

☛ Altrimenti, l’analizzatore assume che non ci siano altri file da scandire e ritorna al chiamante restituendo il valore 0.

☛ La funzione yywrap() può essere usata per stampare tabelle e statistiche circa l’analisi appena terminata. Su molti sistemi, per default, yywrap()restituisce sempre il valore 1.

Il file prodotto continua

42 Regole di fine file

☛ La regola speciale <<EOF>> introduce le azioni da intraprendere alla fine del file quando yywraprestituisce non-zero.

☛ Con tale azione è possibile passare ad un altro file o terminare l’esecuzione.

☛ Questa regola può essere utile unitamente alle start condition per intercettare simboli con delimitatori non bilanciati:\” { BEGIN(quote); }...<quote><<EOF>> { error(“EOF in string”); }

43 Inclusione di file

☛ Negli analizzatori generati tramite il programma flex, dove il testo in ingresso è fortemente bufferizzato, è necessario adottare alcuni accorgimenti per sospendere l’elaborazione del file di ingresso al fine di scandire il contenuto di un secondo file che si intende includere nel primo.

☛ Per gestire questo tipo di situazioni, viene fornito un meccanismo per creare ed attivare nuovi buffer di ingresso.

44 Inclusione di file continua

☛ La funzioneYY_BUFFER_STATE

yy_create_buffer(FILE *file, int size)

crea un nuovo buffer, di dimensione size, relativo al file file.

☛ La funzionevoid yy_switch_to_buffer(YY_BUFFER_STATE

new_buf)

sostituisce il buffer attuale di ingresso con il buffer new_buf.

☛ Si cancella un buffer con la chiamatavoid yy_delete_buffer( YY_BUFFER_STATE buf)

Page 12: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 12

45

☛ Esempio di analizzatore che elabora file inclusi con annidamento.%x incl%{#define MAX_INCLUDE_DEPTH 10YY_BUFFER_STATE include_stack[MAX_INCLUDE_DEPTH];int include_stack_ptr = 0;%}%%include BEGIN(incl);[a-z]+ ECHO;[^a-z\n]*\n? ECHO;

Inclusione di file continua

46

<incl>[ \t]* /* eat the whitespace */

<incl>[^ \t\n]+ {/* process filename */if (include_stack_ptr>=MAX_INCLUDE_DEPTH) {

fprintf( stderr, "Nesting too deep" );exit( 1 );

}include_stack[include_stack_ptr++]=

YY_CURRENT_BUFFER;yyin = fopen( yytext, "r" );if ( ! yyin ) error( ... );yy_switch_to_buffer(

yy_create_buffer(yyin,YY_BUF_SIZE));BEGIN(INITIAL);

}

Inclusione di file continua

47

%%

int yywrap(){

if ( --include_stack_ptr < 0 )return(1);

else {yy_switch_to_buffer(

include_stack[include_stack_ptr]);return(0);

}}

Inclusione di file continua

48 Uso della funzione yylex()

☛ Per un normale uso all’interno di un compilatore, è opportuno che la funzione yylex() restituisca il controllo ogni volta che incontra un identificatore.

☛ A tale scopo è necessario assegnare a ciascuna classe di simboli che si intende riconoscere un opportuno valore.

☛ Poiché tale valore deve essere conosciuto anche dalriconoscitore sintattico, è pratica comune creare un file di definizioni dei simboli del linguaggio ed includere tale file nella sezione delle dichiarazioni.

☛ I valori assegnati sono interi, in genere > 256.

Page 13: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 13

49 Lex: parametri ed opzioni

☛ Lex viene invocato con la seguente sintassi:lex [opzioni] file.lex

☛ Si ottiene come risultato il file lex.yy.c.☛ Tra le opzioni, le più comuni sono:

✏ -t copia il programma generato su stdout,✏ -v stampa una statistica circa il programma compilato,✏ -p stampa informazioni sulle performance dello scanner,✏ -ooutput genera il file output invece di lex.yy.c ,✏ -B(-I) genera uno scanner Batch (Interattivo),✏ -f ottimizza le prestazioni dello scanner,✏ -i genera uno scanner case insensitive,✏ -d attiva i messaggi di debug nello scanner.

50 Linguaggi CF e Riconoscitori

☛ Grammatiche CF✏ Alberi di derivazione✏ Ambiguità

☛ Parser bottom-up☛ Introduzione a YACC

✏ Definizione dei simboli✏ Codifica della grammatica✏ Integrazione con l’analizzatore lessicale✏ Formato del programma prodotto da YACC

☛ Ambiguità e conflitti✏ Conflitti shift-reduce e reduce-reduce✏ Definizione di operatori e gestione delle priorità in YACC✏ Gestione degli errori sintattici

51 Grammatiche context free continua...

☛ Il concetto di ricorsione è fondamentale nella scrittura di grammatiche context-free.

☛ È lo strumento per descrivere delle sequenze lunghe a piacere.

☛ Lista (grammatica ricorsiva sinistra) :✏ List →→→→ List ‘,’ Element✏ List →→→→=

==

=Element

☛ Lista (grammatica ricorsiva destra) :✏ List →→→→ Element Tail✏ Tail →→→→==

====

==’,’ Element Tail✏ Tail →→→→==

====

==εεεε

52 Alberi di derivazione

☛ Data una sequenza di simboli che appartiene al linguaggio definito da una data grammatica, è possibile definire l’albero di derivazione dell’espressione nel seguente modo:

✏ la radice dell’albero è il simbolo iniziale della grammatica✏ ciascun nodo dell’albero è etichettato con un simbolo della

grammatica✏ le foglie dell’albero sono etichettate con simboli terminali✏ la sequenza delle foglie, lette da sinistra a destra, corrisponde alla

sequenza di ingresso✏ un nodo n domina una sequenza ordinata di nodi n1, ..., nm, se e

solo se la grammatica contiene la regolan → n1 ... nm

Page 14: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 14

53 Alberi di derivazione

List →→→→ List ‘,’ elList →→→→=

==

=el

el ‘,’ el ‘,’ el

List

List

List

List →→→→ el TailTail →→→→==

====

==’,’ el TailTail →→→→==

====

==εεεε

el ‘,’ el ‘,’ el

List

Tail

Tail

εεεε

Tail

54 Costrutti Pascal-like

☛ Un frammento di programma di un linguaggio simile al Pascal può essere definito dalle seguenti regole:

✏ P →→→→ ‘begin’ Is ‘end’ ‘.’✏ Is →→→→ Is I | εεεε✏ I →→→→ ‘if’ C ‘then’ I ‘else I ‘;’✏ I →→→→ ‘while’ C ‘do’ I’;’ ✏ I →→→→ ‘repeat’ Is ‘until’ C ‘;’ ✏ I →→→→==

====

==var ‘:=‘ E ‘;’✏ I →→→→==

====

==‘begin’ Is ‘end’✏ C →→→→ E Op E✏ Op →→→→ ‘=‘ | ‘>‘ | ‘<‘ | ‘>=‘ | ‘<=‘ | ‘~=‘✏ E →→→→ algebric expression

55 Ambiguità

☛ Una grammatica si dice ambigua se esiste almeno una sequenza di simboli del linguaggio per cui esistono due o più alberi di derivazione distinti.

☛ Esercizio: trovare gli alberi di derivazione per if (i=1) then if (j=2) then a:=0 else a:=1

data la grammatica:✏ S → I✏ I → ‘if’ C ‘then’ I✏ I → ‘if’ C ‘then’ I ‘else I✏ I →=var ‘:=‘ E✏ C → ‘(‘ E ‘=‘ E ‘)’✏ E → algebric expression

56

☛ La grammatica che descrive le espressioni algebriche è context-free ed usa la ricorsione:

✏ S →→→→ E✏ E →→→→ E ‘+’ T✏ E →→→→ E ‘-’ T✏ E →→→→ T✏ T →→→→ T ‘*’ F✏ T →→→→ T ‘/’ F✏ T →→→→ F✏ F →→→→ ‘(‘ E ‘)’✏ F →→→→ number

Espressioni algebriche

Page 15: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 15

57 Esempio

☛ I simboli T e F della grammatica algebrica servono a togliere l’ambiguità sulla priorità degli operatori ‘+’ e ‘-’ rispetto agli operatori ‘*’ e ‘/’.

☛ La grammatica per i costrutti del tipo if-then-else può essere resa non ambigua come segue:

✏ I →=I1✏ I1 → ‘if’ C ‘then’ I1✏ I1 → ‘if’ C ‘then’ I2 ‘else’ I1✏ I1 →=I3✏ I2 → ‘if’ C ‘then’ I2 ‘else’ I2✏ I2 →=I3✏ I3 →=var ‘:=‘ E

58 Esercizi

☛ Scrivere due grammatiche di liste di 0 o più identificatori aventi “;” l’una come terminatore, l’altra come separatore.

☛ Scrivere una grammatica non ambigua che descriva le espressioni logiche e che assegni l’opportuna priorità agli operatori ‘and’, ‘or’ e ‘not’.

☛ Costruire la grammatica per i test con un delimitatore alla fine del costrutto e verificare che non è ambigua sull’esempio:

if C then if C then A end else A end

59 Separatori e terminatori

☛ Una lista, eventualmente vuota, di identificatori separati da “;”

✏ List � id_list✏ List � εεεε✏ id_list � id_list ‘;’ id✏ id_list � id

☛ Una lista , eventualmente vuota, di identificatori terminati da “;”

✏ List � List id ‘;’✏ List � εεεε

List � List ‘;’ idList � idList � εεεε

60 Riconoscitori e analizzatori sintattici

☛ Data una grammatica non ambigua ed una sequenza di simboli in ingresso, un riconoscitore è un programma che verifica se la sequenza appartiene o no al linguaggio definito dalla grammatica.

☛ Un analizzatore sintattico (parser) è un programma che è in grado di associare alla sequenza di simboli in ingresso il relativo albero di derivazione.

☛ Gli algoritmi di analisi possono essere✏ top-down (dalla radice alle foglie)✏ bottom-up (dalle foglie alla radice).

Page 16: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 16

61 Riconoscitori Bottom-Up

☛ Il problema principale di un analizzatore top-down è decidere quale produzione deve essere usata per espandere un dato simbolo non terminale.

☛ Nei riconoscitori bottom-up il problema è capire quando si sono incontrati tutti i simboli del lato sinistro di una produzione così da poterli sostituire con il relativo non terminale.

☛ Il problema diventa più complesso se la grammatica non è ε-free.

☛ Se la grammatica è LR(k) si può usare un analizzatore shift-reduce.

62 La tecnica shift-reduce continua…

☛ Si usa uno stack, inizialmente vuoto, per memorizzare i simboli già riconosciuti.

☛ La sequenza dei simboli sullo stack, seguita dai simboli in ingresso non ancora trattati costituisce sempre una forma sentenziale destra (se l’input è corretto).

☛ I token vengono immessi sullo stack (azione di shift), fino a che la cima dello stack non contiene un handle: quando ciò avviene, l’handle viene ridotto (reduce) con il non-terminale relativo.

☛ Si ha successo se esaminati tutti i simboli in ingresso, lo stack contiene solo il simbolo distintivo della grammatica.

63

☛ Per un corretto funzionamento bisogna:✏ comprendere quando si è raggiunta la fine di un handle;✏ determinare la lunghezza dell’handle;✏ decidere quale non terminale sostituire all’handle nel caso in cui

ci siano più produzioni con lo stesso lato destro.

☛ Si risolve il problema definendo un insieme di stati del parser e calcolando due tabelle

✏ Action Table: dice quale azione deve eseguire il parser (shift, reduce, terminate, error) in funzione dello stato corrente.

✏ Goto Table: descrive in quale stato deve portarsi il parser.

☛ Il modo con cui vengono calcolati stati e tabelle porta a soluzioni diverse, sia in termini di complessità che di completezza.

La tecnica shift-reduce …continua

64 LALR(1)

S : exp

exp : exp '+' T| T

T : NUM

S : exp

exp : exp '+' T| T

T : NUM

S ���� . expexp ���� . exp ‘+’ Texp ���� . Texp ���� . NUM

0

T ���� NUM . 1

exp ���� T .3

exp ���� exp ‘+’ T .5

exp ���� exp ‘+’ . T4

S ���� exp .exp ���� exp . ‘+’ T

2 exp

‘+’

T

NUM

T

NUM

Page 17: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 17

65 LALR(1)

0

1

2

4

3

5

exp T

NUM+NUM

T

NUM + NUM

0

NUM + NUM

0

shift, go to state 1

1

eof

eof

66 LALR(1)

NUM + NUM

01

reduce ( T ���� NUM )reduce ( T ���� NUM )

0

1

2

4

3

5

exp T

NUM+NUM

T

NUM

0T

NUM + NUM

03

T

eof

eof

67 LALR(1)

NUM + NUM

03

reduce ( exp ���� T )reduce ( exp ���� T )

0

1

2

4

3

5

exp T

NUM+NUM

T

NUM

0exp

NUM + NUM

02

T

exp

eof

eof

68 LALR(1)

0

1

2

4

3

5

exp T

NUM+NUM

T

NUM + NUM

0

NUM + NUM

0

shift, go to state 4

2

2

NUM

T

exp

eof

eof4

Page 18: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 18

69 LALR(1)

0

1

2

4

3

5

exp T

NUM+NUM

T

NUM + NUM

0

NUM + NUM

0

shift, go to state 1

2

2

NUM

T

exp 4

eof

eof

4

1

70 LALR(1)

NUM + NUM

reduce ( T ���� NUM )reduce ( T ���� NUM )

0

1

2

4

3

5

exp T

NUM+NUM

T

NUM

T

NUM + NUMT

exp

eof

eof

NUM

T

0241

0245

024

71 LALR(1)

NUM + NUM

reduce ( exp ���� exp ‘+’ T )reduce ( exp ���� exp ‘+’ T )

0

1

2

4

3

5

exp T

NUM+NUM

T

NUM

exp

NUM + NUM

02

T

exp

eof

eof

NUM

T

0245

0exp

+

72 Introduzione a Yacc

☛ Yacc è un generatore di analizzatori sintattici che trasforma la descrizione di una grammatica context-free LALR(1) in un programma C che riconosce ed analizza la grammatica stessa.

☛ Oltre alle regole sintattiche, è possibile specificare quali azioni devono essere eseguite in corrispondenza del riconoscimento dei vari simboli della grammatica.

☛ È necessario integrare il parser così generato con un analizzatore lessicale: alcune convenzioni ne semplificano sensibilmente l’integrazione con lo scanner generato da Lex.

Page 19: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 19

73 Il formato del file di ingresso

☛ Il file di ingresso su cui opera Yacc è formato da tre sezioni:

✏ le dichiarazioni,✏ le regole,✏ le procedure

☛ Le sezioni sono separate dal simbolo ‘%%’☛ La prima e l’ultima sezione possono essere vuote.☛ Se l’ultima sezione è vuota, il secondo separatore

può essere omesso.☛ Possono essere introdotti commenti racchiusi dai

simboli ‘/*’ e ‘*/’.

74 Dichiarazioni

☛ Un file Yacc inizia con la sezione delle dichiarazioni.☛ In essa vengono definiti

✏ i simboli terminali,✏ il simbolo distintivo della grammatica,✏ le regole di associatività e precedenza tra gli operatori,✏ alcune informazioni semantiche,✏ codice C racchiuso tra i simboli ‘%{’ e ‘%}’.

☛ I simboli non terminali della grammatica sono nomi: ✏ un nome è formato da lettere, ‘_’, ‘.’ e cifre (non iniziali).

☛ I simboli terminali sono nomi o letterali: ✏ un letterale è un singolo carattere racchiuso tra apici.

75

☛ La parola chiave ‘%token’ definisce una lista di nomi di terminali, separati tra loro da uno o più spazi.

☛ Tale parola chiave può comparire più volte in questa sezione.

☛ I letterali non sono dichiarati.☛ La parola chiave ‘%start’ definisce il simbolo

distintivo della grammatica. È lecita una sola occorrenza di questa parola chiave.

☛ Non è necessario dichiarare i simboli non terminali -tranne quando si vuole associare loro un valore semantico.

Dichiarazioni continua

76 Codifica della grammatica

☛ La sezione delle regole è costituita da una o più regole del tipo:

NonTerminale : CorpoDellaRegola ;

☛ dove NonTerminale è un nome, e CorpoDellaRegolaè una sequenza di 0 o più nomi o letterali.

☛ Se per un dato non terminale esistono più produzioni, queste possono essere raggruppate tra loro e separate dal carattere ‘|’.

Page 20: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 20

77 Esempio

%token integer%start Expression

%%

Expression : Expression ‘+’ Term| Term;

Term : Term ‘*’ Factor| Factor;

Factor : integer| ‘(’ Expression ‘)’;

78 La sezione delle procedure

☛ Tutto ciò che segue il secondo delimitatore ‘%%’ forma la sezione delle procedure.

☛ Questa porzione di file viene ricopiata tale e quale in uscita.

☛ All’interno di tale sezione vengono comunemente posti:

✏ le procedure semantiche usate nel corso dell’analisi,✏ l’analizzatore lessicale,✏ il corpo principale del programma.

79 Formato del programma generato

☛ Il parser generato fa capo alla funzioneint yyparse()

☛ Tale funzione ritorna 1 se è stato incontrato un errore nel testo in ingresso, altrimenti ritorna il valore 0.

☛ Bisogna definire il corpo per la funzione void yyerror(char *)

che viene invocata quando si incontra un errore.☛ Inoltre, il programma generato non contiene il

main(), che deve essere definito dal programmatore.

80 Integrazione con l’analizzatore lessicale

☛ Il parser generato presuppone l’esistenza di una funzione che realizzi l’analisi lessicale. Tale funzione deve restituire un numero intero positivo che definisce il token letto oppure 0, se è stata raggiunta la fine del file di ingresso.

☛ La funzione di analisi lessicale è così definita:int yylex();

☛ Il valore semantico, eventualmente associato al simbolo terminale, deve essere memorizzato nella variabile yylval. Tale variabile è allocata all’interno di Yacc.

Page 21: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 21

81

☛ Parser e scanner devono accordarsi sui valori associati ai token. Tali valori possono essere scelti da Yacc o dal programmatore.

☛ I terminali introdotti come letterali sono associati al codice ASCII del carattere.

☛ Quando si introduce un terminale per mezzo della parola chiave %token, yacc associa a tale simbolo un valore intero maggiore di 256, mediante il costrutto ‘#define’ del pre-processore C.

☛ Se il nome del token è seguito da un numero intero, esso viene interpretato come valore da associare al token stesso.

Integrazione con l’analizzatore lessicale continua

82

☛ Si possono integrare i programmi generati da Lex e da Yacc in due modi:

✏ in fase di compilazione✏ in fase di link.

☛ Nel primo caso, il file C prodotto da Lex (il file ‘lex.yy.c’) viene incluso nel programma generato da Yacc mediante la direttiva ‘#include’ posta nella sezione delle procedure.

☛ Ciò rende le definizione dei token visibili direttamente in fase di compilazione.

Integrazione con l’analizzatore lessicale continua

83 Esempio: inclusione del sorgente

language.llanguage.l

language.ylanguage.y#include “lex.yy.c”

y.tab.cy.tab.c#include “lex.yy.c”

YaccYacc

LexLex lex.yy.clex.yy.c includeinclude cccc

parser.objparser.obj

84

☛ Nel secondo caso, si chiede a Yacc (mediante l’opzione ‘-d’) di generare un file (‘y.tab.h’) che contiene le definizioni dei token.

☛ Tale file deve essere incluso dallo scanner in modo che siano definiti i valori dei simboli.

☛ Di conseguenza, il file lex.yy.c, prodotto da Lex, può essere compilato dopo aver generato y.tab.c e y.tab.h tramite Yacc.

☛ Scanner e parser vengono quindi compilati separatamente ed integrati in fase di link.

Integrazione con l’analizzatore lessicale continua

Page 22: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 22

85 Esempio: compilazione separata

language.ylanguage.y YaccYacc y.tab.cy.tab.c

-d-d

y.tab.hy.tab.h

LexLex

includeinclude

cccc

cccc

lex.yy.clex.yy.c#include “y.tab.h”

parser.objparser.obj

scanner.objscanner.obj

language.llanguage.l#include “y.tab.h”

86 Ambiguità e conflitti in Yacc

☛ Se la grammatica è ambigua, o se non è LALR(1), possono verificarsi dei conflitti.

☛ Ciò significa che l’analizzatore deve scegliere tra più azioni alternative plausibili.

☛ Il problema viene, di solito, risolto modificando la grammatica per renderla non ambigua oppure fornendo indicazioni a Yacc su come comportarsi in caso di ambiguità.

☛ La seconda ipotesi richiede una comprensione adeguata dell’algoritmo di analisi, per evitare di generare comportamenti scorretti.

87 Conflitti shift-reduce

Il prossimo simbolo in ingresso è ‘else’.Possono succedere due cose:

✏ ridurre le prime quattro voci dello stack, secondo la produzione 1;

✏ introdurre ‘else’ nello stack secondo quanto previsto dalla produzione 2.

☛ In queste situazioni, Yacc decide, in mancanza di altri suggerimenti, di eseguire lo shift.

if

E

ifE

then

thenS

Top ofStack

1) S →==if E then S2) S →==if E then S else S3) S →==var = E

88 Conflitti reduce-reduce

Il prossimo simbolo in ingresso è ‘$’.Possono succedere due cose:

✏ ridurre le prime due voci dello stack, secondo la produzione 3;

✏ ridurre la prima voce secondo quanto previsto dalla produzione 4.

☛ In queste situazioni, Yacc decide, in mancanza di altri suggerimenti, di ridurre la regola che è stata definita per prima (la n°3).

a

b

Top ofStack

1) S →==a B2) S →==B3) B →==a b 4) B →== b

Page 23: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 23

89 Definizione degli operatori e gestione delle priorità☛ In certi casi la grammatica può essere resa

volutamente ambigua al fine di limitare il numero delle regole.

☛ È necessario però fornire indicazioni sulla risoluzione delle ambiguità.

☛ Un caso tipico è dato dalle espressioni algebriche:1) E →→→→==

====

==E ‘+’ E2) E →→→→==

====

==E ‘*’ E3) E →→→→==

====

==’(‘ E ‘)’4) E →→→→==

====

==integer

☛ Questa grammatica è fortemente ambigua.

90 Operatori

☛ La regola 1 (così come la 2) è ambigua in quanto non specifica l’associatività dell’operatore ‘+’ (‘*’).

☛ Inoltre le regole 1 e 2 non specificano la precedenza tra gli operatori ‘+’ e ‘*’.

☛ E’ possibile suggerire a Yacc come comportarsi aggiungendo due informazioni nella sezione delle dichiarazioni.

☛ La parola chiave ‘%left’ introduce un operatore associativo a sinistra, ‘%right’ introduce un operatore associativo a destra, ‘%nonassoc’ introduce un operatore non associativo.

☛ L’ordine con cui gli operatori sono dichiarati è inverso alla loro priorità.

91 Regole di risoluzione dell’ambiguità

☛ Ad ogni regola che contiene almeno un terminale definito come operatore, Yacc associa la precedenza ed l’associatività dell’operatore più a destra.

☛ Se la regola è seguita dalla parola chiave ‘%prec’, la precedenza e l’associatività sono quelle dell’operatore specificato.

☛ In caso di conflitto shift-reduce, viene favorita l’azione adatta alla regola con la precedenza maggiore.

☛ Se la precedenza è la stessa, si usa l’associatività: sinistra implica reduce, destra shift.

92 Esempio

%token integer%left ‘+’ ‘-’ /* lowest priority */%left ‘*’ ‘/’%left uminus /* highest priority */%%E : E ‘+’ E

| E ‘-’ E| E ‘*’ E| E ‘/’ E| ‘-’ E %prec uminus| ‘(’ E ‘)’| integer;

Page 24: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 24

93 Gestione degli errori sintattici continua...

☛ In genere quando un parser incontra un errore non dovrebbe terminare brutalmente l’esecuzione

✏ Un compilatore in genere cerca di provvedere alla situazione perpoter analizzare il resto del file, in modo da segnalare il maggior numero possibile di errori

☛ Per default, un parser generato da YACC, quando incontra un errore:

✏ segnala, tramite yyerror(), un “parse error”✏ restituisce il valore 1

☛ Il comportamento di default può essere accettabile per un semplice parser interattivo.

94 Gestione degli errori sintattici ...continua...

☛ Il simbolo predefinito ‘error’ indica una condizione di errore. Esso può essere usato all’interno della grammatica per consentire al parser di riprendere l’esecuzione dopo un eventuale errore.

stmts : /* empty statement */| stmts ‘\n’| stmts exp ‘\n’| stmts error ‘\n’;

95 Gestione degli errori sintattici ...continua...

☛ Quando il parser generato da Yacc incontra un errore, comincia a svuotare lo stack fino a che incontra uno stato in cui il simbolo ‘error’ è lecito.

☛ Fa lo shift del simbolo error.☛ Se il precedente simbolo di look-ahead è

accettabile procede nell’analisi.☛ Altrimenti il parser continua a leggere e scartare

simboli finché non ne trova uno accettabile

96 Gestione degli errori sintattici ...continua...

☛ Una semplice strategia per la gestione degli errori è quella di saltare lo statement corrente:

stmt : error ‘;’

☛ A volte può essere utile recuperare un delimitatore di chiusura corrispondente ad uno di apertura:

primary : ‘(‘ expr ‘)’| ‘(‘ error ‘)’

Page 25: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 25

97 Gestione degli errori sintattici ...continua

☛ Le strategie di recupero degli errori si basano su scommesse; quando si perde la scommessa il rischio è che un errore sintattico ne produca altri spuri.

☛ Per limitare la proliferazione di errori spuri, dopo il riconoscimento di un errore, la segnalazione è sospesa finché non vengono shiftati almeno tre simboli consecutivi.

☛ È possibile riattivare immediatamente la segnalazione degli errori utilizzando la macro ‘yyerrok’.

98 Regole semantiche

☛ Traduzione guidata dalla sintassi☛ La semantica in YACC

✏ Definizione di valori semantici ✏ Calcolo di attributi sintetizzati ✏ Sintesi di attributi in Lex✏ Calcolo di attributi ereditati✏ Trasformazione delle grammatiche✏ Gestione degli errori semantici

☛ Abstract Syntax Tree

99 Definizioni Guidate dalla Sintassi

☛ Sono una generalizzazione delle grammatichecontext-free.

☛ Ad ogni simbolo viene associato un insieme di attributi:

✏ sintetizzati: calcolati in base al valore degli attributi dei figli di un nodo nell’albero di derivazione,

✏ ereditati: calcolati in base al valore degli attributi dei nodi fratelli e del nodo padre nell’albero di derivazione,

☛ Ad ogni produzione viene associato un insieme di regole semantiche che specificano come vengono calcolati gli attributi.

100

☛ Se una definizione guidata dalla sintassi usa solo attributi sintetizzati è detta definizione ad attributi S.

☛ È possibile calcolare i valori degli attributi di una definizione ad attributi S bottom-up, dalle foglie alla radice dell’albero di derivazione.

E E1 ‘+’ T E.value = E1.value + T.value

E T E.value = T.value

T number T.value = number.value

Attributi sintetizzati

Page 26: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 26

101 Attributi ereditati

☛ Sono utili per esprimere la dipendenza di costrutti di un linguaggio dal contesto in cui si trovano.

D L ‘:’ T ‘;’ L.type = T.type

L L1 ‘,’ id L1.type = L.typenew_var(id.name,L.type)

L id new_var(id.name,L.type)

T ‘integer’ T.type = type_int

102 Attributi L

☛ L’ordine di valutazione degli attributi dipende dall’ordine con cui vengono creati o visitati i nodi dell’albero di derivazione.

☛ Comunemente i parser seguono l’ordine di una visita in profondità dell’albero.

☛ Una grammatica è detta ad attributi L se è possibile il calcolo dei valori degli attributi con una visita in profondità dell’albero di derivazione.

☛ In tali grammatiche si ha una propagazione delle informazioni da sinistra a destra (dell’albero di derivazione).

103 La semantica in Yacc continua...

☛ Accanto allo stack che contiene i simboli sintattici, c’è un secondo stack che contiene i valori semantici ad essi associati.

☛ Ad ogni regola può essere associata un’azione da eseguirsi ogni qualvolta la regola è applicata nel processo di analisi.

☛ Tale azione provvede ad aggiornare i valori semantici associati a ciascun simbolo.

☛ Un azione è composta da una o più istruzioni di codice C racchiuse tra i simboli ‘{‘ e ‘}’, ed è posta (di norma) al termine della regola.

104

☛ All’interno di ogni azione, si fa riferimento ai valori semantici associati ai successivi simboli del lato destro della regola mediante i nomi ‘$1’, ‘$2’, ‘$3’, ...

☛ Il valore semantico associato al simbolo del lato sinistro della regola è indicato dal nome ‘$$’.

☛ Il traduttore generato da Yacc associa ad ogni regola che non contiene un’azione esplicita la seguente azione di default:

{ $$ = $1; }

La semantica in Yacc ...continua

Page 27: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 27

105 Esempio

☛ Data la grammatica delle espressioni algebriche, la regola seguente associa al simbolo ‘E’ la somma o la sottrazione dei valori degli addendi/sottraendi:

E : E ‘+’ T { $$ = $1 + $3; }

| E ‘-’ T { $$ = $1 - $3; }

;

☛ Un’azione può contenere istruzioni qualsiasi, anche facenti riferimento a variabili globali:

A : B C D { printf(“%d\n”,i); } ;

106

☛ Per default, ad ogni simbolo viene associato un solo attributo di tipo intero.

☛ Ciò può essere cambiato ridefinendo (mediante typedef) il simbolo YYSTYPE, oppure servendosi, nella sezione delle dichiarazioni, dei costrutti ‘%union’ e ‘%type’ .

☛ Nel primo caso, tutti i simboli sono legati ad un valore semantico il cui tipo è quello attribuito a YYSTYPE.

☛ In alternativa, ‘%union’ consente di definire un insieme di interpretazioni alternative dei valori semantici contenuti nellostack.

☛ ‘%type’ associa una data interpretazione ad uno o più simboli non terminali.

Tipi dei valori semantici

107 Esempio☛ ‘Func’ ha due attributi: un intero ed un puntatore a

carattere. I simboli ‘E’, ‘T’ ed ‘F’ hanno un solo attributo di tipo reale, ‘Args’ un solo attributo di tipo intero.

%{ typedef struct _function_desc {int arg_number;char *name;

} function_desc;%}%union { function_desc function;

float expression;char * id;int other; }

%type <expression> E T F%type <function> Func%type <other> Args%% /* ... rules here... */

108 Calcolo di attributi sintetizzati

☛ Un attributo sintetizzato viene calcolato assegnando, nell’azione associata ad una data regola, un valore alla pseudo-variabile ‘$$’.

☛ Tale valore è funzione, in genere, di uno o più degli attributi dei simboli del lato destro della regola stessa.

☛ Se ad un simbolo sono stati associati più attributi sotto forma di ‘struct’, è possibile far riferimento a ciascuno di essi con la sintassi $n.attr.

Func: identifier ‘(‘ Args ‘)’

{ $$.name=$1;

$$.arg_number=$3; }

;

Page 28: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 28

109 Sintesi di attributi in Lex

☛ Se ad un simbolo terminale sono associati degli attributi, bisogna definirne il tipo nella sezione delle dichiarazioni con la sintassi seguente:

%token <expression> number

☛ L’analizzatore lessicale deve provvedere a depositare, all’atto del riconoscimento del simbolo, il suo valore semantico nella variabile globale esterna ‘yylval’.

☛ Se si è usato il costrutto %union, nello scanner bisogna citare esplicitamente il campo della unione a cui ci si riferisce:

[0-9]+”.”[0-9]* {

yylval.field = atof(yytext);

return(number);}

110 Esercizi

☛ Si scriva una grammatica per riconoscere delle espressioni regolari tipo Lex e costruire l’albero di derivazione relativo all’espressione riconosciuta. Si utilizzino le primitive:

✏ treenode* create_node(char* name)

✏ void add_son(treenode* father, treenode* son)

☛ Si scriva una grammatica che riconosca sequenze di espressioni matematiche separate dal simbolo ‘=‘ e che stampi, in conseguenza del riconoscimento di tale simbolo il valore dell’espressione riconosciuta.

111 Calcolo di attributi ereditati continua...

☛ In un riconoscitore bottom-up, non viene riservato spazio sullo stack semantico fino a che il corrispondente simbolo sintattico non è riconosciuto.

☛ Ciò rende problematica la gestione degli attributi ereditati.

☛ Se la grammatica è ad attributi L, e le regole semantiche per il calcolo degli attributi ereditati sonocopy-rules è possibile “aggirare l’ostacolo” in maniera semplice.

☛ Se le regole sono più complesse è possibile inserire dei marker, cioè dei non-terminali che si espandono in ε.

112

☛ Una produzione che usa una copy-rule:

(1) Decl T D1 D1.i = T.s

(2) D1 id var(D1.i,id.n)

Calcolo di attributi ereditati …continua...

T.sid.n

...

Situazione dellostack prima di ridurre D1

$1 contesto della regola (2)contesto della regola (2)

Page 29: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 29

113

☛ Il simbolo ‘Decl1’ ha l’attributo ereditato ‘type’.

☛ Il valore di tale attributo è presente sullo stack semantico (nella posizione associata a ‘T’) prima che venga creato il simbolo ‘Decl1’.

☛ Tuttavia esso è al di fuori del contesto semantico della regola relativa a ‘Decl1’.

Calcolo di attributi ereditati ...continua...

Decl

Decl1Ttype: inttype: int type: inttype: int

idname: jname: j

114

☛ È possibile accedere ai valori semantici precedentemente memorizzati nello stack purché se ne conosca la posizione relativa alla regola corrente.

☛ la pseudo-variabile ‘$0’ indica il valore associato al simbolo che precede immediatamente (nel parsing left-to-right rightmost-derive) la regola, ‘$-1’ quello ancora precedente e così via.

☛ Assumendo che il simbolo ‘Decl1’ sia sempre preceduto da un identificatore di tipo:Decl1: id { $$.type = $0.type;

add_id($1.name, $$.type); }

;

Calcolo di attributi ereditati ...continua...

115 Calcolo di attributi ereditati ...continua...☛ Aggiungendo la regola Decl1: id ‘,’ Decl1 ;

non è più vero che ‘Decl1’ è sempre preceduto da un identificatore di tipo.

☛ Si può aggiungere un non-terminale che porta questa informazione:

Decl1: id ‘,’ Empty Decl1{ $$.type= $0.type;

add_id($1.name,$$.type); } ;

Empty: {$$=$-2;};

Decl

Decl1Ttype: inttype: int type: inttype: int

idname: iname: i

Decl1type: inttype: int

‘,’

idname: jname: j

Emptytype: inttype: int

116

☛ Si può evitare di introdurre esplicitamente un non terminale riscritto come stringa nulla, utilizzando, nella parte destra delle regole, le azioni intermedie.

☛ Esse vengono automaticamente sostituite da YACC con un simbolo non terminale, a sua volta riscritto come ε.

☛ Nelle azioni intermedie, ‘$$’ indica il valore associato al non-terminale implicito.

☛ Per associare a tale valore un’interpretazione tra quelle introdotte con il costrutto %union, si usa la sintassi ‘$<union_field>$’.

Calcolo di attributi ereditati ...continua

Page 30: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 30

117 Esempio%{ typedef struct {

int type_name;char* id_name;

} YYSTYPE;%}%token integer char id%start Decls%%Decls : /* empty */

| Decls Decl;

Decl : TypeName Decl1 ‘;’;

TypeName : integer {$$.type_name=1; }

| char {$$.type_name=2; };

Decl1 : id {$$.id_name =

$1.id_name;$$.type_name =

$0.type_name}

| id ‘,’ {$$ = $0;}Decl1 {

$$.id_name =$1.id_name;

$$.type_name =$0.type_name;

};

%%

118 Trasformazione della grammatica☛ È possibile evitare l’uso degli attributi ereditati

trasformando la grammatica.

D L ‘:’ T

T integer | real

L L ‘,’ id | id

D id L

L ‘,’ id L | ‘:’ T

T integer | real

TT

LLDD

LL

i , j : integer

TT

LLDD

LL

i , j : integer

119 Aggiunta di marker

☛ L’uso di marker, ovvero di simboli non terminali che si espandono in ε è molto utile in varie occasioni; tuttavia può generare alcune complicazioni.

☛ La seguente trasformazioni genera una grammatica che non è LR(1):

L L b | a L M L b | aM ε

120 Gestione degli errori semantici

☛ All’interno delle azioni possono essere eseguiti i vari controlli che verificano la correttezza semantica dei costrutti sintattici (ad esempio, verificano la giusta corrispondenza tra tipi di operandi).

☛ Si possono utilizzare le seguenti macro:✏ YYABORT: termina l’esecuzione del parser e restituisce 1;✏ YYACCEPT: termina l’esecuzione del parser e restituisce 0;✏ YYERROR: genera un errore sintattico (non chiama yyerror).

☛ Alternativamente si può cercare di ricuperare almeno parzialmente l’errore e proseguire nella analisi sintattica.

Page 31: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 31

121 Abstract Syntax Tree

☛ Gli AST sono una forma condensata degli alberi di derivazione.

☛ Gli operatori e le parole chiave non compaiono come foglie dell’albero.

☛ Esse sono associate ai nodi intermedi che sarebbero stati i genitori delle foglie.

S if B then S1 else S2if-then-else

B S1 S2

122 Esercizi

☛ Si modifichi la grammatica delle dichiarazioni C in modo che usi solo attributi ereditati.

☛ Si scriva una grammatica per riconoscere delle espressioni regolari tipo Lex e costruire l’AST relativo all’espressione riconosciuta. Si utilizzino le primitive:

✏ ast_node* create_leaf(char ch)✏ ast_node* create_node(char optr,

ast_node* opnd1,ast_node* opnd2)

✏ la seconda primitiva può accettare 1 o 2 operandi, ovvero il secondo operando può essere NULL;

✏ gli operatori sono rappresentati dai caratteri ‘+’ ‘*’ ‘?’ ‘|’ ‘&’, dove l’ultimo rappresenta la concatenazione.

123 Controllo dei tipi

☛ Type expressions✏ sistemi di tipi,✏ espressioni di tipi,✏ costruttori di tipo.

☛ Costruzione di type-expression☛ Symbol tables☛ Implementazione di un type-checker

✏ strutture dati✏ grammatica✏ semantica

124 Sistemi di tipi

☛ Il controllo dei tipi è basato su:✏ i costrutti sintattici del linguaggio,✏ il concetto di tipo,✏ le regole per assegnare i tipi ai costrutti.

☛ In generale, i tipi possono essere:✏ primitivi (int, float, char)✏ costruiti (struct, union)

☛ Ad ogni costrutto del linguaggio deve essere possibile associare un tipo, descritto per mezzo di una type-expression.

Page 32: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 32

125 Type-expressions

☛ Una type-expression è formata da un tipo primitivo, oppure è un costruttore di tipo applicato ad una type-expression.

☛ I tipi primitivi sono tutti quelli necessari al linguaggio, più i due tipi speciali:

✏ void : denota l’assenza di un tipo,✏ type_error : indica un errore rilevato durante il controllo dei tipi.

☛ È possibile assegnare un nome ad un tipo o ad un suo campo.

☛ Un nome è una type-expression.

126 Costruttori di tipo

☛ Array: array( I , T )✏ I : dimensione dell’array✏ T : type expression

☛ Puntatori: pointer( T )☛ Prodotto: T1 X T2☛ Strutture: struct( T )

char v[10] array(10,char)

struct {int i;char s[5];

} struct((i x int )x( s x array(5,char)))

127 Costruttori di tipo

☛ Una funzione mappa un elemento del proprio dominio in un elemento del range.

☛ Funzioni: T1 T2✏ T1 : tipo del dominio,✏ T2 : tipo del range.

☛ La funzione int* f(char a, char b) viene rappresentata dalla seguente type expression:(char x char) pointer(int)

☛ Teoricamente una funzione può avere come dominio e codominio tipi qualsiasi:((int int) x (int int)) (int int)

128 Grafo dei tipi

☛ Un modo efficace di rappresentare le type expression è l’uso di grafi (alberi o DAG).

(char x char) pointer(int)

X pointer

char integer

Page 33: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 33

129 Costruzione di type-expression

☛ Il calcolo dei tipi per il C potrebbe essere fatto tramite la seguente definizione guidata dalla sintassi:

D T Vl ‘;’ Vl.type=T.type

Vl V V.type=Vl.type

Vl Vl1 ‘,’ V V.type=Vl.type

V ‘*’ V1 V1.type=pointer(V.type)

V Va Va.type=V.type

Va Va1 ‘[‘ num ‘]’ Va1.type=array(num.val,Va.type)Va id add_var(id.name,Va.type);

130 Costruzione di type-expression

char * argv [ 10 ] ;

T id num

Va

Va

VVVl

D

char

pointer(char)

array(10,pointer(char))

Non è ad attributi L

131 Costruzione di type-expression

D T Vl ‘;’ Vl.type=T.type

Vl V V.type=Vl.type

Vl Vl1 ‘,’ V V.type=Vl.type

V P id A P.base=V.typeA.base=P.typeadd_var(id.name,A.type)

P ε P.type=P.base

P P1 ‘*’ P.type=pointer(P1.type)P1.base=P.base

A ε A.type=A.base

A A1 ‘[‘ num ‘]’ A.type=array(num.val,A1.type)A1.base=A.base

132 Costruzione di type-expression

char * argv [ 10 ] ;

ε

AA

ε

PP

numid

V

Vl

D

T

charpointer(char)

array(10,pointer(char))

Page 34: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 34

133 Costruzione di type-expression

☛ Occorre costruire type expression anche per le espressioni:

E literal E.type = char

E num E.type = integer

E id E.type = lookup(id.name)

E ‘&’ E1 E.type = pointer(E1.type)

E ‘*’ E1 E.type = if E1.type=pointer(T)then T else type_error

E E1 [ E2 ] E.type = if E1.type=array(s,T)and E2.type=integerthen T else type_error

134 Equivalenza

☛ Il modo più naturale di definire l’equivalenza tra duetype-expression è l’equivalenza strutturale.

☛ Due espressioni sono uguali se:✏ sono lo stesso tipo primitivo, oppure✏ sono l’applicazione del costruttore di tipo a tipi equivalenti.

☛ Utilizzando la naturale rappresentazione delle type expression come alberi, è possibile utilizzare un algoritmo a discesa ricorsiva per verificare l’equivalenza.

135 Nomi dei tipi

☛ La possibilità di assegnare dei nomi ai tipi complica la verifica dell’equivalenza.

☛ Esempio:typedef cell* link; type link = ^cell;link p; var p : link;cell* q; q : ^cell;

☛ Le variabili p e q sono uguali? La risposta varia se viene utilizzata l’equivalenza di nomi o l’equivalenza strutturale.

☛ In C viene utilizzata l’equivalenza strutturale, mentre il Pascal adotta l’equivalenza dei nomi.

136 Riferimenti circolari

☛ Molte strutture dati basilari, come le liste concatenate o gli alberi, sono definite ricorsivamente.

☛ Tale configurazione introduce, potenzialmente, dei cicli nel grafo di tipi.

struct cell

X X

info integer next pointer

X

struct cell

1

2

Page 35: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 35

137 Riferimenti circolari

☛ Un semplice algoritmo per la verifica dell’equivalenza strutturale di tipi può non funzionare in presenza di riferimenti circolari.

☛ In C è sempre necessario dichiarare un nome prima dell’uso, con l’unica eccezione di puntatori a struct

non ancora dichiarate.☛ Se il nome del record è parte del costruttore di

struttura; il test per l’equivalenza si ferma quando raggiunge due strutture:

✏ se hanno lo stesso nome, sono equivalenti;✏ altrimenti sono diversi.

138 Esercizi

☛ Si scriva la definizione guidata dalla sintassi per la costruzione della type-expression relativa ad unastruct C.

☛ Date le seguenti dichiarazioni C:typedef struct {int a,b;

} CELL, *PCELL;CELL foo[100];PCELL bar(int x, CELL y);

si scrivano le type-expression corrispondenti.

139 Type checker

☛ Un type-checker è formato da un insieme di moduliinteroperanti:

✏ scanner: riconosce il lessico,✏ parser: verifica la sintassi ed aggiunge la semantica,✏ gestore di type-expresssion,✏ gestore di symbol table.

Parser

Scanner

Symbol table Typeexpression

140 Symbol table

☛ Le tabelle dei simboli associano valori a nomi per rendere accessibili informazioni semantiche, legate ad un identificatore, al di fuori del contesto in cui esso è stato dichiarato.

☛ Le informazioni associate a ciascun nome vengono utilizzate per verificare il corretto uso semantico degli identificatori di un programma.

☛ Tali informazioni possono essere aggiornate dinamicamente via via che nuove caratteristiche del nome sono dichiarate nel programma o dedotte dal compilatore.

Page 36: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 36

141 Symbol table

☛ Le informazioni che vengono memorizzate all’interno di una tabella dei simboli vengono dette entry.

☛ Ogni operazione di inserimento definisce una chiave(solitamente una stringa) tramite la quale poter, successivamente, reperire le informazioni.

☛ In generale, le informazioni memorizzate in una symbol table non sono omogenee, perciò conviene memorizzare dei puntatori invece delle informazioni stesse.

☛ Un traduttore utilizza diverse tabelle dei simboli per memorizzare informazioni diverse o appartenenti a contesti diversi.

142 Symbol table: interfaccia

☛ Una porzione di codice che implementa una tabella dei simboli dovrebbe consentire le seguenti operazioni astratte:

✏ Create() costruttore✏ Destroy() distruttore✏ Enter(K,I) aggiunge alla tabella le informazioni I,

associandole alla chiave K.✏ Lookup(K) cerca nella tabella le informazioni associate alla

chiave K.

☛ Nell’ottica del riutilizzo, è conveniente associare ad ogni tabella un handle, che identifica l’istanza della tabella su cui si desidera operare.

143 Symbol table: implementazione

☛ Da un punto di vista implementativo una tabella può essere realizzata con una delle seguenti tecniche

✏ Liste disordinate✏ Liste ordinate✏ Alberi binari✏ Tabelle hash✏ BTree …

☛ La scelta dipende dal numero di simboli da memorizzare, dalle prestazioni che si intendono ottenere, dalla complessità del codice che si intende produrre.

144 Symbol table: implementazione

☛ L’interfaccia di un modulo per la gestione di symboltable basate su hash-table potrebbe essere:

ht_handle ht_create(int size);

void ht_destroy(ht_handle);

int ht_insert(ht_handle, char* key, void* info);

void* ht_lookup(ht_handle, char* key);

void* ht_delete(ht_handle, char* key);

Page 37: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 37

145 Type expression

☛ La rappresentazione naturale delle type expressiontramite grafi di tipi può essere trasformata in una rappresentazione interna.

☛ La gestione delle type expression richiede✏ la definizione della struttura dati dei nodi del grafo,✏ la definizione delle primitive per operare sui nodi.

☛ I nodi devono essere in grado di rappresentare i diversi costruttori di tipo ed i tipi di base.

☛ Le primitive servono per nascondere la rappresentazione interna dei nodi e consentire all’utilizzatore di scrivere del codice il più semplice possibile.

146 Type expression: implementazione

☛ Ogni nodo di un grafo dei tipi contiene: ✏ un tag, che rappresenta il tipo di nodo;✏ un campo fisso, che rappresenta il primo operando;✏ una union di vari campi

typedef struct te_node_tag {int tag;struct te_node_tag* left;union {struct te_node_tag* right;int size;int code;char* name;

} r;} te_node;

147 Type expression: implementazione

☛ Il modulo di gestione delle TE deve offrire le seguenti primitive:

te_node* te_make_base(int code);te_node* te_make_name(char* name);

te_node* te_make_product(te_node* l, te_node* r);

te_node* te_make_struct(te_node* flds, char* n);

te_node* te_make_fwdstruct(char* name);

te_node* te_make_function(te_node* d, te_node* r);

te_node* te_make_pointer(te_node* base);te_node* te_make_array(int size, te_node* base);

void te_cons_struct(te_node* str, te_node* flds);

148 Type checker: semantica

☛ Oltre alle funzioni offerte dal modulo di gestione delletype expression, è opportuno fornire alcune primitive per l’accesso alle tabelle dei simboli.

te_node* type_lookup(char* );

int add_type(char* name,te_node* type);

int add_var(char* name,te_node* type);

☛ Oltre a semplificare la scrittura delle azioni semantiche, permettono di nascondere i dettagliimplementativi delle tabelle.

Page 38: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 38

149 Type checker: semantica

☛ L’organizzazione delle tabelle dei simboli può seguire due strade.

☛ Due tabelle separate:✏ per i tipi, definiti con typedef o struct tag,✏ per le variabili.

☛ Un’unica tabella in cui vengono memorizzati:✏ il “genere” del simbolo,✏ le informazioni.

typedef struct {int symbol_kind;char* name;te_node* type;

} table_entry;

150 Type checker: semanticaDecl : T Vlist { }

;T : TYPE { $$=$1; }

;Vlist : V

| Vlist ',' { $$=$<type>0 } V;

V : Ptr ID {$$=$1;} Ary { add_var($2,$4); };

Ptr : /* empty */ { $$=$<type>0; }| Ptr '*' { $$=te_make_pointer($1); };

Ary : /* empty */ { $$=$<type>0; }| Ary '[' NUM ']' { $$=te_make_array($3,$1); };

151 Type checker: semantica

%union {te_node* type;char* name;int ival;

}

%token <ival> NUM%token <name> ID%token <type> TYPE

%type <type> Vlist%type <type> V%type <type> T%type <type> Ptr%type <type> Ary

ID [A-Za-z_][0-9A-Za-z_]*

%%

{ID} {te_node* pte;pte = type_lookup(yytext);if(pte!=NULL){yylval.type=pte;return TYPE;

}yylval.name=strdup(yytext);return ID;

}

YACC LEX

152 Type checker: grammatica

S : /* empty */| S Decl ';';

Decl : T Vlist| TYPEDEF T Vlist;

T : TYPE| STRUCT ID '{' SFL '}'| STRUCT '{' SFL '}'| STRUCT ID;

SFL : Field| SFL Field;

Field : T Vlist;

Vlist : V| Vlist ',' V;

V : Ptr ID Ary;

Ptr : /* empty */| Ptr '*';

Ary : /* empty */| Ary '[' NUM ']';

Page 39: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 39

153 Type checker: semantica

☛ Il non terminale V può corrispondere a:✏ una variabile,✏ un campo di una struttura,✏ un nome di un nuovo tipo definito dall’utente.

☛ Volendo utilizzare la grammatica precedente, occorre inserire, nella semantica di V, tre alternative per trattare i diversi casi.

☛ Per distinguere i casi occorre impostare una variabile di stato in corrispondenza:

✏ del riconoscimento della keyword typedef, oppure✏ della keyword struct seguita da graffe.

154 Ambienti di esecuzione

☛ Procedure e parametri✏ Parametri formali ed attuali✏ Record di attivazione

☛ Tabelle a blocchi☛ Allocazione della memoria☛ Passaggio di parametri

✏ Stack frame✏ Tail recursion

155 Procedure ed attivazioni

☛ Un programma può essere visto come un insieme di procedure o funzioni; essenzialmente, una procedura assegna un identificatore (nome) ad un insieme distatement, (corpo).

☛ È bene distinguere il codice sorgente di una procedura dalla sua attivazione all’interno di un processo.

☛ Si ha una chiamata ad una procedura quando il suo nome compare in uno statement eseguibile.

☛ Quando viene eseguita la chiamata, la procedura viene attivata cioè vengono eseguiti gli statement del suo corpo.

156 Parametri

☛ Nella definizione di una procedura possono comparire dei parametri formali.

☛ All’atto della chiamata, vengono specificati degli argomenti o parametri attuali, corrispondenti ai parametri formali.

☛ Ad ogni attivazione di una procedura vengono forniti dei parametri formali.

☛ Una procedura è ricorsiva se una sua attivazione può iniziare prima che ne finisca una precedente.

☛ È possibile la ricorsione solo se ad ogni attivazione i parametri attuali vengono memorizzati in locazioni diverse.

Page 40: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 40

157 Stack di controllo

☛ Ad ogni attivazione di una procedura occorre associare un record di attivazione che contiene le informazioni utilizzate dal codice nell’attivazione corrente.

☛ Risulta naturale memorizzare i record di attivazione in uno stack di controllo.

☛ È possibile associare l’ambito di visibilità (scope) legato ad una procedura al suo record di attivazione. Come conseguenza, ogni attivazione ha le proprie variabili locali.

☛ Il concetto di scope ha un’applicazione più ampia del semplice contesto di una procedura.

158 Tabelle strutturate a blocchi

☛ Nei linguaggi strutturati è possibile introdurre più identificatori con lo stesso nome, in contesti separati e/o nidificati.

☛ Gli identificatori sono racchiusi in unità (moduli, procedure, blocchi, ecc.) che definiscono un contesto (scope).

☛ Data un’istruzione, l’unità più interna che la racchiude viene detta contesto corrente.

☛ Tutte le unità che racchiudono lo scope corrente sono dette contesti aperti.

☛ Le restanti unità del programma formano i contesti chiusi.

159 Regole di visibilità

☛ Per ciascun punto di un programma, solo i nomi dichiarati nel contesto corrente o nei contesti aperti sono accessibili.

☛ Se un nome è dichiarato in più di un contesto, la dichiarazione più interna (più vicina al contesto corrente) viene utilizzata per interpretare un’occorrenza del nome.

☛ Possono essere fatte nuove dichiarazioni solo nel contesto corrente.

160 Tabelle a blocchi: realizzazione

☛ Le regole di visibilità suggeriscono la realizzazione di un’insieme di symbol table organizzate a stack.

☛ La tabella in cima allo stack corrisponde al contesto corrente; quella successiva al contesto immediatamente più esterno, e cosi via.

☛ Dato un simbolo, esso viene cercato dapprima in cima allo stack e, se non trovato, via via nelle tabelle successive.

☛ Ogni volta che si apre un nuovo contesto, una tabella vuota viene inserita in cima allo stack.

☛ Alla chiusura del contesto, tale tabella viene rimossa dallo stack.

Page 41: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 41

161 Tabelle a blocchi: interfaccia

☛ Si può realizzare un modulo che incapsula il comportamento a stack delle tabelle:

✏ Push(Table) inserisce una tabella in cima allo stack, essadiventa la tabella del contesto corrente,

✏ Pop() rimuove la tabella del contesto corrente,✏ CurrentScope() restituisce la tabella corrente,✏ Lookup(Id) cerca nello stack l’elemento Id ritorna un

riferimento all’oggetto.

☛ All’atto della creazione di una nuova tabella, è necessario memorizzarne un riferimento, per poter accedere in seguito alle informazioni in essa contenute.

162 Estensioni per i record

☛ Insieme alla definizione di un record (struttura, unione, classe) è necessario memorizzare una tabella dei simboli riportante le informazioni relative ai singoli campi.

☛ Quando il compilatore incontra un riferimento ad una variabile di tipo record, ottiene dalla tabella dei simboli dei tipi un puntatore alla tabella dei campi del record.

☛ Mediante tale puntatore determina le caratteristiche del campo selezionato.

163 Dichiarazioni implicite

☛ In alcuni linguaggi non è necessaria una dichiarazione esplicita di un simbolo prima del suo uso.

☛ In questi casi, quando il simbolo viene incontrato il compilatore deve dedurre dal contesto il maggior numero di informazioni possibili ed inserirle nella tabella dei simboli.

☛ Se il linguaggio ammette anche contesti annidati, bisogna determinare se il simbolo appartiene ad un contesto esistente o costituisce una nuova definizione nel contesto corrente.

164 Riferimenti in avanti

☛ Un caso particolare di dichiarazione implicita è dato dall’uso dei nomi delle etichette di salto (label) in alcuni linguaggi.

☛ Quando il compilatore incontra un riferimento all’etichetta, essa può non essere ancora stata definita.

☛ Non è possibile, in tal caso, creare dei compilatori a passo singolo: i riferimenti a entità sconosciute sono registrati temporaneamente e, al termine dell’analisi, sostituiti con i relativi indirizzi. Solo dopo può essere generato il codice.

Page 42: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 42

165 Integrazione con il parser

☛ Nello scrivere la grammatica diventa necessario introdurre azioni semantiche che interagiscano con la tabella dei simboli per la corretta interpretazione degli identificatori.

☛ Tali azioni possono essere intermedie rispetto a qualche regola (in particolare con i record). In tal caso la grammatica può diventare non LALR(1) e, come tale, non essere utilizzabile con generatori automatici come YACC.

☛ La seguente dichiarazione può essere una variabile oppure una funzione:

pippo (x);

166 Dipendenza dal contesto

☛ La semantica associata ad un costrutto sintattico può variare in funzione del contesto in cui esso si trova.

☛ Per utilizzare generatori di parser context-freeoccorre introdurre, nel parser, uno stato (semantico) che modifichi l’interpretazione dei costrutti sintattici.

☛ Questo tipo di soluzione può creare problemi se si utilizzano regole per il recupero degli errori.

☛ Occorre ripristinare lo stato precedente al riconoscimento del costrutto sintattico in cui si intercetta l’errore.

167 Allocazione della memoria

☛ All’interno di un programma possono essere utilizzate, anche contemporaneamente, diverse strategie di allocazione della memoria.

☛ Il principale aspetto distintivo è costituito dal momento in cui viene assegnata una locazione di memoria ad una variabile.

☛ In C si definiscono tre grandi classi di allocazione:✏ statica: a tempo di compilazione✏ automatica: all’attivazione di una procedura.✏ dinamica: in qualsiasi momento durante l’esecuzione.

168 Variabili statiche

.globl s1

.section .rodata

.LC0:.string "pippo"

.datas1:

.long .LC0.globl s2s2:

.string "pluto".section .rodata.LC1:

.string "minni"

char* s1="pippo";char s2[]="pluto";char* s3(){return "minni";

}

char* s1="pippo";char s2[]="pluto";char* s3(){return "minni";

}

*s1=‘a’;

*s3()=‘a’;

*s2=‘a’;

Page 43: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 43

169 Stack frame

☛ Il record di attivazione viene memorizzato nello stackdi esecuzione.

☛ Ad esso vengono affiancate le informazioni di stato del processore.

Variabililocali etemporanee

control linkCPU status

frame link

parametriattuali

return value

Responsabilitàdel chiamante

Responsabilitàdel chiamato

170 Esempio

☛ Codice sorgente e stack frame:

int set

(int i, int* p)

{

int j;

j=*p;

*p = i;

return j;

}

return address

0 1 2 3

BPold

i

*p

return value

j

BPBP

BP-4

BP+4

BP+8

BP+12

BP+16

SP

BP

situazionea regime

situazioneall’ingresso

171 Esempio

☛ Il chiamante pone sullo stack i parametri.☛ Il valore di ritorno viene passato attraverso un

registro della CPU per motivi di efficienza.☛ Per convenzione i parametri vengono depositati in

ordine inverso a quello con cui compaiono nella definizione della procedura.

leal -8(%ebp),%eaxpushl %eax

movl -4(%ebp),%eaxpushl %eax

call _set

secondo parametro: *p

primo parametro: i

esecuzione della chiamata

172 Esempio

pushl %ebpmovl %esp,%ebpsubl $4,%esp

movl 12(%ebp),%eaxmovl (%eax),%edxmovl %edx,-4(%ebp)

movl 12(%ebp),%eaxmovl 8(%ebp),%edxmovl %edx,(%eax)

movl -4(%ebp),%eax

leaveret

finisce di costruirelo stack frame

distrugge il framee restituisce il controllo

j=*p;

*p=i;

return j;

Page 44: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 44

173 Tail recursion

☛ Si dice che una procedura è tail-recursive se il suo ultimo statement è la una chiamata ricorsiva alla procedura stessa.

☛ Le funzioni tail-recursive possono essere oggetto di ottimizzazione.

int exp(int n)

{

if(n==1) return 1;

return n*exp(n-1);

}

int rexp(int n,int r)

{

if(n==1) return r;

return rexp(n-1,r*n);

}

1

174 Tail recursionpushl %ebpmovl %esp,%ebppushl %ebxmovl 8(%ebp),%ebxcmpl $1,%ebxje L2leal -1(%ebx),%eaxpushl %eaxcall _expimull %ebx,%eaxjmp L3

L2:movl $1,%eax

L3:movl -4(%ebp),%ebxleaveret

pushl %ebpmovl %esp,%ebpmovl 8(%ebp),%edxmovl 12(%ebp),%eax

L6:cmpl $1,%edxje L5imull %edx,%eaxdecl %edxjmp L6

L5:leaveret

14*N+10 5*N+8

if(n==1)

return

1

return

n*exp(n-1)

invece di:pushl %eaxpushl %edxcall _rexp

return

rexp(n-1,r*n)

if(n==1)

return

r

175 Manipolazioni dello stack

☛ L’uso dello stack sia per il passaggio di parametri che per memorizzare gli indirizzi di ritorno delle call, consente manipolazioni particolari.

☛ È possibile modificare l’indirizzo di ritorno di una funzione saltando ad altro codice.

☛ È possibile passare dei parametri senza utilizzare l’istruzione di push:

✏ si utilizza un valore di ritorno memorizzato da una call per indirizzare un’area di memoria.

176 Manipolazione dello stack

void nascosta(){printf("Nascosta\n");

}void visibile(){

int new_bp;

new_bp = *(&new_bp + 1);*(&new_bp+1)

= (int)(&nascosta);__asm__(" addl $-4,%ebp");

}

Ret Addr

BPoldBPnew_bp

Ret Addr

&nascosta

BP BPold

Page 45: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 45

177 Manipolazioni dello stack

void main(){ printf("Eccoci...\n");__asm__(" jmp my_end

my_bg:call _printfadd $0x4,%esp

jmp my_outmy_end:

call my_bg.string \"Ciao\\n\"

my_out: ");printf("!!!!!!!!\n");}

178 Manipolazione dello stack

BPSP

BPSPret

0x40107f:call 0x401075 <my_bg>0x401084:”Ciao\n”0x40108a:add $0xfffffff4,%esp

prima della call

dopo la call

179 Codici intermedi

☛ Tipi di codici intermedi✏ AST, notazione post-fissa, three-address code, RTL

☛ Rappresentazione a quadruple✏ Tipologie di quadruple✏ Implementazione✏ Espressioni matematiche e logiche✏ Puntatori, strutture ed array

☛ Trasformazione delle strutture di controllo✏ if / while / for✏ flussi di controllo per espressioni logiche

☛ Accesso ad elementi di tipi composti✏ array✏ strutture

180 Rappresentazione intermedia

☛ Più semplice da generare rispetto al codice finale.☛ Più facilmente ottimizzabile.

Scanner Parser Rappres.Intermedia

SymbolTable

GeneratoreCodice

Ottimizzatore

Assembler

Page 46: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 46

181 Tipi di rappresentazioni

☛ Esistono varie rappresentazioni intermedie, ne esaminiamo quattro:

✏ syntax tree✏ notazione post-fissa✏ three-address code✏ RTL

☛ Per confrontarle consideriamo le traduzioni della seguente espressione:

a = b * (- c) + b * (- c);

182 Notazione post-fissa e syntax tree

☛ La notazione post-fissa rappresenta così:a b c uminus * b c uminus * + assign

☛ Un sintax tree può essere completo o in forma di dag se vengono identificate le espressioni comuni:

assign

+

**

a

c

uminusb

c

uminusb

c

uminusb

assign

+a

*

183 Three-address code

☛ È una sequenza di istruzioni del tipo x = y op zdove x , y e z sono:

✏ variabili,✏ costanti,✏ varibili temporanee generate dal compilatore.t1 = - c

t2 = b * t1t3 = - c

t4 = b * t3t5 = t2 + t4

a = t5

184 Tipologie di 3-address code

☛ Assegnamento:✏ con operatori binari: x = y op z

✏ con operatori unari: x = op y

✏ di copia: x = y

✏ indicizzato x = y[i]x[i] = y

☛ Salto:✏ non condizionale: goto L

✏ condizionale: if x relop y goto Lif x goto L

☛ Puntatori ed indirizzi:✏ puntatore: x = *y

✏ indirizzo: x = &y

Page 47: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 47

185 RTL

☛ È una rappresentazione più vicina alla macchina.☛ Utilizza degli pseudo registri e fa riferimento ad una

disposizione in memoria delle variabili (GCC).

set reg_22 mem(reg_18 - 8)

set reg_23 mem(reg_18 - 8)

set reg_21 reg_22 + reg_23

set reg_24 - mem(reg_18 - 12)

set reg_25 reg_21 * reg_24

set mem(reg_18 - 4) reg_25

b

c

a a = ( b + b )*( a = ( b + b )*( --c )c )

186 Implementazione di 3-address code

☛ Quadruple: rappresentano gli operandi, il tipo di operazione ed il risultato in una struttura a quattro campi:

☛ Triple: non rappresentano esplicitamente il risultato ma identificano esse stesse un risultato:

ADD b t1 t2UMINUS c t1

ADD b (1)UMINUS c

1

2

1

2

187 Tipi di quadruple

☛ Aritmetiche:✏ su interi:

ADDI opnd1, opnd2, resSUBI opnd1, opnd2, resMULI opnd1, opnd2, resDIVI opnd1, opnd2, resNEGI opnd, - , resITOF opnd, - , res

✏ su floating point: ADDF, SUBF, MULF, DIVF, NEGF, FTOI

☛ Logiche:✏ booleane: AND, OR, NOT, XOR✏ relazionali: EQ, NE, GT, LT, GE, LE

188 Tipi di quadruple

☛ Flusso di controllo:✏ label: LABEL n, -, -✏ salti: JUMP -, -, n

JUMP_IF_TRUE opnd, - , nJUMP_IF_FALSE opnd, - , n

☛ Assegnamento:✏ ASSIGN opnd, size, res✏ ASSIGNP opnd, size, ptr(res)✏ ASSIGN[] opnd, offset, res

☛ Su variabili:✏ ADDRESS opnd, - , res✏ DEREF opnd, size, res

Page 48: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 48

189 Struttura dati

☛ Le quadruple possono essere realizzate tramite listelinkate, descritte da una struct C:

typedef union {

t_label label;

t_var var;

t_offset add;

double fval;

long ival;

} t_op_data;

typedef struct q_tag {t_code code;t_op op1,op2,op3;struct q_tag *next;

} quad;

typedef struct op_tag {t_code tag;t_op_data data;

} t_opnd, * t_op;

190 Primitive

☛ Si possono definire un insieme di primitive per operare sulle quadruple:quad *mkquad(t_code,t_op,t_op,t_op);quad *append(quad*,quad*);t_op mklabel(void);t_op mk_op(int);t_op mk_var(st_entry*);t_op new_add(t_offset);t_op new_ival(long);t_op new_fval(double);

☛ Occorre definire delle costanti che corrispondano ai vari tipi di quadruple.

191 Programma di esempio

int i,j,k,l,m;{

i = 3 + m*2 - l;

if(i==1) m=1;else m=2;

while(i<5){i = i+1;

}for(i=0; i<10; i=i+1){ m = k+i; }

if( i==j && k==l && m==1 || j>3) { i=1; }}

192 Attributi

☛ Code: contiene le quadruple associate al simbolo non-terminale.

☛ Place: un riferimento alla variabile che contiene il risultato del non-terminale; è utilizzato se si tratta di espressioni.

☛ True, False: nomi delle label a cui salta un’espressione logica.

☛ Nella valutazioni di espressioni vengono generate nuove variabili; esse vengono inserite normalmente nella symbol table del contesto corrente.

Page 49: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 49

193 Espressioni aritmetiche

i + 1i + 1

ID NUM‘+’

Expr Expr

VAR ID IVAL 1

Expr

place place

code code

ADD VAR %1

code place

t_opnd

t_opnd

t_opnd

quad

194 Espressioni aritmetiche

Expr : Expr '+' Expr {

$$.type=compose($1.type,$3.type);

$$.place=new_temp($$.type);

$$.code=append(

append($1.code,$3.code) ,

mkquad(qADDI,$1.place,$3.place,$$.place));

}

| Expr '-' Expr { … }

| primary { $$=$1; }

;

$1.code

ADD

$3.code

195 Espressioni aritmetiche

primary:

'(' Expr ')' { $$=$2; }

| NUM { $$.type=st_lookup("int")->type;

$$.code=NULL;

$$.place=new_ival($1);}

| ID { st_entry* ste;

ste=st_lookup($1);

$$.code=NULL;

$$.type=ste->type;

$$.place=mk_var(ste); }

196 Esempio di espressione aritmetica

☛ Ogni nodo genera una quadrupla che usa i risultati dei suoi sotto-nodi.

1 MULI m 2 %0

2 ADDI 3 %0 %1

3 SUBI %1 l %2

4 ASSIGNV %2 i

i = 3 + m * 2 - l ;i = 3 + m * 2 - l ;

1

2

3

4

Page 50: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 50

197 Costrutti di controllo

☛ I blocchi di istruzioni che rappresentano costrutti di controllo ed espressioni booleane richiedono due ulteriori attributi:

☛ True: etichetta a cui il costrutto salta quando la condizione è vera.

☛ False: etichetta a cui il costrutto salta quando la condizione è falsa.

☛ Le etichette vengono generate durante la traduzione delle espressioni booleane o di confronto e vengono utilizzate per guidare i costrutti di controllo.

198 If-then

if_stmt : IF '(' bool_exp ')' Stmt {

$$=append(

append($3.code,

mkquad(qLABEL,NULL,NULL,$3.true)

),

append($5,

mkquad(qLABEL,NULL,NULL,$3.false)

)

);

}

$3.code

$5

: $3.false

: $3.true

199 If-then-else

| IF '(' bool_exp ')' Stmt ELSE Stmt {t_op next;

next=new_label();

$$=append(append($3.code,

mkquad(qLABEL,NULL,NULL,$3.true)),

append(append($5,append(

mkquad(qJUMP,NULL,NULL,next),mkquad(qLABEL,NULL,NULL,$3.false))),

append($7,

mkquad(qLABEL,NULL,NULL,next)))

);}

;

$3.code

jump next

$5 true

$7 false

:$3.false

:$3.true

:next

200 Esempio di if-then-else

☛ I due rami dell’if vengono racchiusi tra salti e label.

JEQ i 1 L0JUMP L1LABEL L0ASSIGNV 1 mJUMP L2LABEL L1ASSIGNV 2 mLABEL L2

if(i==1)m = 1;

elsem = 2;

if(i==1)m = 1;

elsem = 2;

Page 51: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 51

201 Espressioni logiche - confronto

rel_exp : Expr REL_OP Expr {if(log_lab_flag){$$.true=$<bexp>0.true;$$.false=$<bexp>0.false;

}else{$$.true=new_label();$$.false=new_label();

}$$.code=append(append($1.code,$3.code),

append(mkquad($2,$1.place,$3.place,$$.true),mkquad(qJUMP,NULL,NULL,$$.false)));

}

$1.code

jump $$.false

$3.code

J($2) $$.true

F T

202 Espressioni logiche - op. booleani

bool_exp: bool_exp AND { $<bexp>$.true=new_label();$<bexp>$.false=$1.false;log_lab_flag=1; }

bool_exp {$$.code=append($1.code, append(

mkquad(qLABEL,NULL,NULL,$1.true),$4.code));

$$.true=$4.true;$$.false=$1.false;log_lab_flag=0; }

| NOT {$<bexp>$=$<bexp>0;} bool_exp {

$$.code=$3.code;$$.true=$3.false;$$.false=$3.true; }

$1.code

$1.true

$4.code

F T

$4.code

TF

203 Espressioni logiche - op. booleani

| bool_exp OR { $<bexp>$.false=new_label();$<bexp>$.true=$1.true;log_lab_flag=1; }

bool_exp {$$.code=append($1.code,append(

mkquad(qLABEL,NULL,NULL,$1.false),$4.code));

$$.true=$1.true;$$.false=$4.false;log_lab_flag=0;

}| rel_exp { $$=$1; };

$1.code

$1.false

$4.code

F T

204 Esempio di espressioni logiche

JEQ i j L9JUMP L10LABEL L9JEQ k l L11JUMP L10LABEL L11JEQ m 1 L12JUMP L10LABEL L10JGT j 3 L12JUMP L13LABEL L12ASSIGNV 1 iLABEL L13

if( i==j && k==l&& m==1 || j>3){ i=1; }

if( i==j && k==l&& m==1 || j>3){ i=1; }

Page 52: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 52

205 While

while_stmt: WHILE '(' bool_exp ')' Stmt {t_op lab;bgin=new_label();$$=append(append(

mkquad(qLABEL,NULL,NULL, bgin),append($3.code,mkquad(qLABEL,NULL,NULL,$3.true))),

append($5,append(mkquad(qJUMP,NULL,NULL,bgin),mkquad(qLABEL,NULL,NULL,$3.false))));}

$3.code

jump begin

$5

:$3.false

:$3.true

:begin

206 Esempio di while

☛ Una label iniziale consente di fare un ciclo che ripete il test ed esegue il corpo del while.

LABEL L5JLT i 5 L3JUMP L4LABEL L3ADDI i 1 %3ASSIGNV %3 iJUMP L5LABEL L4

while(i<5){i = i+1;

}

while(i<5){i = i+1;

}

207 For

for_stmt: FOR '('Expr';'bool_exp';'Expr')' Stmt{ t_op lab;

bgin=new_label();$$=append(append(append(

$3.code,mkquad(qLABEL,NULL,NULL,bgin)),append($5.code,mkquad(qLABEL,NULL,NULL,$5.true))),

append(append($9,$7.code),append(mkquad(qJUMP,NULL,NULL,bgin),mkquad(qLABEL,NULL,NULL,$5.false))));

};

$3.code

$7.code

$9

:$5.false

:$5.true

:begin

$5.code

jump begin

208 Esempio di for

☛ Come nel while, salvo che prima viene messo il codice di inizializzazione.

ASSIGNV 0 iLABEL L8JLT i 10 L6JUMP L7LABEL L6ADDI k i %5ASSIGNV %5 mADDI i 1 %4ASSIGNV %4 iJUMP L8LABEL L7

for(i=0; i<10; i=i+1){

m=k+i;}

for(i=0; i<10; i=i+1){

m=k+i;}

Page 53: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 53

209 Accesso con spiazzamento

☛ Per accedere agli elementi di un array o ai campi di una variabile strutturata è necessario associare un nuovo attributo:

☛ Offset: rappresenta lo spiazzamento dell’elemento a cui si vuole accedere, rispetto alla variabile di riferimento, memorizzata nell’attributo place.

☛ È necessario sommare gli offset man mano che vengono calcolati.

210 Accesso ad elementi di array

☛ La disposizione in memoria degli array può seguire due strategie:✏ row major: riga per riga, utilizzata da C e Pascal✏ column major: colonna per colonna, adottata in Fortran.

☛ Il tipo di un array deve essere costruito da destra verso sinistra; può servire la ricorsione destra:

Decl : T Vdecl | Decl ‘,’ Vdecl ;

Vdecl : ID {$$=$0;} Ary { ... } ;

Ary : /* empty */ { $$=$<type>0; }

| '[' NUM ']' { $$=$<type>0; }

Ary { $$=te_make_array($2,$5); }

;

211 Accesso ad elementi di array

☛ Costruzione dell’offset:

v [ j ][ k ];v [ j ][ k ];

Expr Expr

Expr

Expr

Expr array, 5size=40

array, 2size=8

intsize=4place=v

offset=null

place=voffset=j*8

place=voffset=j*8+4*k

int i,j,k;int v[5][2];…i = v[j][k];

int i,j,k;int v[5][2];…i = v[j][k];

212 Accesso ad elementi di array

| Expr '[' Expr ']' {t_op ofs;$$.type=$1.type->left;$$.code=append($1.code,$3.code);ofs=new_ival($1.type->left->size);$$.offset=new_temp(st_lookup("int")->type);$$.code=append($$.code,

mkquad(qMULI,$3.place,ofs,$$.offset));if($1.offset!=NULL){ofs=new_temp(st_lookup("int")->type);append($$.code,mkquad(qADDI,$1.offset,$$.offset,ofs));$$.offset=ofs;

}}

Page 54: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 54

213 Accesso ad elementi di array

☛ Istruzioni di costruzioni dell’offset seguite da un assegnamento con dereferenziazione ed offset.

MULI j 20 %0MULI k 4 %1

ADDI %0 %1 %2

DRF_OFS v %2 i

☛ La quadrupla:DRF_OFS base, offset, dest

equivale a:dest = base [ offset ]

int i,j,k;int v[5][2];…i = v[j][k];

int i,j,k;int v[5][2];…i = v[j][k];

214 Accesso ad elementi di strutture

☛ Rappresentazione di un tipo struttura:struct tag {

int i;

struct x {

int a;

int b;

} cf;

} s;

i = s.cf.b;

structsize=12

intsize=4

i offset=0

cf offset=4

structsize=8

a offset=0

b offset=4

215 Accesso ad elementi di strutture

| Expr '.' ID { st_entry* stp; t_op ofs;stp=(st_entry*)ht_lookup($1.type->r.table,$3);$$.type=stp->type;$$.code=$1.code;if($1.offset==NULL){$$.offset=new_ival(stp->offset);

}else{t_op ofs;ofs=new_ival(stp->offset);$$.offset=new_temp(st_lookup("int")->type);$$.code=append($1.code,

mkquad(qADDI,$1.offset,ofs,$$.offset));}

}

216 Accesso ad elementi di strutture

☛ Costruzione dell’offset ed assegnamento condereferenziazione ed offset, come per gli array.

☛ Invece degli indici utilizza glioffset dei campi all’internodelle strutture.

ADDI 4 4 %0

DRF_OFS s %0 i

struct tag {

int i;

struct x {

int a;

int b;

} cf;

} s;

...

i = s.cf.b;

struct tag {

int i;

struct x {

int a;

int b;

} cf;

} s;

...

i = s.cf.b;

Page 55: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 55

217 Uso avanzato di Bison

☛ Gestione degli errori sintattici✏ errori negli statement✏ gestione del contesto✏ interazione con lo scanner

☛ Debugging dei parser✏ conflitti shift-reduce✏ conflitti reduce-reduce

218 Grammatica

file : funcs

;

funcs : /* empty */

| funcs func

;

func : ID '(' ')'compound

;

stmts : /* empty */

| stmts stmt

;

stmt : exp ';'| compound;

compound: '{' stmts '}';

exp : NUM| exp '+' exp| exp '-' exp| exp '*' exp| exp '/' exp| '-' exp %prec NEG;

219 Statement ed espressioni

yyerror(char* s){ }

void error(char* s){

printf("%s:%d: %s\n",filename,yylineno,s);

}

stmt : …| error ';' { error("syntax error in statement"); }

compound : …| '{' stmts error '}'{ error("missing ; before '}'");}

exp: …|'(' error ')'{ error("syntax error in expression"); }

220 Errori negli statementstmts -> stmts . stmt compound -> '{' stmts . '}'compound -> '{' stmts . error '}'

10

stmt -> error . ';'compound -> '{' stmts error . '}'

11

exp -> NUM . 12NUM

errorstmt -> exp . ';'exp -> exp . '+' exp...

18

exp

exp -> exp '+' . exp 25

‘+’

3 + ;

10 (stmts)25 (exp)

+

Page 56: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 56

221 Contesto

if(!context_printed){

if(context)

printf("%s: In function %s:\n",filename,context);

else

printf("%s: At top level:\n",filename);

context_printed = 1;

}

func : ID '(' ')' { new_context($1); }compound { out_context(); }

222 Interazione con lo scanner

void pushback(char ch){ unput(ch); }

exp: …| '(' error ';' {

error("missing ')' before ';'");pushback(';'); }

funcs : … | funcs error{ if(yychar<256){ char buf[255];

sprintf(buf,"syntax error before '%c'",yychar);error(buf);yyclearin;

}elseerror("syntax error"); }

nello scannerperchè unputè una macro!

nello scannerperchè unputè una macro!

223 Esempio

f(){1 + 1 ;1 + ( 6 + ) ;1 + (5 + ;3 + ;{ 3 + 2 ; }}?g(){{ 3 + 3 }{ 3 + (3-) }

3+1}

123456789

1011121314

prova.c: In function f:prova.c:3: syntax error in expressionprova.c:4: missing ')' before ';'prova.c:5: syntax error in statementprova.c: At top level:prova.c:8: syntax error before '?'prova.c: In function g:prova.c:10: missing ; before '}'prova.c:11: syntax error in expressionprova.c:11: missing ; before '}'prova.c:14: missing ; before '}'

prova.c: In function f:prova.c:3: syntax error in expressionprova.c:4: missing ')' before ';'prova.c:5: syntax error in statementprova.c: At top level:prova.c:8: syntax error before '?'prova.c: In function g:prova.c:10: missing ; before '}'prova.c:11: syntax error in expressionprova.c:11: missing ; before '}'prova.c:14: missing ; before '}'

224 Debugging del parser

☛ L’opzione -v di Bison genera un file, con estensione out, in cui è descritto il parser generato.

☛ La struttura del file è la seguente:✏ elenco delle anomalie e dei conflitti (eventualmente risolti)✏ regole della grammatica senza alternative✏ lista dei terminali con i loro usi✏ lista dei non terminali, con definizioni e usi✏ elenco degli stati del parser

Page 57: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 57

225 Liste

☛ rule 1 S -> '{' L '}' … ☛ rule 2 L -> /* empty */

S : ‘{‘ L ‘}’ …L : /* epsilon */| E| L E ;

E : ID ;

state 1S -> '{' . L '}' … (rule 1)ID shift, and go to state 2ID [reduce using rule 2 (L)]$default reduce using rule 2 (L)L go to state 3E go to state 4

226 Liste

Eεεεε

LL

L

L : /* empty */| E| L E;

L : /* empty */| Lst;

Lst : E| Lst E;

Riscrittura

è ambiguaè ambigua

227 Azioni intermedieS : ‘{‘ L ‘}’ ‘{‘ M ‘}’ …M : ID '(' ')' { /* */ } '{' '}'| ID '(' ')' '{' '?' '}'

☛ rule 6 @1 -> /* empty */

state 12M -> ID '(' ')' . @1 '{' '}' (rule 7)M -> ID '(' ')' . '{' '?' '}' (rule 8)'{' shift, and go to state 14'{' [reduce using rule 6 (@1)]$default reduce using rule 6 (@1)@1 go to state 15

228 Reduce-reduce e contesto☛ S : … ‘{‘ A ‘}’☛ A : B

| A ',' B☛ rule 20 C -> ID☛ rule 21 D -> ID

state 27C -> ID . (rule 20)D -> ID . (rule 21)'}' reduce using rule 20 (C)'}' [reduce using rule 21 (D)]',' reduce using rule 20 (C)',' [reduce using rule 21 (D)]$default reduce using rule 20 (C)

B : C| D;

C : ID;

D : ID| D ',' ID;

B : C| D;

C : ID;

D : ID| D ',' ID;

Page 58: Contenuti Tel. (011 564) 7081Poiché la trasformazione di espressioni regolari in automi a stati finiti deterministici e la implementazione di questi ultimi sono processi meccanici

Politecnico di Torino a.a 1999/2000 Laboratorio di Compilatori

Versione 1.2 © 2000 Marco Torchiano 58

229 Reduce-reduce ed epsilonstate 16

S -> … '{' N . '}' (rule 1)N -> N . O (rule 10)N -> N . Q (rule 11)'}' shift, and go to state 19ID reduce using rule 12 (O)ID [reduce using rule 14 (Q)]'}' [reduce using rule 12 (O)]'}' [reduce using rule 14 (Q)]'!' reduce using rule 12 (O)'!' [reduce using rule 14 (Q)]$default reduce using rule 12 (O)

S : … ‘{‘ N ‘}’ …N : /* empty */| N O| N Q;

O : /* empty */| O ID;

Q : /* empty */| Q '!’;

S : … ‘{‘ N ‘}’ …N : /* empty */| N O| N Q;

O : /* empty */| O ID;

Q : /* empty */| Q '!’;

9910101111

12121313

14141515