Traductores e intérpretes en los primeros encuentros colombinos
Compiladores e Intérpretes Análisis Sintácticolc/cei/downloads/Clases... · Compiladores e...
Transcript of Compiladores e Intérpretes Análisis Sintácticolc/cei/downloads/Clases... · Compiladores e...
Compiladores e Intérpretes
Análisis SintácticoSebastian Gottifredi
Universidad Nacional del Sur
Departamento de Ciencias e Ingeniería de la Computación
2019
1
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Contexto
• Para entender y controlar la estructura de un programa fuente
hay que analizar si sigue las reglas de sintaxis del lenguaje
• Estas reglas están expresadas en términos de tokens,
mientras que el fuente es una cadena de caracteres
• El Analizador Léxico es el encargado armar los tokens
2
Analizador
Léxico
if(id1>10)var=_“hola”;elseprint_“chau”;//fin
if ( id > num ) …
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Contexto
• La tarea del Analizador Sintáctico será controlar que la
cadena de tokens resultante del Léxico sea válida según la
sintaxis del lenguaje
• ¿Qué necesitamos para realizar esta tarea?
• La especificación las reglas de sintaxis
• Un método para identificar las cadenas de tokens válidas
3
Compiladores e Intérpretes 2018
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Reglas de Sintaxis
4
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Reglas de Sintaxis
• Para especificar la sintaxis se utilizan lenguajes formales
• ¿Qué lenguaje formal usamos?
• Por eficiencia y simplicidad tratamos de usar el más “chico” posible
• ¿Por qué no los lenguajes regulares?
• No pueden expresar anidamiento
• Por ejemplo, controlar ( ) balanceados: () (()) ((())), etc.
• Usamos los Lenguajes Libres de Contexto
5
Un autómata finito no
puede recordar la
cantidad de veces que
pasó por un estado
Los lenguajes libres de contexto son generados
por las gramáticas libres de contexto
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Reglas de Sintaxis
• Formalmente una Gramática Libre de Contexto es (T,NT,I,P)
donde:
• Un conjunto de terminales T
• Un conjunto de no terminales NT
• Un símbolo de NT inicial I
• Un conjunto de producciones P de la forma:
A → b1b2…bn
Donde los bi son terminales de T, no terminales de NT o vacío (e)
6
Los no terminales los
notamos con mayúsculas
y los terminales con
símbolos y minúsculas
Producciones Abreviadas:
A → b1…bn | g1…gn
Es equivalente a
A → b1…bn
A → g1…gn
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Reglas de Sintaxis
• Por ejemplo:
7
Expresiones Básicas
E → E + E
E → (E)
E → id
E → num
Declaración de Vars
D → T R
R → id
R → R,id
T → int
T → bool
Sentencias (if y asignación)
S → if E then S else S
S → if E then S
S → id = E
Además de estar en minúscula los
terminales los notamos en azul
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Reglas de Sintaxis
• Cadena derivable: partiendo del símbolo inicial I aplicamos
producciones y llegamos a la cadena
8
Expresiones Básicas
E → E + E
E → (E)
E → id
E → num
((id+num)+id)
Pizarrón!
Aplicar producción: reemplazar NT por la
parte derecha de una producción que lo tenga
como lado izquierdo.
Una cadena derivable en una
gramática G pertenece al
lenguaje de G.
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Reglas de Sintaxis
• Otros conceptos importantes para el análisis sintáctico:
• Forma sentencial: es una cadena de símbolos (T o NT) de la
gramática, si es un paso intermedio de una derivación valida
• Derivación a Izquierda: expandimos NT de más a la izquierda
• Derivación a Derecha: expandimos NT de más a la derecha
9
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Reglas de Sintaxis
• Una derivación puede verse como un Árbol Sintáctico
10
Pizarrón!
E(E)(E+E)((E)+E)((E+E)+E)((id+E)+E)((id+num)+E)((id+num)+id)
Terminales son hojas del árbol
No terminales son nodos internos
Símbolo Inicial es la raíz
E
( E
E
( E
E
id
+ E
num
)
+ E
id
)
El árbol sintáctico muestra claramente qué
producciones se aplicaron en una
derivación
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Reglas de Sintaxis
• Las gramáticas libres de contexto nos permiten especificar la sintaxis del lenguaje de programación
• Con la gramática podemos generar (mediante derivación o su árbol sintáctico) cadenas (programas) del lenguaje
• ¿Con esto alcanza para entender y controlar el programa?
• No… lo que falta es la estrategia que se valga de la derivación/árbol para reconocer la cadena
• Pero antes…
11
Compiladores e Intérpretes 2018
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Ambigüedad!
12
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Ambigüedad
• Una gramática es ambigua cuando para una misma cadena
existen al menos dos derivaciones diferentes
• Dificulta las estrategias de análisis sintáctico!
• Hay que dotar al analizador sintáctico de mecanismos especiales
para decidir entre alternativas sintácticamente equivalentes
• Problema Semántico: dificulta el entendimiento del
significado del programa
13
Pizarrón!
E → E + E
E → E * E
E → id
E → num
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Ambigüedad
• Soluciones: (no siempre posible!)
• Reescribir la gramática en una gramática no ambigua que genere el mismo lenguaje
• Agregar reglas especiales (precedencia / asociatividad) al analizador sintáctico
14
E → E + E
E → E * E
E → id
E → num
E → E + T
E → T
T → T * F
T → F
F → id
F → num
Compiladores e Intérpretes 2018
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Construyendo Analizadores Sintácticos
15
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Construyendo Analizadores Sintácticos
• Nosotros ya tenemos la cadena… ¿Cómo seguimos?
• A partir de la cadena reconstruir la derivación / árbol
sintáctico (implícitamente) analizando qué producciones se
hubiesen aplicado para llegar a ella.
16
((id+num)+id)Expresiones Básicas
E → E + E
E → (E)
E → id
E → num
Analizador
Sintáctico
/
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Construyendo Analizadores Sintácticos
• Estrategia exhaustiva: pruebo todas las combinaciones de
producciones que me lleven a la cadena.
• Utilizar una estrategia más sofisticada
• Elegir producciones ni bien sea conveniente a medida que
solicitamos tokens
17
Muy costoso! Es del orden de la cantidad de producciones
elevado a la cantidad de tokens de la cadena!
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Construyendo Analizadores Sintácticos
Dos principales estrategias:
• Top-Down: simula el proceso de derivación partiendo desde el
primer no terminal y trata de llegar a la cadena.
• Bottom-Up: simula el proceso de derivación de manera
inversa y reduce la cadena hasta llegar al NT inicial
18
Hoy
Compiladores e Intérpretes 2018
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Analizadores Sintácticos Descendentes
19
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Analizadores Sintácticos Descendentes
• Arrancando del no terminal Inicial reconstruir la derivación a izquierda hasta llegar a la cadena
• Intuición: Simular la derivación a izquierda
• Emular la forma sentencial de cada paso de la derivación, analizando el símbolo (T o NT) de más a la izquierda y el token actual enviado por el Léxico
• En vez de llegar a la cadena, arrancamos del NT inicial y vamos a ir consumiendo los tokens actuales del Léxico cuando corresponda y por lo tanto reconoceremos la cadena cuando lleguemos a la forma vacía
• Si el símbolo de más a la izquierda de la forma es:
• NT: elegir una producción para ese no terminal y reemplazarlo por la parte derecha
• T: ver que “matchee” con el token actual, en caso de hacerlo remover ese T y solicitar un nuevo token al Léxico
20
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
var1+56+33
Analizadores Sintácticos Descendentes
21
E → E + T
E → T
T → (E)
T → id
T → num
var1+56+33 Léxico Eid
E +Tid
E +T+Tid
T +T+Tid
id +T+Tid
+ T+T+
T +Tnum
num +Tnum
+ T+
Tnum
numnum
var1+56+33 Léxico
var1+56+33 Léxico
var1+56+33 Léxico
var1+56+33 Léxico
E → E + T
E → E + T
E → T
T → id
NextToken!Match!
Match!NextToken!
T → num
NextToken!Match!
Match!
Match!
NextToken!
T → num
SintácticoNextToken!
¿Cuando se produce un error sintáctico?¿Cómo
nos daríamos cuenta utilizando esta estrategia?
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Analizadores Sintácticos Descendentes
• La clave de la estrategia está en cómo elegir las
producciones cuando estoy analizando un NT
• Una alternativa es probar y utilizar back-tracking
• Utilizar backtracking puede ser muy costoso…
22
S → A | L
A → id = E | num
L → id (E)
Pizarrón!
Debemos llevar una traza de todos los
puntos donde se tomó una decisión
sobre qué producción utilizar!
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Analizadores Sintácticos Descendentes
• Alternativa para el backtracking: utilizar Predicción
• La intuición es utilizar los tokens actuales enviados por el léxico para elegir la producción adecuada cuando tengamos opciones
• Las producciones de la gramática deben cumplir que
• Dado un NT sus producciones tienen que tener la propiedad de que al mirar k tokens siempre podemos elegir la correcta
• Este tipo de gramáticas son llamadas LL(k)
• L: cadena de entrada se procesa de izquierda a derecha (Left scannig)
• L: derivación a izquierda (Leftmost derivation)
• k: cantidad de símbolos de predicción
23
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Analizadores Sintácticos Descendentes
• Alternativa para el backtracking: utilizar Predicción
• La intuición es utilizar los tokens actuales enviados por el léxico para elegir la producción adecuada cuando tengamos opciones
• Las producciones de la gramática deben cumplir que
• Dado un NT sus producciones tienen que tener la propiedad de que al mirar k tokens siempre podemos elegir la correcta
• Este tipo de gramáticas son llamadas LL(k)
• L: cadena de entrada se procesa de izquierda a derecha (Left scannig)
• L: derivación a izquierda (Leftmost derivation)
• k: cantidad de símbolos de predicción
24
Gramáticas
LL(1)
Gramáticas
No Ambiguas
Nosotros vamos a trabajar con
gramáticas LL(1), es decir con 1
símbolo/token de predicción
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Analizadores Sintácticos Descendentes
• Por ejemplo
• Para T siempre podemos elegir, si el token actual es:
• ( elegimos la primera
• Id la segunda
• num la tercera
25
T → (E) | id | num
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Analizadores Sintácticos Descendentes
• Hay gramáticas que tienen producciones donde no podemos
elegir, por ejemplo
• Problema: producciones con prefijo izquierdo común (o que
llevan a un prefijo izquierdo común)
26
T → (E) | id | id(E) | num
S → A | L
A → id = E | num
L → id(E) En T si el token
actual es id no sé si
elegir la 2da o la 3ra
Se puede solucionar reescribiendo la
gramática factorizando a izquierda
En S si el token
actual es id no sé si
elegir la 1ra o la 2da
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Analizadores Sintácticos Descendentes
• Factorización a Izquierda:
• Intuición: Para cada no terminal X
• Hacer factor común de producciones que comparten prefijos directamente
X → ab1 | ab2 | … | abn | g1 | … | gm
• Transformar en:
X → aR | g1 | … | gm
R → b1 | b2 | … | bn
27
T → (E) | id | id(E) | num
Pizarrón!
Las letras griegas ai y bi
representan secuencias
de T y/o NT o vacio
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Analizadores Sintácticos Descendentes
• Factorización a Izquierda:
• Intuición: Para cada no terminal X
• Si el prefijo no es directo, el proceso es más manual…
• Tratar de subir las producciones que llevan al prefijo común hasta NT, y
después hacer factor común
28
S → A | L
A → id=E | num
L → id(E)
S → id=E | num | id(E) S → id R | num
R → =E | (E)
Tener cuidado cuando subimos las producciones de un NT
X porque X podría aparecer en otras partes de la gramática
Si no somos cuidadosos podemos cambiar el lenguaje!!!
Pizarrón!
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Analizadores Sintácticos Descendentes
• Si la gramática tiene recursión a izquierda → no se cuando
cortar la recursión!
29
La recursión a
izquierda se puede
eliminar reescribiendo
la gramática!
E → E + T
E → T
…
Eid
E +Tid
E +T+Tid
T +T+Tid
E → E + T
E → E + T
E → T
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Analizadores Sintácticos Descendentes
• Eliminando la recursión a izquierda
• Directa:
• A → Aa1 | Aa2 | … | Aan | b1 | b2 | … | bm
• Transforma en:
• A → b1R| b2R| … | bmR
• R → a1 R | a2R | … | anR | e
30
E → E + T | T
T → (E) | id | num
Pizarrón!
Recursión a izq. Indirecta: para más detalles
ver “A. Aho, M. Lam, R. Sethi, J. Ullman -
Compilers: Principles Techniques and Tools”
Las letras griegas ai y bi
representan secuencias
de T y/o NT o vacio
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Analizadores Sintácticos Descendentes
• Si partimos de una gramática no factorizada y con recursión a
izquierda, es importante primero eliminar la recursión y luego
factorizar
• ¿Por qué?
• Factorizar de más (especialmente cuando el prefijo no es
directo) puede ser negativo para el compilador
• ¿Por qué?
31
Al eliminar la recursión a izquierda podemos
generar nuevas producciones no factorizadas
Muchas veces las reglas de la gramática original tienen un
significado, al modificarla al analizador semántico le puede
llegar a ser más difícil entender el programa
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Analizadores Sintácticos Descendentes
• Una vez que podemos asegurar que nuestra gramática:
• Es Libre de contexto
• No es ambigua
• No tiene recursión a izquierda
• Está factorizada a izquierda
• Construir el Analizador Sintáctico Descendente Predictivo:
• Basado en Tabla
• Recursivo
32
Hoy
E → T E’
E’→ +T E’ | e
T → (E) | id | num
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Analizadores Sintácticos Descendentes
• La tabla de análisis sintáctico descendente tiene:
• Como filas no terminales
• Como columnas todos los posibles terminales
• Cada celda [X,y] representa qué producción de X elegir cuando el
token actual es y
• ¿Qué representa que una celda [W, z] esté en blanco?
33
No hay ninguna forma de
derivar z desde W
Hay un error sintáctico en
el programa fuente!
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Analizadores Sintácticos Descendentes
• Por ejemplo:
• La entrada [T,(] representa que cuando el token actual es un “(” y estoy viendo como expandir T tengo que hacerlo usando la producción T → (E)(Comenzando a procesar una expresión parentizada)
• La entrada [E’,)] representa que cuando el token actual es un “)” y estoy viendo como expandir E’ tengo que sacarlo (termine de procesar la expresión parentizada)
34
E → T E’
E’→ +T E’ | e
T → (E) | id | num
+ ( ) id num $
E TE’ TE’ TE’
E’ +TE’ e e
T (E) id num
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Analizadores Sintácticos Descendentes
• Con tabla tenemos la forma de elegir las producciones en base al no terminal a expandir y el token actual
• Tenemos que ver como implementar la estrategia que vimos antes para los descendentes usando esa tabla
• Intuitivamente:
• Si estoy analizando un terminal lo comparo con el token actual
• Si estoy analizando un no terminal y hay una entrada con el tokenactual, paso a analizar los símbolos de la entrada en la tabla de izquierda a derecha
35
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Analizadores Sintácticos Descendentes
36
Token Actual
Símbolo de mas
a la izquierda
¿Como puedo modelar la
simulación forma sentencial?
Una Pila!
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Analizadores Sintácticos Descendentes
• Algoritmo Analizador Sintáctico Descendente con Tabla
37
pila.apilar($)pila.apilar(NT inicial)tokenAct = lexico.nextToken() while(not pila.vacia())
if (pila.tope() es terminal)if(pila.tope().nombre == tokenAct.nombre)
pila.desapilar()tokenAct = lexico.nextToken()
else Reportar error!else if(tabla[pila.tope(),tokenAct] no es vacío)
NTAct = pila.tope()pila.desapilar()foreach(simb en tabla[NTAct,tokenAct])
pila.apilar(simb) //en orden inversoelse Reportar error!
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Analizadores Sintácticos Descendentes
38
E → T E’
E’→ +T E’ | e
T → (E) | id | num
+ ( ) id num $
E TE’ TE’ TE’
E’ +TE’ e e
T (E) id num
Pizarrón!
pila.apilar($)pila.apilar(NT inicial)tokenAct = lexico.nextToken() while(not pila.vacia())
if (pila.tope() es terminal)if(pila.tope().nombre == tokenAct.nombre)
pila.desapilar()tokenAct = lexico.nextToken()
else Reportar error!else if(tabla[pila.tope(),tokenAct] no es vacío)
NTAct = pila.tope()pila.desapilar()foreach(simb en tabla[NTAct,tokenAct])
pila.apilar(simb) //en orden inversoelse Reportar error!
Compiladores e Intérpretes 2019
Departamento de Ciencias e Ingeniería de la Computación
Universidad Nacional del Sur
Analizadores Sintácticos Descendentes
• ¿Cómo construimos la tabla?...
• Eso lo vemos la próxima
39