Crea tu propio compilador – Cap. 1 – Introducción

Sí. Así como suena. En esta serie de artículos explicaré 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.

1.1 Introducción

Binary Code, Binary, Binary System, Byte, Bits, Administrator, Virus, Trojan, Computer, Digital, 1

Este proyecto nace de la necesidad de querer responder una pregunta recurrente, que me suelen hacer cuando presento algún proyecto referido a compiladores: ¿Cómo se hace un compilador? Pero a la vez responde otra pregunta, en la que probablemente, no se haya investigado lo suficiente: ¿Qué tan simple se puede hacer un compilador para que sea capaz de compilarse a sí mismo?

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 de desarrollo y software mucho más avanzados 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 autómatas finitos.

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 los de 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. Una de mis ú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 que estaré creando.

No espere 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.

1.2 Desarrollo

Este compilador, al que llamaremos «Titán», 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.

El desarrollo lo he estructurado en los siguientes capítulos:

Cada capítulo incluirá una parte teórica inicial introductoria (a la que llamaré Teoría de Compiladores) y una parte práctica relativa al desarrollo de nuestro compilador.

Como hacer un compilador completo, es una tarea titánica, y puede tomar años de trabajo, para este proyecto reduciremos nuestras expectativas considerablemente. Las especificaciones de nuestro compilador serán:

  1. Como lenguaje fuente (el que vamos a compilar), usaremos un lenguaje de alto nivel, imperativo, tipado , con pocas instrucciones y con solo dos tipos de datos.
  2. Nuestro compilador generará código ensamblador, que luego será convertido a código binario, con ayuda de un ensamblador y enlazador.
  3. Solo generaremos un archivo ensamblador para la arquitectura Intel x86 de 32 bits y para la plataforma Windows.
  4. Nuestro compilador será de una sola pasada y no generará representaciones intermedias.
  5. Nuestro compilador no realizará una etapa de optimización.
  6. Escribiremos código minimalista en un solo archivo y sin dependencias.

Estas especificaciones nos permitirán reducir el tiempo de implementación y, por otro lado, nos ayudarán a cumplir los objetivos propuestos.

Es importante notar que la especificación 1 nos obliga a usar un lenguaje de alto nivel. De otra forma podríamos terminar escribiendo un compilador para un lenguaje exótico que sea fácil de compilar a código máquina, pero totalmente impráctico de escribir.

Las especificaciones 4, 5 y 6 solo son formas de limitar la complejidad de este proyecto, y se podría  resumir diciendo «Hacer el compilador lo más sencillo posible».

El resultado de la compilación serán 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 ya obsoleto en 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.

1.3 Objetivos del proyecto

En cuanto a los objetivos de este proyecto, podemos enumerar:

  1. Mostrar el caso práctico de creación de un compilador en unas pocas líneas de código, alejado de la complejidad de los compiladores de varias capas y de miles de línea de código.
  2. Motivar en la experimentación creando un lenguaje y su correspondiente compilador a código nativo.
  3. Mostrar cómo se puede realizar un compilador funcional con poco esfuerzo y en poco tiempo, si se usa el método adecuado.
  4. Mostrar, como recurso pedagógico o simple curiosidad, un caso de práctico de compilador compilándose a sí mismo.
  5. Entender las limitaciones de un compilador con diseño minimalista como justificación en la aplicación de las técnicas actuales del diseño de compiladores.
  6. Explorar y estudiar la simplicidad del diseño de un compilador de alto nivel, cuya requisito mínimo sea el de poder compilarse a sí mismo.

El objetivo 6, a pesar de ser el último, es probablemente el más importante y el que determina también las características del lenguaje a usar. Personalmente, este objetivo fue la principal justificación para plantearme este proyecto.

Los primero 5 objetivos son más bien el querer aprovechar los beneficios colaterales de este proyecto.

El objetivo 4, que implica que nuestro compilador será autocontenido, trae a la vez dos consecuencias importantes:

  1. Que nuestro compilador no será un juguete, sino que debe ser lo suficientemente complejo como para que pueda compilar a un compilador.
  2. Que código fuente de nuestro compilador debe usar un lenguaje tan restringido como el mismo que queremos soportar.

El punto 2 es interesante porque lo debemos tener en cuenta en todo el proceso de desarrollo cuando vayamos escribiendo el código fuente, de otra forma, al final tendremos problemas para compilar nuestro propio código fuente.

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.

Para terminar de aclarar este proyecto, convendría también indicar los No-objetivos:

  1. No es objetivo de este proyecto realizar un compilador optimizado el tamaño o velocidad del código generado. La optimización se sacrifica en aras de la simplicidad.
  2. No es objetivo de este proyecto inventar un lenguaje nuevo que pueda competir con los existentes. El lenguaje a crear es de los más simples y solo tiene un fin: Permitir escribir un compilador.
  3. No es objetivo de este proyecto realizar un compilador profesional que sirva para la creación de programas útiles. El único programa «útil» que esperamos crear es el mismo compilador.

1.4 ¿Qué debo saber?

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.

1.5 Instalando las herramientas

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

Todas las herramientas son gratuitas y están disponibles en la Web. Doy por sentado que el lector interesado puede instalar estas herramientas sin problemas (además hay amplia información en la red).

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 requeriremos 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.

Una instalación típica del MASM32 debería ser simple y después de seguir las opciones por defecto, deberíamos terminar con una pantalla como esta:

Para nuestro caso, usaremos muy poco la IDE que viene con el paquete y solo usaremos el ensamblador y el enlazador por línea de comandos para poder obtener los binarios, a partir del programa en ensamblador que generaremos. Sin embargo, si el lector desea más información sobre el uso de la IDE, puede revisar este enlace.

En cuanto al editor de texto usaremos Visual Studio Code que se presta muy bien para este tipo de trabajo, pero como siempre, hay libertad de elegir algún otro. En este aspecto, hay considerables opciones disponibles. 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.

Visual Studio Code - Wikipedia

Aquí usaremos en VSCode por su facilidad para integrarlo con una herramienta externa y su actualización automática de archivos, sin necesidad de usar complementos adicionales.

Solo como referencia, la versión de Lazarus que estoy usando es la versión 2.2.0 de 64 bits, la versión de MASM es la 6.1411, y Visual Studio Code versión 1.75.1

En la siguiente sección estaré mostrando cómo configurar estas herramientas para iniciar nuestro trabajo.


10 comentarios

  1. Muchísimas gracias por exponer tu conocimiento en compiladores (y en castellano). Soy un seguidor tuyo de tu p65pas compiler. Muchas gracias por hacer el proyecto open source. En lo posible voy a ayudarte con ese proyecto.

  2. Thank you for your work. Still can’t believe that there is a tutorial on how to write a pure compiler in Pascal. Hope you will have the opportunity to finish such an exciting project!

  3. ¡Excelente proyecto! He ido siguiendo una a una las secciones de desarrollo pero veo que hoy, Feb/28/2023 todas las páginas con excepción de ésta, aparecen como «páginas NO encontradas». Ojalá logres corregir el problema, y OJALÁ logres dar continuidad y terminar el proyecto. Es un magnífico tutorial y sería una lástima que quedara inconcluso.
    Saludos y felicitaciones desde la Ciudad de México!

    • Hola. Estoy haciendo un revisión y actualización de todos los artículos publicados. Por eso es que se rompieron los enlaces, pero irán apareciendo uno a uno conforme los vaya actualizando. La buena noticia es que ahora se ha mejorado, considerablemente, la parte teórica, se han agregado diversos diagramas, se ha simplificado el código fuente del compilador y se está integrando el compilador con Visual Studio Code. Además, todo el trabajo lo estaré redactando en forma de libro e iré agregando nuevos Capítulos. De momento ya aparece un nuevo capítulo sobre el uso del ensamblador. Gracias por seguir este trabajo.

  4. IMPORTANTE!!
    Los enlaces (links) para el desarrollo del compilador que aparecen arriba, a partir del cuarto (Inventando un lenguaje) siguen sin funcionar. ¿Podrías por favor repararlos? Me gustaría seguir avanzando en el proyecto, y ojalá en un futuro cercano tuvieras tiempo para concluirlo. Saludos.

  5. Felicidades por tu trabajo, algunas preguntas si no es molestia
    -Que libros, manuales u otros proyectos has utilizado como base para este desarrollo ???
    -Por mas que busco algo «generico» para aprender a generar codigo, no veo nada de calidad, que recomiendas ???
    Un saludo

    • Hola. Con respecto a libros y manuales, en estos artículos, me baso más en mis conocimientos sobre creación de compiladores, y en diversa literatura técnica como los clásicos «Advanced Compiler Design and Implementation» de Steven S. Muchnick o «Compilers Principles Techniques and Tools», además de diversa literatura en línea, como la referencia de LLVM.
      Con respecto a la generación de código, puedes consultar la misma bibliografía de compiladores, pero es un tema un poco más oscuro y no tan estándar como el análisis léxico.

Dejar una contestacion

Tu dirección de correo electrónico no será publicada.


*