Documentos de Académico
Documentos de Profesional
Documentos de Cultura
ANALIZADOR
SINTÁCTICO
1. INTRODUCCIÓN
El analizador léxico tiene como entrada el código fuente en forma de una sucesión de caracteres.
El analizador sintáctico tiene como entrada los lexemas que le suministra el analizador léxico y
su función es comprobar que están ordenados de forma correcta (dependiendo del lenguaje que
queramos procesar). Los dos analizadores suelen trabajar unidos e incluso el léxico suele ser una
subrutina del sintáctico. Al analizador sintáctico se le suele llamar párser. El párser genera de
manera teórica un árbol sintáctico. Este árbol se puede ver como una estructura jerárquica que
para su construcción utiliza reglas recursivas. La estructuración de este árbol hace posible
diferenciar entre aplicar unos operadores antes de otros en la evaluación de expresiones. Es
decir, si tenemos esta expresión en Java:
x = x * y – 2;
El valor de x dependerá de si aplicamos antes el operador producto que el operador suma. Una
manera adecuada de saber qué operador aplicamos antes es elegir qué árbol sintáctico generar
de los dos posibles.
En resumen, la tarea del analizador sintáctico es procesar los lexemas que le suministra el
analizador léxico, comprobar que están bien ordenados, y si no lo están, generar los informes
de error correspondientes. Si la ordenación es correcta, se generará un árbol sintáctico teórico.
Controla el flujo de tokens reconocidos por parte del analizador léxico y comprueba si la cadena
pude ser generada por la gramática del lenguaje fuente.
Informa de la naturaleza de los errores sintácticos que encuentra e intenta recuperarse de ellos
para continuar la compilación.
3. DISEÑO DE GRAMATICA
Para que un analizador sintáctico funcione, debemos especificar el lenguaje que debe poder
leer. Para especificar este lenguaje, debemos representarlo con unas reglas únicas y bien
formadas de manera que el párser (Analizador sintáctico) funcione de una manera bien definida.
Es decir, el lenguaje debe ser formal (tener unas reglas bien definidas). A estas reglas se le llama
gramática. Por lo tanto, el primer paso para poder implementar un analizador sintáctico es
definir la gramática que debe ser capaz de analizar.
La gramática que acepta el analizador sintáctico es una gramática de libre contexto, una
gramática de libre contexto es una especificación para la estructura sintáctica de un lenguaje de
programación. Una especificación así es muy similar a la especificación de la estructura léxica de
un lenguaje utilizando expresiones regulares(patrón), excepto que una gramática libre de
contexto involucra reglas de recursividad. Una gramática describe de forma natural la estructura
jerárquica de muchas construcciones de los lenguajes de programación. Una gramática libre de
contexto G queda definida por una tupla de cuatro elementos (N, T, P, S) donde:
Esta gramática reconoce expresiones aritméticas con los operadores de suma y producto.
Vemos también que hay tres reglas y el axioma inicial es el antecedente de la primera regla de
producción. E es recursiva porque se presenta en ambos lados de la producción.
Ahora veamos con un ejemplo cómo podemos crear una gramática. Para esto recodemos como
se declara una variable en java:
int a;
double b;
DERIVACIÓN: Aplicación de las reglas de producciones de una gramática para obtener una
cadena de terminales.
Una regla de producción puede considerarse como equivalente a una regla de reescritura,
donde el no terminal de la izquierda es sustituida por la pseudocadena del lado derecho de la
producción. Podemos considerar que una pseudocadena es cualquier secuencia de terminales
y/o no terminales.
𝐸 → 𝐸 + 𝐸 ȁ𝐸 ∗ 𝐸 ȁ𝑛𝑢𝑚ȁ𝑖𝑑ȁ(𝐸)
A partir de estas derivaciones se puede construir sus árboles sintácticos. Pero hay cosas en que
cada posible derivación dará lugar a un árbol sintáctico diferente. Esto significa que la gramática
es ambigua.
Veamos un ejemplo. Supongamos que tenemos la gramática anterior y queremos procesar estos
tokens:
La raíz del árbol es el axioma inicial y, según nos convenga, lo dibujaremos en l a cima o en el
fondo del árbol. Como nodos internos del árbol, se sitúan los elementos no terminales de las
reglas de producción que vayamos aplicando, y cada uno de ellos poseerá tantos hijos como
símbolos existan en la parte derecha de la regla aplicada.
Supongamos que queremos expresar la estructura de un número entero compuesto por su signo
seguido por un número indeterminado de números entre el 0 y el 9. Lo podríamos expresar con
estas reglas:
𝐸𝑛𝑡𝑒𝑟𝑜 → 𝑠𝑖𝑔𝑛𝑜
Donde dígito representa cualquiera de los números del 0 al 9. Mediante esas dos reglas
podemos representar la estructura de cualquier número entero sea de la longitud que sea.
𝐴→𝑎𝐴𝑏
B. LA AMBIGÜEDAD
Cuando una gramática contiene una cadena para la que hay más de un árbol de análisis
sintáctico se dice que es ambigua. Debido a que una gramática de estas características permite
que a partir del mismo código fuente se puedan obtener diferentes códigos intermedios, no es
válida para construir un compilador (habría que ayudar con otras técnicas más complicadas).
Si una gramática tiene alguna de estas características, podremos afirmar que es ambigua:
𝑆→𝐴
ANALIZADOR SINTÁCTICO
𝑆→𝑎
𝐴→𝑆
𝐸 → 𝐸….𝐸
Gramáticas con unas reglas que ofrezcan caminos alternativos entre dos puntos.
Por ejemplo:
𝑆→𝐵
𝑆→𝐶
𝐵−𝐶
Por ejemplo:
𝑆 →𝐴𝐵𝑆
𝑆→𝑆
𝐴 → 𝑎 ȁ€
𝐵 → 𝑏 ȁ€
Por ejemplo:
𝐴→𝐴𝐵
𝐴→𝑎ȁ€
𝐵 → 𝑏 ȁ𝑎ȁ€
ANALIZADOR SINTÁCTICO
SUPRESION DE LA AMBIGÜEDAD
A veces, una gramática ambigua se puede reescribir para eliminar la ambigüedad. Como
ejemplo, se eliminará la ambigüedad de la siguiente gramática con “else ambiguo”:
𝑃𝑅𝑂𝑃 → 𝑖𝑓 𝐸𝑋𝑃𝑅 𝑡ℎ𝑒𝑛 𝑃𝑅𝑂𝑃 ห 𝑖𝑓 𝐸𝑋𝑃𝑅 𝑡ℎ𝑒𝑛 𝑃𝑅𝑂𝑃 𝑒𝑙𝑠𝑒 𝑃𝑅𝑂𝑃 ห 𝑜𝑡𝑟𝑎
Aquí, “otra” representa cualquier otra proposición. Esta gramática es ambigua, puesto que la
cadena
Figura 4.2| Dos árboles de análisis sintáctico para una frase ambigua
ANALIZADOR SINTÁCTICO
C. LA ASOCIATIVIDAD
La asociatividad es un concepto que aparece cuando se operan tres o más operandos. La
asociatividad de un operador es por la izquierda si cuando aparecen tres o más operandos se
evalúan de izquierda a derecha. Si es de derecha a izquierda, la asociatividad es por la derecha.
Por ejemplo, si tenemos “6/3/2”, por convención es equivalente a (6/3) /2. Cuando un operando
como 3 tiene operadores a su izquierda y derecha, se necesitan convenciones para decir qué
operador considera ese operando. si el operador “/” tiene asociatividad por la izquierda,
primero se opera “6/3” el resultado se opera con “2” es 1.
Algunos operadores comunes, como el exponenciación. Son asociativos por la derecha. Otro
ejemplo análogo, el operador de asignación “=” en java es asociativo por la derecha: en java, la
expresión a=b=c se trata igual que la expresión a=(b=c).
Las cadenas como a=b=c. Con un operador asociativo por la derecha. Son generados por la
siguiente gramática:
𝐿𝐸𝑇𝑅𝐴 → 𝑎ȁ𝑏ȁ𝑐 … ȁ𝑧
El contraste entre un árbol de análisis sintáctico para un operador asociativo por la izquierda
como “/”, y un árbol de análisis sintáctico para un operador asociativo por la derecha como “=”,
El árbol de análisis sintáctico para 6/3/2 desciende hacia la izquierda, mientras que el árbol de
análisis sintáctico a=b=c desciende hacia la derecha.
D. LA PRECEDENCIA
La precedencia de un operador indica el orden en que se aplicará respecto a los demás
operadores en caso de poder aplicar más de uno. Es decir, si en una regla podemos aplicar más
de un operador, comenzaremos aplicando el de más precedencia y terminaremos por aplicar el
de menor precedencia. La manera de reflejar la precedencia en una gramática es utilizar para
cada operador una variable en la gramática y situarla más cerca del símbolo inicial cuanto menor
sea la precedencia.
_____________________________________________________________________________
SOLUCIÓN A LA PROCEDENCIA:
se crean dos no terminales EXPR y TERMINO para los dos niveles de precedencia, y un no
terminal adicional factor para generar unidades básicas en las expresiones. Ponemos primero
las reglas con menor procedencia “+” y “- “y al final de mayor precedencia “*” y “/”
Las unidades básicas de las expresiones son de momento dígitos y expresiones entre paréntesis.
Hay un problema con producciones recursivas por la izquierda en la que el símbolo situado más
a la izquierda del lado derecho de la producción es el mismo que el no terminal del lado izquierdo
de la producción.
𝐴 → 𝐴𝑎 ȁ𝑏
𝐸𝑋𝑃𝑅 → 𝐸𝑋𝑃𝑅"+"𝑇𝐸𝑅𝑀𝐼𝑁𝑂ȁ𝑇𝐸𝑅𝑀𝐼𝑁𝑂
se remplaza A por b
EL RESULTADO FINAL:
Se trata de una herramienta que facilita la construcción de analizadores léxicos y sintácticos por
el método de las funciones recursivas, aunque permite una notación relajada muy parecida a la
BNF.
Al aplicar el comando javacc Ejemplo.jj produce ficheros de salida relacionados con el analizador
sintáctico:
Token.java: clase que implementa el objeto a través del cual se comunican el analizador
léxico y el sintáctico.
{ 𝑐𝑜𝑑𝑖𝑔𝑜𝐽𝑎𝑣𝑎1 }
{ 𝑒𝑥𝑝𝑟𝐵𝑁𝐹1 }
{ 𝑐𝑜𝑑𝑖𝑔𝑜𝐽𝑎𝑣𝑎2 }
{ 𝑒𝑥𝑝𝑟𝐵𝑁𝐹2 }
Dado que cada no terminal se convertirá en una función en Java, el desarrollador no sólo
debe indicar su nombre, sino la cabecera completa de dicha función. Este hecho es de
fundamental importancia puesto que:
6. REFERENCIAS