Yet Another Compiler

Transcript

Yet Another Compiler
Yet Another Compiler-Compiler
Generazione automatica di
analizzatori sintattici
2
Marco Maggini
YACC –
Tecnologie per l'elaborazione del
linguaggio
Yet Another Compiler-Compiler
•  YACC (Bison) è un generatore di analizzatori sintattici a partire dalla
descrizione di una grammatica LALR(1)
▫  Genera un sorgente C
•  L’ingresso è un file che descrive la grammatica con un formalismo
analogo alla BNF (Backus-Naur Form) per la specifica dei linguaggi
▫  Simboli non terminali – identificatori per convenzione in minuscolo
  expr, stmt
▫  Simboli terminali – identificatori per convenzione in maiuscolo o singoli
caratteri
  INTEGER, FLOAT, IF, WHILE, ‘;’, ‘.’
▫  Regole grammaticali (produzioni)
  expr:
expr ‘+’ expr | expr ‘*’ expr ;
E → E+E|E*E
3
Marco Maggini
Tecnologie per l'elaborazione del
linguaggio
YACC – valori e azioni
•  Ad ogni elemento è associato un tipo e un valore semantico
▫  Tipo: INTEGER, IDENTIFICATORE
▫  Valore: Il valore dell’intero, il riferimento dell’identificatore nella tavola
dei simboli
•  Anche le categorie sintattiche possono avere associato un valore
▫  Il risultato della valutazione di un’espressione
•  Le azioni corrispondono a codice C associato a ciascuna produzione
▫  Ogni volta che viene applicata una produzione si esegue l’azione
associata ad essa
▫  In genere l’azione permette di combinare i valori associati agli elementi
nel lato destro della produzione per ottenere il valore del simbolo non
terminale sul lato sinistro
  expr: expr ‘+’ expr { $$ = $1 + $3;};
1 2 3
4
Marco Maggini
YACC –
Tecnologie per l'elaborazione del
linguaggio
integrazione con l’analizzatore lessicale
•  Per estrarre i simboli terminali dal file da analizzare occorre usare
un analizzatore lessicale
▫  l’analizzatore sintattico prodotto a YACC chiama l’analizzatore lessicale
quando deve leggere il prossimo simbolo terminale
file di
input
Analizzatore
lessicale
INTEGER
IDENTIFIER
….
prossimo
elemento
Analizzatore
sintattico
azioni
•  L’analizzatore sintattico è sotto forma di una funzione yyparse() e
occorre corredarla dell’analizzatore lessicale (es. yylex()), le funzioni
di gestione degli errori e del programma che la invoca
5
Marco Maggini
Tecnologie per l'elaborazione del
linguaggio
I file di grammatica
•  La grammatica è definita in un file di testo (per convenzione con
estensione .y)
%{
dichiarazioni C (include/define/variabili globali)
%}
dichiarazioni YACC (definizione di simboli terminali/non-terminali e loro proprietà)
%%
regole grammaticali e azioni associate
%%
codice C (copiato nel file generato dopo la funzione yyparse())
6
Marco Maggini
Tecnologie per l'elaborazione del
linguaggio
Simboli terminali
•  Rappresentano una classe di elementi equivalenti dal punto di vista
sintattico
▫  sono rappresentati con codici numerici associati al loro identificatore
  Corrispondono a dei #define
  L’analizzatore lessicale yylex() deve produrre tale codice quando trova un
elemento della classe (si può usare l’opzione –d per generare il file xx.tab.h che
contiene i define per le classi di simboli terminali)
while return WHILE; /* regola lex */
  Sono dichiarati nella sezione delle dichiarazioni YACC
%token WHILE
  Gli elementi letterali sono utilizzati come le costanti carattere del C (es. ‘+’) e
non devono essere dichiarati a meno che non si voglia specificare il tipo di
valore semantico associato, l’associatività o la precedenza (il codice associato è
il valore ASCII corrispondente)
7
Marco Maggini
Definizione delle regole -
Tecnologie per l'elaborazione del
linguaggio
1
•  Una regola grammaticale ha la forma
risultato: componenti….. ;
▫  <risultato> è il simbolo non terminale a cui si riduce l’espressione del
lato destro della produzione
▫  Il lato destro della produzione è costituito da una sequenza di
componenti che comprendono simboli terminali, non terminali e azioni
(codice C compreso fra {…})
simbolo
terminale
expr:
expr ‘+’ expr {$$=$1+$3; }
AZIONE
8
Marco Maggini
Definizione delle regole -
Tecnologie per l'elaborazione del
linguaggio
2
•  Si possono elencare regole alternative che portano alla riduzione nello
stesso simbolo non terminale
expr:
expr ‘+’ expr {$$=$1+$3} |
expr ‘*’ expr {$$=$1*$3} ;
•  Se il corpo della produzione è vuoto la regola viene soddisfatta anche dalla
stringa vuota
expr:
/* vuota */ |
expr1 ;
•  La regola è ricorsiva se il non-terminale a sinistra (<risultato>) compare
anche nel lato destro (è preferibile evitare la ricorsione a destra..)
exprseq: expr |
exprseq ‘,’ expr ;
9
Marco Maggini
Tecnologie per l'elaborazione del
linguaggio
Definizione della semantica - 1
•  La semantica dipende dai valori associati agli elementi e dalle azioni
eseguite quando si fanno le riduzioni (si applica una regola)
expr ‘+’ expr
expr
L’applicazione della regola
expr: expr ‘+’ expr;
deve associare al simbolo expr risultante
dalla riduzione il valore della somma dei
valori associati ai due simboli expr
nell’espressione sul lato destro
▫  Per default a tutti i simboli è associato un valore di tipo int
▫  Si può modificare con un altro tipo C mettendo nella parte di
dichiarazioni C
#define YYSTYPE <tipoC>
10
Marco Maggini
Tecnologie per l'elaborazione del
linguaggio
Definizione della semantica -
2
•  Se si vogliono usare tipi dati diversi per simboli diversi
▫  Si deve specificare la lista completa dei tipi nella parte di dichiarazioni
YACC
%union {
Tipi C double val;
char
*sptr; Nomi associati ai tipi in YACC
}
▫  Si associa uno dei tipi definiti ai simboli terminali/non-terminali con le
dichiarazioni %token e %type
%token <val> NUM
%type <sptr> string_ass
11
Marco Maggini
Tecnologie per l'elaborazione del
linguaggio
Definizione della semantica -
3
•  Un’azione è una parte di codice C che viene eseguita quando una regola
viene applicata (riduzione)
▫  Le azioni possono comparire anche nel mezzo della stringa che
rappresenta il lato destro della produzione e in tal caso sono eseguite
quando la regola è soddisfatta parzialmente (è però poco leggibile)
▫  Il codice C dell’azione può far riferimento ai valori semantici associati
agli elementi della regola
expr:
$$
 
 
 
 
expr ‘+’ expr
$1
{$$=$1+$3} ;
$3
Il valore dell’elemento n nella sequenza è associato a $n
$$ rappresenta il valore del lato sinistro
Se non si specifica un’azione risulta per default $$=$1
Il tipo associato a $n è quello specificato per l’elemento corrispondente (si può
eventualmente forzare con $<tipo>n
12
Marco Maggini
Tecnologie per l'elaborazione del
linguaggio
Dichiarazioni YACC
•  Tutti i simboli terminali che non sono singoli caratteri vanno
dichiarati
▫  Si utilizza la direttiva %token ID (genera un #define)
▫  E’ possibile specificare la precedenza e l’associatività (a sinistra/a destra)
degli operatori per semplificare la scrittura della grammatica
%left simboli
%left <tipo>simboli
%right simboli
%right <tipo>simboli
%nonassoc simboli
x op y op z : (x op y) op z
x op y op z : x op (y op z)
x op y op z : errore di sintassi
▫  La precedenza è determinata dall’ordine delle dichiarazioni
%left ‘+’ ‘-’
%left ‘*’ ‘/’
+
priorità
13
Marco Maggini
Tecnologie per l'elaborazione del
linguaggio
Interfaccia col C
•  La funzione di analisi yyparse() legge simboli terminali, esegue
azioni e ritorna il controllo quando
▫  Raggiunge la fine del file (valore 0)
▫  Incontra un errore di sintassi non recuperabile (valore 1)
▫  In un’azione si usa la macro YYACCEPT (valore 0) o YYABORT (valore
1)
•  I simboli terminali sono individuati dall’analizzatore lessicale - es.
yylex() - che fornisce il codice corrispondente (ASCII o #define di
YACC)
▫  Il valore semantico eventualmente associato al simbolo terminale deve
essere memorizzato nella variabile globale yylval
▫  Se in YACC si usa un solo tipo YYSTYPE allora yylval è di tale tipo
altrimenti è una union C
14
Marco Maggini
Interfaccia col C -
Tecnologie per l'elaborazione del
linguaggio
yylval
•  Se yylval è di un tipo unico nell’azione di lex si avrà
….
yylval = value;
return ID;
}
•  Se si usano invece più tipi
….
yylval.sptr = string;
return STRING;
%union {
double val;
char
*sptr;
}
}
▫  Se il valore è un puntatore, l’indirizzo deve essere relativo ad un’area di
memoria globale o nello heap (allocata con malloc)
15
Marco Maggini
Gestione degli errori -
1
Tecnologie per l'elaborazione del
linguaggio
16
Marco Maggini
Gestione degli errori -
Tecnologie per l'elaborazione del
linguaggio
2
•  Dopo aver eseguito yyerror() l’analizzatore tenta di recuperare
l’errore se è stata scritta una funzione di recupero da errori,
altrimenti esce da yyparse con valore 1
•  La variabile yynerrs memorizza il numero di errori trovati (è una
variabile globale per analizzatori non rientranti)
•  In genere si vuole evitare che l’analisi si blocchi al primo errore
incontrato
▫  Si può definire cosa fare in corrispondenza di un errore di sintassi
facendo in modo che la regola riconosca il simbolo terminale error
▫  Il simbolo speciale error è generato dall’analizzatore tutte le volte che si
genera un errore di sintassi
17
Marco Maggini
Gestione degli errori -
Tecnologie per l'elaborazione del
linguaggio
3
stmt: /* vuoto */ |
stmt ‘\n’ |
stmt exp ‘\n’ |
stmt error ‘\n’
•  Se si verifica un errore in exp
▫  ci sono probabilmente delle derivazioni incomplete nello stack dell’analizzatore e
altri simboli terminali nell’input prima di arrivare a ‘\n’
▫  il parser forza l’applicazione della regola eliminando parte del contesto sintattico
dallo stack e dall’input
  elimina stati e oggetti dallo stack fino a che la regola che comprende error è accettabile
(trova lo stmt precedente)
  sposta nello stack il simbolo error
  legge simboli dall’ingresso fino a che non trova un simbolo terminale di lookahead
accettabile (‘\n’ in questo caso)
18
Marco Maggini
Gestione degli errori –
Tecnologie per l'elaborazione del
linguaggio
the art of…
•  In genere è difficile decidere (e realizzare) la migliore strategia per la
gestione degli errori
▫  Ad esempio si può saltare il resto della linea di ingresso o il comando
corrente in caso di errore
▫ 
stmt: error ‘;’ /* in caso di errore si prosegue
fino a leggere ; */
▫  Si può cercare di bilanciare le parentesi per evitare che si generino errori
a catena correlat i al primo
item: ‘(‘ expr ‘)’
| ‘(‘ error ‘)’ /* rileva un errore in expr
senza generare errori
di bilanciamento di parentesi */
19
Marco Maggini
Gestione degli errori –
Tecnologie per l'elaborazione del
linguaggio
the art of… 2
•  Facendo le scelte sbagliate un errore di sintassi ne può innescare un
altro..
•  Per evitare una produzione incontrollata di messaggi di errore
l’analizzatore non emette messaggi nuovi per un errore di sintassi
che compare subito dopo l’ultimo rilevato (si devono leggere almeno
due nuovi simboli per generare un nuovo errore)
•  Si può riattivare l’emissione di messaggi di errore chiamando la
funzione yyerrok in una azione
20
Marco Maggini
Tecnologie per l'elaborazione del
linguaggio
Un esempio - calcolatrice
•  parser per gestire le operazioni di una calcolatrice multifunzione che
prevede
▫  Operatori artimetici (‘+’, ‘-’, ‘*’, ‘/’,’^’)
▫  Funzioni predefinite (sin, cos, exp, log,…) che si invocano come f(x)
▫  Variabili con nome e assegnazioni (v=1)
•  File sorgente per YACC/Bison
•  File sorgente per LEX
•  File di supporto in C (gestione tavola dei simboli)
•  L’analizzatore sintattico si genera con bison –d calc.y
▫  I files prodotti sono calc.yy,tab.c e calc.yy.tab.h
21
Marco Maggini
Tecnologie per l'elaborazione del
linguaggio
Parser generators
•  Sono disponibili numerosi generatori di parser lessicali e/o sintattici
▫  Generano codice per linguaggi target diversi
▫  Implementano strategie di parsing diverse
▫ 
▫ 
▫ 
▫ 
▫ 
▫ 
BYACC/J genera parser Java di tipo LALR(1)
Coco/R genera parser di tipo LL(k) in C, C++, C#, Java, Pascal…
CUP genera parser LALR(1) in Java
JavaCC genera parser LL(k) in Java
Lime genera parser LALR(1) in PHP
…..
▫  Si veda http://en.wikipedia.org/wiki/Comparison_of_parser_generators