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