Вы находитесь на странице: 1из 20

COMPILADORES

Teora e implementacin

Jacinto Ruiz Cataln


COMPILADORES. Teora e implementacin
Jacinto Ruiz Cataln

ISBN: 978-84-937008-9-8

EAN: 9788493700898

Copyright 2010 RC Libros


RC Libros es un sello y marca comercial registrada por
Grupo Ramrez Cogollor, S.L. (Grupo RC)

COMPILADORES. Teora e implementacin. Reservados todos los derechos.


Ninguna parte de este libro incluida la cubierta puede ser reproducida, su contenido
est protegido por la Ley vigente que establece penas de prisin y/o multas a
quienes intencionadamente reprodujeren o plagiaren, en todo o en parte, una obra
literaria, artstica o cientfica, o su transformacin, interpretacin o ejecucin en
cualquier tipo de soporte existente o de prxima invencin, sin autorizacin previa
y por escrito de los titulares de los derechos de la propiedad intelectual.

RC Libros, el Autor, y cualquier persona o empresa participante en la redaccin, edicin o


produccin de este libro, en ningn caso sern responsables de los resultados del uso de su
contenido, ni de cualquier violacin de patentes o derechos de terceras partes. El objetivo de la
obra es proporcionar al lector conocimientos precisos y acreditados sobre el tema pero su venta no
supone ninguna forma de asistencia legal, administrativa ni de ningn otro tipo, si se precisase
ayuda adicional o experta debern buscarse los servicios de profesionales competentes. Productos
y marcas citados en su contenido estn o no registrados, pertenecen a sus respectivos propietarios.

Sun, el logotipo de Sun, Sun Microsystems, y Java son marcas o marcas registradas de
Sun Microsystems Inc. EE.UU. y otros pases.
JLex est liberado con licencia GPL.
Cup est protegido por licencias de cdigo abierto, siendo compatible con la licencia GPL.
Ens2001 es un Proyecto Fin de Carrera creado por Federico Javier lvarez para su Licenciatura
en Informtica por la Universidad Politcnica de Madrid.

RC Libros
Calle Mar Mediterrneo, 2
Parque Empresarial Inbisa, N-6 P.I. Las Fronteras
28830 SAN FERNANDO DE HENARES, Madrid
Telfono: +34 91 677 57 22
Fax: +34 91 677 57 22
Correo electrnico: info@rclibros.es
Internet: www.rclibros.es

Diseo de coleccin, preimpresin y cubierta: Grupo RC


Impresin y encuadernacin: Grficas Deva, S.L.
Depsito Legal: M-
Impreso en Espaa

14-13 12 11 10 (03)
Prlogo

El presente libro pretende ser un manual de ayuda para estudiantes y


estudiosos de procesadores de lenguajes y/o compiladores.
La teora de compiladores es un mundo apasionante dentro de la
informtica. Pero a la vez complejo. El desarrollo de un compilador para un
lenguaje medianamente potente es una tarea dura y costosa, tanto en tiempo
como en recursos.
Pero al tiempo de ser una tarea costosa, puede ser muy gratificante, al
implicar campos bsicos de la informtica como son la teora de autmatas, de
lenguajes, estructura y arquitectura de computadores, lenguajes de
programacin y algunos otros ms.
Al construir un compilador, el desarrollador debe adentrarse en
aspectos especficos de lo que es un computador y de lo que es un lenguaje de
programacin. Esto le da una visin profunda de aspectos clave del mundo de
la informtica.
Este libro pretende ocupar un espacio que est poco tratado, sobre todo
en espaol. Se trata de la construccin de un compilador paso a paso, desde la
especificacin del lenguaje hasta la generacin del cdigo final (generalmente,
un ejecutable). Hay muchos libros que tratan la teora y algunos ejemplos ms
o menos complejos de compiladores. Existen tambin libros que desarrollan
pequeos compiladores pero hasta ciertas fases, sin llegar a completar todo el
proceso de desarrollo. Lo que pretendemos con este libro es dar las bases
tericas suficientes para poder abordar la construccin de un compilador
completo, y luego implementarlo.
El libro consta de 5 partes, un prlogo y el ndice.
COMPILADORES

En la parte I se analizan los aspectos tericos de los procesadores de


lenguajes y/o compiladores. Ocupa casi la mitad del libro. En estos ocho
captulos se desgranan las fases en las que se distribuye el proceso de creacin
de un compilador.
El captulo 1 es una introduccin, el captulo 2 trata del anlisis lxico,
el 3 del sintctico, el 4 del anlisis sintctico descendente, el 5 del ascendente,
el 6 de la tabla de tipos y de smbolos, el 7 del anlisis semntico y el 8 de la
generacin de cdigo intermedio y final.
En las siguientes tres partes se desarrollan completamente sendos
compiladores o traductores. Cada parte es ms completa y compleja que la
anterior.
En la parte II se desarrolla la creacin de un traductor para un lenguaje
de lgica de proposiciones. El lenguaje se llama L-0. En esta parte, el captulo 9
trata la especificacin del lenguaje, en el 10 se realiza el anlisis lxico, en el 11
el anlisis sintctico y en el 12 el semntico y la generacin de cdigo.
En la parte III se desarrolla un compilador para un subconjunto de C.
Le llamamos C-0 y es bastante simple. Pero es necesario su estudio para poder
abarcar la parte IV, en la que se construye un compilador para C ms complejo
(C-1). Se deja para el lector la construccin de un compilador an ms
complejo, que podramos llamarle C-2.
Dentro de la parte III, el captulo 13 trata la especificacin del lenguaje,
el 14 el anlisis lxico, sintctico y semntico, el 15 la generacin de cdigo
intermedio y el 16 la generacin de cdigo final.
La parte IV, en la que se desarrolla un compilador para C ms
complejo, C-1, consta del captulo 17 en el que se trata la especificacin del
lenguaje, el captulo 18 para el anlisis lxico y sintctico, el 19 para el anlisis
semntico y el 20 para la generacin de cdigo.
La parte V consta de dos apndices y la bibliografa. El apndice A
explica las herramientas que se han utilizado en el desarrollo de los
compiladores y el apndice B explica las instrucciones de cdigo intermedio y
final en Ens2001 para el lenguaje C-1. Esta parte concluye con la bibliografa.

XX RC Libros
PRLOGO

Este libro puede servir de material para un curso de un semestre sobre


compiladores o para un curso de dos semestres. Si se utiliza para un semestre,
que suele ser lo ms normal para una Ingeniera Tcnica en Informtica, se
puede estudiar la parte I (captulos 1 a 7) y luego implementar el compilador
L-0 de la parte II (captulos 9, 10, 11 y 12) y el compilador C-0 de la parte III
(slo captulos 13 y 14). Por ltimo, el apndice A.
El resto del libro se podra incluir en un curso de dos semestres, que
suele ser lo habitual en un segundo ciclo de Ingeniera Informtica.

Curso de 1 semestre Ingeniera Tcnica en Informtica


Captulos 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14 y apndice A.
Curso de 2 semestres Ingeniera Informtica Captulos del
1 al 20 y apndices A y B.
Espero que este libro sea de inters del lector y que pueda sacar
provecho a su lectura. Tanto como he sacado yo al escribirlo.
Nota: El cdigo fuente de los compiladores se encuentra en la pgina
web: www.rclibros.es en la seccin Zona de archivos, tambin lo puede
solicitar al autor en su direccin de correo electrnico jacinruiz@hotmail.com.

Jacinto Ruiz Cataln


Ingeniero en Informtica
Ingeniero Tcnico en Informtica de Sistemas
Baena, Octubre de 2009

RC Libros XXI
PARTE I. TEORA

1. Introduccin
2. Anlisis lxico
3. Anlisis sintctico
4. Anlisis sintctico descendente
5. Anlisis sintctico ascendente
6. Tabla de tipos y de smbolos
7. Anlisis semntico
8. Generacin de cdigo intermedio y final
CAPTULO 1

Introduccin
1.1 Definicin de compilador
Un compilador es un tipo especial de traductor en el que el lenguaje
fuente es un lenguaje de alto nivel y el lenguaje objeto es de bajo nivel (figura
1.1).

Figura 1.1. Esquema de un compilador


COMPILADORES

Un traductor es un programa que convierte el texto escrito en un


lenguaje en texto escrito en otro lenguaje (figura 1.2).

Figura 1.2. Esquema de un traductor


Un ensamblador es un compilador donde el lenguaje fuente es un
lenguaje ensamblador y el lenguaje objeto es el cdigo de la mquina.
La diferencia entre compilador e intrprete es que el compilador
analiza todo el programa fuente, crea el programa objeto y luego permite su
ejecucin (slo del programa objeto obtenido) y el intrprete lee sentencia por
sentencia el programa fuente, la convierte en cdigo objeto y la ejecuta. Por lo
tanto, es fcil comprender que tras compilar un programa, su ejecucin es
mucho ms rpida que la ejecucin de un programa interpretado.
Uno de los motivos de la existencia de programas interpretados es que
hay algunos lenguajes de programacin que permiten aadir sentencias
durante la ejecucin, cosa que no se podra hacer si fueran compilados.
Algunos ejemplos son las versiones ms antiguas de Basic y actualmente el
lenguaje Phyton.

4 RC Libros
CAPTULO 1: INTRODUCCIN

El compilador es asistido por otros programas para realizar su tarea,


por ejemplo, se utiliza un preprocesador para aadir ficheros, ejecutar macros,
eliminar comentarios, etctera.
El enlazador se encarga de aadir al programa objeto obtenido, las
partes de las libreras necesarias.

El depurador permite al programador ver paso a paso lo que ocurre


durante la ejecucin del programa.

Hay compiladores que no generan cdigo mquina sino un programa


en ensamblador, por lo que habr que utilizar un programa ensamblador para
generar el cdigo mquina.

1.2 Estructura de un compilador

Un compilador es un programa complejo que consta de una serie de


pasos, generalmente entrelazados, y que como resultado convierte un
programa en un lenguaje de alto nivel en otro de bajo nivel (generalmente
cdigo mquina o lenguaje ensamblador).

Los pasos o fases de la compilacin estn actualmente bien definidos y


en cierta medida sistematizados, aunque no estn faltos de dificultad. Esta
aumenta conforme se incrementa la riqueza del lenguaje a compilar.

Las fases del proceso de compilacin son las siguientes (figura 1.3):

RC Libros 5
COMPILADORES

Figura 1.3. Fases del proceso de compilacin

6 RC Libros
CAPTULO 1: INTRODUCCIN

1.2.1 Anlisis lxico

Esta fase consiste en leer el texto del cdigo fuente carcter a carcter e
ir generando los tokens (caracteres relacionados entre s). Estos tokens
constituyen la entrada para el siguiente proceso de anlisis (anlisis
sintctico). El agrupamiento de caracteres en tokens depende del lenguaje que
vayamos a compilar; es decir, un lenguaje generalmente agrupar caracteres
en tokens diferentes de otro lenguaje.

Los tokens pueden ser de dos tipos; cadenas especficas como palabras
reservadas, puntos y comas, etc., y no especficas, como identificadores,
constantes y etiquetas. La diferencia entre ambos tipos de tokens radica en si ya
son conocidos previamente o no. Por ejemplo, la palabra reservada while es
conocida previamente en un lenguaje que la utilice, pero el nombre de una
variable no es conocido anteriormente ya que es el programador el que le da
nombre en cada programa.

Por lo tanto, y resumiendo, el analizador lxico lee los caracteres que


componen el texto del programa fuente y suministra tokens al analizador
sintctico.

El analizador lxico ir ignorando las partes no esenciales para la


siguiente fase, como pueden ser los espacios en blanco, los comentarios, etc.,
es decir, realiza la funcin de preprocesador en cierta medida.

Los tokens se consideran entidades con dos partes: su tipo y su valor o


lexema. En algunos casos, no hay tipo (como en las palabras reservadas). Esto
quiere decir que por ejemplo, si tenemos una variable con nombre x, su tipo
es identificador y su lexema es x.

Por ejemplo, supongamos que tenemos que analizar un trozo de


programa fuente escrito en lenguaje Java:
x = x + y 3;
Los tokens suministrados al analizador sintctico seran estos (el
nombre que le demos a los tipos de tokens depende de nosotros):

RC Libros 7
COMPILADORES

x : Tipo identificador, lexema x


= : Lexema =
x : Tipo identificador, lexema x
+ : Lexema +
y : Tipo identificador, lexema y
- : Lexema
3 : Tipo entero, lexema 3
; : Lexema ;

1.2.2 Anlisis sintctico

El analizador lxico tiene como entrada el cdigo fuente en forma de


una sucesin de caracteres. El analizador sintctico tiene como entrada los
lexemas que le suministra el analizador lxico y su funcin es comprobar que
estn ordenados de forma correcta (dependiendo del lenguaje que queramos
procesar). Los dos analizadores suelen trabajar unidos e incluso el lxico suele
ser una subrutina del sintctico.

Al analizador sintctico se le suele llamar prser. El prser genera de


manera terica un rbol sintctico. Este rbol se puede ver como una
estructura jerrquica que para su construccin utiliza reglas recursivas. La
estructuracin de este rbol hace posible diferenciar entre aplicar unos
operadores antes de otros en la evaluacin de expresiones. Es decir, si tenemos
esta expresin 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 sintctico generar de los dos posibles.

8 RC Libros
CAPTULO 1: INTRODUCCIN

Figura 1.4. Arbol sintctico

Figura 1.5. Arbol sintctico


En resumen, la tarea del analizador sintctico es procesar los lexemas que le
suministra el analizador lxico, comprobar que estn bien ordenados, y si no lo estn,
generar los informes de error correspondientes. Si la ordenacin es correcta, se
generar un rbol sintctico terico.
1.2.3 Anlisis semntico
Esta fase toma el rbol sintctico terico de la anterior fase y hace una
serie de comprobaciones antes de obtener un rbol semntico terico.

RC Libros 9
COMPILADORES

Esta fase es quizs la ms compleja. Hay que revisar que los


operadores trabajan sobre tipos compatibles, si los operadores obtienen como
resultado elementos con tipos adecuados, si las llamadas a subprogramas
tienen los parmetros adecuados tanto en nmero como en tipo, etc.
Esta fase debe preparar el terreno para atajar las fases de generacin de
cdigo y debe lanzar los mensajes de error que encuentre. En resumen, su
tarea es revisar el significado de lo que se va leyendo para ver si tiene sentido.
Esta fase, las anteriores y las siguientes se detallarn ms adelante, en
el desarrollo de los otros captulos.
1.2.4 Generacin de cdigo intermedio
El cdigo intermedio (CI) es la representacin en un lenguaje sencillo
(incluso inventado por el creador del compilador) de la secuencia real de las
operaciones que se harn como resultado de las fases anteriores.
Se trata de representar de una manera formalizada las operaciones a
llevar a cabo en un lenguaje ms cercano a la mquina final, aunque no a una
mquina concreta sino a una mquina abstracta. Es decir, no consiste en
generar cdigo ensamblador para una mquina basada en un procesador
M68000, por ejemplo, sino en generar cdigo que podra luego implementarse
en cualquier mquina. Este lenguaje intermedio debe de ser lo ms sencillo
posible y, a la vez, lo ms parecido a la manera de funcionar de la mquina
final.
Hay dos ventajas clave por las que se debe utilizar la generacin de
cdigo intermedio:

Permite crear compiladores para diferentes mquinas con bastante


menos esfuerzo que realizar todo el proceso para cada una de ellas.
Permite crear compiladores de diferentes lenguajes para una
misma mquina sin tener que generar cada vez el cdigo final
(puesto que tenemos ya el cdigo intermedio creado).
Se suele utilizar una forma normalizada de instrucciones para este
lenguaje intermedio que se llama cdigo de tres direcciones.
10 RC Libros
CAPTULO 1: INTRODUCCIN

Este cdigo tiene su origen en la necesidad de evaluar expresiones,


aunque se utiliza para todo el proceso (modificando el significado inicial de
direccin).
Veamos un ejemplo aclaratorio. Supongamos que tenemos una
mquina terica que consta de registros numerados del R1 en adelante que
pueden contener valores de tipo entero. Pensemos que tenemos este cdigo en
Java:
x = x +1;
Sea que x representa un valor entero y que hay un mtodo que nos
convierte el lexema de x en su valor entero. Supongamos que 1 es un lexema
que representa otro valor entero, en este caso el valor 1 y que tenemos un
mtodo para convertir el lexema en su valor entero. Supongamos tambin que
tenemos un mtodo para convertir un nmero entero en su lexema.
Los pasos para generar el CI de la sentencia anterior seran:
R1 = valor(x)
R2 = valor(1)
R3 = R1 + R2
x = lexema(R3)
Estas operaciones las podramos representar con el siguiente cdigo
intermedio:
CARGAR x null R1
CARGAR 1 null R2
SUMAR R1 R2 R3
CARGAR R3 null x
Si revisamos lo que hemos hecho, veremos que hay dos tipos de
instrucciones de CI, una para cargar y otra para sumar (en el captulo en que
explicamos ms detalladamente la generacin de CI veremos que hay
bastantes ms instrucciones). Pero las dos tienen un punto en comn, constan
de un identificador de instruccin y de tres parmetros, aunque alguno de
ellos puede ser nulo.
Cualquier expresin puede ser representada por una o varias de estas
instrucciones de tres direcciones. Las llamamos de tres direcciones porque en

RC Libros 11
COMPILADORES

realidad se utilizan direcciones de memoria (llamadas temporales) y no


registros.
El cdigo de tres direcciones consta de un identificador de cdigo, dos
direcciones de operandos y una direccin de resultado de la operacin.
Para realizar cualquier operacin, es suficiente con generar diferentes
instrucciones de cdigo de este tipo, por lo que podemos tener un CI sencillo,
verstil y til.
Veremos un caso real para ver cmo funciona:
Supongamos que tenemos un programa en lenguaje C que consiste
slo en declarar la variable x. Cargarla con el valor 0 y luego sumarle 1.
Supongamos que nuestro programa se va a alojar a partir de la direccin de
memoria 0. Vamos a guardar los contenidos de las variables a partir de la
direccin 9000 y vamos a utilizar las direcciones a partir de la 10000 como
direcciones temporales.
El programa sera algo as:

int x
main() {
x = 0;
x = x + 1;
}
Se podra generar este cdigo intermedio:

CARGAR 0 null 10000


CARGAR 10000 null 9000
CARGAR 9000 null 10001
CARGAR 1 null 10002
SUMAR 10001 10002 10003
CARGAR 10003 null 9000

Podemos ver que vamos utilizando direcciones temporales conforme


las vamos necesitando para guardar resultados parciales.

12 RC Libros
CAPTULO 1: INTRODUCCIN

Al final de todo, vemos que hemos recuperado el resultado parcial


guardado en la direccin 10003 y lo hemos cargado en la direccin donde
guardbamos el valor de la variable x. Por lo que al final, el valor de x es 1,
que es el contenido de la direccin 9000.
La optimizacin de cdigo intermedio consiste en el proceso de ir
reutilizando estas direcciones temporales para que el consumo de memoria no
se dispare. Adems, consiste en optimizar el cdigo generado para reducir el
nmero de instrucciones necesarias para realizar las mismas operaciones.
1.2.5 Generacin de cdigo final
La generacin de cdigo final (CF) es un proceso ms mecnico, ya que
consiste en ir pasando las distintas instrucciones de CI (que suelen ser de
pocos tipos diferentes) al lenguaje ensamblador de la mquina que se vaya a
utilizar (ms adelante, se puede ensamblar el cdigo y obtener un ejecutable,
pero este proceso ya es automtico).
Dependiendo de la cantidad de memoria disponible o del nmero de
registros disponibles, quizs sea necesario utilizar en vez de direcciones de
memoria como temporales, registros, ya que as se reduce la memoria a usar.
Esto es recomendable sobre todo para programas grandes porque casi todas
las operaciones que se realizarn van a ser de cargas y almacenamientos de
valores.
1.2.6 Tablas de smbolos y de tipos
Ponemos aqu esta seccin, aunque no es una fase del proceso de
compilacin, porque es una parte importantsima de todo el proceso.
La tabla de smbolos es una estructura de datos auxiliar donde se va a
guardar informacin sobre las variables declaradas, las funciones y
procedimientos, sus nombres, sus parmetros y en generar cuanta
informacin vaya a ser necesaria para realizar todo el proceso de compilacin.
La tabla de tipos no es menos importante, ya que va a guardar
informacin tanto sobre los tipos bsicos como sobre los tipos definidos en el
programa a compilar.

RC Libros 13
COMPILADORES

El compilador debe tener acceso a estas tablas a lo largo de todo el


proceso de compilacin. Adems, el nmero de accesos suele ser alto, por lo
que es conveniente optimizar la estructura de estas tablas para que su
manipulacin sea lo menos costosa en tiempo y as reducir el tiempo de
compilacin (aunque no habr variacin en el tiempo de ejecucin del
programa compilado ya que el proceso de manipulacin de las tablas de tipos
y smbolos se hace en tiempo de compilacin).
1.2.7 Manejo de errores
El manejo de errores es vital para el proceso ya que informa al
programador dnde hay errores, de qu tipo son, etc. Cuanta ms informacin
sea capaz de dar al programador, mejor.
Por lo tanto, no hay que dejar atrs este aspecto del proceso de
compilacin.
La deteccin de errores no siempre es posible en la fase en que se
producen, algunas veces se detectarn en fases posteriores y algunos de ellos
no se podrn detectar ya que se producirn en el tiempo de ejecucin (sern
responsabilidad del programador que ha realizado el cdigo fuente).
Generalmente, es en las fases de anlisis sintctico y semntico donde
suelen aparecer y es importante que el proceso no se pare al encontrar un error, sino
que contine su proceso de anlisis hasta el final y luego informe de todos los
errores encontrados.

1.3 Fases del proceso de compilacin


Aunque las fases del proceso son las descritas en el epgrafe anterior,
estas pueden agruparse desde un punto de vista diferente. Se pueden agrupar
en una fase inicial o front-end, y en una fase final o back-end. El front-end agrupa
las fases dependientes del lenguaje fuente e independientes de la mquina
final y el back-end las fases independientes del lenguaje fuente pero
dependientes del cdigo intermedio y de la mquina de destino.

14 RC Libros
CAPTULO 1: INTRODUCCIN

Cada fase comienza con un cdigo y termina con otro diferente. El


front-end comienza con el cdigo fuente y finaliza con el cdigo intermedio. Y
el back-end comienza con el cdigo intermedio y acaba con el cdigo final.

Figura 1.6. Front-end y back-end

RC Libros 15
COMPILADORES

1.4 Herramientas y descripcin del lenguaje


A la hora de hacer todo el proceso de confeccin de un compilador se
suelen utilizar herramientas, que al no abarcar todas las fases, suelen
combinarse. Aparte de las herramientas, se debe utilizar un lenguaje base para
programar los pasos a seguir. Este lenguaje base debe ser compatible con las
herramientas que utilicemos.
Por otra parte, antes de nada hay que saber para qu lenguaje vamos a
hacer el compilador. Debemos definir su especificacin lxica (los lexemas que
utiliza), su especificacin sintctica (la ordenacin vlida de esos lexemas) y su
especificacin semntica (descripcin del significado de cada lexema y las
reglas que deben cumplirse).
Tambin debemos saber si existe ya un cdigo intermedio que
podamos utilizar o hay que crear uno nuevo (este aspecto se suele hacer
cuando llegamos a la fase correspondiente) y el cdigo final que utilizaremos
(tambin se suele hacer durante la fase de generacin de cdigo intermedio y
generacin de cdigo final).
Otro aspecto que debemos tener en cuenta es el lenguaje base que
vamos a utilizar, que depender tanto de nuestras preferencias como de las
herramientas de ayuda que utilicemos.

16 RC Libros