Por ser los compiladores
el tipo de traductor más utilizado en la actualidad, desarrollaremos el proceso
de compilación, que consiste en la traducción de un programa fuente, escrito en
lenguaje de alto nivel, a su correspondiente programa objeto, escrito en
lenguaje máquina, dejándolo listo para la ejecución con poca o ninguna
preparación adicional.
Antes del proceso de
compilación, se crea el programa fuente utilizando cualquier aplicación
disponible con capacidades de edición de textos.
Para llevar a cabo la compilación, el programa debe
residir en memoria principal simultáneamente con el compilador. El resultado de
la compilación puede dar lugar a la aparición de errores, en cuyo caso no se
genera el programa objeto, sino que se realiza un listado de compilación, un
informe indicando la naturaleza y situación de los errores detectados por el
compilador en el programa fuente. Con el programa editor se corrigen y se
comienza de nuevo el proceso.
Una vez finalizada la compilación y obtenido el programa
objeto, se somete a un proceso de montaje donde se enlazan los distintos
módulos que lo componen, en caso de tratar con programas que poseen
subprogramas (que pueden ser compilados separadamente). Además, se incorporan
las denominadas rutinas de biblioteca en caso de solicitarlas el propio
programa. Este proceso de montaje es realizado por un programa denominado
montador o encuadernador, que también recibe el nombre de editor de enlace o
linker.
La compilación es un
proceso complejo que consume a veces un tiempo muy superior a la propia
ejecución del programa. Este proceso consta, en general, de dos etapas
fundamentales: la etapa de análisis del programa fuente y la etapa de síntesis
del programa objeto. Cada una de estas etapas conlleva la realización de varias
fases.
El compilador utiliza internamente una tabla de símbolos
para introducir determinados datos que necesita. Esta tabla interviene
prácticamente en todas las fases del proceso de compilación; así mismo, el
compilador posee un módulo de tratamiento de errores que permite determinarlas
reacciones que se deben producir ante la aparición de cualquier tipo de error.
3.1. Análisis del programa fuente
Durante esta etapa, el programa fuente se divide en sus
elementos componentes, creándose una representación intermedia del mismo.
Durante este proceso, es posible detectar posibles errores en la escritura del
programa.
3.1.1. Análisis lexicográfico
Se examina el programa fuente de izquierda a derecha,
reconociendo las unidades básicas de información pertenecientes al lenguaje.
Estas unidades básicas se denominan unidades léxicas o tokens. Un token es un
elemento o cadena (secuencia de caracteres) que tiene un significado propio en
el lenguaje.
Además de reconocer cada
token, almacena en la tabla de símbolos aquella información del símbolo que
pueda ser necesaria para las restantes fases de compilación.
En el caso de que no existan errores en este primer
análisis, se obtiene una representación del programa formada por:
• La descripción de símbolos en la tabla de símbolos.
• Una secuencia de símbolos en la que se incluye, junto a
cada símbolo, una referencia a la ubicación de dicho símbolo en la tabla de
símbolos. Esta secuencia es denominada tira de tokens.
En esta nueva
representación, se ha eliminado previamente toda la información superflua, como
comentarios, tabulaciones y espacios en blanco no significativos.
Los errores que pueden
ser reconocidos se deben a la detección de cadenas de caracteres del programa
fuente que no se ajustan al patrón de ningún token, como identificadores de
variables incorrectos, números expresados incorrectamente, cadenas de
caracteres mal delimitadas, etc.
3.1.2. Análisis sintáctico
La sintaxis de un lenguaje de programación especifica
cómo deben escribirse los programas basándose en un conjunto de reglas sintácticas
que determinan la gramática del lenguaje. Un programa es sintácticamente
correcto cuando sus estructuras (expresiones, sentencias, asignaciones, etc.)
aparecen dispuestas de forma correcta de acuerdo a la gramática del lenguaje.
El análisis sintáctico
recibe la tira de tokens del analizador lexicográfico e investiga en ella los
posibles errores sintácticos que aparezcan, como expresiones estructuradas
incorrectamente, falta de algún token dentro de una sentencia, formato
incorrecto en una asignación, etc.
En caso de no encontrar
errores, se agrupan los componentes léxicos en frases gramaticales que serán
utilizadas en la síntesis del programa objeto. Estas frases gramaticales se
suelen representar mediante un árbol sintáctico que pone de manifiesto la
estructura jerárquica de los componentes de un programa.
3.1.3. Análisis semántico
Una construcción de un lenguaje de programación puede ser
sintácticamente correcta y carecer de significado. Por tanto, si queremos
generar código en lenguaje máquina con el mismo significado que el código
fuente, tendremos que determinar la validez semántica de las construcciones del
código fuente.
El análisis semántico se
encarga de estudiar la coherencia semántica del código fuente a partir de la
identificación de las construcciones sintácticas y de la información almacenada
en la tabla de símbolos. El análisis semántico debe ser capaz de detectar
construcciones sin un significado correcto. Por ejemplo, asignar a una variable
de tipo entero un valor de tipo cadena de caracteres, sumar dos valores
booleanos, etc.
3.2. Síntesis del programa objeto
Construye el programa objeto a partir de la
representación obtenida en la etapa de análisis.
3.2.1. Generación de código intermedio
Se traduce el resultado de la etapa de análisis (en el
caso de ausencia de errores) a un lenguaje intermedio propio del compilador.
Esta representación intermedia (código intermedio) debe poseer las siguientes
características:
• Debe ser independiente de la máquina.
• Debe ser fácil de producir.
• Debe ser fácil de traducir a un lenguaje máquina.
Esta fase aparece, generalmente, en aquellos compiladores
construidos para su implantación en distintas máquinas, ya que su existencia
hace que todos los módulos de análisis del compilador sean comunes para todas
las máquinas (aumenta la portabilidad del compilador y se abaratan sus costes
de construcción).
3.2.2. Optimización de código
Esta fase se suele introducir para mejorar la eficiencia
del código producido por el compilador. Se toma el código intermedio de forma
global y se optimiza, adaptándolo a las características del procesador al que
va dirigido para mejorar los resultados de su ejecución.
La optimización se
realiza en cualquier parte del programa, pero es en los bucles donde se
consiguen los mejores resultados; por ejemplo, sacar una sentencia que se
repite fuera del bucle, consiguiendo que sólo se ejecute una vez. De esta
forma, se consigue un programa igualmente correcto, pero que consume menos
tiempo de ejecución.
Un problema de la
optimización es que incrementa el tamaño y la complejidad del compilador.
3.2.3. Generación de código objeto
Se traduce el código intermedio optimizado a código
final, es decir, al lenguaje máquina (en algunos casos a ensamblador) del procesador
al que va dirigido el compilador. Para ello, se reserva la memoria necesaria
para cada elemento del código intermedio, y cada instrucción en lenguaje
intermedio es traducida a varias instrucciones en lenguaje máquina.
3.3. Detección y tratamiento de errores
Aunque es en la etapa de análisis donde se detectan la
mayoría de los errores, cada etapa del proceso puede encontrar errores. Para
tratar adecuadamente un error, interviene el módulo de tratamiento de errores
que comprende generalmente la realización de dos acciones:
• Diagnóstico del error: Trata de buscar su localización
exacta y la posible causa del mismo, para ofrecer al programador un mensaje de
diagnóstico, que será incluido en el listado de compilación.
• Recuperación del error: Después de detectado un error,
cada fase debe tratarlo de alguna forma para poder continuar con la compilación
y permitir la detección de más errores, aunque no se genere código objeto.
Los tipos de errores que
puede tener un programa son los siguientes:
• Errores lexicográficos: Se producen por la aparición de
tokens no reconocibles, es decir, cadenas que no se ajustan al patrón de ningún
símbolo elemental del lenguaje.
• Errores sintácticos: Aparecen cuando se violan las
reglas de sintaxis del lenguaje.
• Errores semánticos: Se detectan cuando aparece una
secuencia sintácticamente correcta, pero que carece de sentido dentro del
contexto del programa fuente.
• Errores lógicos: Son debidos a la utilización de un
algoritmo o expresión incorrecta para el problema que se trata de resolver.
• Errores de ejecución: Son errores relacionados con
desbordamientos, operaciones matemáticamente irresolubles, etc.
En ocasiones, se indican
determinados errores que pueden existir pero que no perjudican al resto del
proceso de compilación e incluso pueden permitir el funcionamiento del programa
final. Estos mensajes de error se denominan advertencias o warnings. Un ejemplo
típico puede ser la
declaración de una
variable que no se utiliza en ningún bloque del programa.
No hay comentarios:
Publicar un comentario