1.4.-Traductores
1.4. Traductores de lenguajes de alto nivel¶
Idea principal
Un programa escrito en un lenguaje de alto nivel necesita transformarse o procesarse para que el equipo pueda ejecutarlo. La compilación, la interpretación y la compilación JIT son estrategias distintas para recorrer ese camino, con efectos sobre el rendimiento, la portabilidad y las herramientas necesarias.
En los apartados anteriores has diferenciado el código fuente, el código objeto y el código intermedio. Ahora veremos qué herramientas producen o procesan esas representaciones. Entenderlas permite explicar por qué un proyecto en C genera un ejecutable para una plataforma concreta, mientras que una aplicación Java necesita una JVM o una aplicación Python necesita su intérprete.
El objetivo de este tema no es memorizar siglas aisladas, sino seguir el recorrido del programa desde el código fuente hasta su ejecución y justificar qué traductor interviene en cada caso de uso profesional.
Según la normativa del módulo Entornos de Desarrollo, este contenido contribuye al siguiente resultado de aprendizaje y criterios de evaluación:
| Código | Descripción |
|---|---|
| RA 1 | Reconoce los elementos y herramientas que intervienen en el desarrollo de un programa informático, analizando sus características y las fases en las que actúan hasta llegar a su puesta en funcionamiento. |
| CE 1.c | Se han diferenciado los conceptos de código fuente, objeto y ejecutable. |
| CE 1.d | Se han reconocido las características de la generación de código intermedio para su ejecución en máquinas virtuales. |
Qué deberías saber al terminar
Al acabar este apartado deberías poder:
- diferenciar compilación anticipada, interpretación, transpilación y compilación JIT;
- describir las fases de análisis y síntesis de un compilador;
- relacionar código fuente, código intermedio, código máquina y ejecutable con las herramientas que los producen o ejecutan;
- explicar qué papel desempeñan el frontend, el optimizador y el backend de un compilador.
Mapa del tema
En este documento seguiremos esta secuencia:
- qué hace un traductor y cómo funciona la compilación;
- qué fases y componentes tiene un compilador;
- cómo funciona la interpretación;
- cómo mejora el rendimiento la compilación JIT.
1. Compilación¶
Un compilador transforma un programa escrito en un lenguaje de programación en otra representación que permita continuar su ejecución o distribución. El caso más conocido es la compilación a código máquina, que produce un ejecutable para una plataforma determinada. Cuando esta traducción se completa antes de arrancar el programa se denomina compilación anticipada o AOT (Ahead-Of-Time).
Compilación AOT
Es la traducción del código fuente a código nativo antes de ejecutar la aplicación. El resultado suele ser un ejecutable o una biblioteca para un sistema operativo y una arquitectura concretos.
Lenguajes como Fortran, C, C++, Rust o Go suelen generar código nativo antes de la ejecución. Este enfoque permite aplicar optimizaciones costosas durante la construcción del programa y adaptar el resultado a la arquitectura de destino, por ejemplo x86-64 o ARM. A cambio, normalmente hay que generar y distribuir una versión distinta para cada plataforma compatible.
No toda compilación termina en código máquina. Algunos lenguajes, como Java, Kotlin sobre la JVM o C#, generan primero bytecode o código intermedio portable, que después procesa una máquina virtual. Este modelo se estudia con más detalle en el apartado anterior.
También existe la compilación fuente a fuente (source-to-source): el resultado es código fuente en otro lenguaje, no código máquina. A esta transformación se la suele llamar transpilación. Es habitual en desarrollo web porque los navegadores ejecutan JavaScript:
| Lenguaje de entrada | Resultado habitual | Uso representativo |
|---|---|---|
| TypeScript | JavaScript | Aplicaciones web con comprobación de tipos durante el desarrollo |
| Kotlin/JS | JavaScript | Interfaces web programadas con Kotlin |
| Dart | JavaScript | Aplicaciones web compiladas para navegador |
| PureScript | JavaScript | Aplicaciones web con enfoque funcional |
TypeScript en un proyecto web
Una persona desarrolladora escribe saludo.ts con anotaciones de tipos. La herramienta tsc comprueba esos tipos y genera saludo.js. El navegador no ejecuta TypeScript: descarga y ejecuta el JavaScript producido.
1.1. El proceso de compilación¶
La compilación es un proceso complejo que puede agruparse en dos grandes fases: análisis, para comprender y validar el programa, y síntesis, para producir una representación ejecutable o cercana a ella.
| Fase | Qué hace | Resultado o ejemplo |
|---|---|---|
| Análisis léxico | Agrupa los caracteres del código en tokens: palabras reservadas, identificadores, literales u operadores. | int suma = a + b; se convierte en una secuencia de tokens. |
| Análisis sintáctico | Comprueba que los tokens respetan la gramática y construye una representación estructurada. | Un AST (Abstract Syntax Tree) refleja que a + b forma parte de una asignación. |
| Análisis semántico | Revisa que el programa tenga sentido: tipos compatibles, nombres declarados y reglas del lenguaje. | Detecta que no se puede asignar un número a una variable de tipo texto sin conversión válida. |
| Generación de código intermedio | Produce una representación interna más cómoda para optimizar y generar distintos destinos. | Una IR (Intermediate Representation) del compilador. |
| Optimización | Modifica la representación sin alterar el comportamiento observable del programa. | Puede simplificar 5 * 10 a 50. |
| Generación de código objeto y enlazado | Genera código nativo y combina el resultado con bibliotecas necesarias. | Archivos objeto y, finalmente, un ejecutable. |
Un AST no es un ejecutable
El árbol de sintaxis abstracta es una estructura de datos que representa el programa durante el análisis. No contiene instrucciones que la CPU pueda ejecutar directamente; sirve de base para comprobar el código y generar representaciones posteriores.
1.2. Estructura de un compilador¶
Los compiladores actuales suelen dividir sus responsabilidades en tres partes. Esta organización permite reutilizar trabajo entre lenguajes y plataformas.
| Componente | Responsabilidad | Dependencia principal |
|---|---|---|
| Analizador o frontend | Realiza los análisis léxico, sintáctico y semántico; transforma el código fuente en una representación intermedia. | Lenguaje de origen |
| Optimizador | Mejora la representación intermedia sin cambiar el significado del programa. | Puede compartirse entre varios lenguajes y destinos |
| Generador o backend | Convierte la representación intermedia en código para el destino y prepara el enlazado. | Arquitectura y plataforma de destino |
Por ejemplo, un compilador puede tener un frontend para C y otro para C++, que producen una representación intermedia común. Después, distintos backends generan código para x86-64, ARM o RISC-V. Así no es necesario construir un compilador completo para cada combinación de lenguaje y procesador.
2. Interpretación y lenguajes interpretados¶
La interpretación es otra forma de ejecutar un programa. En lugar de entregar al sistema operativo un ejecutable nativo completo, un programa llamado intérprete analiza una representación del programa y realiza las operaciones indicadas. Esa representación puede ser el propio código fuente, un árbol de sintaxis abstracta o, con frecuencia, bytecode.
Intérprete
Es un programa que procesa el código fuente o una representación intermedia y realiza las operaciones que indican. Para ejecutar la aplicación, el intérprete debe estar disponible en el sistema.
Un intérprete puede recorrer directamente un AST, aunque este enfoque suele tener más sobrecoste. Es muy habitual que primero convierta el código fuente a bytecode y que ejecute ese bytecode. Por ello, clasificar un lenguaje como «compilado» o «interpretado» es una simplificación útil para empezar, pero el comportamiento real depende de la implementación y de su entorno de ejecución.
Python, PHP y Perl cuentan con implementaciones que usan bytecode o estructuras internas para ejecutar los programas. Ruby 1.8 empleaba árboles de sintaxis abstracta; las implementaciones actuales han evolucionado y no todas funcionan igual. En Python, por ejemplo, CPython compila normalmente el código fuente a bytecode y su máquina virtual lo ejecuta. Por tanto, ejecutar python programa.py no significa que la CPU lea el archivo fuente línea a línea.
No confundas el lenguaje con una implementación
Decir que «Python es interpretado» describe el uso habitual de CPython, pero existen otras implementaciones, como PyPy, que incorporan JIT. Para razonar con precisión, indica la herramienta o el entorno de ejecución concreto.
3. Compilación en tiempo de ejecución (JIT)¶
La compilación JIT (Just-In-Time) combina ideas de la interpretación y de la compilación. Un entorno de ejecución puede comenzar procesando bytecode y, mientras el programa está en marcha, detectar los métodos o fragmentos que se usan con frecuencia. Entonces los traduce a código máquina y conserva ese resultado en memoria para reutilizarlo.
El JIT dispone de información que una compilación AOT no conoce todavía, como qué rutas del programa se ejecutan más, los tipos observados o los valores habituales. Con ella puede optimizar las partes «calientes» del programa. Es una técnica habitual en la JVM de Java y Kotlin, en el CLR de .NET y en motores modernos de JavaScript.
código fuente → bytecode → entorno de ejecución
↓
identifica código frecuente
↓
JIT → código máquina en memoria → CPU
Una aplicación Java de larga duración
Un servicio Java que procesa muchas peticiones puede ejecutar primero bytecode y, tras detectar que un método de validación se invoca miles de veces, compilarlo con JIT a código nativo. Las llamadas posteriores reutilizan esa versión optimizada mientras siga siendo válida.
La compilación JIT, también llamada dinámica, puede mejorar mucho el rendimiento de una aplicación que se mantiene activa. Sin embargo, no garantiza que sea más rápida que AOT en todos los casos: necesita tiempo de arranque o warm-up, consume recursos para recopilar información y compilar, y sus resultados dependen de la carga real.
4. Elegir y usar traductores con criterio¶
La estrategia no se elige solo por una etiqueta del lenguaje. Depende de las restricciones del proyecto, del entorno de despliegue y de las herramientas disponibles.
| Situación | Enfoque habitual | Razón principal |
|---|---|---|
| Aplicación de sistema o componente con requisitos estrictos de rendimiento y distribución controlada | Compilación AOT | Genera código nativo específico y permite optimizar antes de distribuir. |
| Aplicación Java, Kotlin o .NET desplegada en varios sistemas | Bytecode o código intermedio más entorno de ejecución | Reutiliza el mismo artefacto cuando hay una máquina virtual compatible. |
| Automatización o herramienta ejecutada con el intérprete instalado | Interpretación o bytecode interpretado | Facilita un ciclo rápido de edición y ejecución. |
| Aplicación web escrita en TypeScript | Transpilación a JavaScript | El navegador recibe el formato que puede ejecutar. |
| Servicio de larga duración sobre JVM, .NET o un motor JavaScript | JIT | Puede optimizar el código más usado con datos de ejecución. |
5. Buenas prácticas¶
- Identifica los artefactos que genera el proyecto: distingue archivos fuente, objetos, bytecode, bibliotecas y ejecutables antes de borrarlos o distribuirlos.
- Comprueba el entorno de destino: un bytecode portable sigue necesitando una JVM, un CLR o un intérprete compatibles, además de sus bibliotecas y configuración.
- No deduzcas el modelo de ejecución solo por el nombre del lenguaje: consulta la implementación concreta, por ejemplo CPython, PyPy, JVM o V8.
- Mide antes de atribuir una mejora al JIT: en aplicaciones de larga duración, separa el tiempo de arranque del rendimiento estable y utiliza herramientas de perfilado.
- Incluye la transpilación en el proceso de construcción web: distribuye el JavaScript generado, no los archivos TypeScript, cuando el destino sea un navegador.
6. Errores frecuentes¶
| Error frecuente | Por qué ocurre | Cómo evitarlo |
|---|---|---|
| Creer que todo lenguaje es exclusivamente compilado o interpretado. | Las etiquetas simplifican modelos que pueden combinar bytecode, intérprete y JIT. | Indica el compilador o entorno concreto y qué representación procesa. |
| Confundir bytecode con código máquina. | Ambos son resultados de una traducción, pero el bytecode necesita normalmente un entorno de ejecución. | Relaciona el bytecode con la JVM, el CLR o la máquina virtual correspondiente. |
| Pensar que un transpilador genera un ejecutable. | La transpilación produce código fuente en otro lenguaje. | Revisa la extensión y el destino: TypeScript genera JavaScript, no un binario nativo. |
| Atribuir cualquier mejora de rendimiento al JIT. | El rendimiento también depende de la entrada/salida, la base de datos, la memoria y la configuración. | Perfila la aplicación y compara medidas en condiciones equivalentes. |
| Confundir un AST con código ejecutable. | El AST representa la estructura del programa, pero es una estructura interna de análisis. | Sitúalo entre los tokens y la generación de código intermedio o nativo. |
7. Resumen¶
En este tema has aprendido que:
- la compilación transforma código fuente a otra representación y, en AOT, suele producir código nativo antes de ejecutar;
- un compilador analiza el código mediante fases léxica, sintáctica y semántica, y después genera, optimiza y enlaza el resultado;
- un compilador se organiza habitualmente en frontend, optimizador y backend para reutilizar componentes entre lenguajes y plataformas;
- un intérprete procesa el código fuente o una representación intermedia durante la ejecución;
- JIT compila en tiempo de ejecución las partes más utilizadas y puede mejorar el rendimiento tras un periodo de warm-up;
- la transpilación transforma código fuente en otro lenguaje, como TypeScript a JavaScript.
Idea clave
Reconocer qué traductor interviene y qué artefacto genera permite explicar cómo llega un programa a la CPU, qué debe instalarse para ejecutarlo y qué compromisos existen entre rendimiento, portabilidad y rapidez de desarrollo.
8. Para seguir practicando¶
- Realiza la Práctica 1.2: Herramientas para generar un programa y compara el proceso de construcción y ejecución de dos lenguajes.
- Compila un programa Java con
javac, ejecutajavap -c NombreClasey localiza el bytecode generado; compáralo con un ejecutable nativo de C o C++. - Crea un archivo TypeScript mínimo, ejecútalo con
tscy revisa el archivo
Bibliografía y fuentes¶
- GCC, GCC online documentation.
- LLVM, LLVM Language Reference Manual.
- Oracle, Java Virtual Machine Specification.
- TypeScript, documentación del compilador.
- Python Software Foundation, documentación de CPython.
- Intérpretes de Python.
- Elegir un intérprete de Python.
- JDKs de Java y otros intérpretes para Java.
- Qué JDK usar.