Un compilador (en inglés, compiler) es un programa que traduce otro programa, escrito en un lenguaje de programación que las personas pueden leer, a las instrucciones numéricas que ejecuta el procesador. El texto de partida se llama código fuente (source code) y el resultado, código objeto (object code) o código máquina (machine code). Entre una línea como total = precio * cantidad y lo que hace el procesador hay decenas de decisiones: en qué registro se guarda cada valor, qué instrucción de multiplicar se usa, si el resultado cabe o se desborda, dónde queda total en la memoria. El compilador las toma todas, y lo hace de forma que el programador no tenga que pensar en ellas.
Hace el trabajo en fases, cada una de las cuales entrega su resultado a la siguiente. El analizador léxico (lexer) corta el texto en palabras: nombres, números, operadores, signos de puntuación. El analizador sintáctico (parser) comprueba que esas palabras siguen la gramática del lenguaje y las organiza en un árbol sintáctico (syntax tree), en el que la multiplicación cuelga de la asignación y los dos factores cuelgan de la multiplicación. El análisis semántico (semantic analysis) mira el significado: que las variables existen, que no se suma un texto con un número si el lenguaje no lo permite, que la función recibe los argumentos que espera. Después, el compilador traduce el árbol a una representación intermedia (intermediate representation), una especie de lenguaje de máquina genérico, sobre la que aplica la optimización (optimization): quitar cálculos repetidos, sacar de un bucle lo que no cambia dentro de él, sustituir una multiplicación por dos por un desplazamiento de bits. Por último, la generación de código (code generation) produce las instrucciones del procesador concreto. Los errores que un programador ve al compilar salen de las tres primeras fases; los de la cuarta y la quinta, cuando los hay, son del compilador.
La alternativa es el intérprete (interpreter), que no traduce el programa entero antes de ejecutarlo sino que lo lee y lo ejecuta instrucción por instrucción. Es más cómodo, porque se puede probar cada línea al momento, y más lento, porque la traducción se repite cada vez. Python y JavaScript nacieron interpretados, C y Fortran compilados, y los sistemas actuales mezclan las dos cosas: Java compila a un código intermedio que luego interpreta una máquina virtual, y los navegadores usan un compilador en tiempo de ejecución (just-in-time compiler, JIT) que traduce a código máquina las partes de un programa JavaScript que más se repiten mientras se está ejecutando.
La historia empieza con una desconfianza. A principios de los años cincuenta se programaba en código máquina o en ensamblador, y la idea de que un programa pudiera escribir otro programa parecía, a muchos, un lujo de ineficiencia. Grace Hopper escribió en 1952 el A-0 para el UNIVAC, que ensamblaba subrutinas a partir de una notación más cómoda y al que ella misma llamó compilador, aunque se parecía más a lo que hoy se llama un enlazador. El cambio lo hizo FORTRAN. John Backus convenció a IBM en 1954 de que le dejara un equipo para construir un traductor de fórmulas matemáticas para el IBM 704, y el compilador se entregó en abril de 1957, tras dieciocho años-persona de trabajo. Backus sabía que el lenguaje solo se aceptaría si el código que producía era casi tan rápido como el escrito a mano, y el equipo dedicó la mayor parte del esfuerzo a la optimización. Lo consiguió, y en pocos años FORTRAN era el lenguaje de la ciencia. Sigue siéndolo en algunos campos, como la predicción meteorológica.
La teoría llegó después y convirtió un arte en una disciplina. Noam Chomsky clasificó las gramáticas formales en 1956, el informe de ALGOL 60 describió la sintaxis de un lenguaje entero con la notación que se llamaría de Backus-Naur, y Donald Knuth mostró en 1965 cómo construir analizadores sintácticos que leen el programa de izquierda a derecha una sola vez, sin volver atrás, para una familia muy amplia de gramáticas. Desde los años setenta, las dos primeras fases de un compilador casi no se programan: se describen con una gramática y las genera otro programa. El manual que fijó la disciplina, el de Alfred Aho, Ravi Sethi y Jeffrey Ullman, de 1986, se conoce entre los programadores como el libro del dragón por el dibujo de su portada.
Un compilador suele estar escrito en el mismo lenguaje que compila, lo que plantea un problema del huevo y la gallina: la primera versión se compila con otro lenguaje, y a partir de ahí cada versión se compila con la anterior. Ken Thompson, uno de los autores de Unix, dedicó su discurso de aceptación del premio Turing, publicado en 1984, a una consecuencia inquietante de ese proceso, que llamó «reflexiones sobre confiar en la confianza». Un compilador puede estar modificado para introducir una puerta trasera cada vez que compila el programa de acceso al sistema, y para reproducir esa misma modificación cada vez que se compila a sí mismo; después se borra el cambio del código fuente, y el compilador sigue propagándolo sin que quede rastro legible en ninguna parte. La conclusión de Thompson era que no se puede confiar en un programa que uno no ha escrito del todo, y nadie escribe del todo su compilador. Las respuestas actuales son dos: la compilación doble diversa (diverse double-compiling), que David Wheeler propuso en 2005 y que compila el compilador sospechoso con otro independiente para comparar los resultados, y las compilaciones reproducibles (reproducible builds), que permiten a cualquiera comprobar que un programa distribuido sale exactamente del código fuente publicado.
Casi todo lo que se ejecuta en un ordenador ha pasado por un compilador, y casi nadie lo ve. Los errores que señala al programador, la rapidez del programa final y, en parte, su seguridad dependen de él. Es el lugar donde la intención escrita de una persona se convierte en lo que la máquina hará de verdad, y por eso la depuración de un error extraño termina a veces leyendo el código máquina que produjo.
Fuentes
- John W. Backus y otrosThe FORTRAN Automatic Coding SystemWestern Joint Computer Conference1957enlace
- Donald E. KnuthOn the Translation of Languages from Left to RightInformation and Control 8 (6)1965enlace
- Ken ThompsonReflections on Trusting TrustCommunications of the ACM 27 (8)1984enlace
- Alfred V. Aho, Monica S. Lam, Ravi Sethi y Jeffrey D. UllmanCompilers: Principles, Techniques, and ToolsAddison-Wesley2006