Está en la página 1de 6

FASE DE ANALISIS DE COMPILADOR

La fase de análisis de un compilador descompone un programa fuente en piezas componentes y


produce una representación interna, a la cual se le conoce como código intermedio. La fase de
síntesis traduce el código intermedio en el programa destino. El análisis se organiza de acuerdo
con la “sintaxis” del lenguaje que se va a compilar. La sintaxis de un lenguaje de programación
describe el formato apropiado de sus programas, mientras que la semántica del lenguaje define lo
que sus programas significan; es decir, lo que hace cada programa cuando se ejecuta.

Un analizador léxico permite que un traductor maneje instrucciones de varios caracteres como
identificadores, que se escriben como secuencias de caracteres, pero se tratan como unidades
conocidas como tokens durante el análisis sintáctico; por ejemplo, en la expresión cuenta+1, el
identificador cuenta se trata como una unidad. El analizador léxico en la sección 2.6 permite que
aparezcan números, identificadores y “espacio en blanco” (espacios, tabuladores y caracteres de
nueva línea) dentro de las expresiones.

Definición de sintaxis

Una gramática describe en forma natural la estructura jerárquica de la mayoría de las


instrucciones de un lenguaje de programación.

Definición de gramáticas

Una gramática libre de contexto tiene cuatro componentes:

1. Un conjunto de símbolos terminales, a los que algunas veces se les conoce como “tokens”. Los
terminales son los símbolos elementales del lenguaje definido por la gramática. Ç

2. Un conjunto de no terminales, a las que algunas veces se les conoce como “variables
sintácticas”. Cada no terminal representa un conjunto de cadenas o terminales, de una forma que
describiremos más adelante.

3. Un conjunto de producciones, en donde cada producción consiste en un no terminal, llamada


encabezado o lado izquierdo de la producción, una flecha y una secuencia determinales y no
terminales, llamada cuerpo o lado derecho de la producción. La intención intuitiva de una
producción es especificar una de las formas escritas de una instrucción; si el no terminal del
encabezado representa a una instrucción, entonces el cuerpo representa una forma escrita de la
instrucción.

4. Una designación de una de los no terminales como el símbolo inicial. Para especificar las
gramáticas presentamos sus producciones, en donde primero se listan las producciones para el
símbolo inicial. Suponemos que los dígitos, los signos como < y <=, y las cadenas en negritas como
while son terminales. Un nombre en cursiva es un no terminal, y se puede asumir que cualquier
nombre o símbolo que no esté en cursiva es un terminal.

Decimos que una producción es para un no terminal, si el no terminal es el encabezado de la


producción. Una cadena de terminales es una secuencia de cero o más terminales. La cadena de
cero terminales, escrita como, se llama cadena vacía.

Derivaciones
Una gramática deriva cadenas empezando con el símbolo inicial y sustituyendo en forma repetida
un no terminal, mediante el cuerpo de una producción para ese no terminal. Las cadenas de
terminales que pueden derivarse del símbolo inicial del lenguaje definido por la gramática.

El análisis sintáctico (parsing) es el problema de tomar una cadena de terminales y averiguar cómo
derivarla a partir del símbolo inicial de la gramática, y si no puede derivarse a partir de este
símbolo, entonces hay que reportar los errores dentro de la cadena.

Árboles de análisis sintáctico

Un árbol de análisis sintáctico muestra, en forma gráfica, la manera en que el símbolo inicial de
una gramática deriva a una cadena en el lenguaje. Si el no terminal A tiene una producción A →
XYZ, entonces un árbol de análisis sintáctico podría tener un nodo interior etiquetado como A, con
tres hijos llamados X, Y y Z, de izquierda a derecha:

De manera formal, dada una gramática libre de contexto, un árbol de análisis sintáctico de
acuerdo con la gramática es un árbol con las siguientes propiedades:

1. La raíz se etiqueta con el símbolo inicial.

2. Cada hoja se etiqueta con un terminal.

3. Cada nodo interior se etiqueta con un no terminal.

4. Si A es el no terminal que etiqueta a cierto nodo interior, y X1, X2,. . ., Xn son las etiquetas de los
hijos de ese nodo de izquierda a derecha, entonces debe haber una producción A → X1X2 · · · Xn.
Aquí, cada una de las etiquetas X1, X2,. . ., Xn representa a un símbolo imparte un orden natural
de izquierda a derecha a sus hojas, con base en la idea de que si X y Y son dos hijos con el mismo
padre, y X está a la izquierda de Y, entonces todos los descendientes de X están a la izquierda de
los descendientes de Y.

Ambigüedad

Tenemos que ser cuidadosos al hablar sobre la estructura de una cadena, de acuerdo a una
gramática. Una gramática puede tener más de un árbol de análisis sintáctico que genere una
cadena dada de terminales. Se dice que dicha gramática es ambigua. Para mostrar que una
gramática es ambigua, todo lo que debemos hacer es buscar una cadena de terminales que sea la
derivación de más de un árbol de análisis sintáctico. Como una cadena con más de un árbol de
análisis sintáctico tiene, por lo general, más de un significado, debemos diseñar gramáticas no
ambiguas para las aplicaciones de compilación, o utilizar gramáticas ambiguas con reglas
adicionales para resolver las ambigüedades.

Asociatividad de los operadores


Por convención, 9+5+2 es equivalente a (9+5)+2 y 9−5−2 es equivalente a (9−5) −2. Cuando un
operando como 5 tiene operadores a su izquierda y a su derecha, se requieren convenciones para
decidir qué operador se aplica a ese operando. Decimos que el operador + se asocia por la
izquierda, porque un operando con signos positivos en ambos lados de él pertenece al operador
que está a su izquierda. En la mayoría de los lenguajes de programación, los cuatro operadores
aritméticos (suma, resta, multiplicación y división) son asociativos por la izquierda.

Algunos operadores comunes, como la exponenciación, son asociativos por la derecha. Como otro
ejemplo, el operador de asignación = en C y sus descendientes es asociativo por la derecha; es
decir, la expresión a=b=c se trata de la misma forma que la expresión a= (b=c).

Precedencia de operadores

Considere la expresión 9+5*2. Hay dos posibles interpretaciones de esta expresión: (9+5)*2 o 9+
(5*2). Las reglas de asociatividad para + y * se aplican a las ocurrencias del mismo operador, por lo
que no resuelven esta ambigüedad. Las reglas que definen la precedencia relativa de los
operadores son necesarias cuando hay más de un tipo de operador presente. Decimos que * tiene
mayor precedencia que +, si * recibe sus operandos antes que +. En la aritmética ordinaria, la
multiplicación y la división tienen mayor precedencia que la suma y la resta. Por lo tanto, * recibe
el 5 tanto en 9+5*2 como en 9*5+2; es decir, las expresiones son equivalentes a 9+ (5*2) y
(9*5)+2, respectivamente.

Podemos construir una gramática para expresiones aritméticas a partir de una tabla que muestre
la asociatividad y la precedencia de los operadores. Empezamos con los cuatro operadores
aritméticos comunes y una tabla de precedencia, mostrando los operadores en orden de menor a
mayor precedencia. Los operadores en la misma línea tienen la misma asociatividad y
precedencia: asociativo por la izquierda: + −

Asociativo por la derecha: * /

Traducción orientada a la sintaxis

La traducción orientada a la sintaxis se realiza uniendo reglas o fragmentos de un programa a las


producciones en una gramática.

Esta sección introduce dos conceptos relacionados con la traducción orientada a la sintaxis:

 Atributos. Un atributo es cualquier cantidad asociada con una construcción de


programación. Algunos ejemplos de atributos son los tipos de datos de las expresiones, el
número de instrucciones en el código generado, o la ubicación de la primera instrucción
en el código generado para una construcción, entre muchas otras posibilidades.
 Esquemas de traducción (orientada a la sintaxis). Un esquema de traducción es una
notación para unir los fragmentos de un programa a las producciones de una gramática.
Los fragmentos del programa se ejecutan cuando se utiliza la producción durante el
análisis sintáctico. El resultado combinado de todas estas ejecuciones de los fragmentos,
en el orden inducido por el análisis sintáctico, produce la traducción del programa al cual
se aplica este proceso de análisis/síntesis.

Notación postfija
La notación postfija para una expresión E puede definirse de manera inductiva, como se muestra a
continuación:

1. Si E es una variable o constante, entonces la notación postfija para E es la misma E.

2. Si E es una expresión de la forma E1 op E2, en donde op es cualquier operador binario, entonces


la notación postfija para E es E 1 E 2 op, en donde E 1 y E 2 son las notaciones postfija para E1 y E2,
respectivamente.

3. Si E es una expresión con paréntesis de la forma (E1), entonces la notación postfija para E es la
misma que la notación postfija para E1.

Atributos sintetizados

La idea de asociar cantidades con construcciones de programación (por ejemplo, valores y tipos
con expresiones) puede expresarse en términos de gramáticas. Asociamos los atributos con los no
terminales y terminales. Después, unimos reglas a las producciones de la gramática; estas reglas
describen la forma en que se calculan los atributos en esos nodos del árbol de análisis sintáctico
en donde la producción en cuestión se utiliza para relacionar un nodo con sus hijos.

Una definición orientada a la sintaxis se asocia:

1. Con cada símbolo de gramática, un conjunto de atributos.

2. Con cada producción, un conjunto de reglas semánticas para calcular los valores de los atributos
asociados con los símbolos que aparecen en la producción.

Los atributos pueden evaluarse de la siguiente forma. Para una cadena de entrada x dada, se
construye un árbol de análisis sintáctico para x. Después, se aplican las reglas semánticas para
evaluar los atributos en cada nodo del árbol de análisis sintáctico, como se indica a continuación.

Definiciones simples orientadas a la sintaxis

La definición orientada a la sintaxis en el ejemplo 2.10 tiene la siguiente propiedad importante: la


cadena que representa la traducción del no terminal en el encabezado de cada producción es la
concatenación de las traducciones de los no terminales en el cuerpo de la producción, en el mismo
orden que en la producción, con algunas cadenas adicionales opcionales entrelazadas. Una
definición orientada a la sintaxis con esta propiedad se denomina como simple.

Recorridos de los árboles

Utilizaremos los recorridos de los árboles para describir la evaluación de los atributos y especificar
la ejecución de los fragmentos de código en un esquema de traducción. Un recorrido de un árbol
empieza en la raíz y visita cada nodo del árbol en cierto orden.

Un recorrido del tipo primero en profundidad empieza en la raíz y visita en forma recursiva los
hijos de cada nodo en cualquier orden, no necesariamente de izquierda a derecha. Se llama
“primero en profundidad” debido a que visita cuando pueda a un hijo de un nodo que no haya
sido visitado, de manera que visita a los nodos que estén a cierta distancia (“profundidad”) de la
raíz lo más rápido que pueda.
Esquemas de traducción

La definición orientada a la sintaxis genera una traducción uniendo cadenas como atributos para
los nodos en el árbol de análisis sintáctico. Ahora consideraremos un método alternativo que no
necesita manipular cadenas; produce la misma traducción en forma incremental, mediante la
ejecución de fragmentos del programa.

Al dibujar un árbol de análisis sintáctico para un esquema de traducción, para indicar una acción le
construimos un hijo adicional, conectado mediante una línea punteada al nodo que corresponde a
la cabeza de la producción.

Análisis sintáctico

El análisis sintáctico (parsing) es el proceso de determinar cómo puede generarse una cadena de
terminales mediante una gramática. Al hablar sobre este problema, es más útil pensar en que se
va a construir un árbol de análisis sintáctico, aun cuando un compilador tal vez no lo construya en
la práctica. No obstante, un analizador sintáctico debe ser capaz de construir el árbol en principio,
o de lo contrario no se puede garantizar que la traducción sea correcta.

La mayoría de los métodos de análisis sintáctico se adaptan a una de dos clases, llamadas métodos
descendente y ascendente. Estos términos se refieren al orden en el que se construyen los nodos
en el árbol de análisis sintáctico. En los analizadores tipo descendente, la construcción empieza en
la raíz y procede hacia las hojas, mientras que en los analizadores tipo ascendente, la construcción
empieza en las hojas y procede hacia la raíz. La popularidad de los analizadores tipo arriba-abajo
se debe a que pueden construirse analizadores eficientes con más facilidad a mano, mediante
métodos descendentes. No obstante, el análisis sintáctico tipo ascendente puede manejar una
clase más extensa de gramáticas y esquemas de traducción, por lo que las herramientas de
software para generar analizadores sintácticos directamente a partir de las gramáticas utilizan con
frecuencia éste método.

Análisis sintáctico tipo arriba-abajo

Para introducir el análisis sintáctico tipo arriba-abajo, consideremos una gramática que se adapta
bien a esta clase de métodos. Más adelante en esta sección, consideraremos la construcción de los
analizadores sintácticos descendentes en general. La gramática en la figura 2.16 genera un
subconjunto de las instrucciones de C o Java. Utilizamos los terminales en negrita if y for para las
palabras clave “if” y “for”, respectivamente, para enfatizar que estas secuencias de caracteres se
tratan como unidades, es decir, como símbolos terminales individuales. Además, el terminal expr
representa expresiones; una gramática más completa utilizaría un no terminal expr y tendría
producciones para el no terminal expr. De manera similar, otras es un terminal que representa a
otras construcciones de instrucciones.

Análisis sintáctico predictivo

El análisis sintáctico de descenso recursivo es un método de análisis sintáctico descendente, en el


cual se utiliza un conjunto de procedimientos recursivos para procesar la entrada. Un
procedimiento se asocia con cada no terminal de una gramática. Aquí consideraremos una forma
simple de análisis sintáctico de descenso recursivo, conocido como análisis sintáctico predictivo,
en el cual el símbolo de pre análisis determina sin ambigüedad el flujo de control a través del
cuerpo del procedimiento para cada no terminal.

Un árbol sintáctico abstracto tiene nodos para las construcciones de programación; los hijos de un
nodo proporcionan las su construcciones significativas. De manera alternativa, el código de tres
direcciones es una secuencia de instrucciones, en la cual cada instrucción lleva a cabo una sola
operación.

Las tablas de símbolos son estructuras de datos que contienen información acerca de los
identificadores. La información se coloca en la tabla de símbolos cuando se analiza la declaración
de un identificador. Una acción semántica obtiene información de la tabla de símbolos cuando el
identificador se vuelve a utilizar, por ejemplo, como factor en una expresión.

También podría gustarte