Del código fuente a la ejecución de los programas

Programming programando

Ya tenemos el código fuente, ¿y ahora? ¿cuál es la magia que hace posible que un código fuente se ejecute?

Todo programa necesita un traductor, que compile o interprete el código fuente para que pueda ser ejecutado. Es una primera diferencia que puede marcarse entre los lenguajes de programación, ya que un lenguaje puede ser interpretado, compilado, o incluso ambas cosas. Además, la ejecución puede ser dependiente de la máquina, o ejecutarse en una máquina virtual, que te independiza de la máquina.

Por ejemplo los lenguajes funcionales o lógicos suelen usarse primeramente interpretados, como Prolog o Haskell mientras se desarrolla, pero también tenemos disponible un compilador para mejorar su eficiencia. PHP es interpretado, aunque ha habido compiladores para mejorar su eficiencia de ejecución. Cabe destacar como curiosidad la Hip Hop Virtual Machine de Facebook que crearon para agilizar PHP, que precisamente compila los ficheros fuente a un código objeto intermedio más rápido de ejecutar. Javascript, Typescript, Ruby, también son interpretados. C, C++, VB, C# son compilados, es decir, se necesita compilar el código fuente a códigos objeto, estos códigos objeto a su vez se linkan, se retraducen, para ejecutarse finalmente en la máquina.

Luego hay otra familia intermedia de lenguajes como Java o Scala que corren sobre una máquina virtual. También se consideran compilados, pero igual que PHP con opcache, aunque sea interpretado, se compilan a un código intermedio denominado bytecode. Este bytecode finalmente se ejecutan sobre una máquina virtual que te independiza de la máquina subyacente. Añadamos otra capa a todo esto, hay lenguajes que utilizan una compilación en el momento, llamado JIT (Just In Time). Por ejemplo los lenguajes .NET que se compilan a un código objeto intermedio, Java también es muy conocido en su aumento de rendimiento de las últimas versiones debido a esto. Este código objeto intermedio se traducirá, en JIT, al lenguaje máquina la primera vez que sea necesario, aumentando su velocidad con respecto a no usar JIT. Podremos ver también cómo llega esta compilación en el momento JIT para PHP8, que están tomando ejemplo, para agilizar todavía más el mundo de las webs..

Compilador o intérprete

En ambos casos, todo lenguaje de programación necesita un traductor. Dependiendo de la forma de trabajar del traductor, se le denominará como compilador o intérprete:

  • Un lenguaje es interpretado cuando el traductor lee directamente el código fuente y lo ejecuta. Es decir, el traductor realiza la ejecución en un sólo paso.
  • Un lenguaje es compilado cuando el traductor lee el ćodigo fuente y lo traduce a un código objeto. Si dicho código objeto ya llegó al lenguaje máquina entonces podrá ejecutarse, sino irá pasando de código objeto a otro código objeto hasta lenguaje máquina final.

A menudo las compilaciones a códigos objetos se realizan en varias fases. Puede haber compilación a otro código objeto que sea otro lenguaje. También tenemos el caso anterior de compilación a bytecodes, cacheados.. Generalmente llegan a parar a códigos objeto en lenguaje ensamblador. En dicha fase, el lenguaje ensamblador ya puede ser traducido a lenguaje máquina. Y este lenguaje máquina es el que finalmente se carga en memoria RAM, y se ejecuta por el procesador principal, o en el dispositivo físico que corresponda.

Fases de análisis

Tanto un compilador como un intérprete tienen que hacer lo siguiente con el código fuente:

  • Análisis léxico: que simplemente identifica todas las palabras, símbolos, caracteres.. que se están usando en el código fuente. A todos estos elementos encontrados se les llama tokens. Se suele limitar simplemente a leer todos los caracteres permitidos en el lenguaje.
  • Análisis sintáctico: ahora el traductor analiza si el orden de los tokens está permitido, identificando y organizando las estructuras, agrupando y jerarquizando en conjuntos de tokens todo el ćodigo fuente.
  • Análisis semántico: éste análisis varía mucho de un lenguaje a otro, aquí se analiza si el significado de todos los componentes organizados anteriormente están claros para ejecutar o compilar a código objetivo. Esta fase varía y depende mucho de las especificaciones de diseño.

Con un buen entorno de desarrollo integrado (IDE), deberíamos de tener un buen feedback mientras que programamos. Es decir, estos análisis que también puede realizar el IDE antes de comenzar la traducción final, previenen, guían, encauzan el desarrollo del programa. Incluso podemos tener integrado en el IDE el compilador o intérprete que nos vaya guiando mientras que desarrollamos.

Llegamos a los errores

Línea a línea, traducción a traducción, vamos construyendo mientras que mejoramos el código fuente. Depurando hasta que tenemos un programa funcional capaz de ejecutarse. Pero iremos pasando por los siguientes tipos de errores hasta llegar al último y más difícil de liquidar:

  • Errores léxicos: son los derivados del análisis léxico. Básicamente se suelen limitar al uso de caracteres permitidos por el lenguaje de programación. Por ejemplo si el lenguaje permite #, ‘, “, etcétera.. que escribamos iff cuando queríamos poner if no se detectará aquí.
  • Errores sintácticos: igualmente son los derivados del análisis sintáctico. Si los tokens antes mencionados no ocurren en el momento adecuado saltará este error. Por ejemplo en PHP después de un echo esperamos una cadena de caracteres encerrada entre “comillas dobles” o ‘comillas simples’, o una variable.. en otros casos saltará un error.
  • Errores semáticos: estos igualmente varian mucho de un lenguaje a otro. Si tenemos por ejemplo un lenguaje de variables de tipo fijo, no podremos asignar variables de distinto tipo, y podrá detectarse antes de la ejecución. Si tenemos errores detectables sólo durante la ejecución se dice que son dinámicos, por ejemplo acceder a posiciones de un vector fuera de rango, dividir entre cero.
  • Errores lógicos: estos son los que finalmente nos quiebran la cabeza. Ahora llegamos a un programa que no da errores ni léxicos, ni sintácticos, ni semánticos. Es decir, no tenemos errores detectables a priori, y ¡el programa se puede ejecutar! Pero una cosa es que no de errores, y otra que haga lo que realmente tiene que hacer. Por ejemplo, si estamos calculando un factorial, una ordenación de elementos, etc.. tiene que hacer dichas acciones.

Llegados a este punto, hay que tener presente que es muy importante disponer de un buen IDE. O un buen conjunto de herramientas, quizá eres de los de un buen editor de texto supervitaminado. Así estas traducciones y esta construcción será eficiente, práctica, productiva y divertida.. sin demasiados quebraderos de cabeza.

¡Que tengan una buena codificación!
O como se dice en inglés: Happy Coding! 😉

Compartir..

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

veinte − diecinueve =