Crea tu propio compilador – Parte 1 – Introducción

Sí. Así como suena. En esta serie de artículos explicaré (trataré) de forma simple (al menos es la expectativa), el diseño e implementación de un compilador, prácticamente desde cero. Aunque elemental, este compilador podrá generar código binario, con la ayuda de un ensamblador, para la arquitectura x86 de Intel.

Introducción

Este proyecto nace de la necesidad de querer responder una pregunta constante y recurrente, cuando presento algún proyecto referido a compiladores: ¿Cómo se hace un compilador?

No es fácil explicar cómo se desarrolla un proyecto de esta naturaleza, y como suele pasar en muchas áreas del conocimiento, no existe una forma única de desarrollarlo; por lo que la pregunta quizá está mal planteada. Preguntar: Cómo se hace un compilador, puede llevar implícita la afirmación errónea de que existe una sola forma de hacerlo. Nada más lejos de la realidad. Existen tantas formas de hacer un compilador como formas de hacer cualquier proyecto de software. El campo de la informática, y más específicamente el de desarrollo de software, está plagado de metodologías de desarrollo (probablemente más que en cualquier otra área del conocimiento), cada cual clamando ser la mejor.

Es cierto que los compiladores son proyectos muy especiales, y de los más difíciles que existen, especialmente cuando son autocontenidos, pero no por eso dejan de ser proyectos de software y como tal deben manejarse como se maneja cualquier otro programa de gran complejidad.

Los primeros compiladores, al igual que las primeras computadoras, fueron hechos por equipos reducidos o hasta por una sola persona. El primer compilador de Fortran requirió años-hombre de trabajo y fue escrito por un equipo de profesionales destacados de IBM mientras que el compilador Free Pascal, fue iniciado por una sola persona, aunque actualmente cuenta con un equipo de programadores.

El hecho de que un compilador completo sea un proyecto complejo de ingeniería de software, no significa que no podamos hacerlo nosotros mismos. Es totalmente factible realizar un pequeño compilador en poco tiempo y sin conocimientos avanzados, solo para satisfacer un poco la curiosidad.

Esto es posible en la actualidad, porque contamos con herramientas modernas de desarrollo y software mucho más avanzado que los que se tenían hace 30 o 40 años. Hoy en día, las IDE modernas y las herramientas RAD, nos permiten desarrollos complejos en corto tiempo. No es necesario ya, manejar complicadas líneas de comando, conocer a profundidad el hardware y ser un experto en ensamblador y código binario.

En estos tiempos, es totalmente factible hacer, con poco esfuerzo, un compilador sencillo como los que se manejan en cursos de compiladores.

En este proyecto, no voy a hablar de complejas técnicas de desarrollo, de metodologías modernas, patrones de diseño o gramáticas libres de contexto.

Inclusive, y debido a la naturaleza de este programa , no voy a usar programación orientada a objetos, ni paradigmas nuevos. Simplemente usaré programación estructurada e imperativa, del más bajo nivel. Esto por una parte, permitirá hacer el código más legible para aquellos que se inician en el mundo de la programación, y por otra parte ayudará a cumplir el objetivo de hacer un compilador autocontenido.

Un compilador autocontenido, es aquel que se puede compilar a sí mismo, usando su propio compilador. Esto, aunque suene al problema de la gallina y huevo, es totalmente factible y se ha usado en varios compiladores modernos como C, C++, Pascal. Para ello es necesario empezar haciendo un compilador en cualquier otro lenguaje, para luego, cuando el compilador, sea lo suficientemente completo, poder migrar y traducir el compilador usando su propio lenguaje.

Crear un programa con un compilador es como usar una planta o fábrica para crear equipos o piezas. Crear un compilador con otro compilador es como usar una fábrica para crear otra fábrica. Pero crear un compilador autocontenido es como usar una fábrica para crear una fábrica que fabrique sus propias piezas. En un momento dado, la fábrica “fabricada” dejará de depender de la fábrica que lo creo y será independiente. Eso implicará que un compilador autocontenido debe estar escrito en su propio lenguaje y cuando así sea, será una criatura que vivirá alimentándose de si mismo. Sin ayuda. Estará solo en el mundo. No habrá software, garantía, soporte, foros, o ayuda en línea. Un compilador autocontenido dependerá solo de sí mismo.

Esta es la magnitud del proyecto que pretendo realizar. A la fecha, quien escribe, ha creado algunos compiladores e intérpretes, para diversos lenguajes y arquitecturas. Uno de últimas creaciones es un compilador en Pascal para el microcontrolador 6502: https://github.com/t-edson/P65Pas. A pesar de ello, no me considero un experto en el tema pero sí alguien con cierta experiencia. Sin embargo, este será el primer compilador autocontenido (si lo logro) que estaré creando.

No espero convertirse en un experto en compiladores, solamente con poder crear uno, siguiendo un tutorial. El desarrollo de compiladores es un tema muy extenso y complejo y debería ser estudiado a cabalidad para quien tenga interés. Estos artículos solo mostrarán como se crea uno sencillo y con fines didácticos.

Desarrollo

Este compilador, al que aún no he bautizado con un nombre, lo construiremos desde cero, con la ayuda de otro compilador, y un ensamblador. No usaremos ninguna herramienta moderna tipo LEX o YACC o LLVM, porque complicaría el proyecto y porque no se cumpliría el objetivo de hacerlo autocontenido, ya que estas herramientas son desarrollos independientes.

Empezaremos el código desde la parte más sencilla que vendría a ser las rutinas del analizador léxico y terminaremos con las rutinas más complejas del generador de código. No usaremos diseños complicados en el software (se sacrificarán muchas características), así que el código debería ser fácil de seguir.

El desarrollo lo he estructurado en los siguiente partes:

Estos son los temas que se tienen definido, al menos por ahora. ya que el proyecto está solo en su fase inicial.

Como hacer un compilador completo, es una tarea titánica (asumiendo que los Titanes escribían compiladores) , y puede tomar años de trabajo, para este proyecto reduciremos nuestras espectativas/objetivos, considerablemente:

  • Como lenguaje fuente (el que vamos a compilar), usaremos un sencillo lenguaje imperativo, tipado y de muy pocas instrucciones.
  • El código será compilado a código ensamblador, que luego será convertido a código binario, con ayuda de un ensamblador y enlazador.
  • No se incluirán tipos complejos, solo un tipo numérico y un tipo para cadenas.
  • Escribiremos código minimalista con poca modularidad y sin dependencias.

El último objetivo está orientado a poder lograr el objetivo más ambicioso que es el poder lograr que el compilador sea autocontenido.

El obejtivo de la compilación es crear binarios para la arquitectura x86 de Intel en 32 bits, en el Windows. Inicialmente pensé en compilar para Intel de 16 bits (que es donde tengo más experiencia), pero considerando que el código de 16 bits es, por estos días, obsoleto las PC comunes, decidí compilar para 32 bits. Elegí el sistema operativo Windows, porque es el que tiene más información sobre sus API, y hay mucho desarrollo al respecto.

Para empezar a crear el código fuente del compilador, usaremos el lenguaje Pascal, que es fácil de manejar y se adapta bien para los objetivos y restricciones del proyecto.

Requisitos

Realizar un compilador es una tarea que involucra diversos conocimientos. Para nuestro caso, y dada la simplicidad de nuestro proyecto, solo requeriremos conocimientos de:

  • Ensamblador para la arquitectura Intelx86 de 32 bits, porque esta será la arquitectura para la que vamos a generar código.
  • Conocimiento básico de Pascal, al menos en su modo Turbo Pascal, ya que no se usarán características avanzadas del lenguaje.
  • Conocimiento básico de teoría de compiladores, al menos en cuanto a entender las partes que contiene y la función de cada una de estas partes.

Demás está decirlo, pero se supone que se maneja bien el sistema Operativo Windows, en cuanto al manejo de archivos y la línea de comandos del CMD.

Si no se conoce bien alguno de estos temas, es recomendable repasarlos primero para entender mejor cómo va esto de compiladores. De otra forma, estaremos trabajando de forma “mecánica” sin tener claro los fundamentos.

Instalando las herramientas

Para este ambicioso proyecto vamos a usar algunas herramientas/programas que necesitaremos instalar sobre Windows. Estas son:

  • El entorno de desarrollo Lazarus (https://www.lazarus-ide.org/)
  • El ensamblador MASM32  (http://www.masm32.com/download.htm)
  • Un editor de texto, como el notepad++ (https://notepad-plus-plus.org/download/v7.6.2.html)

Doy por sentado que el lector interesado puede instalar estas herramientas sin problemas (además hay amplia información en la red), ya que si no es capaz de hacerlo, mucho menos será capaz de crear un compilador, como el que se pretende en este proyecto.

Como herramienta de desarrollo, vamos a usar el entorno gratuito Lazarus para desarrollar la primera versión del compilador. Es decir que usaremos el lenguaje Pascal, para crear el compilador. Pero no se usarán objetos ni características avanzadas del lenguaje para facilitar luego la migración del código fuente al nuevo lenguaje del compilador. Se elige Pascal, entre otras bondades, porque el lenguaje que vamos a usar se parecerá al Pascal estándar. Sin embargo, se puede usar otro lenguaje/IDE si así lo desea, pero en estos artículos usaremos Pascal y Lazarus.

No reqeuriremos instalar ningún otro paquete adicional y de Lazarus solo usaremos un proyecto de tipo consola básico.

Como ensamblador, vamos a usar el MASM32 porque además de ser libre, viene ya preparado con todo el kit de desarrollo (ensamblador y enlazador), haciendo la instalación más sencilla y viene ya preparado para trabajar con 32 bits  que es la arquitectura que vamos a usar en este proyecto. De la misma forma, se puede usar algún otro ensamblador si se desea. Yo no soy un dictador informático.

Para nuestro caso, usaremos muy poco la IDE que viene con el paquete y solo usaremos el ensamblador y el enlazador para poder obtener los binarios, a partir del programa en ensamblador que generaremos.

El editor de texto que voy a usar es el Notepad++, pero como siempre, hay libertad de elegir algún otro. En este aspecto, hay considerables opciones diponibles. Diversos editores de texto pululan por la red, muchos de una calidad sorprendente y gratuitos. Esto es más cuestión de gustos. Si se desea, se puede también usar el simple notepad de Windows. No hay problema. Hay gente que aún compila usando editores de línea de comandos.

Puede ser conveniente usar el complemento NppExec, que al menos en mi caso, me ha sido muy útil al momento de probar código ensamblador.

Solo como referencia, la versión de Lazarus que estoy usando es la versión 1.8.0 de 64 bits, la versión 11, y Notepad++ versión 7.2.2

 

Puntuación: 5 / Votos: 2

Deja un comentario

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