Documente Academic
Documente Profesional
Documente Cultură
Materiales de lectura
• Cap. 3 de Appel
• Cap. 4 de Aho
• Manuales de Yacc – JCUP - Bison
Diseño de Compiladores
Analizador Sintáctico
Analizador Analizador
programa
Lexicograf.
get_token( ) Sintáctico árbol de
parser
Tabla de
Símbolos
Diseño de Compiladores
Analizador Sintáctico
• IF a < 4 THEN a := a + 3 ELSE a := 10
parser: árbol
IF_STMT
(id,“a”) EXPR_ARIT
(id,“a”) “<“ (INT,4)
(id,“a”) + (INT,3)
Diseño de Compiladores
Gramáticas Independientes de Contexto
Describen la sintaxis de los lenguajes.
SàS;S E à id LàE
S à id := E E à num LàL,E
S à print( L ) EàE+E
Eà(S,E)
Diseño de Compiladores
Derivamos aplicando producciones
S
S;S
S ; id := E;
id := E; id := E;
id := num; id := E;
id := num; id := E+E;
id := num; id := E+(S, E);
id := num; id := id+(S, E);
id := num; id := id+(id := E, E);
id := num; id := id+(id := E+E, E);
id := num; id := id+(id := E+E, id);
id := num; id := id+(id := num+E, id);
id := num; id := id+(id := num+num, id);
a:= 7 ; b:= c + (d := 5 +6 , d)
Diseño de Compiladores
Y se va construyendo el árbol de
S parser
S ; S
id := E id := E
num ( S , E )
id := E id
E + E
num num
Diseño de Compiladores
Gramática ambigua
cuando de una misma tira se construyen dos árboles de
S parser diferentes, por ej: id + id + id
S
id := E
id := E
E + E
E + E
E + E id
id E + E
id id id id
¿Qué pasa si la tira derivada fuese id := id – id – id
o id := id + id * id ?
Diseño de Compiladores
Gramática no ambigua
Una gramática ambigua:
E à id EàE/E Eà(E)
E à num EàE+E
EàE*E EàE-E
EàE+T TàT *F F à id
EàE -T TàT /F F à num
EàT TàF Fà(E)
Diseño de Compiladores
Gramática no ambigua
E • Ahora existe un único
árbol de parser asociado
a la tira id + id * id
E + T
T T * F
F F id
id id
Diseño de Compiladores
Parsers
• Top-Down - LL
• Bottom-up - LR
Diseño de Compiladores
Parsing Top-Down
• Construye el árbol de parser comenzando por la raíz y
“adivinando” el próximo paso de derivación
S --> cAd
A --> ab | a
Diseño de Compiladores
Parsers Predictivo: Recursivo
Descendente
Dada la gramática:
S à if E then S else S
| begin S L
| print E
L à end
| ;SL
E à num = num
Se escriben usando:
• Una función por cada no terminal.
• Una cláusula por cada producción del no terminal
Diseño de Compiladores
Parsers Recursivo Descendente
void S( ) { switch( tok )
case IF: eat (IF) ; E ( ); eat (THEN); S( ) ;
eat (ELSE); S( ) ; break ;
case BEGIN: eat (BEGIN) ; S( ) ; L( ) ; break ;
case PRINT: eat (PRINT) ; E( ) ; break ;
default : error ( ) ; }
void L( ) { switch(tok)
case END: eat (END); break;
case SEMI: eat(SEMI); S( ) ; L( ) ; break;
default: error( ); }
void E( ) { eat ( NUM) ; eat(EQ) ; eat(NUM) ; }
SàE$
EàE+T TàT *F F à id
EàE -T TàT /F F à num
EàT TàF Fà(E)
Diseño de Compiladores
Parsers Recursivo Descendente
void S( ) { E ( ) ; eat(EOF); }
void T( ) { switch(tok)
case ?: T ( ); eat (MULT) ; F ( ); break ;
case ?: T ( ); eat (DIV) ; F ( ); break ;
case ?: F( ); break ;
default: error( ); }
Diseño de Compiladores
Parsers Recursivo Descendente
La función E no puede determinar que cláusula usar.
Ejemplo:
( 1 * 2 –3 ) + 4 usaría EàE+T
pero
( 1 * 2 –3 ) usaría E à T
Diseño de Compiladores
CONJUNTO FIRST
Sea α string de terminales y no terminales
• Si X à α 1 | α 2 si k ∈ FIRST(α 1 )
k ∈ FIRST(α 2 )
la gramática no puede ser parseada por un
parser recursivo descendente
Diseño de Compiladores
CONJUNTO FIRST
• Sea la gramática:
Diseño de Compiladores
Cálculo de FIRST
Diseño de Compiladores
CONJUNTOS NULLABLE y
FOLLOW
Diseño de Compiladores
Cálculo de FOLLOW
• Si X->α Y β
• Si X->α Y ο
X->α Y β y FIRST(β ) contiene ε
Diseño de Compiladores
Algoritmo FIRST – FOLLOW - NULLABLE
For each símbolo terminal Z, FIRST( Z ) = { Z }
For each producción X à Y1 Y2 ....... YK
For each i from 1 to k, each j from i+1 to k
if todos los Yi son anulables
then nullable[ X ] = true
if Y1 ...... Yi-1 son anulables
then FIRST[ X ] = FIRST[ X ] U FIRST[Yi ]
if Yi+1...... YJ-1son anulables
then FOLLOW[Yi] = FOLLOW[Yi ] U FIRST[YJ ]
if Yi+1...... Yk son anulables
then FOLLOW[Yi]=FOLLOW[Yi ] U FOLLOW[X ]
Diseño de Compiladores
Ejemplo: cálculo NULLABLE - FIRST
1. Z à d 3. Y à ε 5. X à Y
2. Z à X Y Z 4. Y à c 6. X à a
De 2. FIRST ( Z ) à FOLLOW ( Y )
De 2. FIRST ( Y ) à FOLLOW ( X )
De 2. FIRST ( Z ) à FOLLOW ( X )
De 5. FOLLOW (X) à FOLLOW (Y)
Diseño de Compiladores
Construcción de tabla de parsing
predictivo para el ejemplo
Entrar la producción Xà α, en fila X, columna t, para cada
t ∈ FIRST (α). Si α es anulable, entrar la producción en fila X,
columna t, para cada t ∈ FOLLOW (X).
Diseño de Compiladores
Tabla de parsing predictivo
Existen entradas duplicadas en la tabla à gramática no sirve
para parsing recursivo descendente, no alcanza con conocer el
no terminal y el símbolo de lookahead, para elegir la producción
a utilizar.
Zàd y Z à XYZ à d
Diseño de Compiladores
Tabla de parsing predictivo LL1 - LLk
Gramáticas ambiguas è entradas duplicadas en la
tabla de parsing predictivos
Diseño de Compiladores
Eliminación de la recursividad
por la izquierda
• Gramáticas con recursividad por la izquierda no pueden ser LL1
EàE+T TàT *F F à id
EàT TàF F à(E)
E à T E’ T à F T’ F à id
E’ à + T E’ T’ à * F T’ F à(E)
E’ à ε T’ à ε
Diseño de Compiladores
Eliminación de la recursividad
por la izquierda
• Gramáticas recursivas en más de un paso:
1. S à A a | b
2. A à A c | S d | ε
SàAaàS daàAadaàSdada
Diseño de Compiladores
Factorización por la izquierda
• Gramáticas con una producción que comienza con el mismo
terminal por la izquierda no pueden ser LL1, no sabemos que
producción elegir con un símbolo de lookahead.
• Ejemplo:
S à if E then S else S
S à if E then S
se transforma en:
S à if E then S X
Xà else S | ε
Diseño de Compiladores
Parsing Predictivos
• Parsers:
- Recursivos descendentes ( con procesos recursivos ),
anteriormente vistos.
Diseño de Compiladores
Parsing Predictivos no recursivos
• Usamos stack en vez de llamadas a procesos recursivos
Algoritmo:
Si X es un terminal
Si X=$, fin exitoso
a + b $ input Si X=a pop(X) y avanza input
Sino error
X Sino /* X no es un terminal*/
Si M[X,a]= X-->UVW
Y /*Reemplazar X por UVW*/
prog.
Z parsing pop()
output push(W) push(V) push(U)
$ predictivo Sino /* M[X,a] vacía*/
Error
tabla de
parsing M[X,a]
Diseño de Compiladores
Parsing Predictivos no recursivos
Diseño de Compiladores
Parsing Predictivos no recursivos
Ejemplo expresiones aritméticas
1. S à E $
2. E --> T E’ 4. T --> F T’ 6. F --> ( E ) | id
3. E’--> + T E’ | ε 5. T’ --> * F T’ | ε
De 3 y 5. E’ y T’ son anulables.
De 6. FIRST (F) ßid ( De 5. FIRST (T’) ß * De 3. FIRST (E’) ß +
nullable FIRST FOLLOW
De 4. FIRST(F) à FIRST(T) S - ( id
De 2. FIRST(T) à FIRST(E) E - ( id
De 1. FIRST(E) à FIRST(S) E’ Si +
T - ( id
T’ Si *
F - ( id
Diseño de Compiladores
Parsing Predictivos no recursivos
Ejemplo expresiones aritméticas
1. S à E $
2. E --> T E’ 4. T --> F T’ 6. F --> ( E ) | id
3. E’--> + T E’ | ε 5. T’ --> * F T’ | ε
Diseño de Compiladores
Parsing Predictivos no recursivos
Ejemplo expresiones aritméticas
1. S à E $
2. E --> T E’ 4. T --> F T’ 6. F --> ( E ) | id
3. E’--> + T E’ | ε 5. T’ --> * F T’ | ε
Diseño de Compiladores
Recuperación de errores en parser
predictivos
• Un blanco en una fila T,columna x de una tabla de parsing LL1,
indica que la función T() no espera un x.
• Si viene una x à se produce un error.
• Políticas de recuperación:
• Reportar error y abandonar: no es amigable
• Insertar, borrar o reemplazar tokens (modo pánico)
Diseño de Compiladores
Recuperación de errores en parser
predictivos
• Consideremos la gramática de las expresiones aritméticas, y la
producción : T’ à * F T ‘ | ε.
• FOLLOW(T’) = { ) + $ }