You are on page 1of 16

GENERATORI DI ANALIZZATORI SINTATTICI

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.

Struttura di un Programma Yacc

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 */

Yacc: Sezione Definizioni

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: Analisi Lessicale

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.

Yacc: Ambiguit e Conflitti

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)

JavaCUP: Un generatore LALR per Java


Definizione dei Tokens Espressioni Regolari JLex Grammatica BNF-like Specification JavaCUP

Java File: Scanner Class


Riconoscimento dei Tokens

Java File: Parser Class


Use lo Scanner per recuperare I Tokens Parses Stream of Tokens

Syntactic Analyzer

Passi per utilizzare JavaCup

Scivere una specifica JavaCup (cup file)

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

Eseguire JavaCup per generare il parser


Scrivere il programma che usa il parser

Compilare ed eseguire il programma

Struttura della specifica Java Cup


java_cup_spec ::= package_spec import_list code_part init_code scan_code symbol_list precedence_list start_spec production_list

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

Specifica JavaCup (calc.cup)


terminal PLUS, MINUS, TIMES, DIVIDE, LPAREN, RPAREN; terminal Integer NUMBER; non terminal Integer expr; precedence left PLUS, MINUS; precedence left TIMES, DIVIDE; expr ::= expr PLUS expr | expr MINUS expr | expr TIMES expr | expr DIVIDE expr | LPAREN expr RPAREN | NUMBER ;

la grammatica ambigua?
Come possiamo ottenere PLUS ...? Sono i terminali restituiti dallo scanner. Come per connettersi con lo scanner?

Ambiguous Grammar Error

Se inseriamo la grammatica
Expression ::= Expression PLUS Expression;

Senza le precedenze JavaCUP segnaler:


Shift/Reduce conflict found in state #4 between Expression ::= Expression PLUS Expression . and Expression ::= Expression . PLUS Expression under symbol PLUS Resolved in favor of shifting.

La grammatica ambigua! Possiamo inserire in JavaCUP che PLUS associativo a sinistra.

10

Specifica dello scanner corrispondente (calc.lex)


Connesione con il parser import java_cup.runtime.*; imports java_cup.runtime.*, Symbol, %% Scanner. %implements java_cup.runtime.Scanner implements Scanner %type Symbol next_token: definito in Scanner %function next_token interface %class CalcScanner CalcSymbol, PLUS, MINUS, ... %eofval{ return null; new Integer(yytext()) %eofval} NUMBER = [0-9]+ %% "+" { return new Symbol(CalcSymbol.PLUS); } "-" { return new Symbol(CalcSymbol.MINUS); } "*" { return new Symbol(CalcSymbol.TIMES); } "/" { return new Symbol(CalcSymbol.DIVIDE); } {NUMBER} { return new Symbol(CalcSymbol.NUMBER, new Integer(yytext()));} \r\n {} . {}

Run JLex
java JLex.Main calc.lex
Nota

che il package prefix JLex Il programma generato calc.lex.java

javac calc.lex.java
La

classe gnenerata: CalcScanner.class

11

Generated CalcScanner class


1. 2. 3. 4. 5. 6. 7.

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()));} ... ... } }

Interface Scanner definita nel package java_cup.runtime


public interface Scanner { public Symbol next_token() throws java.lang.Exception; }

Run javaCup

Eseguire javaCup per generare il parser

java java_cup.Main -parser CalcParser -symbols CalcSymbol < calc.cup

Le

classi generate:

CalcParser; CalcSymbol;

Compilare il parser
javac

CalcParser.java CalcSymbol.java CalcParserUser.java

Usare il parser
java

CalcParserUser

12

The token class Symbol.java


1. 2. 3. 4. 5. 6. 7. 8. 9. 10.

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()));}

Le azioni nel file lex :

CalcSymbol.java (default name is sym.java)


1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11.

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 }

MINUS = 3; DIVIDE = 5; NUMBER = 8; EOF = 0; PLUS = 2; error = 1; RPAREN = 7; TIMES = 4; LPAREN = 6;

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

The program that uses the CalcParser


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))); parser.parse(); } catch (Exception e) { e.printStackTrace(); } } }

The program that uses the CalcParser

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

You might also like