Top Down (Descendente) José M. Castaño Teoría de Lenguajes 2012 Departamento de Computación FCEyN, UBA
Outline 1 2 3 4 5
Introducción Reconocer/Generar un lenguaje independiente de contexto (CFL). Poder expresivo CFL léxico (tokenización, lexers, scanners) sintáctico Mientras se reconoce se construye árbol (AST). Hacer algo mas...
Compiladores Léxico Sintáctico Semántico Generación Código Intermedio Optimización Generación Código
Repaso: Derivaciones αaβ αγβ dado A γ αaβ αγβ dado A γ Derivación a izquierda (leftmost derivation). reescribe el NT más a la izquierda. Derivación a derecha (rightmost derivation). reescribe el NT más a la derecha.
Árboles de Árboles que describen la derivación Las hojas corresponden a la cadena (generada/reconocida) La derivación especifica cómo generar la cadena El árbol especifica la estructura implícita.
Sintáctico Verificar que w L(G) Recuperar la estructura G define la posible secuencia de tokens que devuelve el analizador léxico
Ambigüedad G es ambigua si w L(G) con al menos dos derivaciones. No es decidible si una CFG es ambigua Existen CFL inherentemente ambiguas. Sin embargo la ambigüedad puede residir sólo en la gramática. Importancia de ambigüedad: depende de la semántica
Ambigüedad E int E + E E E E E E + E E - E E - E E + E 7 E + E 7 5 3 2 5 E + E 3 2
Resolviendo Ambigüedad Reescribir la gramática si es posible Operadores de precedencia (más allá de CF) Usado en muchos (e.g. bison)
ASTs: Abstract Syntax Trees árbol para codificar la estructura lógica (Abstracción de la sintaxis particular) Elimina símbolos inútiles Elimina nodos internos Traducción última.
Cómo se construye el AST? Acciones semánticas Asociar el código con cada producción Mientras se hace el análisis sintáctico se ejecuta el código para construir el AST El orden exacto del código depende del parsing traducción dirigida por la sintaxis
Ejemplo E T + E E T T int T int T E 1.val = T.val + E 2.val E.val = T.val T.val = int.val T 1.val = int.val T 2.val
Síntesis El parser construye la estructura de la derivación Una gramática ambigua construirá más de un árbol para un input dado. No hay algoritmos para decidir/eliminar ambigüedad Las acciones semánticas se usan para construir AST
Tipos de Descendente (Top Down) Ascendente (Bottom Up) Determinístico No-Determinístico
Ejemplo E T E T + E T int T (E) int + ( int + int )
Descendente S E$ E T E T + E T int T (E) int + ( int + int ) $
S E$ E T E T + E T int T (E) Descendente S int + ( int + int ) $
Descendente S E$ E T E T + E T int T (E) S E $ int + ( int + int ) $
int + ( int + int ) $ S E$ E T E T + E T int T (E) Descendente S E $ T + E
S E$ E T E T + E T int T (E) Descendente S E $ T + E int
S E$ E T E T + E T int T (E) Descendente S E $ T int + E T
S E$ E T E T + E T int T (E) Descendente E S $ T + E int T ( E ) int + ( int + int ) $
Descendente S E$ E T T + E T int (E) S T + E E $ int T ( E ) T + E int + ( int + int ) $
S E$ E T T + E T int (E) Descendente S E $ T + E int T ( E ) T + E int + ( int + int ) $ int T
Descendente: Inconvenientes Sin información sobre el input Hipótesis: todas las posibles Cómo restringir espacio de búsqueda? Analogía: parsing y búsqueda Explorar el grafo de las posibles derivaciones
Descendente S E$ T$ int$ (E)$ T+T+E$ (T)$ (T+E)$ int+t+e$ T+E$ T+T$ int+e$ T+(E)$ int+t$ int+(e)$ int+int$
Descendente: Búsqueda S E$ T$ int$ (E)$ T+T+E$ (T)$ (T+E)$ int+t+e$ T+E$ T+T$ int+e$ T+(E)$ int+t$ int+(e)$ int+int$
Descendente: BFS Genera un gran número de derivaciones que no van a tener relación con el input Gran consumo de memoria Gran factor de ramificación # no terminales (en forma sentencial). # producciones
Recortar Espacio de Búsqueda Necesitamos derivar w (input) Supongamos obtenemos uα Si u es prefijo de w puede ser uα w Si u no es prefijo de w no puede ser uα w Obteniendo prefijos primero se recorta el espacio de búsqueda
Derivación a izquierda Reescribe el no terminal más a la izquierda. BFS con derivación a izquierda Reduce el factor de ramificación Aumenta la probabilidad de encontrar prefijos Recorta las formas sentenciales que no son prefijos
Derivación y Recursión a izquierda S A$ A Aa Ab c INPUT: caaaa Derivaciones: S Aaa$ S Aba$ S Abb$ BFS y DI: tiempo y espacio exponencial
DI y DFS Menos espacio (memoria): un rama por vez Mejor tiempo: En muchas gramáticas corre rápido. Fácil de implementar: funciones recursivas Problemas con recursión a izquierda (RI)
DI,DFS y RI S A$ A Aa c Input: c$ S Aaaaa$ Recursión a Izquierda (RI): Un no-terminal A es recursivo a izquierda si: A Aα Es posible eliminar recursión a izquierda
BFS vs DFS con DI BFS cualquier CFG tiempo EXP espacio EXP No se usa Explora todo DFS CFG sin RI tiempo EXP espacio lineal recursivo descendente backtracking
Parsers Predictivos Puede alcanzar tiempo lineal Se puede usar una tabla para mejorar No toda gramática acepta parser predictivo Trade-off entre expresividad y performance
Lookahead Predecir próxima regla a usar Mirar los próximos tokens
Parser Descendente (Top-Down) Predictivo L: Recorre tokens de izquierda a derecha L:. Derivación a izquierda. (1): Se usan un token como lookahed. Construye derivación a izquierda En cada paso de reescritura se predice la producción en base al próximo token
Tabla en 1 S E$ 2 E int 3 E (EOE) 4 O + int ( ) + $ S 1 1 E 2 3 O 4
Tabla en 1 S E$ 2 E int 3 E (EOE) 4 O + int ( ) + $ S 1 1 E 2 3 O 4 S ( int + int )$
Tabla en 1 S E$ 2 E int 3 E (EOE) 4 O + int ( ) + $ S 1 1 E 2 3 O 4 S ( int + int )$
Tabla en 1 S E$ 2 E int 3 E (EOE) 4 O + int ( ) + $ S 1 1 E 2 3 O 4 S ( int + int )$ E$ ( int + int )$
Tabla en 1 S E$ 2 E int 3 E (EOE) 4 O + int ( ) + $ S 1 1 E 2 3 O 4 S ( int + int )$ E$ ( int + int )$
Tabla en 1 S E$ 2 E int 3 E (EOE) 4 O + int ( ) + $ S 1 1 E 2 3 O 4 S ( int + int )$ E$ ( int + int )$ ( E O E )$ ( int + int )$
Tabla en 1 S E$ 2 E int 3 E (EOE) 4 O + int ( ) + $ S 1 1 E 2 3 O 4 S ( int + int )$ E$ ( int + int )$ ( E O E )$ ( int + int )$
Tabla en 1 S E$ 2 E int 3 E (EOE) 4 O + int ( ) + $ S 1 1 E 2 3 O 4 S ( int + int )$ E$ ( int + int )$ ( E O E )$ ( int + int )$ E O E )$ int + int )$
Tabla en 1 S E$ 2 E int 3 E (EOE) 4 O + int ( ) + $ S 1 1 E 2 3 O 4 S ( int + int )$ E$ ( int + int )$ ( E O E )$ ( int + int )$ E O E )$ int + int )$ int O E ) $ int + int )$
Tabla en 1 S E$ 2 E int 3 E (EOE) 4 O + int ( ) + $ S 1 1 E 2 3 O 4 S ( int + int )$ E$ ( int + int )$ ( E O E )$ ( int + int )$ E O E )$ int + int )$ int O E ) $ int + int )$ O E ) $ + int )$
Tabla en 1 S E$ 2 E int 3 E (EOE) 4 O + int ( ) + $ S 1 1 E 2 3 O 4 S ( int + int )$ E$ ( int + int )$ ( E O E )$ ( int + int )$ E O E )$ int + int )$ int O E ) $ int + int )$ O E ) $ + int )$
Tabla en 1 S E$ 2 E int 3 E (EOE) 4 O + int ( ) + $ S 1 1 E 2 3 O 4 S ( int + int )$ E$ ( int + int )$ ( E O E )$ ( int + int )$ E O E )$ int + int )$ int O E ) $ int + int )$ O E )$ + int )$ + E ) $ + int )$
Tabla en 1 S E$ 2 E int 3 E (EOE) 4 O + int ( ) + $ S 1 1 E 2 3 O 4 S ( int + int )$ E$ ( int + int )$ ( E O E )$ ( int + int )$ E O E )$ int + int )$ int O E ) $ int + int )$ O E )$ + int )$ + E ) $ + int )$ E ) $ int )$
Tabla en 1 S E$ 2 E int 3 E (EOE) 4 O + int ( ) + $ S 1 1 E 2 3 O 4 S ( int + int )$ E$ ( int + int )$ ( E O E )$ ( int + int )$ E O E )$ int + int )$ int O E ) $ int + int )$ O E )$ + int )$ + E ) $ + int )$ E ) $ int )$ int ) $ int )$
Tabla en 1 S E$ 2 E int 3 E (EOE) 4 O + int ( ) + $ S 1 1 E 2 3 O 4 S ( int + int )$ E$ ( int + int )$ ( E O E )$ ( int + int )$ E O E )$ int + int )$ int O E ) $ int + int )$ O E )$ + int )$ + E ) $ + int )$ E ) $ int )$ int ) $ int )$ ) $ )$
Tabla en 1 S E$ 2 E int 3 E (EOE) 4 O + int ( ) + $ S 1 1 E 2 3 O 4 S ( int + int )$ E$ ( int + int )$ ( E O E )$ ( int + int )$ E O E )$ int + int )$ int O E ) $ int + int )$ O E )$ + int )$ + E ) $ + int )$ E ) $ int )$ int ) $ int )$ ) $ )$ $ $
Algoritmo Dada una tabla T y un input w Inicialize la Pila con S y c la posición inicial de w Repetir hasta que en la pila quede solo $: Sea c el caracter de w if el tope de la pila es un terminal t: if t c, rechazar. else t = c consumir ambos. else el tope de la pila es NT A if T [A, c] no está definido rechazar else reemplazar tope de pila con T [A, c]
Construccion de T Concepto: El próximo caracter c, define la regla correspondiente, por lo tanto debe ser una regla que empieza con ese caracter. T [A, c], debe estar en la tabla si A A 1 α y A 1 cβ
El conjunto FIRST (PRIMEROS) FIRST define el conjunto terminales que están al inicio de una cadena derivada por un NT A FIRST (A) = {t A tw} Inicial: FIRST (A) = {t A tw} FIRST se obtiene computando la clausura transitiva por cada A Bβ compute iterativamente: FIRST (A) = FIRST (A) FIRST (B) Si A A 1 A 2... A n por A i, A i+1 mientras A i ɛ FIRST (A i ), contiene a FIRST (A i+1 )
El conjunto SIGUIENTES (FOLLOW) Intuición: qué terminales pueden seguir a un NT FOLLOW (A) = {t B uatz} Usada cuando hay producciones λ.
El conjunto SIGUIENTES (FOLLOW) Para todas las producciones B αaβ entonces: FOLLOW(A) = FIRST(β) Si β ɛ FOLLOW(A) = FOLLOW (A) FOLLOW (B)
Obtener FIRST (A) y FOLLOW (A) para todos los NT, A. Para cada regla A w y cada t FIRST (w), T [A, t] = w. Para cada regla A w donde ɛ FIRST (w), T [A, t] = w para cada t t FOLLOW (A)
Definiciones Definition (Símbolo activo) Dada una gramática G = V N, V T, P, S el no-terminal A es activo sii A G w con w V T. Definition (Símbolo alcanzable) Dada una gramática G = V N, V T, P, S el no-terminal A es alcanzable sii S G αaβ con α, β (V T V N ). Definition (Gramática reducida) Una gramática G = V N, V T, P, S es una gramática reducida sii todos sus no-terminales son activos y alcanzables
Definiciones Dada una gramática G = V N, V T, P, S y una cadena α (V T V N ), definimos: Definition (FIRST(α)) FIRST(α) = { z Σ : z = a V T y α G aw para alguna w V T } o z = λ y α λ. G FIRST K (α) genera hasta k terminales que se derivan de α Definition (FIRST K (α)) FIRST K (α) = { z Σ : z < k y α G z, o z = k y α G zx para alguna x V T }.
Gramática LL(k) Definition (Gramática LL(k)) Una gramática G = V N, V T, P, S es LL(k) sii, siempre que existan dos derivaciones a izquierda S L waα L wβα wx S L waα L wγα wy en las que FIRST K (x) = FIRST K (y), es cierto que β = γ.
Gramática LL(k) Theorem Una gramática G = V N, V T, P, S es LL(k) sii, para todo par de producciones A β y A γ, con β γ, FIRST K (βα) FIRST K (γα) = φ, para todos los waα tales que S waα. L Proof.. : si x V + T, x FIRST K (βα) FIRST K (γα) φ entonces, S L waα L wβα wxy S L waα L wγα wxz, donde, si x < k entonces y = z = λ.
Gramática LL(k) Proof. : si G no es LL(k) entonces existirán dos derivaciones S L waα L wβα wx S L waα L wγα wy tales que z V + T, z k y z = FIRST K (x) = FIRST K (y), por lo que z FIRST K (βα) y z FIRST K (γα) y por lo tanto FIRST K (βα) FIRST K (γα) φ
SIGUIENTES (FOLLOW) Definition (FOLLOWS K (A)) Dada una gramática G = V N, V T, P, S y un no-terminal A V N, definimos FOLLOWS K (A) como FOLLOWS K (A) = { } z : S αaγ y z FIRST K (γ). FOLLOW K (A) genera hasta k terminales que siguen a A. Theorem Una gramática G = V N, V T, P, S es sii, para todo par de producciones A β y A γ, con β γ, FIRST(β FOLLOWS(A)) FIRST(γ FOLLOWS(A)) = φ.
Gramática Definition (Gramática simple) Una gramática G = V N, V T, P, S sin reglas borradoras es simple sii todas sus producciones son de la forma A a 1 β 1... a k β k donde a 1,..., a k V T y β 1,..., β k (V T V N ).
Gramática Definition (Símbolos directrices) Definimos la función SD : P P (V T ) de la siguiente manera { FIRST(β) si β no es anulable SD (A β) = FIRST(β) FOLLOWS(A) si β es anulable
no es RI Theorem Para toda gramática G = V N, V T, P, S reducida (o sea, una en las cual todos los no-terminales son alcanzables y activos), si G es independiente de contexto y, entonces G no es recursiva a izquierda.
Demostración no es RI Proof. Supongamos que la gramática G es recursiva a izquierda. Entonces, A V N tal que A + Aα. Como G es reducida, entonces el no-terminal A generará alguna cadena de terminales. Consideraremos dos casos: uno en el que A es capaz de generar una cadena no nula, o sea tτ V + T tal que, A + L tτ, y el otro, en el que lo único que A puede generar es la cadena nula: A + L λ.
no es RI Proof. En el primer caso, tenemos que A L B 1 β 1 L... L B i β i L γβ i L... L tτ L B i+1 δβ i L... L Aα L... L tτα Ambas derivaciones desde A coinciden hasta B i β i, allí B i es expandido mediante B i B i+1 δ, continuando por la rama superior, o mediante B i γ, continuando por la rama inferior. En la rama superior se considera el caso recursivo y en la rama inferior el caso no-recursivo De aquí surge que t SD (B i B i+1 δ) y t SD (B i γ), y por lo tanto, G no es
no es RI Proof. Segundo caso, como A solamente genera λ, tenemos que L B i+1 δβ i L... L Aα L... L λ A L B 1 β 1 L... L B i β i L γβ i L... L λ donde las ramas superior e inferior son análogas a las del primer caso. Aquí se ve que B i+1 δβ i+1 y γβ i son anulables, (o sea, B i+1 δβ i+1 L λ y γβ i L λ) y por lo tanto B i+1 δ y γ también son anulables, respectivamente, y por lo tanto FOLLOWS(B i ) SD(B i B i+1 δ) y FOLLOWS(B i ) SD(B i γ), por lo que SD(B i B i+1 δ) SD(B i γ). Entonces, en este caso, la gramática G tampoco es.
Primeros X Algorithm 1 Algoritmo para obtener FIRST(X) 1: if X V T then 2: FIRST(X) = {X} 3: else 4: FIRST(X) 5: for todas las producciones X Y 1 Y 2... Y k P do 6: FIRST(X) FIRST(X) FIRST(Y 1 ) 7: i = 2 8: while i k Y 1 Y 2... Y i 1 G λ do 9: FIRST(X) FIRST(X) FIRST(Y i ) 10: i = i + 1 11: end while 12: if X λ then G 13: FIRST(X) FIRST(X) {λ} 14: end if 15: end for 16: end if
Primeros X n Algorithm 2 FIRST(X 1 X 2... X n ), con n > 1 1: FIRST(X 1 X 2... X n ) FIRST(X 1 ) {λ} 2: i=2 3: while i n X 1 X 2... X i 1 G λ do 4: FIRST(X 1 X 2... X n ) FIRST(X 1 X 2... X n ) (FIRST(X i ) {λ}) 5: i = i + 1 6: end while 7: if X 1 X 2... X n G λ then 8: FIRST(X 1 X 2... X n ) FIRST(X 1 X 2... X n ) {λ} 9: end if
FOLLOWS(B) Algorithm 3 FOLLOWS(B), para todo B V N 1: FOLLOWS(S) {$} 2: for todos los A V N diferentes de S do 3: FOLLOWS(B) 4: end for 5: for todas las A αbβ P do 6: FOLLOWS(B) FOLLOWS(B) (FIRST(β) {λ}) 7: end for 8: repeat 9: for todos los B V N tales que A αbβ P β G λ do 10: FOLLOWS(B) FOLLOWS(B) FOLLOWS(A) 11: end for 12: until ningún FOLLOWS(B) cambie más
Símbolos Activos Algorithm 4 Algoritmo para obtener el conjunto de símbolos activos de una gramática 1: Act 2: repeat 3: for cada producción A α P do 4: if α (Act V T ) then 5: Act Act {A} 6: end if 7: end for 8: until Act no cambie
Símbolos Alcanzables Algorithm 5 Algoritmo para obtener el conjunto de símbolos alcanzables de una gramática 1: Alc {S} 2: repeat 3: for cada producción A α para un no-terminal A Alc do 4: Incorporar los no-terminales presentes en α al conjunto Alc 5: end for 6: until Alc no cambie
Eliminación de RI Algorithm 6 Eliminación de la recursión a izquierda 1: Ordenar los no-terminales V N = {A 1,..., A n } 2: for i 1, n do 3: for j 1, i 1 do 4: Reemplazar producciones A i A j α por producciones A i β 1 α... β m α donde A j β 1... β m son todas las producciones de A j. 5: end for 6: Reemplazar las producciones del no-terminal A i, A i A i α i... A i α m β 1... β p (en las cuales, ningún β j comienza con un A k si k i) por A i A i β 1... β p β 1 A i... β p A i α 1... α m α 1 A i... α m A i 7: end for
de Tabla Algorithm 7 para analizador predictivo descendente 1: for cada producción A α P do 2: for cada a FIRST(α) do 3: M[A, a] M[A, a] {A α} 4: end for 5: if λ FIRST(α) then 6: for cada b FOLLOWS(A) do 7: M[A, b] M[A, b] {A α} 8: end for 9: if $ FOLLOWS(A) then 10: M[A, $] M[A, $] {A α} 11: end if 12: end if 13: end for 14: for toda posición M[A, a] == do 15: M[A, a] error 16: end for
Analizador Algorithm 8 Analizador predictivo descendente con tabla 1: Pila = $S, PI = p.i. de w, X tope pila, a PI. 2: repeat 3: if X (V T {$}) then 4: if X == a then 5: Extraer X y avanzar PI 6: else 7: devolver error 8: end if 9: else 10: if M[X, a] == X Y 1 Y 2... Y k then 11: Extraer X 12: Apilar Y k Y k 1... Y 1 (con Y 1 en el tope) 13: Emitir el nro. de X Y 1 Y 2... Y k 14: else 15: devolver error 16: end if 17: end if
Gramáticas no : RI A Aa b FIRST (A) = {b} No se puede hacer la tabla. Por que? Conflicto FIRST/FIRST
Eliminación de RI En general, se puede convertir recursión a izquierda en recursión a derecha. A Aa c A ca + A ca A aa ɛ A A A a c A A a a A c a
Problemas con S E$ E T E T + E T int T (E) S E $ FIRST(E) = { int, ( } FIRST(T) = { int, (}
Factorización a izquierda S E$ E T E T +E T int T (E) S E$ E T Y T int T (E) Y +E ɛ
Un conjunto de lenguajes de programació usaron/usan : LISP Python JavaScript
es sencillo Rápida implementación con una tabla Puede implementarse con Descendiente Recursivo Una función para cada no terminal Las funciones se llaman en base al token de lookahead Es eficiente: parse en O(n f (G)) Donde f (G), es una función sobre G
Conclusiones TD parsing sigue las derivaciones desde el símbolo inicial BFS por izquierda nos muestra el espacio a recorrer (no-determinístico). DFS por izquierda nos muestra como se puede recortar (no-determinístico). recorre de izquierda a derecha usa DI, parsing determinístico. FIRST define conjunto de terminales que son primer símbolo en la derivación. FOLLOW define el conjunto de terminales que siguen a un NT en una derivación. RI y FI, son los problemas pra.