Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Email: jaime.rodriguez.vizuete@utelvt.edu.ec
INTRODUCCIÓN
La asignatura de programación IV corresponde a la Unidad de Formación
Profesionalizante y al Campo de Formación de praxis profesional; Tiene como
propósito el estudio y comprensión del proceso de traducción (compilación) de un
código fuente a un código objeto (lenguaje máquina). La asignatura brindará al
estudiante la capacidad de desarrollar aplicaciones informáticas más eficientes
aplicando los conceptos relacionados con traductores y profundizando
progresivamente en el estudio del proceso de la compilación que comprende tres
etapas: Análisis Léxico (scanner) con el reconocimiento de tokens, Análisis Sintáctico
(Parsing) con la construcción de árboles sintácticos y el Análisis Semántico con
revisión de la estructura semántica.
OBJETIVO
Diferenciar las etapas de un compilador según la función que cumple el analizador
léxico, sintáctico y semántico, para traducir un código fuente a un lenguaje de
máquina, que sea comprendido por el computador.
UNIDAD 1. COMPILADORES
Lenguaje de programación
Los lenguajes de programación son notaciones que describen los cálculos a las
personas y las máquinas. Nuestra percepción del mundo en que vivimos depende de
los lenguajes de programación, ya que todo el software que se ejecuta en todas las
computadoras se escribió en algún lenguaje de programación. Pero antes de poder
ejecutar un programa, primero debe traducirse a un formato en el que una
computadora pueda ejecutarlo. Los sistemas de software que se encargan de esta
traducción se llaman compiladores.
Traductores
1
ELABORADO POR: Ing. Orlen Ismael Araujo Sandoval, Mg.
El traductor es una herramienta esencial en la programación o desarrollo,
encargándose de convertir código fuente de un determinado lenguaje de
programación a código máquina que puede «entender» directamente el ordenador.
De acuerdo al modo en que llevan a cabo el proceso de conversión, los traductores se
dividen en dos conjuntos: intérpretes y compiladores.
Compiladores
Dicho en forma simple, un compilador es un programa que puede leer un programa en
un lenguaje (el lenguaje fuente) y traducirlo en un programa equivalente en otro
lenguaje (el lenguaje destino). Una función importante del compilador es reportar
cualquier error en el programa fuente que detecte durante el proceso de traducción.
Intérprete
Un intérprete es otro tipo común de procesador de lenguaje. En vez de producir un
programa destino como una traducción, el intérprete nos da la apariencia de ejecutar
directamente las operaciones especificadas en el programa de origen (fuente) con las
entradas proporcionadas por el usuario.
Nota. BASIC, Perl, Python, Ruby y PHP son algunos de los lenguajes de programación
más famosos que dependen de un intérprete para ser traducidos de código fuente a
código máquina. Por ello, también suelen llamarse lenguajes interpretados.
La estructura de un compilador
Cuando se ha hablado de programas equivalentes en distintos lenguajes (fuente y
objeto), nos referimos a “tener el mismo significado” (aunque no fijemos ahora
exactamente el concepto significado). Por ello, la compilación requiere dos grandes
partes o tareas:
Análisis: en la que se analiza el programa fuente para dividirlo en componentes y
extraer de algún modo el significado
Síntesis: en la que el significado obtenido se escribe en el lenguaje objeto
Fases de un compilador
TRABAJO
Ejercicio 1.1.1: ¿Cuál es la diferencia entre un compilador y un intérprete?
Ejercicio 1.1.2: ¿Cuáles son las ventajas de un compilador sobre un intérprete, y las de
un intérprete sobre un compilador?
Ejercicio 1.1.3: ¿Qué ventajas hay para un sistema de procesamiento de lenguajes en el
cual el compilador produce lenguaje ensamblador en vez de lenguaje máquina?
Ejercicio 1.1.4: Describa algunas de las tareas que necesita realizar un ensamblador.
Analizador Léxico
Como la primera fase de un compilador, la principal tarea del analizador léxico es leer
los caracteres de la entrada del programa fuente, agruparlos en lexemas y producir
como salida una secuencia de tokens para cada lexema en el programa fuente. El flujo
de tokens se envía al analizador sintáctico para su análisis. Con frecuencia el analizador
léxico interactúa también con la tabla de símbolos. Cuando el analizador léxico
descubre un lexema que constituye a un identificador, debe introducir ese lexema en
la tabla de símbolos. En algunos casos, el analizador léxico puede leer la información
relacionada con el tipo de información de la tabla de símbolos, como ayuda para
determinar el token apropiado que debe pasar al analizador sintáctico.
Por lo regular, la interacción se implementa haciendo que el analizador sintáctico llame
al analizador léxico. La llamada, sugerida por el comando obtenerSiguienteToken, hace
que el analizador léxico lea los caracteres de su entrada hasta que pueda identificar el
siguiente lexema y producirlo para el siguiente token, el cual devuelve al analizador
sintáctico.
Algunas veces, los analizadores léxicos se dividen en una cascada de dos procesos:
a) El escaneo consiste en los procesos simples que no requieren la determinación de
tokens de la entrada, como la eliminación de comentarios y la compactación de los
caracteres de espacio en blanco consecutivos en uno solo.
b) El propio análisis léxico es la porción más compleja, en donde el escanear produce la
secuencia de tokens como salida.
1. Un token para cada palabra clave. El patrón para una palabra clave es el mismo
que para la palabra clave en sí.
2. Los tokens para los operadores, ya sea en forma individual o en clases como el
token comparación, mencionado en la figura anterior.
3. Un token que representa a todos los identificadores.
4. Uno o más tokens que representan a las constantes, como los números y las
cadenas de literales.
5. Tokens para cada signo de puntuación, como los paréntesis izquierdo y
derecho, la coma y el signo de punto y coma.
Análisis Sintáctico
La segunda fase del compilador es el análisis sintáctico o parsing. El parser (analizador
sintáctico) utiliza los primeros componentes de los tokens producidos por el analizador
de léxico para crear una representación intermedia en forma de árbol que describa la
estructura gramatical del flujo de tokens. Una representación típica es el árbol
sintáctico, en el cual cada nodo interior representa una operación y los hijos del nodo
representan los argumentos de la operación.
En nuestro modelo de compilador, el analizador sintáctico obtiene una cadena de
tokens del analizador léxico, como se muestra en la figura 4.1, y verifica que la cadena
de nombres de los tokens pueda generarse mediante la gramática para el lenguaje
fuente. Esperamos que el analizador sintáctico reporte cualquier error sintáctico en
forma inteligible y que se recupere de los errores que ocurren con frecuencia para
seguir procesando el resto del programa. De manera conceptual, para los programas
bien formados, el analizador sintáctico construye un árbol de análisis sintáctico y lo
pasa al resto del compilador para que lo siga procesando. De hecho, el árbol de análisis
sintáctico no necesita construirse en forma explícita, ya que las acciones de
comprobación y traducción pueden intercalarse con el análisis sintáctico, como
veremos más adelante.
Por ende, el analizador sintáctico y el resto de la interfaz de usuario podrían
implementarse sin problemas mediante un solo módulo.
El árbol tiene un nodo interior etiquetado como *, con (id, 3) como su hijo izquierdo, y
el entero 60 como su hijo derecho. El nodo (id, 3) representa el identificador velocidad.
El nodo etiquetado como * hace explicito que primero debemos multiplicar el valor de
velocidad por 60. El nodo etiquetado como + indica que debemos sumar el resultado
de esta multiplicación al valor de inicial. La raíz del árbol, que se etiqueta como =,
indica que debemos almacenar el resultado de esta suma en la ubicación para el
identificador posicion. Este ordenamiento de operaciones es consistente con las
convenciones usuales de la aritmética, las cuales nos indican que la multiplicación
tiene mayor precedencia que la suma y, por ende, debe realizarse antes que la suma.
Análisis Semántico
El analizador semántico utiliza el árbol sintáctico y la información en la tabla de
símbolos para comprobar la consistencia semántica del programa fuente con la
definición del lenguaje. También recopila información sobre el tipo y la guarda, ya sea
en el árbol sintáctico o en la tabla de símbolos, para usarla más tarde durante la
generación de código intermedio.
Una parte importante del análisis semántico es la comprobación (verificación) de tipos,
en donde el compilador verifica que cada operador tenga operandos que coincidan.
Por ejemplo, muchas definiciones de lenguajes de programación requieren que el
índice de un arreglo sea entero; el compilador debe reportar un error si se utiliza un
número de punto flotante para indexar el arreglo.
La especificación del lenguaje puede permitir ciertas conversiones de tipo conocidas
como coerciones. Por ejemplo, puede aplicarse un operador binario aritmético a un
par de enteros o a un par de números de punto flotante. Si el operador se aplica a un
número de punto flotante y a un entero, el compilador puede convertir u obligar a que
se convierta en un número de punto flotante.
Comprobación semántica
la salida que genera el análisis semántico, en el caso de que no haya detectado errores,
es un árbol de derivación con anotaciones semánticas. Dichas anotaciones se pueden
usar para comprobar que el programa es semánticamente correcto, de acuerdo con las
especificaciones del lenguaje de programación. Hay que comprobar, por ejemplo, que:
Cuando se utiliza un identificador, éste ha sido declarado previamente.
• Se ha asignado valor a las variables antes de su uso.
• Los índices para acceder a los arrays están dentro del rango válido.
• En las expresiones aritméticas, los operandos respetan las reglas sobre los tipos
de datos permitidos por los operadores.
• Cuando se invoca un procedimiento, éste ha sido declarado adecuadamente.
Además, el número, tipo y posición de cada uno de sus argumentos debe ser
compatible con la declaración.
• Las funciones contienen al menos una instrucción en la que se devuelve su valor
al programa que las invocó.
Suponga que posicion, inicial y velocidad se han declarado como números de punto
flotante, y que el lexema 60 por sí solo forma un entero. El comprobador de tipo en el
analizador semántico descubre que se aplica el operador * al número de punto
flotante velocidad y al entero 60. En este caso, el entero puede convertirse en un
número de punto flotante. Observe en la figura 1.7 que la salida del analizador
semántico tiene un nodo adicional para el operador inttofloat, que convierte de
manera explícita su argumento tipo entero en un número de punto flotante. En el
capítulo 6 hablaremos sobre la comprobación de tipos y el análisis semántico.
Generación de código
El generador de código recibe como entrada una representación intermedia del
programa fuente y la asigna al lenguaje destino. Si el lenguaje destino es código
máquina, se seleccionan registros o ubicaciones (localidades) de memoria para cada
una de las variables que utiliza el programa. Después, las instrucciones intermedias se
traducen en secuencias de instrucciones de máquina que realizan la misma tarea. Un
aspecto crucial de la generación de código es la asignación juiciosa de los registros
para guardar las variables.
Bibliografía