lfac9
TRANSCRIPT
Curs 9 - plan
• Sarcinile unui parser
• Gramatici CF – remember
• Metode de parsare
• Generatoare de parsere
• Parsare cu Yacc / Bison
– Specificații Yacc
– Utilizare Lex / Flex cu Yacc / Bison
• Parsare top - down
Parserul
2
Lexical
Analyzer
Parser
and rest of
front-end
Source
Program
Token,
tokenval
Symbol Table
Get next
token
Lexical error Syntax error
Semantic error
Intermediate
representation
Parserul
• Un parser implementează o gramatică CF
• Rolul unui parser:
– Verifică sintaxa(= string recognizer)
• raportare cu precizie a erorilor
– Invocă acțiunile semantice:
• semantica statică: verificarea tipului expresiilor, funcțiilor etc.
• traducere orientată sintactic: cod sursă pentru reprezentarea intermediară
3
Traducere ”Syntax-Directed”
• Un rol important al parserului: producerea unei reprezentări intermediare a programului sursă folosind metode de traducere ”dirijate sintactic”
• Reprezentări intermediare posibile:
– Arbore abstract(ASTs)
– Cod cu trei adrese (quadruple)
– Forma poloneză
4
Tratarea erorilor • Identificarea și localizarea erorilor
– Erori lexicale : importante, compilatorul le
poate recupera ușor și poate continua
– Erori sintactice: cele mai importante pentru compilator, pot fi aproape totdeauna recuperate
5
Tratarea erorilor • Identificarea și localizarea erorilor
– Erori de semantică statică : importante, uneori pot fi recuperate
– Erori de semantică dinamică: greu sau imposibile de detectat la momentul compilării, e nevoie de verificat la execuție
– Erori logice: greu sau imposibil de detectat
6
Proprietatea ”Viable-Prefix”
• Proprietatea viable - prefix a parserelor LL/LR permite detectarea timpurie a erorilor de sintaxă
– Scop: detectarea unei erori cât mai devreme posibil fără a se consuma intrări nenecesare
– Cum: detectarea unei erori atunci când prefixul cuvântului de la intrare nu se potrivește cu nici un cuvânt din limbaj
7
…
for (;)
…
…
DO 10 I = 1;0
…
Error is
detected here
Error is
detected here
Prefix Prefix
Strategii de recuperare a erorilor • Modul ”panică”
– Renunțarea la simboluri de intrare până când se găsește un token dintr-o mulțime specificată
• Recuperare la nivel de frază – Efectuarea de corecții locale pentru a repara eroarea
• Producții ”error” – Se mărește gramatica cu producții pentru construcții
eronate
– Se alege o secvență minimă de modificări pentru a obține o corecție de cost minim
8
Gramatici (Recap)
G = (N, T, S, P)
– T simboluri terminale
–N simboluri neterminale
–P o mulțime finită de producții de forma (NT)* N (NT)* , (NT)*
– S N simbol de start
9
Convenții • Terminali:
a, b, c, … T 0, 1, id, +
• Neterminali: A, B, C ,… N expr, term, stmt
10
• Simboluri generice: X, Y, Z (NT)
• Șiruri de terminali: u, v, w, x, y, z T*
• Șiruri oarecare: , , (NT)*
Derivări (Recap) • Derivare one-step :
A unde A P
• Derivări extrem stângi (drepte): –lm dacă nu conține nici un neterminal –rm dacă nu conține nici un neterminal
• Închidere reflexivă și tranzitivă * (zero sau mai mulți pași)
• Închidere tranzitivă + (unul sau mai mulți pași)
• Limbajul generat de G: L(G) = {w T* | S + w}
11
Derivări
12
G = ({E}, {+,*,(,),-,id}, P, E)
P = E E + E
E E * E
E ( E )
E - E
E id
E - E - id
E * E
E + id * id + id
E rm E + E rm E + id rm id + id
Derivări:
E * id + id
Metode de parsare • Universal (orice gramatică CF)
– Cocke-Younger-Kasami (CYK) – Earley
• Top-down (gramatici CF cu restricții) – Recursiv descendent (predictive parsing) – Metode LL (Left-to-right, Leftmost derivation)
• Bottom-up (gramatici CF cu restricții) – Parsare bazată pe relații de precedență – Metode LR (Left-to-right, Rightmost derivation)
• SLR, LR, LALR
13
Generatoare de parsere
• ANTLR (http://www.antlr.org/) – Generează parsere LL(k)
• YACC (Yet Another Compiler Compiler) – 1975 S. C. Johnson la Bell Laboratory
– Generează parsere LALR(1)
• Bison – Versiunea îmbunătățită a lui Yacc
– Robert Corbett şi Richard Stallmann (2006 – versiunea 2.3)
14
Generatoare de parsere
• http://dinosaur.compilertools.net/
• http://epaperpress.com/lexandyacc/index.html
• http://www.gnu.org/software/bison/
• http://catalog.compilertools.net/lexparse.html
• http://members.cox.net/slkpg/
• http://wiki.python.org/moin/LanguageParsing
• http://scottmcpeak.com/elkhound/
15
Crearea unui parser cu Yacc/Bison
16
Yacc or Bison
compiler
yacc
specification yacc.y
y.tab.c
input
stream
C
compiler
a.out output
stream
y.tab.c
a.out
Specificații Yacc • O specificație yacc constă din 3 părți:
declarații yacc, eventual declarații C în %{ %} %% reguli de traducere %% proceduri auxiliare definite de utilizator
17
Specificații Yacc • regulile de traducere sunt de forma:
production1 { semantic action1 } production2 { semantic action2 } … productionn { semantic actionn }
18
Scrierea unei gramatici în Yacc • Producțiile sunt de forma:
Nonterminal : tokens/nonterminals | tokens/nonterminals … ;
• Token-urile definite ca un singur caracter pot fi incluse direct, de exemplu ‘+’
• Token-urile ce au un nume trebuiesc declarate în partea de declarații sub forma: %token NumeToken
19
Atribute sintetizate • Acțiunile semantice pot referi valori ale
atributelor sintetizate ale terminalilor și neterminalilor din producția respectivă :
X : Y1 Y2 Y3 … Yn { acțiune }
• acțiune: expresie ce conține $$ și $i – $$ referă valoarea atributului lui X – $i referă valoarea atributului lui Yi
• De exemplu factor : ‘(’ expr ‘)’ { $$=$2; }
20
Exemplul 1
21
%{ #include <ctype.h> %}
%token DIGIT
%%
line : expr ‘\n’ { printf(“%d\n”, $1); }
;
expr : expr ‘+’ term { $$ = $1 + $3; }
| term { $$ = $1; }
;
term : term ‘*’ factor { $$ = $1 * $3; }
| factor { $$ = $1; }
;
factor : ‘(’ expr ‘)’ { $$ = $2; }
| DIGIT { $$ = $1; }
;
%%
int yylex()
{ int c = getchar();
if (isdigit(c))
{ yylval = c-’0’;
return DIGIT;
}
return c;
}
Also results in definition of #define DIGIT xxx
Attribute of token (stored in yylval)
Attribute of term (parent)
Attribute of factor (child)
Example of a very crude lexical
analyzer invoked by the parser
Procesare ambiguități
• Se pot specifica gramatici ambigui dacă se precizează precedența operatorilor și asociativitatea stângă sau dreaptă :
E E+E | E-E | E*E | E/E | (E) | -E | num
• În partea de declarații se specifică:
%left ‘+’ ‘-’
%left ‘*’ ‘/’
%right UMINUS
22
Exemplul 2
23
%{
#include <ctype.h>
#include <stdio.h>
#define YYSTYPE double
%}
%token NUMBER
%left ‘+’ ‘-’
%left ‘*’ ‘/’
%right UMINUS
%%
lines : lines expr ‘\n’ { printf(“%g\n”, $2); }
| lines ‘\n’
| /* empty */
;
expr : expr ‘+’ expr { $$ = $1 + $3; }
| expr ‘-’ expr { $$ = $1 - $3; }
| expr ‘*’ expr { $$ = $1 * $3; }
| expr ‘/’ expr { $$ = $1 / $3; }
| ‘(’ expr ‘)’ { $$ = $2; }
| ‘-’ expr %prec UMINUS { $$ = -$2; }
| NUMBER
;
%%
Double type for attributes and yylval
Exemplul 2 (cont)
24
%%
int yylex()
{ int c;
while ((c = getchar()) == ‘ ‘)
;
if ((c == ‘.’) || isdigit(c))
{ ungetc(c, stdin);
scanf(“%lf”, &yylval);
return NUMBER;
}
return c;
}
int main()
{ if (yyparse() != 0)
fprintf(stderr, “Abnormal exit\n”);
return 0;
}
int yyerror(char *s)
{ fprintf(stderr, “Error: %s\n”, s);
}
Run the parser
Crude lexical analyzer for
fp doubles and arithmetic
operators
Invoked by parser
to report parse errors
Utilizare Lex/Flex cu Yacc/Bison
25
Yacc or Bison
compiler
yacc
specification yacc.y
lex.yy.c
y.tab.c
input
stream
C
compiler
a.out output
stream
y.tab.c
y.tab.h
a.out
Lex or Flex
compiler
Lex specification lex.l
and token definitions y.tab.h
lex.yy.c
Specificații Lex pentru Exemplul 2
26
%option noyywrap
%{
#include “y.tab.h”
extern double yylval;
%}
number [0-9]+\.?|[0-9]*\.[0-9]+
%%
[ ] { /* skip blanks */ }
{number} { sscanf(yytext, “%lf”, &yylval);
return NUMBER;
}
\n|. { return yytext[0]; }
Generated by Yacc, contains #define NUMBER xxx
yacc -d example2.y
lex example2.l
gcc y.tab.c lex.yy.c
./a.out
bison -d -y example2.y
flex example2.l
gcc y.tab.c lex.yy.c
./a.out
Defined in y.tab.c
Recuperare erori în Yacc
27
%{
…
%}
…
%%
lines : lines expr ‘\n’ { printf(“%g\n”, $2; }
| lines ‘\n’
| /* empty */
| error ‘\n’ { yyerror(“reenter last line: ”);
yyerrok;
}
;
…
Reset parser to normal mode Error production:
set error mode and
skip input until newline
Parsare Top-Down
• Metode LL (Left-to-right, Leftmost derivation) și parsare recursiv-descendentă
28
Gramatica:
E T + T
T ( E )
T - E
T id
Derivare extrem stângă:
E lm T + T
lm id + T
lm id + id
+
E E
T
+
T
id id
E
T T
E
T
+
T
id
Left Recursion (Recap)
• Producțiile de forma A A | | sunt stâng recursive
• În cazul existenței recursiei stângi un parser predictiv ciclează la nesfârșit pentru anumite intrări
29
Eliminarea recursiei stângi
30
Se stabilește o ordine a neterminalilor: A1, A2, …, An
for i = 1, …, n do
for j = 1, …, i-1 do
replace each
Ai Aj
with
Ai 1 | 2 | … | k
where
Aj 1 | 2 | … | k
enddo
eliminate the immediate left recursion in Ai
enddo
Eliminarea recursiei stângi imediate
31
Producțiile (recursie stânga):
A A
|
|
| A
se înlocuiesc cu (recursie dreapta):
A AR
| AR
AR AR
| AR
|
Exemplu
32
A B C | a
B C A | A b
C A B | C C | a
Ordinea: A, B, C
i = 1: -
i = 2, j = 1: B C A | A b
B C A | B C b | a b
(imm) B C A BR | a b BR
BR C b BR |
i = 3, j = 1: C A B | C C | a
C B C B | a B | C C | a
i = 3, j = 2: C B C B | a B | C C | a
C C A BR C B | a b BR C B | a B | C C | a
(imm) C a b BR C B CR | a B CR | a CR
CR A BR C B CR | C CR |
Factorizare stângă
• Dacă un neterminal are două sau mai multe producții care în partea dreaptă încep cu aceleași simboluri, gramatica nu este LL(1) și nu poate fi parsată predictiv
• Se înlocuiesc producțiile A 1 | 2 | … | n | cu: A AR | AR 1 | 2 | … | n
33
Parsare predictivă • Se elimină recursia stângă (dacă este cazul)
• Se factorizează stânga (dacă este cazul)
• Se determină FIRST și FOLLOW
• Se alege una din variante: – Recursivă (recursive calls)
– Non-recursivă (table - driven)
34
FIRST
• FIRST() = { mulțimea terminalilor ce încep cuvintele derivate din (incluzând )} FIRST(a) = {a} if a T FIRST() = {} FIRST(A) = A FIRST() for A P FIRST(X1X2…Xk) = if for all j = 1, …, i-1 : FIRST(Xj) then add non- in FIRST(Xi) to FIRST(X1X2…Xk) if for all j = 1, …, k : FIRST(Xj) then add to FIRST(X1X2…Xk)
35
FOLLOW • FOLLOW(A) = { mulțimea terminalilor ce pot urma
imediat după neterminalul A în formele propoziționale } FOLLOW(A) = for all (B A ) P do add FIRST()\{} to FOLLOW(A)
if A is the start symbol S then add $ to FOLLOW(A) for all (B A ) P and FIRST() do add FOLLOW(B) to FOLLOW(A) for all (B A) P do add FOLLOW(B) to FOLLOW(A)
36
Gramatici LL(1) • O gramatică G este LL(1) dacă nu este stâng
recursivă și pentru orice colecție de producții: A 1 | 2 | … | n pentru neterminalul A au loc: 1. FIRST(i) FIRST(j) = for all i j 2. if i
* then 2.a. j
* for all i j 2.b. FIRST(j) FOLLOW(A) = for all i j
echivalent cu: FIRST(i FOLLOW(A)) FIRST(j FOLLOW(A)) = for all i j
37
Exemple S E | B E
B a | begin SC end C | ; SC
S ABC A aA |
B bB |
C cC |
S a R a R S |
38
Exemple Non-LL(1)
39
Gramatica Nu este LL(1) deoarece:
S S a | a Este stâng recursiva
S a S | a FIRST(a S) FIRST(a)
S a R |
R S | R: S * and *
S a R a
R S |
R:
FIRST(S) FOLLOW(R)
Parsare recursiv descendentă • Gramatica trebuie să fie LL(1)
• Pentru fiecare neterminal se construiește o procedură (eventual recursivă) care realizează parsarea categoriei sintactice corespunzătoare acelui neterminal
• Când un neterminal are producții multiple, fiecare producție este implementată într-o ramură a instrucțiunii de selectare, corespunzătoare informațiilor din intrare (look-ahead information)
40
Utilizare FIRST și FOLLOW
41
expr term rest
rest + term rest
| - term rest
|
term id
procedure rest();
begin
if lookahead in FIRST(+ term rest) then
match(‘+’); term(); rest()
else if lookahead in FIRST(- term rest) then
match(‘-’); term(); rest()
else if lookahead in FOLLOW(rest) then
return
else error()
end;
FIRST(+ term rest) = { + }
FIRST(- term rest) = { - }
FOLLOW(rest) = { $ }
Parsare predictivă ne-recursivă (bazată pe tabela de parsare)
• Dată o gramatică LL(1) G = (N, T, P, S) se construiește o tabelă M[A,a] pentru fiecare A N, a T și un ”driver program” cu o stivă:
42
Predictive parsing
program (driver)
Parsing table
M
a + b $
X
Y
Z
$
stack
input
output
Construirea Tabelei de parsare LL(1)
43
for each production A do
for each a FIRST() do // a
add A to M[A, a]
enddo
if FIRST() then
for each b FOLLOW(A) do
add A to M[A, b]
enddo
endif
enddo
Mark each undefined entry in M error
Exemplu
44
E T ER
ER + T ER |
T F TR
TR * F TR |
F ( E ) | id
id + * ( ) $
E E T ER E T ER
ER ER + T ER ER ER
T T F TR T F TR
TR TR TR * F TR TR TR
F F id F ( E )
A FIRST() FOLLOW(A)
E T ER ( id $ )
ER + T ER + $ )
ER $ )
T F TR ( id + $ )
TR * F TR * + $ )
TR + $ )
F ( E ) ( * + $ )
F id id * + $ )
LL(1) vs. ambiguitate
45
Gramatică ambiguă
S i E t S SR | a
SR e S |
E b
a b e i t $
S S a S i E t S SR
SR SR
SR e S SR
E E b
A FIRST() FOLLOW(A)
S i E t S SR i e $
S a a e $
SR e S e e $
SR e $
E b b t Error: duplicate table entry
Parsare predictivă: Programul (Driver)
46
push($)
push(S)
a := lookahead
repeat
X := pop()
if X is a terminal or X = $ then
match(X) // moves to next token and a := lookahead
else if M[X, a] = X Y1Y2…Yk then
push(Yk, Yk-1, …, Y2, Y1) // such that Y1 is on top
… invoke actions and/or produce output …
else error()
endif
until X = $
Exemplu
47
Stack
$E
$ERT
$ERTRF
$ERTRid
$ERTR
$ER
$ERT+
$ERT
$ERTRF
$ERTRid
$ERTR
$ERTRF*
$ERTRF
$ERTRid
$ERTR
$ER
$
Input
id+id*id$
id+id*id$
id+id*id$
id+id*id$
+id*id$
+id*id$
+id*id$
id*id$
id*id$
id*id$
*id$
*id$
id$
id$
$
$
$
Production applied
E T ER
T F TR
F id
TR
ER + T ER
T F TR
F id
TR * F TR
F id
TR
ER
Panic Mode Recovery
48
id + * ( ) $
E E T ER E T ER synch synch
ER ER + T ER ER ER
T T F TR synch T F TR synch synch
TR TR TR * F TR TR TR
F F id synch synch F ( E ) synch synch
FOLLOW(E) = { ) $ }
FOLLOW(ER) = { ) $ }
FOLLOW(T) = { + ) $ }
FOLLOW(TR) = { + ) $ }
FOLLOW(F) = { + * ) $ }
Add synchronizing actions to
undefined entries based on FOLLOW
synch: the driver pops current nonterminal A and skips input till
synch token or skips input until one of FIRST(A) is found
Pro: Can be automated
Cons: Error messages are needed
Phrase-Level Recovery
49
id + * ( ) $
E E T ER E T ER synch synch
ER ER + T ER ER ER
T T F TR synch T F TR synch synch
TR insert * TR TR * F TR TR TR
F F id synch synch F ( E ) synch synch
Change input stream by inserting missing tokens
For example: id id is changed into id * id
insert *: driver inserts missing * and retries the production
Can then continue here
Pro: Can be automated
Cons: Recovery not always intuitive
Error Productions
50
id + * ( ) $
E E T ER E T ER synch synch
ER ER + T ER ER ER
T T F TR synch T F TR synch synch
TR TR F TR TR TR * F TR TR TR
F F id synch synch F ( E ) synch synch
E T ER
ER + T ER |
T F TR
TR * F TR |
F ( E ) | id
Add “error production”:
TR F TR
to ignore missing *, e.g.: id id
Pro: Powerful recovery method
Cons: Cannot be automated