Este es el primero de una serie de posts que pretende explicar a los lectores de este blog que es, como funciona y esta diseñado un compilador. Al final de las serie haré un compendio de todos los posts y publicare el resultado en github bajo la licencia GPL, espero que sea de su utilidad.
Generalidades
Un compilador traduce el código escrito en un lenguaje entendible para los humanos a otro lenguaje que es interpretado por una máquina sin cambiar el significado del programa. Se espera entonces que el compilador no solo traduzca el código si no que este sea eficiente y además óptimo en términos de espacio y tiempo.
Las computadoras son una mezcla balanceada entre hardware y software. El hardware no es mas que un conjunto de dispositivos mecanicos o electronicos que son controlados al final por un software compatible. El hardware comprende las instrucciones que se le dan en forma de pulsos eléctricos de 5 voltios en donde 5 voltios representan un 1 y 0 voltios representan un 0 lo cual describe el formato binario en el que recibe las instrucciones. Ahora bien para un humano seria muy dificil y tardado escribir sus programas en código binario, razón por la cual existen los compiladores que escriben ese código (binario).
Sistema de procesamiento de lenguaje
Entonces los compiladores escriben codigo que los humanos no podemos o nos es difícil comprender pero que las máquinas si pueden, y los humanos escribimos código en un lenguaje de alto nivel que es fácil de entender y recordar para nosotros. Estos programas son los que sirven de entrada para una serie de programas y herramientas propias de los sistemas operativos que obtienen el código objetivo que puede ser entendido por una computadora, esto es conocido como el sistema de procesamiento de lenguaje.
El lenguaje de alto nivel o código fuente es convertido entonces a código binario en varias fases.
Un compilador es entonces un programa que convierte codigo fuente en codigo ensamblador y similarmente el ensamblador convierte lenguaje ensamblador a lenguaje de máquina.
Para comprender mejor este proceso analicemos como un programa, usando el compilador de C, es ejecutado en una computadora.
- El usuario escribe el programa en lenguaje C de alto nivel
- El compilador de C compila el programa y lo convierte en lenguaje ensamblador de bajo nivel
- El ensamblador entonces convierte el código ensamblador en un lenguaje de máquina o codigo objeto
- El enlazador entonces es el encargado de enlazar todas las partes del programa para su ejecución
- Finalmente el cargador carga este codigo ejecutable en la memoria y lo deja listo para su ejecución.
Hay algunas otras herramientas que entran en juego cuando diseñamos un compilador que describimos a continuación.
Preprocesador
Un preprocesador es una herramienta del compilador que produce entradas, se encarga de procesamiento a nivel macro, aumento, inclusión de archivos, extensiones del lenguaje, etc.
Interprete
Un intérprete similarmente a un compilador, convierte codigo de alto nivel a código de bajo nivel. La diferencia reside en la forma en la cual lee el código fuente de entrada. Un compilador lee todo el código fuente de una sola pasada, crea tokens, revisa la semántica, genera codigo intermedio, ejecuta todo el programa y podría involucrar en ciertos casos muchas pasadas. En contraste, un intérprete lee información de una declaración como codigo de entrada, la convierte en codigo intermedio, lo ejecuta, y luego toma la siguiente declaración de la secuencia. Si un error se reporta la ejecución se detiene por completo. Mientras que el compilador lee el programa completo y se detiene si hay error, la diferencia clave está en que el intérprete entonces lee/compila/ejecuta parte por parte.
Ensamblador
Un ensamblador traduce codigo de ensamblador a codigo de maquina, la salida del ensamblador es llamado código objeto, el cual contiene una combinación de codigo de maquina y las instrucciones para poner este codigo de maquina en la memoria.
Enlazador
Un enlazador es un programa de computadora que enlaza los diversos objetos que el ensamblador produce con el objetivo de hacer un archivo ejecutable. Todos esos archivos objeto podrían haber sido compilados por ensambladores distintos, pero la tarea principal del enlazador es buscar y localizar los módulos y rutinas referenciados en un programa y determinar la ubicación en la memoria donde estos códigos serán cargados, haciendo que las instrucciones del programa tengan referencias absolutas.
Cargador
Es la parte del sistema operativo que es responsable de cargar los archivos ejecutables en la memoria y ejecutarlos. Entre sus tareas estan calcular el tamaño del programa (datos e instrucciones) y reserve el espacio en la memoria para el mismo. Inicializa varios registros para iniciar con la ejecución.
Compilador Cruzado
Es un compilador que corre en una plataforma (A) y es capaz de generar codigo ejecutable para una plataforma (B)
Compilador fuente-fuente
Es un compilador que toma el codigo fuente de un lenguaje de programación y lo traduce en otro lenguaje de programación.
Un buen ejemplo de esto es coffee script, que traduce lenguaje de coffee script a codigo javascript. Aunque personalmente creo que esto es un desastre y no debería hacerse 🙂 es solo mi humilde opinión.