Compiladores (21/04/23 01:50) - 4.1 -
Tema 4. Gramáticas y Análisis SintácticoPARSER
Lección 11,12,13
Gramáticas Libres del Contexto
Compiladores (21/04/23 01:50) - 4.2 -
Definición de Gramática
• Una gramática G es una tupla (T,N,I,P) donde:– T conjunto de símbolos terminales.
– N conjunto de símbolos no terminales.
– Isímbolo inicial.
– P conjunto de producciones.• Una producción es de la forma
(T|N)+ (T|N)*
• L={x T*| I x} es el lenguaje generado por la gramática G.
• es un paso de derivación si P
• representa la aplicación iterada de pasos de derivación.
• Notación:– Letras mayúsculas representan símbolos no
terminales.
– Letras minúsculas representan símbolos terminales.
*
*
Compiladores (21/04/23 01:50) - 4.3 -
Ejemplo de Gramática
• La gramática G=(N,T,I,P) donde,– N={I,A,B}
– T={x,(,),+,-,*,/}
– P={II+I
II-I
IA
AA*A
AA/A
A(I)
Ax
}
• Secuencias generadas por G:– x, x*x
– x*x+x
– x*(x/x)
– x-x+x*x/x
Compiladores (21/04/23 01:50) - 4.4 -
Pasos de Generación
• Secuencia: x*x+x
I I+I A+I A*A+I A*A+A
x*A+A x*x+A x*x+x
• Arbol sintáctico: I
I + I
A A
A * A x
x x
Compiladores (21/04/23 01:50) - 4.5 -
Clasificación de las Gramáticas
• Regulares– Producciones:
• N• N
– Analizador: autómata finito determinista.
– Complejidad del análisis: lineal
• Libres del Contexto– Producciones:
• N
– Analizador: autómata con pila.
– Complejidad del análisis: lineal
• Dependientes del Contexto– Producciones:
• , donde (T|N)* y ||– Complejidad del análisis: exponencial
• Generales– Complejidad del análisis: no se puede con
memoria finita.
Compiladores (21/04/23 01:50) - 4.6 -
Gramáticas de los Lenguajes de Programación
• Un lenguaje de programación se puede generar con una gramática dependiente del contexto.– Las declaraciones provocan la necesidad de
analizar el contexto.
– No utilizaremos estas gramáticas por la complejidad temporal de su analizador.
• Solución:– Utilizar una gramática libre del contexto
que tiene una complejidad lineal y dejar las características del lenguaje dependientes del contexto para el análisis semántico.
Compiladores (21/04/23 01:50) - 4.7 -
Notación BNF Extendida
• Regla BNF:no terminal ::= expresión BNF
• Terminal:– Forma única: secuencia de caracteres que
lo forma.• Ej. if, +, then, for
– Forma variable: nombre que lo identifica.• Ej. identificador, numero, string, carácter, literal
• No Terminal: nombre entre < y >– Ej. <programa>, <lista de argumentos>,
<expresión>, <instrucción>• Secuencia vacía: • Operadores:
– Unión: | – Secuencia:
– Opcional: [ | – Repetición: {}= | | | |...
Compiladores (21/04/23 01:50) - 4.8 -
Ejemplo
• Gramática simplificada de la declaración de una variable en C.
<dec>::=<tipo> <var> {, <var>};<tipo>::=[signed|unsigned] (int|char)<var>::={*} identificador {‘[‘ número ‘]’}
• Símbolos terminales: signed, unsigned, int, char, identificador, número, ‘[‘, ‘]’, ‘,’
• Símbolos no terminales: <dec>, <tipo>, <var>
• Símbolo inicial: <dec>• Secuencias válidas:
int a,**b[3][4];unsigned char *p;signed char str[100];
Compiladores (21/04/23 01:50) - 4.9 -
Estructura Sintáctica de un Lenguaje y su Gramática
• Gramática de expresiones aritméticas de números y variables. Operaciones: +,-, *, /, - (cambio de signo), paréntesis.
<expresión>::=[-]<operando>
{(+|-|*|/) [-]<operando>}
<operando>::=‘(‘<expresión>‘)’ |
identificador | número• Secuencias valida: a+10*30/(-bd+2)• Arbol sintáctico generado para a+b*c
<expresión>
<operando> + <operando> * <operando>
identificador identificador identificador
• Este árbol sintáctico no representa el orden en que se han de realizar + y *.
Compiladores (21/04/23 01:50) - 4.10 -
Prioridad de las Operaciones y el Arbol Sintáctico
• Nueva gramática:
<expresión>::=<término>{(+|-) <término>}<término>::=<factor>{(*|/) <factor>}<factor>::=[-](‘(‘ <expresión> ‘)’ |
identificador | número)• Arbol sintáctico generado para a+b*c
<expresión>
<término> + <término>
<factor> <factor> * <factor>
identificado identificador identificador
• En este árbol cada operador se agrupa con sus operandos y las operaciones más prioritarias están más abajo.
Compiladores (21/04/23 01:50) - 4.11 -
Asociatividad y el Arbol Sintáctico
• Operación asociativa:
abc = abc) = (ab)c Ej. +, *
• Operación asociativa por la izquierda:
abc = (ab)c Ej. -, /
<operación>::=<operando>{<operando>}
<operación>
<operando> <operando> <operando>• Operación asociativa por la derecha:
abc = abc) Ej. = (asignación)
<operación>::=<operando>[<operación>]
<operación>
<operando> <operación>
<operando> <operando>
Compiladores (21/04/23 01:50) - 4.12 -
Ambigüedad Gramatical
• Una gramática es ambigua si puede generar una secuencia de símbolos con dos árboles sintácticos diferentes.
• Ejemplo: <a> ::= <a> (+ | *) <a> | número
Secuencia: 1*2+3
Arboles sintácticos generados:
<a> <a>
<a> * <a> <a> + <a>
número <a> + <a> <a> * <a> número
número número número número
• Una gramática no se puede utilizar para la realización de un compilador por que no especifica la estructura sintáctica del lenguaje.
Compiladores (21/04/23 01:50) - 4.13 -
Diagramas Sintácticos
No Terminal
Terminal
No TerminalDiagrama sintáctico de un
símbolo no terminal
Símbolo no terminal
Símbolo Terminal
Ejemplo: <sumas>::= número {+ número}
sumas
número
+número
Compiladores (21/04/23 01:50) - 4.14 -
Ejemplo
• Diagramas sintácticos para expresiones aritméticas.
expresión
factor
término
término+
-
factor-
número
identificador
términofactor
factor*
/
)( expresión
Compiladores (21/04/23 01:50) - 4.15 -
Paso de BNF a Diagramas Sintácticos
• A cada símbolo no terminal le corresponde una única regla.• Por cada regla se crea un único diagrama sintáctico.
<no terminal>::= expresión BNF
• Aplicar las siguientes reglas de conversión para pasar de la expresión BNF a su diagrama sintáctico.
Diagrama sintácticode la expresión BNF
No terminal
No Terminal
Terminal
<no terminal>
Terminal
Compiladores (21/04/23 01:50) - 4.16 -
Paso de BNF a Diagramas Sintácticos
Diag. sint. de
n
Diag. sint. de
Diag. sint. de n
n
Diag. sint. de
Diag. sint. de
Diag. sint. de n
Compiladores (21/04/23 01:50) - 4.17 -
Paso de BNF a Diagramas Sintácticos
Diag. sint. de
Diag. sint. de
Compiladores (21/04/23 01:50) - 4.18 -
Ejemplo de Paso de BNF a Diagrama Sintáctico
• Gramática de instrucciones PASCAL simplificada:<instrucción>::=
if <condición> then <instrucción>[else <instrucción>]
Diagrama sintáctico deif <condición> then <instrucción> [else <instrucción>]
instrucción
Compiladores (21/04/23 01:50) - 4.19 -
Ejemplo de Paso de BNF a Diagrama Sintáctico
instrucción
Diagrama sintáctico deif <condición> then <instrucción> [else <instrucción>]
Diag. if
Diag. then
Diag. <condición>
Diag. <instrucción>
Diag. [else <instrucción>]
Diag. if
Diag. <condición>
if
Diag. then then
condición
Diag. <instrucción> instrucción
Compiladores (21/04/23 01:50) - 4.20 -
Ejemplo de Paso de BNF a Diagrama Sintáctico
Diag. [else <instrucción>]
Diag. sint. de else <instrucción>
Diag. else
Diag. <instrucción>
Diag. else <instrucción>
Diag. else else
Diag. <instrucción> instrucción
instrucciónif instruccióncondición then
else instrucción
Compiladores (21/04/23 01:50) - 4.21 -
Gramática LL(1) Simple
• Un parser para gramáticas libres de contexto debe utilizar Backtracking
• Reducir las gramáticas utilizables a aquellas que permitan un reconocimiento determinista, en base al primer símbolo terminal de la secuencia.
• Una gramática LL(1) simple es aquella gramática libre de contexto sin reglas y para cada símbolo A N las alternativas para A empiezan con un símbolo terminal distinto.
A a11| a22 ... | ann , ai aj para i j y ai T para 1 i n
Compiladores (21/04/23 01:50) - 4.22 -
Ejemplo de Gramáticas LL(1) Simple
• Ejemplo:S aS
S bA
A d
A ccA
• Reconocer:
aabccd
S
a S
a S
b A
c c A
d
Compiladores (21/04/23 01:50) - 4.23 -
Conjunto de Primeros de una Secuencia de Terminales y no Terminales
• Primeros: concepto T+ Primero() = { x | x... y x T}
• Gramática LL(1) sin reglas es:
una gramática libre de contexto en la que para todas las reglas de la forma A 1| 2 ... | n , los conjuntos Primero(1), Primero(2),..., Primero(n) son disjuntos.
Primero(i) Primero(j) = i j
• Ejemplo:S ABePrimero(S) = {a,b,c,d}
S C
A dB Primero(A) = {a,c,d}
A aS
A c
B AS Primero(B) = {a,b,c,d}
B b
C b Primero(C) = {b}
*
Compiladores (21/04/23 01:50) - 4.24 -
Gramáticas LL(1) con reglas
• Primeros: concepto (T|N)+ Primero() = { | ... y T}
• Siguientes: concepto N Siguiente() = { | S
Primero() y
S es el símbolo inicial y
T}
• Gramática LL(1) con reglas es una gramática libre de contexto en la que para cada par de reglas de la forma A y A se cumple que:
Primero(Siguiente(A)) Primero(Siguiente(A)) =
• O lo que es lo mismo, para todas las reglas de la forma A 1| 2 ... | n :
Primero(i) Primero(j) = i j
y si i , entonces
Primero(j) Siguiente() = j1..n
*
*
*
Compiladores (21/04/23 01:50) - 4.25 -
Cálculo del Conjunto de Símbolos Anulables
• Un símbolo N es anulable si y solo si
• Cálculo del conjunto de Símbolos Anulables:
Anulables0={N| A }
Repetir
Anulablesn+1=Anulablesn
{BN| A12 ...m y
im iAnulablesn}
Hasta que Anulablesn==Anulablesn+1
*
Compiladores (21/04/23 01:50) - 4.26 -
Ejemplo: Cálculo del Conjunto de Símbolos Anulables
• Gramática:IDIa
IvI
Af
B+I
CAB
DCA
• Aplicación del AlgoritmoAnulables0={A,B}
Anulables1={A,B,C}
Anulables2={A,B,C,D}
Compiladores (21/04/23 01:50) - 4.27 -
Cálculo del Conjunto de Primerosde los no Terminales
ASi AAnulables entonces P0(A)={
sino P0(A)=
Repetir producción A de G
Pn+1(A)= Pn(A) PrimerosDe
Hasta que APn(A)== Pn+1(A)
PrimerosDe(}
PrimerosDe(a)={a} ,aT
PrimerosDe(A)= AN
si Pn(A) entonces
(Pn(A)-{})PrimerosDe()
sino Pn(A)
Compiladores (21/04/23 01:50) - 4.28 -
Ejemplo de Cálculo de Primeros
• Gramática:ITFC
B+TB|-TB|Fx
C*FC|/FC|
Anulables={B,C}
• Cálculo del conjunto de primerosP(I) P(T) P(B) P(F) P(C)
{}{}
{} {x} {}2 {x} {} {x}
{}3 {x} {x} {} {x}
{}4 {x} {x} {} {x}
{}
Compiladores (21/04/23 01:50) - 4.29 -
Cálculo del Conjunto de Siguientes
ASi A es el inicial entonces S0(A)={
sino S0(A)=
Repetir producción A B de G
Sn+1(B)= Sn(B) PrimerosDe
si PrimerosDe) entonces
Sn(A)
sino Hasta que ASn(A)== Sn+1(A)
Compiladores (21/04/23 01:50) - 4.30 -
Ejemplo de Cálculo de Siguientes
• Gramática:ITFC
B+TB|-TB|Fx
C*FC|/FC|
• PrimerosP(I) P(T) P(B) P(F) P(C)
{x} {x} {} {x}{}
• Cálculo del conjunto de siguientesS(I) S(T) S(B) S(F) S(C)
{} {}{} {} {} {}{} {} {} {}{}{} {} {} {}
Compiladores (21/04/23 01:50) - 4.31 -
Cálculo de Primeros y Siguientes en BNF
• Primeros:
P(n)=P()
P(n)=P()P()P(n)
P([])=P()P()
P({})=P()P()
P() en <A>::= ... es S(<A>)
• Siguientes
S( A ) PX : A
( )
• Problema:Estas definiciones generan recursividadesinfinitas que tras su estudio se pueden eliminar
Compiladores (21/04/23 01:50) - 4.32 -
Analizador Sintáctico Dirigido por Tabla
Simbolo Terminal t;
Parser(n) {
if (predicción[t,n]==E) error();
p=Producciones[predicción[t,n]];
while (p!=NULL) {
if (p->symÎT)
if (t==p->sym) t=leer_símbolo();
else error();
else Parser(p->sym);
p=p->siguiente;
}
}
• Producciones[P]: tabla donde los símbolos de cada producción forman una lista encadenada
• predicción[T,N]: tabla que dado un símbolo no terminal y el terminal leído especifica que producción hay que aplicar.
sym siguiente
Compiladores (21/04/23 01:50) - 4.33 -
Ejemplo: Analizador Sintáctico Dirigido por Tabla
• GramáticaITFC
B+TB|-TB|Fx
C*FC|/FC|
• Tabla de Producciones
1
2
3
4
5
T B
6
7
8
9
F C
+ T
- T
x
* F
/ F
B
B
C
C
Compiladores (21/04/23 01:50) - 4.34 -
Ejemplo: Analizador Sintáctico Dirigido por Tabla
• Cálculo de la tabla de predicción– predicción[t,n]=E si no hay ninguna
producción de n cuyos primeros contengan t.
– predicción[t,n]=al número de producción de n tal que t pertenezca a sus primeros
• Tabla de predicción
+ - * / x
F
C
E E E E 1 E
E E E E 2 E
3
E
9
4 E E E 5
E E E 6 E
9 7 8 E 9
Terminales
No
term
inal
es
Compiladores (21/04/23 01:50) - 4.35 -
Construcción de un Parser a Partir de los Diagramas Sintácticos (I)
• Programa principal
Símbolo SLA; /* Símbolo leído */
main() {
SLA=leer_símbolo();
Inicial();
if (SLA!=) Error();
}
Compiladores (21/04/23 01:50) - 4.36 -
Construcción de un Parser a Partir de los Diagramas Sintácticos (II)
{
Parser[
Parser[
...
Parser[n
}
Diagrama sintácticodel No terminal
No terminal
Diag. sint. de
Diag. sint. de
Diag. sint. de n
noterminal ( ) {
Parser[Diagrama sintáctico]
};
Compiladores (21/04/23 01:50) - 4.37 -
Construcción de un Parser a Partir de los Diagramas Sintácticos (III)
switch (SLA) {
case ( Primero()) : Parser[] break;
case (Primero()) : Parser[] break;
...
case (Primero(n)) : Parser[n] break;
default: error();
}
Diag. sint. de
Diag. sint. de
Diag. sint. de n
Compiladores (21/04/23 01:50) - 4.38 -
Construcción de un Parser a Partir de los Diagramas Sintácticos (VI)
if (SLA == Terminal) leer_símbolo();
else error()
Diag. sint. de
No Terminal
Terminal
while (SLAPrimero() )
Parser[
Noterminal( );
Compiladores (21/04/23 01:50) - 4.39 -
Construcción de un Parser a Partir de la Descripción BNF (I)
• Programa principal
Simbolo SLA; /* Simbolo leido */
main() {
SLA=leer_símbolo();
Inicial();
if (SLA!=) Error();
}
• <no terminal>::= expresión BNF
no_terminal ( ) {
Parser[expresión BNF]
};
Compiladores (21/04/23 01:50) - 4.40 -
Construcción de un Parser a Partir de la Descripción BNF (II)
• Parser[n]
{
Parser[
Parser[
...
Parser[n
}
• Parser[n]
switch (SLA) {
case ( Primero()) : Parser[] break;
case (Primero()) : Parser[] break;
...
case (Primero(n)) : Parser[n] break;
default: error();
}
Compiladores (21/04/23 01:50) - 4.41 -
Construcción de un Parser a partir de la descripción BNF (III)
• Parser[{}]
while (SLAPrimero() )
Parser[
• Parser[<no terminal>]
Noterminal( );
• Parser[ Terminal]
if (SLA== Terminal) leer_símbolo();
else error();
Compiladores (21/04/23 01:50) - 4.42 -
Tratamiento de los Errores en Análisis Descendente
• Tratamiento de errores:– Detectar sólo el primero
– Detectar el máximo (razonable)• Sincronizar después del error
• Prever errores típicos y cambiar la gramática a otra que los acepte dando warnings.
• Corregir el error: Decidir cual ha sido el error y reconstruir el programa fuente eliminando dicho error
• Consideraciones:– No se pueden solucionar correctamente
todos los casos.
– Hay que considerar la psicología del programador.
– Como mínimo tratar correctamente los errores más comunes.
– Minimizar:• Error no detectados
• Falsas alarmas
Compiladores (21/04/23 01:50) - 4.43 -
Sincronización Después de Error
• Después de un error se leen símbolos hasta encontrar uno que pertenezca al conjunto de sincronización– Saltar símbolos hasta encontrar uno
correcto y continuar (se supone que era código adicional erróneo)
– Saltar símbolos hasta encontrar uno correcto como siguiente del símbolo no terminal que se está analizando y retornar para continuar con ese símbolo siguiente (se supone que falta un trozo y se continua en un nivel superior)
• El conjunto de sincronización se puede formar a partir del cálculo de los símbolos que pueden seguir a un momento dado del análisis sintáctico.– Acumular los símbolos de estructuras
mayores en el conjunto de sincronización.
Compiladores (21/04/23 01:50) - 4.44 -
Corrección de Errores
• Después de un error añadir o quitar símbolos hasta poder continuar el análisis sintáctico.– Añadir: ir hacia delante dentro de la
gramática.
– Quitar: ir hacia atrás dentro de la gramática.
• En el caso que varias correcciones sean válidas se seleccionará la de menor peso.
• Ponderación de la secuencia:– A cada inserción de un símbolo se le asocia
un peso según lo fácil que sea dejarselo.
– A cada eliminación de un símbolo se le asocia un peso según lo fácil que sea ponerlo de más.
Compiladores (21/04/23 01:50) - 4.45 -
Tema 4. Análisis Sintáctico Ascendente (Bottom-Up)
Lecciones 17,18
Compiladores (21/04/23 01:50) - 4.46 -
Análisis Sintáctico Ascendente (Bottom-Up)
• Un analizador sintáctico ascendente construye el árbol sintáctico de abajo a arriba, por lo tanto– parte de los símbolos terminales que agrupan
o reducen en símbolos no terminales
• Ejemplo– Gramática
II+I | I-IA
AA*A | A/A(I) |num
– Expresión 1+2*3– Derivación: I => I+I => num+I => num+A =>
num+A*A => num+num*A => num+num*num
– La reducción de símbolos supone ir de derecha a izquierda en los pasos de derivación
1 + 2 * 3
num+num*num
A
I
Compiladores (21/04/23 01:50) - 4.47 -
Gramáticas LR(k)
• El conjunto de las gramáticas LR(k) es el conjunto de las gramáticas libres del contexto no ambiguas– El conjunto de las gramáticas LR(k)
contiene las gramáticas LL(k).
– k es el número de símbolos que hay que considerar hacia delante antes de que el analizador sintáctico realice una acción.
– Solo se consideran las gramáticas LR(0) y LR(1) ya que para k>1 el análisis tiende ha ser demasiado costoso.
• Familias de gramáticas
demasiado costoso LR(k)
LR(1)
LALR(1)
SLR(1) LL(1)
demasiado simple LR(0)
equivalentespor transformación
Compiladores (21/04/23 01:50) - 4.48 -
Bases del Análisis Sintáctico con Gramáticas LR(k) (I)
• Derivación de más a la derecha (rightmost derivation) de una secuencia de símbolos X esI => 1 => 2 => 1 =>...=> m = X
donde para todo i [1..m-1]
Bt => t, i= Bt, BP, t T*
• Como ha de actuar el analizador de una gramática LR(k)– es una secuencia de símbolos terminales
y no terminales donde se han realizado todas las reducciones posibles.
– es la nueva reducción que podemos realizar y la sustituimos por el símbolo no terminal B que la genera
– t es el resto de símbolos de la secuencia que faltan por procesar.
– La decisión de que reducción se ha de realizar se toma en base a y a los k primeros símbolos de t si la gramática es LR(k).
Compiladores (21/04/23 01:50) - 4.49 -
Bases del Análisis Sintáctico con Gramáticas LR(k) (II)
• Knuth demostró que se pueden reconocer los prefijos válidos mediante un autómata finito.– hay que considerar que el número de
prefijos puede ser infinito.
– Una vez reconocido el prefijo y leídos los k primeros símbolos de t hay una única reducción válida a aplicar.
• Como es un parser LR– Es una máquina con
• como entrada la secuencia a analizar
• una pila
• un mecanismo de control finito formado por:
– estados de lectura• Lee un símbolo, lo pone en la pila y salta a
un nuevo estado
– estados reductores • sustituyen la secuencia que encuentra en
la pila por B.
– un estado inicial
no es necesario
Compiladores (21/04/23 01:50) - 4.50 -
Gramática LR(0)
• GramáticaSE#
ET | E+T | E - T
Ti | (E)
0
1 6
8
7
9
10 1142
3
5
Estado reductor
Estado de lectura
E
#
+T
+T
-
)E
(
(
i
T
T
(
(i
i
i-
Ti
T(E)
EE+T
ET
EE-T
SE#
Compiladores (21/04/23 01:50) - 4.51 -
Trazado
• Secuencia: i-(i+i)#
Paso Pila Entrada Prefijo Grupo
0 0 i-(i+i)#
1 0 i3 -(i+i)# i i
2 0 T2 -(i+i)# T T
3 0 E1 -(i+i)# E
4 0 E1 -7 (i+i)# E-
5 0 E1 -7 (4 i+i)# E-(
6 0 E1 -7 (4 i3 +i)# E-(i i
7 0 E1 -7 (4 T2 +i)# E-(T T
8 0 E1 -7 (4 E10 +i)# E-(E
9 0 E1 -7 (4 E10 +6 i)# E-(E+
10 0 E1 -7 (4 E10 +6 i3 )# E-(E+i i
11 0 E1 -7 (4 E10 +6 T8 )# E-(E+T E+T
12 0 E1 -7 (4 E10 )# E-(E E+T
13 0 E1 -7 (4 E10 )11 # E-(E) (E)
14 0 E1 -7 T9 # E-T E-T
15 0 E1 # E
16 0 E1 #5 E#
17 0 Aceptado
Compiladores (21/04/23 01:50) - 4.52 -
Gramáticas no LR(0)
• Gramática LR(0)SE#
ET | E+T | E - T
Ti | (E)
• Gramática No LR(0)S E#
E E - T
E T
T F ^ T
T F
F (E)
F i
• Gramáticas SLR(1), LR(1), LALR(1):– Se decide cual es el estado siguiente de una
reducción a partir del estado de la pila y el símbolo leído hacia delante
– Los tres tipos de gramáticas se diferencian en el tratamiento del símbolo leído hacia delante.
Para aplicar una de estas dos reducciones hay que leer el siguiente símbolo
Top Related