Hagamos un lenguaje de programación - Parte 1

Published: by Creative Commons Licence

"Si no sabes cómo funcionan los compiladores, entonces no sabes cómo funcionan las computadoras. Si no estás 100% seguro de saber cómo funcionan los compiladores, entonces no sabes cómo funcionan" - Steve Yegge

La primera vez que leí esta cita me puse bastante incómodo, desesperado… Realmente no sabía cómo funcionaban los compiladores/intérpretes y no podía seguir escribiendo una línea más de código de lo que estaba haciendo hasta entender, aunque sea lo más mínimo acerca de compiladores.

Pequeña historia…

Allá por el 2015, cursaba Base de Datos en la Facultad y uno de los contenidos de la asignatura era Álgebra Relacional, y en la parte práctica utilizábamos una herramienta que tomaba un archivo con una cierta sintaxis y las mostraba como tablas, además permitía, mediante un editor, escribir consultas que luego la herramienta las interpretaba y producía nuevas tablas (salidas).

El problema

Esta herramienta no era (hasta el día de hoy) cross-platform, y por lo tanto no corría en mi Debian, no podía hacer las prácticas, solo me quedaba correr una máquina virtual con Windows y hacerlo desde ahí, cosa que no me gustaba para nada. Busqué alternativas y tampoco las encontré.

Le comenté a la Profesora mi inquietud, y sólo respondió (o sólo quise escuchar): "Ahí tienen algo para hacer". Volviendo a casa pensaba en cómo hacer un software que pasándole un texto/código con una sintáxis determinada, la entendiera. Y por supuesto que funcione en cualquier plataforma. Por ejemplo, quería que entendiera esto: q1 = select id==1 (personas), lo primero que hice fue esto, no pretendo que lo entiendas al código porque me tomó mucho tiempo hacerlo y es muy feo, pero funcionaba y pude liberar la primera versión de una herramienta que entendía esas instrucciones.

Más adelante quise que mi software entendiera esto: q1 := select fecha='2017/01/91' (personas); y también esto: q2 := project id, nombre, skill (q1 njoin skills);. Eso requería un análisis más profundo y hacer q1 = select id==1 (personas) era más fácil porque Python entiende los símbolos =, ==, (, ), pero NO :=.

La solución

La solución era construir un intérprete, o mejor dicho, crear un lenguaje y su respectivo intérprete que entendiera que el símbolo := significa una asignación, que select, njoin, project son palabras claves y tienen una sintaxis específica, etc…

Por suerte el año siguiente cursé la materia Lenguajes Formales y Autómatas, era lo que necesitaba para seguir entendiendo cómo funcionan los compiladores/intérpretes. La primera parte me pareció interesante y de hecho era justo lo que necesitaba, pude entender lo necesario y con ayuda de Compilers: Principles, Techniques, and Tools y de la serie Let's Build A Simple Interpreter de Ruslan Spivak, comencé a escribir un compilador/intérprete para mi software :).

Lo malo, o por lo menos para mí, fue que le dediqué bastante tiempo a mi proyecto que me olvidé de la Facultad, y así me fue con la materia…

Hagamos un lenguaje de programación!…y un intérprete!

¿Por qué deberíamos saber acerca de compiladores e intérpretes?

  • Hacer un software de este tipo requiere de muchas habilidades técnicas que se deben usar en conjunto. Al construir un compilador te ayudará a mejorar esas habilidades y convertirte en un mejor desarrollador de software.

  • Lo que aprendas acerca de compiladores no tan solo sirve para compiladores. Vuelve a leer la cita de Steve Yegge.s

Espero que al final de esta serie de posts realmente entiendas cómo funcionan las computadoras y te sientas más seguro de ti mismo como desarrollador. Por lo menos así me sentí yo…

¿Qué es un compilador?… y un intérprete?

Gráficamente un compilador es esto:

y un intérprete esto:

Un compilador o el objetivo de este y el de un intérprete es de traducir un código fuente, de alto nivel, en algo más. La diferencia es que un compilador traduce el código fuente a código máquina y un intérprete procesa y ejecuta el programa fuente sin traducir a código máquina.

Herramientas necesarias

El lenguaje de implementación que voy a utilizar será Python, podés utilizar el que quieras, da igual, no voy a usar librerias de terceros, solo Python, así te será más fácil de portarlo a otro lenguaje y lo más importante es que así vas a ver el nivel más bajo de un intérprete.

Cómo será nuestro lenguaje?

Simple, muy simple, el lenguaje que vamos a hacer, que ahora llamaremos slang (de simple language), tendrá o soportará lo siguiente:

  • operaciones matemáticas como: 1 + 2, 1 + (2 * 3), (2 / 3) + (2 + 2 * (3 - 5))
  • tipos de datos básicos: integer, float, string
  • stdout con show: show "hola desde slang!"
  • comentarios.
  • compound statements: bloques que dentro de ellos tienen otras cosas, o nada. Estos bloques estarán delimitados con start y end, sip, parecido a Pascal :):

Sabiendo esto, un programita en nuestro lenguaje Slang se verá más o menos así:

start
    b = "hola";  # esto es un comentario
    operacion = 23 + (3 - 3 + 1) * 3;
    start
        a = 5;
        show a;
    end
    show b;
end
  • declaración de tipo: posiblemente tengamos que hacer algo como:
start
    # declarar b como entero antes de usarlo
    integer b;
    b = 5;
    show integer;
end

Comencemos!

Está demás mencionar que vamos a construir un intérprete y no un compilador. Bien, estas son las partes que vamos a construir:

  • Scanner: este componente es la primera fase, es encargado de tomar el código fuente (en nuestro caso, un archivo o texto con codigo Slang) y tirarnos caracter por caracter cuando se lo pidamos, además sería muy útil que este componente lleve información acerca del número de línea y columna de cada caracter. Esto nos ayudará a informar de errores cuando el componente siguiente (Lexer) se enoje.
  • Lexer: generalmente el Scanner y Lexer se unen en solo Lexer, pero me gusta separar estos dos. El Lexer es la segunda fase, va a tomar como entrada al Scanner y su función principal es de agrupar caracteres que significan algo en nuestro lenguaje, esta agrupación la vamos a llamar token, un token válido en Slang puede ser 'Token(INTEGER, 3)', tambien 'Token(LEFT_PAREN, '(')'.
  • Parser: tercera fase, toma como entrada al Lexer y esta parte es la que realmente entiende el lenguaje, además, en esta fase se crea una estructura de árbol que representa nuestro programa, a este árbol se lo llama AST (Abstract Syntax Tree)

En la siguiente parte comenzamos a construir el Scanner :).

Sugerencias y correcciones al issue-tracker.

EOF


If you think something should change in this post, let me know.