Los programadores no siempre han tenido la suerte de contar con lenguajes de programación tan fáciles de comprender como los disponibles en la actualidad. El binario es el único lenguaje que las computadoras realmente hablan, y es ajeno a lo que los humanos están acostumbrados a usar. Los programas escritos en lo que se conoce como lenguajes de “nivel superior” (aquellos que son más fáciles de leer para los humanos) se convierten en una serie de 1 y 0 que las computadoras pueden leer. Esto sucede de dos formas: compilando e interpretando.
Compilación
El trabajo del compilador es traducir un programa a instrucciones específicas para un tipo de computadora, ya sea que ejecute Windows, MacOS, una Raspberry Pi, etc. Cada uno de estos sistemas tiene un hardware diferente que puede responder a diferentes instrucciones, y un compilador solo apunta a uno de ellos a la vez.
Interpretación
Los intérpretes son un poco diferentes: en realidad, no crean otros programas para ejecutarlos, sino que los ejecutan ellos mismos. Puede parecer extraño que haya dos formas diferentes. Los compiladores tienen este paso adicional de traducir antes de ejecutar, mientras que los intérpretes ejecutan el código directamente. Un gran ejemplo es cuando los intérpretes ejecutan código de Python.
Si bien estas dos técnicas son similares en lo que intentan hacer (que los programas se ejecuten), la forma en que lo hacen es extremadamente diferente. Una diferencia clave está en términos de rendimiento. Dado que un compilador apunta a un conjunto particular de hardware, puede aplicar optimizaciones de manera más inteligente para hacer que el programa sea más eficiente. Aunque son dos técnicas diferentes, la mayoría de los pasos necesarios para que un compilador y un intérprete pasen del código escrito por humanos a un programa que una computadora puede ejecutar son los mismos:
Este proceso generalmente se divide en tres secciones: front-end, middle y back-end. Mientras que el front-end se ocupa principalmente de la estructura del programa, el middle se ocupa más del significado y el back-end se ocupa de los niveles inferiores.
1. Exploración
El primer paso para que un programa esté listo para ejecutarse se llama escaneo. Hay algunas cosas que los humanos ponen en sus programas que, en mayor parte, solo están allí, de modo que el código es más legible (espacios, líneas de código, etc.). Es el trabajo del escáner eliminar todo esto y convertir cada parte significativa del programa en piezas más pequeñas llamadas tokens. Estos tokens son como las palabras y la puntuación que se utilizan en los lenguajes tradicionales escritos y hablados. Toma el siguiente código de Python:
def foo (a):
x = 2
Y se convertirá en una serie de tokens como:
En Python, la ubicación de espacios y líneas es importante, al igual que en algunos escritos literarios (por ejemplo, la poesía), por lo que el escáner no los eliminará porque, de hecho, tienen significado. Ahora que se han eliminado todas las cosas innecesarias, el proceso puede continuar con el siguiente paso: análisis.
2. Análisis gramatical
Los lenguajes de programación, como todos los lenguajes humanos, tienen una gramática que define su estructura, orden y el significado de cada palabra y símbolo. En inglés, se ve así:
El objetivo principal de esta fase es generar lo que se llama un árbol de sintaxis abstracta (AST). Esto no es más que un modo de representar el programa en una forma fácil de ver para el intérprete. Mientras se crea el AST, se verificará el programa para asegurarse de que se adhiere a la gramática que requiere el lenguaje. Continuando con el ejemplo anterior, esto podría convertirse en un árbol que se parece a:
Suponiendo que el programa pase todas las pruebas en el front-end, es hora de pasar al middle.
3. Análisis semántico y representaciones intermedias
Si el análisis examina para asegurarse de que el programa se vea bien, el siguiente paso lógico sería asegurarse de que el programa tenga sentido. ¿Intenta sumar cosas que no se pueden sumar? ¿Compara cosas que no tiene sentido comparar? Durante esta fase, el intérprete también creará alguna forma de encontrar las variables que está usando el programa. De esta manera, cuando se encuentra con una declaración, sabe exactamente dónde encontrar lo que necesita.
Armado con esta nueva tabla (a menudo llamada tabla de símbolos) es hora de generar algo que el back-end pueda usar. Esto generalmente se denomina representación intermedia (IR) y es la parte final del middle. Una ventaja clave de crear este IR es que ahora se puede traducir casi cualquier lenguaje y aprovechar todo el poder del back-end. Así comienza la optimización.
4. Optimización
Los programadores no son perfectos. Escribir código que sea fácil de mantener y de entender es un desafío. No solo es difícil escribir un buen código, sino que a menudo ocurre que el código que es fácil de entender tiene el costo de un código que se ejecuta de manera eficiente. El trabajo del optimizador es tomar el código comprensible escrito por humanos y mejorarlo.
Hay una cantidad increíble de complejidad y magia en este paso: el código puede reorganizarse, algunas partes pueden eliminarse y reescribirse. Cualquier cosa para hacer que el programa se ejecute más rápido, use menos memoria, consuma menos energía o lo que sea “mejor”. Ahora el programa se ha optimizado y es hora de crear algo que se pueda ejecutar.
5. Generación de código
Dependiendo de cómo se esté ejecutando el programa, el código que se genere en este paso será muy diferente. Si bien hay muchas formas diferentes de ejecutar un programa, una forma popular para los intérpretes es generar lo que se conoce como código de bytes.
Aunque generar código de bytes es técnicamente “compilar”, no es el código que debe ejecutarse en una máquina física real, por lo que es independiente de lo que hace un compilador clásico. Es un lenguaje que los intérpretes entienden y les dice qué acciones deben realizar. El código de bytes a menudo se ejecutará en lo que se conoce como máquina virtual (VM). Estas máquinas virtuales tienen todo lo que constituye una computadora real, pero todo es software, no hardware. Ahora, cualquier código de bytes que comprenda la VM se puede ejecutar en cualquier computadora que pueda usarla. Como ejemplo de cómo podría verse esto, considera el código de Python anterior y el código de bytes que usa una de las VM que ejecuta Python:
Este código de bytes no se ve tan bonito como el código del que proviene, pero ese es el punto: toma lo que un humano quiere leer y lo convierte en lo que una computadora sabe leer. Ahora que el intérprete tiene esto, puede comenzar a ejecutar el programa, línea por línea.
El viaje ha sido largo, pero ya se acabó. Hay muchos pasos para pasar de lo que escribe un programador a cómo lo ejecuta una computadora, siendo esta solo una de las muchas formas. Sin embargo, al final, mirar cada paso individualmente no es tan malo. La mayoría de los programadores nunca tendrán que lidiar con esto, pero no comprender cómo se ejecutan los programas puede llevarte a no comprender completamente la programación.
Otros lenguajes interpretados:
JavaScript, PHP, C #, Ruby, R.
Autor original: Josh Reese