thumb|300px|Diagrama a bloques de la operación de un compilador multilenguaje típico.

En informática, un compilador es un programa que traduce código escrito en un lenguaje de programación (llamado fuente) a otro lenguaje (conocido como objeto). En este tipo de traductor el lenguaje fuente es generalmente un lenguaje de alto nivel y el objeto un lenguaje de bajo nivel, como assembly o código máquina. Este proceso de traducción se conoce como compilación.

La construcción de un compilador involucra la división del proceso en una serie de fases que variará con su complejidad. Generalmente estas fases se agrupan en dos tareas: el análisis del programa fuente y la síntesis del programa objeto.

Análisis: se trata de la comprobación de la corrección del programa fuente, según la definición del lenguaje en términos de teoría de lenguajes formales. Incluye las fases correspondientes al análisis léxico (que consiste en la descomposición del programa fuente en componentes léxicos), análisis sintáctico (agrupación de los componentes léxicos en frases gramaticales ) y análisis semántico (comprobación de la validez semántica de las sentencias aceptadas en la fase de análisis sintáctico). Síntesis: su objetivo es la generación de la salida expresada en el lenguaje objeto y suele estar formado por una o varias combinaciones de fases de generación de código (normalmente se trata de código intermedio o de código objeto) y de optimización de código (en las que se busca obtener un programa objetivo lo más eficiente posible, según su complejidad computacional o complejidad de Kolmogórov: tiempo de ejecución, espacio durante ejecución, espacio para ser almacenado fuera de ejecución, etc.).

Alternativamente, las fases descritas para las tareas de análisis y síntesis se pueden agrupar en:

Analizador o front-end: es la parte que analiza el código fuente, comprueba su validez, genera el árbol de derivación y rellena los valores de la tabla de símbolos. Esta parte suele ser independiente de la plataforma o sistema para el cual se vaya a compilar, y está compuesta por las fases comprendidas entre el análisis léxico y la generación de código intermedio. Generador o back-end: es la parte que genera el código máquina, específico de una plataforma, a partir de los resultados de la fase de análisis.

Esta división permite que el mismo generador se utilice para crear el código máquina de varios lenguajes de programación distintos y que el mismo analizador que sirve para examinar el código fuente de un lenguaje de programación concreto sirva para producir código máquina en varias plataformas.

Historia

En 1938, Konrad Zuse desarrolló la primera computadora digital electromecánica, denominada Z1 en Alemania, y posteriormente, en 1946, se desarrolló la primera computadora totalmente electrónica ENIAC, sucedida principalmente por la EDVAC (1951), primera computadora electrónica digital. En un principio, estas máquinas ejecutaban instrucciones consistentes en códigos numéricos que señalaban a los circuitos de la máquina los estados correspondientes a cada operación, lo que se denominó lenguaje máquina.

Pronto los primeros usuarios de estos ordenadores descubrieron la ventaja de escribir sus programas mediante claves más fáciles de recordar que esos códigos; al final, todas esas claves juntas se traducían manualmente a lenguaje máquina. Estas claves constituyen los llamados lenguajes ensambladores.

Pese a todo, el lenguaje ensamblador seguía siendo el de una máquina, pero más fácil de manejar (las instrucciones de máquina se reemplazan por mnemónicos). Los trabajos de investigación se orientaron hacia la creación de un lenguaje que expresara las distintas acciones a realizar de una manera lo más sencilla posible para una persona. El primer compilador fue escrito por Grace Hopper, en 1952 para el lenguaje de programación A-0. En 1950 John Backus dirigió una investigación en IBM sobre un lenguaje algebraico. En 1954 se empezó a desarrollar un lenguaje que permitía escribir fórmulas matemáticas de manera traducible por un ordenador; le llamaron FORTRAN (FORmulae TRANslator). Fue el primer lenguaje de alto nivel y se introdujo en 1957 para el uso de la computadora IBM modelo 704.

Surgió así por primera vez el concepto de un traductor como un programa que traducía un lenguaje a otro lenguaje. En el caso particular de que el lenguaje a traducir es un lenguaje de alto nivel y el lenguaje traducido de bajo nivel, se emplea el término compilador.

El trabajo de realizar un compilador fue complicado de realizar. El primer compilador de FORTRAN tardó 18 años-persona en realizarse y era muy sencillo. Este desarrollo de FORTRAN estaba muy influenciado por la máquina objeto en la que iba a ser implementado. Como un ejemplo de ello tenemos el hecho de que los espacios en blanco fuesen ignorados, debido a que el periférico que se utilizaba como entrada de programas (una lectora de tarjetas perforadas) no contaba correctamente los espacios en blanco.

El primer compilador autocontenido, es decir, capaz de compilar su propio código fuente fue el creado para Lisp por Hart y Levin en el MIT en 1962. Desde 1970 se ha convertido en una práctica común escribir el compilador en el mismo lenguaje que este compila, aunque PASCAL y C han sido alternativas muy usadas.

Crear un compilador autocontenido genera un problema llamado bootstrapping, es decir el primer compilador creado para un lenguaje tiene que o bien ser compilado por un compilador escrito en otro lenguaje o bien compilado al ejecutar el compilador en un intérprete.

Tipos de compiladores Esta taxonomía de los tipos de compiladores no es excluyente, por lo que puede haber compiladores que se adscriban a varias categorías:

Compiladores cruzados: generan código para una plataforma distinta a aquella en la que están funcionando. Compiladores optimizadores: realizan cambios en el código para mejorar su eficiencia, pero manteniendo la funcionalidad del programa original. Compiladores de una sola pasada: generan el código máquina a partir de una única lectura del código fuente. Compiladores de varias pasadas: necesitan leer el código fuente varias veces antes de poder producir el código máquina. Compiladores JIT (just in time): forman parte de un intérprete y compilan partes del código según se necesitan.

En las primeras épocas de la informática, los compiladores eran considerados un software de los más complejos existentes.

Los primeros compiladores se realizaron programándolos directamente en lenguaje máquina o en ensamblador. Una vez que se dispone de un compilador, se pueden escribir nuevas versiones del compilador (u otros compiladores distintos) en el lenguaje que compila ese compilador.

Existen herramientas que facilitan la tarea de escribir compiladores o intérpretes informáticos. Estas herramientas permiten generar el esqueleto del analizador sintáctico a partir de una definición formal del lenguaje de partida, especificada normalmente mediante una gramática formal y barata, dejando únicamente al programador del compilador la tarea de programar las acciones semánticas asociadas.

Proceso de compilación

Es el proceso por el cual se traducen las instrucciones escritas en un determinado lenguaje de programación a lenguaje máquina. Además de un traductor, se pueden necesitar otros programas para crear un programa objeto ejecutable. Un programa fuente se puede dividir en módulos almacenados en archivos distintos. La tarea de reunir el programa fuente a menudo se confía a un programa distinto, llamado preprocesador. El preprocesador también puede expandir abreviaturas, llamadas a macros, a proposiciones del lenguaje fuente.

Normalmente la creación de un programa ejecutable (un típico archivo .exe para Windows o DOS) conlleva dos pasos. El primer paso se llama compilación (propiamente dicho) y traduce el código fuente escrito en un lenguaje de programación almacenado en un archivo a código en bajo nivel (normalmente en código objeto, no directamente a lenguaje máquina). El segundo paso se llama enlazado en el cual se enlaza el código de bajo nivel generado de todos los ficheros y subprogramas que se han mandado a compilar y se añade el código de las funciones que hay en las bibliotecas del compilador para que el ejecutable pueda comunicarse directamente con el sistema operativo, traduciendo así finalmente el código objeto a código máquina, y generando un módulo ejecutable.

Estos dos pasos se pueden hacer por separado, almacenando el resultado de la fase de compilación en archivos objetos (un típico.obj para Microsoft Windows, DOS o para Unix); para enlazarlos en fases posteriores, o crear directamente el ejecutable; con lo que la fase de compilación se almacena solo temporalmente. Un programa podría tener partes escritas en varios lenguajes (por ejemplo C, C++ y Asm), que se podrían compilar de forma independiente y luego enlazar juntas para formar un único módulo ejecutable.

Etapas del proceso

El proceso de traducción se compone internamente de varias etapas o fases, que realizan distintas operaciones lógicas. Es útil pensar en estas fases como en piezas separadas dentro del traductor, y pueden en realidad escribirse como operaciones codificadas separadamente aunque en la práctica a menudo se integren juntas.

Fase de análisis

Análisis léxico

El análisis léxico constituye la primera fase, aquí se lee el programa fuente de izquierda a derecha y se agrupa en componentes léxicos (tókenes), que son secuencias de caracteres que tienen un significado. Además, todos los espacios en blanco, líneas en blanco, comentarios y demás información innecesaria se elimina del programa fuente. También se comprueba que los símbolos del lenguaje (palabras clave, operadores, etc.) se han escrito correctamente.

Como la tarea que realiza el analizador léxico es un caso especial de coincidencia de patrones, se necesitan los métodos de especificación y reconocimiento de patrones, se usan principalmente los autómatas finitos que acepten expresiones regulares. Sin embargo, un analizador léxico también es la parte del traductor que maneja la entrada del código fuente, y puesto que esta entrada a menudo involucra un importante gasto de tiempo, el analizador léxico debe funcionar de manera tan eficiente como sea posible.

Análisis sintáctico

En esta fase los caracteres o componentes léxicos se agrupan jerárquicamente en frases gramaticales que el compilador utiliza para sintetizar la salida. Se comprueba si lo obtenido de la fase anterior es sintácticamente correcto (obedece a la gramática del lenguaje). Por lo general, las frases gramaticales del programa fuente se representan mediante un árbol de análisis sintáctico.

La estructura jerárquica de un programa normalmente se expresa utilizando reglas recursivas. Por ejemplo, se pueden dar las siguientes reglas como parte de la definición de expresiones:

Un identificador puede ser una expresión. Un número puede ser una expresión. Si expresión1 y expresión2 son expresiones, entonces también lo son: expresión1 + expresión2 expresión1 * expresión2 ( expresión1 )

Las reglas 1 y 2 son reglas básicas (no recursivas), en tanto que la regla 3 define expresiones en función de operadores aplicados a otras expresiones.