Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

Nota 04

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 6

Análisis Sintáctico: Parser top-down

Compiladores 2022-2, Nota de clase 3


Lourdes Del Carmen González Huesca

24 de marzo de 2022
Facultad de Ciencias UNAM

Esta clase de parsers realizan un análisis para reconocer una cadena al encontrar una derivación más a la
izquierda y construir el árbol desde la raíz y hacia las hojas, creando los nodos en preorden.

Analizador LL
El parsing recursivo descendente es un método top-down en el cual un conjunto de procedimientos recursivos
es usado para procesar el programa. Usualmente se realiza un retroceso o backtracking para encontrar la
producción correcta. Cada uno de ellos está relacionado con cada símbolo no-terminal de la gramática.
Un caso particular es el parsing predictivo que analiza un número fijo k de símbolos subsecuentes por
adelantado. Dependiendo del número k se pueden clasificar las gramáticas en clases de parsers denotados por
LL(k). Analizaremos la clase (decidable) LL(1), que sólo usa un símbolo (el siguiente token).
El nombre LL corresponde a un análisis de izquierda a derecha (Left-to-right) y que construye una derivación
por la izquierda (Leftmost).
Definición 1 (Gramática LL(1)). Decimos que una gramática pertenece a la clase LL(1) si para cualquier
símbolo terminal a la izquierda de las producciones con más de dos reglas, los conjuntos de sus derivaciones son
disjuntas, es decir para cada producción A Ñ α | β con α ‰ β:
1. Firstpαq X Firstpβq “ H

2. Si β Ñ˚ ε entonces Firstpαq X FollowpAq “ H

3. Analogamente si α Ñ˚ ε.
Ninguna gramática recursiva por la izquierda (con producciones de tipo E Ñ Ew) o ambigua puede ser
LL(1). Para algunas gramáticas que no pertenecen a esta clase es posible encontrar una gramática equivalente
que sí lo sea usando transformaciones como factorización, eliminar producciones épsilon y recursión. Sin embargo,
existen grámaticas que no pueden transformarse a una LL(1) como es el caso de la gramática con dangling-else.
Un analizador predictivo se basa en la idea de elegir la producción A Ñ α si el siguiente token a pertenece
a Firstpαq, por lo que reune toda la información necesaria en una tabla auxiliar.

Tabla de análisis
Una tabla de análisis predictivo M rX, as, donde X es un símbolo no terminal y a es un símbolo terminal,
indica qué producción debe usarse si se quiere derivar una cadena aω a partir de X. El procedimiento para
construir dicha tabla se muestra a continuación considerando que existen producciones epsilon en la gramática:
Para cada producción X Ñ α:

1. Para cada terminal a en FirstpXq agregar la entrada M rX, as “ X Ñ α

1
2. Si ε P Firstpαq entonces:
Para cada b P FollowpXq agregar M rX, bs “ X Ñ α
Si ε P Firstpαq y # está en FollowX entonces:
Agregar M rX, #s “ X Ñ α

Las entradas vacías de la tabla, indican que no existe una producción para derivar la entrada. Se puede
construir la tabla de análisis para cualquier gramática, si es LL(1) cada entrada de la tabla tiene una sola
producción o está vacía; si la gramática es recursiva o ambigua, tendrá entradas con múltiples producciones.

Algoritmo para LL
Para reconocer una cadena de una gramática LL(1) manejamos una pila cuyo estado inicial contiene S y el
fondo de la pila es el símbolo de fin de cadena. El algoritmo construye una derivación izquierda y/o un árbol de
sintaxis como objeto final. En cada paso se puede observar la derivación o la parte del árbol con w la (sub)cadena
que se ha reconocido hasta el momento, la pila contiene una secuencia de símbolos α tal que S Ñ˚ wα. En
cada iteración se considera el tope de la pila y el siguiente token, si se tiene un símbolo no terminal entonces se
consulta la tabla para decidir qué producción utilizar, en otro caso se comparan los símbolos (match).

Se inicia con a el primer símbolo de ω y S# el tope de la pila.

Mientras la pila tenga elementos:


sea X el tope de la pila, a el token actual y M la tabla de análisis.

• Si X es un símbolo terminal y X ““ a entonces se saca a X del tope de la pila y


se obtiene el siguiente token
• Si X es un símbolo terminal y X! “ a entonces hay un error
• Si X es un símbolo no-terminal entonces:
˝ M rX, as contiene una producción X Ñ Y1 ..Yk
se saca a X del tope de la pila y se agrega a Yk ...Y1 donde Y1 es el nuevo tope;
se construye el nodo en el árbol de derivación o se registra la producción como parte de la
derivación
˝ M rX, as no contiene una producción entonces hay un error

A continuación el pseudoalgoritmo:
let a be the first symbol of w ;
let X be the top stack symbol ;
while ( X != # ) /* stack is not empty */
{
if ( X = a )
then pop the stack and let a be the next symbol of w ;
else if ( X is a terminal ) error () ;
else if ( M [ X ; a ] is an error entry ) error () ;
else if ( M [ X ; a ] = X -> Y1Y2 ... Yk ) ;
{
output the production X -> Y1Y2 ... Yk ;
pop the stack ;
push Yk , Yk -1 , ... , Y1 onto the stack , with Y1 on top ;
}
let X be the top stack symbol ;
}

2
Ejemplo 1 (Lenguaje de expresiones aritméticas sencillas).
Construcción de la tabla de parsing predictivo

E Ñ T E1
E1 Ñ `T E 1 E1 Ñ ε
T Ñ FT1 T Ñ F
T1 Ñ ˚F T 1 T1 Ñ ε
F Ñ pEq F Ñ id

E E1 T T1 F
First tp, idu t`, εu tp, idu t˚, εu tp, idu
Follow t#, qu t#, qu t`, #, qu t`, #, qu t˚, `, #, qu

Ejecución del algoritmo para input: id ` id ˚ id

Derivación por la izquierda (resultado del almacenamiento de las reglas en la pila) y que también puede
generar un parse-tree que se construye por los nodos en preorden:

E Ñ T E 1 Ñ F T 1 E 1 Ñ idT 1 E 1 Ñ idεE 1 Ñ id ` T E 1 Ñ id ` F T 1 E 1 Ñ id ` idT 1 E 1 Ñ


id ` id ˚ F T 1 E 1 Ñ id ` id ˚ idT 1 E 1 Ñ id ` id ` ˚idε Ñ id ` id ` ˚idεε “ id ` id ˚ id

3
Parsers LL(k)
La clase de parsers fuertes LL(k) (SLL(k)) incluye aquellos analizadores donde los símbolos leidos por
adelantado son suficientes para seleccionar la producción correcta en la gramática para construir el parse tree.
Este tipo de analizadores generaliza el proceso anterior al considerar k símbolos por adelantado. Crea y usa
una tabla de predicción de la misma forma salvo que esta tabla tiene mayor tamaño al considerar las columnas
como cadenas y no sólo un símbolo.
Veamos las generalizaciones de la teoría de cadenas para este proceso así como las generalizaciones de las
funciones First y Follow:
Dado un alfabeto Σ y una palabra ω “ a1 a2 . . . an con ai P Σ se define

k-prefijo de ω como
si n ď k
"
a1 a2 . . . an
ω|k “
a1 a2 . . . ak en otro caso
Ťk
k-concatenación dk : Σ‹ ˆ Σ‹ Ñ Σďk , con Σďk “ i“0 Σ
i, como

a dk b “ pabq|k

Definición 2 (Función Firstk ). Función que calcula el conjunto de prefijos de tamaño k que se pueden
derivadar de α:
Firstk pαq “ tω|k | α Ñ‹ ωu

ď
Firstk pAq “ tFirstk pA1 q dk ¨ ¨ ¨ dk Firstk pAn q | A Ñ A1 A2 . . . An , Ai varu

Definición 3 (Función Followk ). La función Followk para un símbolo no-terminal X calcula el conjunto
de símbolos terminales de a lo más tamaño k que pueden seguir directamente a la variable X:

Followk pXq “ tω P Σ‹ | S Ñ‹ vXγ, y ω P Firstk pγ#qu

Definición 4. Decimos que una gramática pertenece a la clase SLL(k) si para cualquier símbolo no-terminal
con más de dos reglas de producción A Ñ β | γ con β ‰ γ siempre sucede que

Firstk pβq dk Followk pAq X Firstk pγq dk Followk pAq “ H

Toda gramática LL(1) es una gramática SLL(1). Pero para k ą 1, una gramática LL(k) no necesariamente
es también una SLL(k), dado que el conjunto Followk pAq contiene todas las palabras que se pueden generar
desde A hacia la izquierda.
La tabla M rX, ωs tiene por renglones las variables de la gramática y como columnas cadenas de terminales
de longitud k, almacena producciones que se determinan de la siguiente forma:
Sean Y Ñ α1 | α2 | ¨ ¨ ¨ | αr las producciones de la gramática correspondientes a la variable Y .
Dado que los conjuntos Firstk pαi q dk Followk pY q son disjuntos en este tipo de gramáticas entonces

para cada ω P Firstk pα1 qdk Followk pY qYFirstk pα2 qdk Followk pY qY¨ ¨ ¨YFirstk pαr qdk Followk pY q
se tiene que
M rX, ωs “ αi si y sólo si ω P Firstk pαi q dk Followk pY q

en otro caso se deja la entrada de la tabla vacía o se incluye una rutina para manejar el error

La construcción de parsers LL(k) se puede restringir a gramáticas SLL(k). La coincidencia de los símbolos
leidos por adelantado y las columnas en la tabla de parsing hacen que la regla de producción sea única. Pero
esto hace que la tabla pueda ser muy grande para k ą 1, por lo que los parsers k-predictivos son evitados en la
práctica y se usan más los de tipo LL(1) que son SLL(1).

4
Manejo de errores
Durante el análisis sintáctico pueden surgir errores como un ; faltante o algunos paréntesis no balanceados.
El objetivo es detectar el error y reportarlo de forma clara; una vez hecho esto, el analizador debe ‘recuperarse’
y resumir o restaurar el análisis tan rápido como sea posible sin agregar complejidad al proceso. A continuación
se describen dos estrategias.

Modo pánico Si dado el símbolo no-terminal X, la entrada M rX, as está vacía, el modo pánico consiste en
descartar tokens hasta que uno pertenezca al conjunto SY N CHpXq. La decisión de qué elementos pertenecen
a SY N CHpXq puede guiarse por las siguientes estrategias:

1. Incluir elementos en FollowpXq. Si encontramos un token que pertenzca a este conjunto, podemos
saltarnos la derivación de X y resumir el análisis. El error a reportar involucra la derivación de X: “Se
espera un ...”

2. En el caso de lenguajes que definen una jerarquía, se pueden incluir símbolos de la jerarquía superior.

3. Incluir elementos en FirstpXq. Si encontramos un token que pertenezca a este conjunto, podemos resumir
el análisis en ese estado. Se reporta el error con los tokens que fueron descartados: “Tokens inesperados
...”

4. Si X puede derivar la cadena vacía, usar esa producción.

Cuando el tope de la pila es un símbolo terminal que no coincide con el siguiente token, la estrategia más
simple es sacarlo de la pila y marcar un error de inserción: “Se esperaba token ...”
Por ejemplo, para la gramática de expresiones aritméticas, la cadena n ` ˚n es errónea:

reconocido stack entrada


ε E# n ` ˚n#
ε T E1# n ` ˚n#
ε F T 1E1# n ` ˚n#
ε nT 1 E 1 # n ` ˚n#
n T 1E1# ` ˚ n#
n εE 1 # ε ` ˚n#
n E1# ` ˚ n#
n `T E 1 # ` ˚ n#
n` T E1# ˚n# error
˚ no pertenece ni a Follow ni a First
descartar el token con mensaje
error : “Simbolo * inesperado”
n` T E1# n# n está en el First(T), resumir/restaurar parser
n` F T 1E1# n#
.. .. ..
. . .
n` n# n#
n`n # # pila vacía, termina el análisis

Recuperación a nivel de frase En esta estrategia las entradas vacías de la tabla de análisis son llenadas
con funciones que manipulen la pila y/o la entrada para corregir el estado actual y resumir el análisis. En el
ejemplo anterior, la entrada de la tabla M rT, ˚s puede tener una función que inserte un token de identificador
n con el objetivo de reconocer la expresión completa:

5
reconocido stack entrada
.. .. ..
. . .
n` T E1# ˚n# error : T esperaba un identificador
M rT, ˚s crea un identificador nuevo n
se reporta el identificador
n` T E1# n ˚ n# resume análisis
n` F T 1E1# n ˚ n#
n` nT 1 E 1 # n ˚ n#
n`n T 1E1# ˚n#
n`n ˚F T 1 E 1 # ˚n#
n ` n˚ F T 1E1# n#
n ` n˚ nT 1 E 1 # n#
n`n˚n T 1E1# #
n`n˚n E1# #
n`n˚n # #
Cabe resaltar que esta estrategia deriva una cadena distinta al corregirla, sin embargo, las funciones a usar
deben ser diseñadas con cuidado para evitar caer en un loop infinito cuando no se descarta un elemento de la
pila o entrada. La implementación de las funciones es decisión del diseñador del compilador y está fuertemente
relacionada a la gramática.

Referencias
[1] Alfred V. Aho, Monica S. Lam, Ravi Sethi, and Jeffrey D. Ullman. Compilers, Principles, Techniques and
Tools. Pearson Education Inc., Second edition, 2007.

[2] Jean-Christophe Filliâtre. Curso Compilation (inf564) école Polytechnique, Palaiseau, Francia. http:
//www.enseignement.polytechnique.fr/informatique/INF564/, 2018. Material en francés.

[3] Dick Grune and Ceriel J. H. Jacobs. Parsing Techniques: A Practical Guide. Ellis Horwood, USA, 1990.

[4] Dick Grune, Kees van Reeuwijk, Henri E. Bal, Ceriel J.H. Jacobs, and Koen Langendoen. Modern Compiler
Design. Springer Publishing Company, Incorporated, 2nd edition, 2012.

[5] Frank Pfenning. Notas del curso (15-411) Compiler Design. https://www.cs.cmu.edu/~fp/courses/
15411-f14/, 2014.

[6] François Pottier. Presentaciones del curso Compilation (inf564) École Polytechnique, Palaiseau, Francia.
http://gallium.inria.fr/~fpottier/X/INF564/, 2016. Material en francés.

[7] Michael Lee Scott. Programming Language Pragmatics. Morgan-Kauffman Publishers, Third edition, 2009.

[8] Yunlin Su and Song Y. Yan. Principles of Compilers, A New Approach to Compilers Including the Algebraic
Method. Springer-Verlag, Berlin Heidelberg, 2011.

[9] Reinhard Wilhelm, Helmut Seidl, and Sebastian Hack. Compiler Design. Springer-Verlag Berlin Heidelberg,
2013.

[10] Steve Zdancewic. Notas del curso (CIS 341) - Compilers, Universidad de Pennsylvania, Estados Unidos.
https://www.cis.upenn.edu/~cis341/current/, 2018.

También podría gustarte