LAS TABLAS DE SIMBOLOS

Anuncio
LAS TABLAS DE SIMBOLOS
Las tablas de símbolos (también llamadas tablas de identificadores y tablas de
nombres), realizan dos importantes funciones en el proceso de traducción: verificar que
la semántica sea correcta y ayudar en la generación apropiada de código. Ambas
funciones se realizan insertando o recuperando desde la tabla de símbolos los atributos
de las variables usadas en el programa fuente. Estos atributos, tales como: el nombre,
tipo, dirección de almacenamiento y dimensión de una variable, usualmente se
encuentran explícitamente en las declaraciones o más implícitamente a través del
contexto en que aparecen los nombres de variables en el programa.
Una de las estructuras de datos que se encuentran relacionadas con las fases del proceso
de compilación es la tabla de símbolos, la cual tiene como propósito registrar
información que se comparte entre varias etapas y que permite administrar los recursos
asociados a las entidades que manipulará el programa. La tabla de símbolos tiene
típicamente la siguiente estructura:
Una tabla de símbolos puede conceptualizarse como una serie de renglones, cada uno de
los cuales contiene una lista de valores de atributos que son asociados con una variable
en particular. Las clases de los atributos que aparecen en una tabla de símbolos
dependen en algún grado de la naturaleza del lenguaje de programación para el cual se
escribe el compilador. Por ejemplo, un lenguaje puede ser sin tipos, y por lo tanto el
atributo tipo no necesita aparecer en la tabla. Similarmente, la organización de la tabla
de símbolos variará dependiendo de las limitaciones de memoria y tiempo de acceso
MANEJO DE ERRORES
Es una de las misiones más importantes de un compilador, aunque, al mismo tiempo, es
lo que más dificulta su realización.
 A veces unos errores ocultan otros.
 A veces un error provoca una avalancha de muchos errores que se solucionan
con el primero.
Es conveniente un buen manejo de errores, y que el compilador detecte todos los errores
que tiene el programa y no se pare en el primero que encuentre. Hay, pues, dos criterios
a seguir a la hora de manejar errores:
o Pararse al detectar el primer error.
o Detectar todos los errores de una pasada.
ANÁLISIS LÉXICO
El analizador léxico, también conocido como scanner, lee los caracteres uno a uno desde la
entrada y va formando grupos de caracteres con alguna relación entre sí (tokens), que
constituirán la entrada para la siguiente etapa del compilador. Cada token representa una
secuencia de caracteres que son tratados como una única entidad. Por ejemplo, en Pascal un
token es la palabra reservada BEGIN, en C: WHILE, etc.
Hay dos tipos de tokens: tiras específicas, tales como palabras reservadas (if, while, begin,
etc.), el punto y coma, la asignación, los operadores aritméticos o lógicos, etc.; tiras no
específicas, como identificadores, constantes o etiquetas. Se considera que un token tiene dos
partes componentes: el tipo de token y su valor. Las tiras específicas sólo tienen tipo (lo que
representan), mientras que las tiras no específicas tienen tipo y valor. Por ejemplo, si
"Contador" es un identificador, el tipo de token será identificador y su valor será la cadena
"Contador".
El Analizador Léxico es la etapa del compilador que va a permitir saber si es un lenguaje de
formato libre o no. Frecuentemente va unido al analizador sintáctico en la misma pasada,
funcionando entonces como una subrutina de este último. Ya que es el que va leyendo los
caracteres del programa, ignorará aquellos elementos innecesarios para la siguiente fase,
como los tabuladores, comentarios, espacios en blanco, etc.
ANÁLISIS SINTÁCTICO
El analizador sintáctico, también llamado parser, recibe como entrada los tokens que le pasa el
Analizador Léxico (el analizador sintáctico no maneja directamente caracteres) y comprueba si
esos tokens van llegando en el orden correcto (orden permitido por el lenguaje). La salida
"teórica" de la fase de análisis sintáctico sería un árbol sintáctico.
Así pues, sus funciones son:



Aceptar lo que es válido sintácticamente y rechazar lo que no lo es.
Hacer explícito el orden jerárquico que tienen los operadores en el lenguaje de que se
trate. Por ejemplo, la cadena A/B*C es interpretada como (A/B)*C en FORTRAN y comoA/(B*C)
en APL.
Guiar el proceso de traducción (traducción dirigida por la sintaxis).
Análisis Semántico
El análisis semántico es posterior al sintáctico y mucho más difícil de formalizar que éste. Se
trata de determinar el tipo de los resultados intermedios, comprobar que los argumentos que
tiene un operador pertenecen al conjunto de los operadores posibles, y si son compatibles
entre sí, etc. En definitiva, comprobará que el significado de lo que se va leyendo es válido.
La salida "teórica" de la fase de análisis semántico sería un árbol semántico. Consiste en un
árbol sintáctico en el que cada una de sus ramas ha adquirido el significado que debe tener. En
el caso de los operadores polimórficos (un único símbolo con varios significados), el análisis
semántico determina cuál es el aplicable.
Analizando en detalle el proceso de compilación, se divide en dos grandes fases, una
de Análisis y la otra de Síntesis.
Fase de Análisis:
En el llamado análisis lexicográfico o léxico, el compilador revisa y controla que las
"palabras" estén bien escritas y pertenezcan a algún tipo de token (cadena) definido
dentro del lenguaje, como por ejemplo que sea algún tipo de palabra reservada, o si es el
nombre de una variable que este escrita de acuerdo a las pautas de definición del
lenguaje. En esta etapa se crea la tabla de símbolos, la cual contiene las variables y el
tipo de dato al que pertenece, las constantes literales, el nombre de funciones y los
argumentos que reciben etc.
En el análisis sintáctico como su nombre lo indica se encarga de revisar que los
tokens estén ubicados y agrupados de acuerdo a la definición del lenguaje. Dicho de
otra manera, que los tokens pertenezcan a frases gramaticales validas, que el compilador
utiliza para sintetizar la salida. Por lo general las frases gramaticales son representadas
por estructuras jerárquicas, por medio de árboles de análisis sintáctico. En esta etapa se
completa la tabla de símbolos con la dimensión de los identificadores y los atributos
necesarios etc.
El análisis semántico se encarga de revisar que cada agrupación o conjunto de token
tenga sentido, y no sea un absurdo. En esta etapa se reúne la información sobre los tipos
para la fase posterior, en esta etapa se utiliza la estructura jerárquica de la etapa anterior
y así poder determinar los operadores, y operandos de expresiones y preposiciones.
Fase de Síntesis:
Etapa de generación de código intermedio, aunque algunos compiladores no la
tienen, es bueno saber de su existencia, en esta etapa se lleva el código del programa
fuente a un código interno para poder trabajar mas fácilmente sobre él. Esta
representación interna debe tener dos propiedades, primero debe ser fácil de representar
y segundo debe ser fácil de traducir al código objeto.
En la etapa de optimización de código, se busca obtener el código mas corto y rápido
posible, utilizando distintos algoritmos de optimización.
Etapa de generación de código, se lleva el código intermedio final a código maquina
o código objeto, que por lo general consiste en un código maquina relocalizable o
código ensamblador. Se selecciona las posiciones de memoria para los datos (variables)
y se traduce cada una de las instrucciones intermedias a una secuencia de instrucciones
de maquina puro.
La tabla de símbolos no es una etapa del proceso de compilación, sino que una tarea,
una función que debe realizar el proceso de compilación. En ella se almacenan los
identificadores que aparecen en el código fuente puro, como así también los atributos de
los mismos, su tipo, su ámbito y en el caso de los procedimientos el número de
argumentos el tipo de los mismos etc. En otras palabras una tabla de símbolos es una
estructura de datos, que contiene un registro por cada identificador, y sus atributos. La
tabla de símbolo es accedida tanto para escritura como parar lectura por todas las etapas.
Detector de errores o manejador de errores, al igual que la tabla de símbolos no es
una etapa del proceso de compilación, si no que es una función, muy importante, pues al
ocurrir un error esta función debe tratar de alguna forma el error para así seguir con el
proceso de compilación (la mayoría de errores son detectados en las etapas de análisis
léxico, análisis sintáctico, análisis semántico).
Análisis Léxico
El analizador léxico lee los caracteres del programa fuente, y verifica que
correspondan a una secuencia lógica (identificador, palabra reservada etc.). Esta
secuencia de caracteres recibe el nombre componente léxico o lexema. En este caso el
analizador léxico verifica si el identificador id1 (nombre interno para "suma")
encontrado se halla en la tabla de símbolos, si no esta produce un error porque todavía
no fue declarado, si la preposición hubiese sido la declaración del identificador "suma"
en lenguajes C, C++ (int suma;) el analizador léxico agregaria un identificador en la
tabla de símbolos, y así sucesivamente con todos los componentes léxicos que
aparezcan.
id1= id2+ id3 * 10
Análisis Sintáctico
El analizador sintáctico impone una estructura jerárquica a la cadena de componentes
léxicos, generada por el analizador léxico, que es representada en forma de un árbol
sintáctico.
=
/ \
id1 +
/ \
id2 +
/ \
id3 10
Análisis Semántico
El analizador semántico verificara en este caso que cada operador tenga los operandos
permitidos.
=
/ \
id1 +
/ \
id2 +
/ \
id3 tipo_ent
|
10
Generador de código intermedio
En esta etapa se lleva la preposición a una representación intermedia como un
programa para una maquina abstracta.
temp1= tipo_ent(10)
temp2= id3 * temp1
temp3= id2 + tem2
id1= temp3
Optimización de código
El código intermedio obtenido es representado de una forma mas optima y eficiente.
temp1= id3 * 10.0
id1= id2 + temp1
Generador de código
Finalmente lleva el código intermedio a un código objeto que en este caso es un
código relocalizable o código ensamblador (también llamado código no enlazado).
MOVF id3, R2
MULT #10.0, R2
MOVF id2, R1
ADDF R2, R1
MOVF R1, id1
Este el código objeto obtenido que es enviado al modulo de ensamblado. Para
entender todo esto veamos un ejemplo utilizando como lenguaje en este caso al popular
lenguaje de programación C creado por Kernighan y Ritchie. El siguiente código esta
definido de acuerdo al standard ANSI C
Descargar