Professional Documents
Culture Documents
Generatori di parser
Data una specifica sintassi (come uno grammatica context-free), il parser si occupa di leggere i token e di ragrupparli in strutture linguistiche. Il parser verifica la correttezza sintassi e pu produrre un messaggio di errore. Quando viene ricosciuta una struttura sintattica il parser di solito costruisce un albero di sintattico (AST) che una rappresentazione concisa della struttura del programma, che guida la trasformazione semantica. I Parser vengono in genere creati da una CFG usando un generatore di parser (come Yacc, Bison o Java CUP).
Yacc: Introduzione
un tool disponibile su Unix, ma non solo, e permette di generare dei parser. Quando si scrive un programma in Yacc si descrivono le produzioni della grammatica del linguaggio da riconoscere e le azioni da intraprendere per ogni produzione. Gestisce grammatiche LARL(1). Genera una funzione (parser) per riconoscere linput. Il parser usa lanalizzatore lessicale per prelevare dallinput i token e riorganizzarli in base alla produzioni della grammatica utilizzata. Quando una produzione viene riconosciuta viene eseguito il codice ad essa associata.
Ogni programma Yacc consta di tre sezioni: dichiarazioni, regole e programmi ed ha il seguente aspetto:
Dichiarazioni %% Regole %% Sezione routines ausiliarie La sezione delle regole lunica obbligatoria. I caratteri di spaziatura (blank, tab e newline) vengono ignorati. I commenti sono racchiusi, come in C, tra i simboli /* e */
Nella sezione definizione si definiscono alcune informazioni globali da dover usare per interpretare la grammatica. Tramite listruzione: %token1 token2 tokenn si definiscono quali sono i token inseriti nelle regole che sono il risultato dellanalisi lessicale. Tramite listruzione: % start assioma si definisce qual il non terminale della grammatica da considerare come assioma (per default il primo non terminale incontrato).
Sezione regole
La sezione regole composta da una o pi produzioni espresse nella forma: A : BODY ; {Azione} dove A rappresenta un simbolo non terminale e body rappresenta una sequenza di uno pi simboli sia terminali che non terminali. I simboli : e ;sono separatori.
Nel caso la grammatica presenti pi produzioni per lo stesso simbolo terminale, queste possono essere scritte senza ripetere il non terminale usando il simbolo |
Yacc: Azioni
Ad ogni regola pu essere associata unazione che verr eseguita ogni volta che la regola viene riconosciuta. Le azioni sono istruzioni C e sono raggruppate in un blocco. A : B C D {printf (ciao) Le azioni possono apparire ovunque nel body di una regola. Le azioni possono scambiare dei valori con il parser tramite delle pseudo-variabili introdotte dal simbolo ($$, $1, $2, ) A : B {$$ = 1} C {$1 = 2; $2 = 12} La pseudo-variabile $$ associata al lato sinistro della produzione mentre le pseudo-variabili $n sono associate al non terminale di posizione n nella parte destra della produzione.
Yacc si avvale si un analizzatore lessicale (yyLex per leggere linput e convertirlo in token (pi eventuali valori) da passare al parser. Un possibile analizzatore lessicale pu`o essere creato usando LeX. I token sono passati al parser sotto forma di interi. Quindi parser ed analizzatore lessicale devono accordarsi su quali valori rappresentano i token. Laccordo viene preso in modo automatico da Yacc definendo i vari token tramite istruzioni C #define Lanalizzatore lessicale pu`o associare ai token un valore assegnandolo alla variabile predefinita yylval
Yacc: Parser
Il parser generato da Yacc un automa a stati finiti di tipo push-down in grado di avere un token di lookahead. Lautoma ha solo 4 azioni: shift, reduce, accept ed error.
In base allo stato corrente (simbolo sul top dello stack) il parser decide se necessita di un token di lookahead (ottenibile usando yylex per decidere che azione intraprendere. Usando lo stato corrente ed il token di lookahead decide quale azione intraprende e la espleta.
Azione di shift: usa sempre un token di lookahead, e consiste nel confrontare tale token con il token corrente ed in caso di match spilare lo stato dallo stack, inserire il nuovo stato e saltare il token di lookahead. Azione di reduce: evitano il crescere incontrollato dello stack, e sono usate quando il parser esamina il lato destro di una produzione e lo sostituisce con il lato sinistro della stessa. Pu servire un token di lookahead. Azione di accept: linput appartiene al linguaggio descritto dalla grammatica. Azione di error: linput si `e rilevato non appartenente al linguaggio descritto dalla grammatica.
Le produzioni di una grammatica possono essere ambigue, come ad esempio: E:E+E infatti se in input si ha la stringa E + E + E possibile interpretarla sia come E + (E +E) che come (E +E) +E. Yacc in grado di accorgersi di tale ambiguit. Il parser pu applicare unazione di reduce alla parte di stringa E + E ottenendo E E e quindi riapplicare tale azione; oppure pu applicare unazione di shift E + E e quando ha letto tutta la stringa applicare le due azioni di reduce a partire dalla seconda coppia E + E. Questo tipo di situazione detta conflitto di tipo shift-reduce, in modo analogo possibile avere anche conflitti di tipo reduce-reduce.
Nel caso Yacc rilevi un conflitto, produce lo stesso un parser effettuando delle scelte su quale azione intraprendere per prima. Le regole adottate per disambiguare tali conflitti sono:
in un conflitto shift-reduce si da la precedenza allazione di shift; in un conflitto reduce-reduce si da la precedenza alla regola che viene incontrata per prima.
E sempre bene evitare i conflitti alla base riscrivendo la grammatica. Un altromodo per risolvere i conflitti, o per lo meno per pilotarne la risoluzione quello di definire lassociativit dei simboli ambigui, tramite le istruzioni %rigth e %left da inserire nella sezione dichiarazioni. Esempio %rigth = %left + - %left * /
Esempio
Java Cup
Accetta specifiche di un CFG e produce un parser LALR (1) parser (implementato in Java) con le routine relative alle azioni espressa in Java Simile a yacc, ma con alcuni miglioramenti (gestione dei nome) Di solito usato con JLex (o JFlex)
Syntactic Analyzer
Definisce la grammatica e le azioni in un file (e.g., calc.cup) java java_cup.Main < calc.cup Nota il prefisso del package Linput lo standard in Generera un parser.java e sym.java (nome default della clase, ma puo essere modificato) As esempio, UseParser.java
Cosa significa?
Package_spec e import_list consente la gestione del naming Java Code e init_code permette linserimento di codice nelloutput generato Scan code specifica come invocato lo scanner (lexer) Symbol list e precedence list specificano I nomi dei temrinali e dei nonterminali e I nami e le loro precedence Start e production specifica la grammatica e lassioma
la grammatica ambigua?
Come possiamo ottenere PLUS ...? Sono i terminali restituiti dallo scanner. Come per connettersi con lo scanner?
Se inseriamo la grammatica
Expression ::= Expression PLUS Expression;
10
Run JLex
java JLex.Main calc.lex
Nota
javac calc.lex.java
La
11
8. 9. 10.
import java_cup.runtime.*; class CalcScanner implements java_cup.runtime.Scanner { ... .... public Symbol next_token () { ... ... case 3: { return new Symbol(CalcSymbol.MINUS); } case 6: { return new Symbol(CalcSymbol.NUMBER, new Integer(yytext()));} ... ... } }
Run javaCup
Le
classi generate:
CalcParser; CalcSymbol;
Compilare il parser
javac
Usare il parser
java
CalcParserUser
12
public class Symbol { public int sym, left, right; public Object value; public Symbol(int id, int l, int r, Object o) { this(id); left = l; right = r; value = o; } ... ... public Symbol(int id, Object o) { this(id, -1, -1, o); } public String toString() { return "#"+sym; } }
Instance variables:
sym: il tipo del simbolothe symbol type; Lef (rigrh): la posizione a sinistra (desttra) nel file di input value: il valore lessicale the lexical value.
return new Symbol(CalcSymbol.NUMBER, new Integer(yytext()));}
public class CalcSymbol { public static final int public static final int public static final int public static final int public static final int public static final int public static final int public static final int public static final int }
Contiene la dichiazione dei token declaration, ona per ogni token (terminale); E generata dalla lista dei terminali nel file cup terminal PLUS, MINUS, TIMES, DIVIDE, LPAREN, RPAREN; terminal Integer NUMBER utilizzata dallo scanner per riferirsi ail tipo dei simboli (e.g., return new Symbol(CalcSymbol.PLUS);) Class name comes from symbols directive. java java_cup.Main -parser CalcParser -symbols CalcSymbol calc.cup
13
l testo di input per essere analizzato pu essere qualsiasi flusso di input (in questo esempio si tratta di un FileInputStream); Il primo passo quello di costruire un oggetto parser. Un parser pu essere costruito utilizzando uno scanner. Se non c' alcuna segnalazione di errore, l'espressione nel file di input corretto. }
14
Valutazione dellespressione
La specifica precedente, indica solo il successo o il fallimento di un parser. Nessuna azione semantica associata con le regole grammaticali. Per calcolare l'espressione, dobbiamo aggiungere il codice java nella grammatica di svolgere azioni in vari punti. Forma delle azioni semantiche: expr: e1 PLUS expr: e2 {: RESULT = new Integer (e1.intValue () + e2.intValue ());:} Azioni (codice Java) sono racchiusi all'interno di una coppia (::) Etichette E2, E2: gli oggetti che rappresentano il terminale o non terminale corrispondente; RESULT: Il tipo di risultato dovrebbe essere lo stesso del tipo di non-terminali corrispondenti. ad esempio, expr di tipo Integer, cos risultato di tipo intege
Modifiche di calc.cup
terminal terminal Integer PLUS, MINUS, TIMES, DIVIDE, LPAREN, RPAREN; NUMBER; non terminal Integer expr; precedence left PLUS, MINUS; precedence left TIMES, DIVIDE; expr ::= expr:e1 PLUS expr:e2 {: RESULT = new Integer(e1.intValue()+ e2.intValue()); :} | expr:e1 MINUS expr:e2 {: RESULT = new Integer(e1.intValue()- e2.intValue()); :} | expr:e1 TIMES expr:e2 {: RESULT = new Integer(e1.intValue()* e2.intValue()); :} | expr:e1 DIVIDE expr:e2 {: RESULT = new Integer(e1.intValue()/ e2.intValue()); :} | LPAREN expr:e RPAREN {: RESULT = e; | NUMBER:e {: RESULT= e; :} :}
15
Modifiche di CalcParserUser
import java.io.*; class CalcParserUser { public static void main(String[] args){ try { File inputFile = new File ("calc.input"); CalcParser parser= new CalcParser(new CalcScanner(new FileInputStream(inputFile))); Integer result= (Integer)parser.parse().value; System.out.println("result is "+ result); } catch (Exception e) { e.printStackTrace(); } } }
Perch il risultato di parser().value un intero? Ci determinato dal tipo di expr, che il capo della prima produzione nelle specifiche javaCup:
non terminal Integer expr;
16