Oscar++ Mi propio compilador

Pues así como lo escuchan, que Java ni que Python, Oscar++ es el lenguaje de la siguiente generación….

Espero que haya quedado lo suficientemente claro el nivel de sarcasmos del párrafo anterior, pero por si no lo notaste lo reafirmo.

Oscar++ es en realidad un proyecto que desarrolle para la materia de Programación de sistema por allá del año 2000, cuando era un ingenuo y feliz estudiante de de la carrera de Ing. en sistemas.

El proyecto era “simple”, desarrollar un compilador capaz de tomar un código fuente, pasarlo por un analizador léxico, sintáctico y semántico para validar que el código escrito cumplía con la definición del lenguaje, seguido, era crear un árbol sintáctico abstracto y generar a partir de el, un código intermedio.

Observa que he dicho “simple” entre comillas, pues al principio y en la teoría parecía serlo, sin embargo, cuando llego el momento de hacerlo, las cosas cambiaron, la teoría se iba por un caño y era el momento de comenzar a prender y entender todos los conceptos involucrados en la generación de todos estos pasos.

Lo curioso es que rápidamente comprendí como es que lo tenía que hacer y lo logre, incluso termine vendiendo el proyectos a otros compañeros de la universidad, pero esa es otra historia.

En esta artículo no quisiera entrar en detalle sobre las fases de un compilador, pues para eso ya he creado un artículo al respecto.

Instalando el proyecto

Pero bueno, basta de palabrerías y vamos al proyecto, en principio, esta es la forma en que se ve corriendo:

La aplicación se compone de un pequeño editor de texto donde podemos poner nuestro código, una barra de tareas para guarda, abrir, compilar, etc. y del lado izquierdo mis datos de la universidad para que el maestro pudiera avaluar mi trabajo, de los cuales algunos los he borrado por obvias razones.

⚠ Antes de continuar, me gustaría hacer una aclaración, este es un proyecto que desarrolle hace muchos años, sin experiencia y con pésimas prácticas de programación, por lo que podrías encontrar muchas cosas raras de esas que dan miedo ver, pero he querido mantener el proyecto tal cual lo presente en su momento, pues lo único que busco es documentar el trabajo que hice en aquel entonces, por lo que quiero aclarar que no estoy dispuesto a dar soporte o agregar nueva funcionalidad.

Dicho lo anterior, empecemos con descargar el código fuente desde GitHub: https://github.com/oscarjb1/university_oscar-.git

El proyecto está en Eclipse por lo que solo bastará con que lo importes para verlo listo y funcionando en todo su esplendor 😂.

La clase principal es oscar.Main, por lo que solo tendremos que darle click derecho y luego run. El proyecto no requiere de ninguna librería, por lo que debería de funcionar sin ningún problema.

Como funciona Oscar++

El proyecto se compone de dos versiones del compilador:

La primera, Gramática Chica es una versión del compilador que soporta una sintaxis muy pequeña, donde podemos definir y asignar variables y definir sentencias if , la ventaja es que esta gramática genera el árbol sintáctico abstracto y el código intermedio. Por otra, tenemos la Gramática Grande, la cual es una versión del compilador mucho más grande que soporta clases, método, if y algunas expresiones básicas, sin embargo, esta versión no soporta la generación de árboles abstractos ni código intermedio.

La razón por la que existen dos versiones es por que eran dos proyectos diferentes, el primero era en realidad el que tenía que entregar, pero el profesor daba puntos extras al que lograr la segunda gramática que estaba enfocada para los alumnos de Posgrado, al final decidir hacer los dos para mejorar la calificación.

Pero bueno, para probar el compilador deberemos abrir alguno de los programas que generé para probarlo, los cuales puedes encontrar en la testfiles que se encuentra en la raíz del proyecto, los archivos que comienzan con GC son para la gramática chica, mientras que los que comienzan com GG son para la gramática grande, así que abrimos cualquiera de estos y le damos en compilar:

Si todo sale bien, veremos un mensaje de compilación exitosa, como se ve en la imagen anterior.

La sintaxis permitida por el compilador se encuentra definida en la carpeta oscar.fuentes, la cual contiene dos archivos con extensión jj, Oscar+.jj es para la gramática chica y Oscar++.jj es para la gramática grande.

Los archivos extensión jj son utilizados por herramienta llamada JavaCC (
Java Compiler Compiler), la cual es una herramienta para generar el Parses, el cual corresponde al paso por medio del cual se valida la gramática para asegurarse que todos los tokens tiene sentido y están en un orden determinado, sin embargo, no agrega la validación de tipos de datos ni el árbol sintáctico abstracto, el cual yo creo y valido más adelante.

El archivo de JavaCC necesita una serie de expresiones en formato BNF, el cual es un lenguaje para describir lenguajes, es decir, mediante BNF defino la sintaxis valida para mi lenguaje.

Además, creo una especia de método que en realidad definen secciones del lenguaje, por ejemplo:

void programa():{}{
	claseMain() (declaracionClase())* <EOF>
}

Lo que hace el código anterior es decirle a JavaCC que nuestro programa debe de contener una Clase principal (Main Class) seguido de cero o muchas clases, seguido del fin del archivo, es por eso que la gramática grande acepta varias clases dentro del mismo archivo.

A su vez, claseMain() y declaraciónClase() son otros método que definen la sintaxis de la clase principal y las clases secundarias respectivamente.

El resultado de la compilación de estos dos archivos nos da como resultado una serie de clases que podrás encontrar en las carpetas oscar.compilador1 y oscar.compilador2, la primera corresponde a la gramática chica y la 2 a la grande. Sin embargo, las clases que puedes ver allí no están exactamente como las genera JavaCC, si no que las modifique para generar el árbol sintáctico abstracto, veamos el siguiente fragmento de código para explicar este punto:

final public VarDeclaracion declaracionVariable() throws ParseException, SemanticException {
    ....
}

Fragmento de código de la gramática chica

final public void declaracionVariable() throws ParseException {
    ....
}

Fragmento del código de la gramática grande

Quiero que observes los dos fragmentos de código anterior, en la gramática pequeña retornamos siempre un objeto, mientras que en la gramática grande no retornamos nada (void), esto se debe a que en la gramática chica generamos un árbol sintáctico abstracto, por lo que el retorno lo utilizamos para ir construyendo un árbol basado en objetos, el cual conocemos como árbol sintáctico abstracto.

Para no hacerte el cuento muy largo, el árbol sintáctico abstracto se crea a partir de TODAS las clases que están en los paquetes oscar.ArbolAbstracto.xxx, lo cual corresponde a 5 paquetes y 23 clases, solo para generar el árbol sintáctico abstracto.

Lo irónico de esto es que un árbol sintáctico abstracto es en realidad la implementación del patrón de diseño Composite, lo cual yo no tenía ni idea en aquel entonces. Más tarde escribiría el libro Introducción a los patrones de diseño donde explico este y otros 24 patrones de diseño 😉

Otra diferencia que no había mencionado, es que el compilador chico tiene un diccionario de símbolos, lo que valida que no existan variables con el mismo nombre.

Árbol sintáctico abstracto

El árbol sintáctico abstracto se generará solo para la gramática chica en la pestaña llamada [Arbol Generado], la cual se muestra algo así:

VarDeclaracion( )
IntegerTipo
Identificador

VarDeclaracion( )
IntegerTipo
Identificador

If( )
Compracion( )
IdentificadorExp( )
IdentificadorExp( )


Print( )
IdentificadorExp( )

Print( )
IdentificadorExp( )

En este momento me doy cuenta que le falto un poco de orden a lo que se muestra, pero lo que vemos es básicamente las clases ordenadas como se encuentran en el árbol. Por ejemplo las tres primeras líneas:

VarDeclaracion( )
IntegerTipo
Identificador

Esto que en realidad quiere decir es que es VarDeclaracion( IntegerTipo, Identificador) sin embargo, es solo una forma de representarlo. Al final el árbol se puede recuperar y crear una versión más amigable.

Código intermedio

El código intermedio es un código que se genera a partir del código original con la intención de crear un código más fácil de interpretar por un interprete.

Para ser honestos, no recuerdo en que me base para crear este código, pero creo que era un lenguaje basado en Lisp, lo que si te puedo decir es que el código objeto se crea con la clase GeneracionCodigoObjeto del paquete oscar.CodigoObjeto.

Al final, el código intermedio solo se muestra para la gramática chica y lo puedes ver en la pestaña [Codigo Intermedio], el cual se ve de la siguiente manera:

// Declaracion de Variable
ADDI $T1, $ZERO, $ZERO
SW $T1, 0($ESP)
ADDI $ESP, $ESP, -4

// Declaracion de Variable
ADDI $T1, $ZERO, $ZERO
SW $T1, 0($ESP)
ADDI $ESP, $ESP, -4

// Statement IF
LW $T1, -0($FP)
LW $T2, -4($FP)
BEQ $T1, $T2, ETIQUETA0
ADDI $T1, $ZERO, 0
J ETIQUETA1
ETIQUETA0:
ADDI $T1, $ZERO, 1
ETIQUETA1:
ADDI $T2, $ZERO, $ZERO
BEQ $T1, $T2, ETIQUETA2
// Imprimir Linea
LW $T1, -4($FP)
PRINT $T1

J ETIQUETA3
ETIQUETA2: 
// Imprimir Linea
LW $T1, -0($FP)
PRINT $T1

ETIQUETA3:

Y como dice Porky de los Looney Tunes, “Y eso es todo amigo“, la verdad hice todo lo posible por recordar y documentar todo el proceso y como funcionaba, sin embargo, siento que muchas cosas quedaron por fuera de este artículo que me hubiera gustado documentar, pero es tanta información y mucha teoría que esto da para un libro jeje, así que lo dejo hasta aquí y si tienes dudas, me puedes preguntar 🙂

Conclusiones

Como y has podido comprobar Oscar++ no es para nada la competencia de Java ni de Python, si no que es solo un proyecto universitario (proyecto final) el cual combina muchos conceptos y teoría que uno debe de dominar para lograr crear aunque sea un compilador simple como este.

En realidad este fue uno de los proyectos de los que más me divertí en toda la carrera y espero realmente que este material que te estoy entregando te sea de gran ayuda para que comiences con el píe derecho.

32 thoughts to “Oscar++ Mi propio compilador”

  1. Excelente Oscar. Tengo 10 años aprox. programando y por “n” o “y” situaciòn jamas habia explorado dos de mis mas grandes interrogantes en este campo: Como funciona un compilador y Como se crea un lenguaje .
    Esto me sirve de punto de partida ,saludos!

    1. Hola Enrique, es muy interesante como funcionan los compiladores, sobre todo por que son áreas del conocimiento que no solomos explorar en situaciones normales.

  2. hola, e intentado ejecutar me aparece un código con error
    import ch.randelshofer.quaqua.QuaquaLookAndFeel; en la parte del ch.
    si me podrías explicar como resolverlo

    1. Quaqua es una librería de Look and Feel que simula la apariencia de MacOS, creo que la descontinuaron hacer mucho, así que simplemente puedes eliminar toda dependencia a esta librería para que te deje compilar, la única diferencia es que cambiara la forma en que se ve fisicamente.

  3. Disculpa oscar una pregunta, serias tan amable de indicarme en que parte de tu compilador se puede cambiar digamos el idioma del lenguaje, si en ves de que tu compilador reconozca que existe un if detecte que en ves de if sea un si, en ves de else que analice que debe de estar declarado un sino.

    1. Huy amigo, me matas con esa pregunta, realmnete no recuerdo, pero creo que podrías editar el archivo /src/oscar/compilador1/Oscar2Constants.java y cambiar la palabra en el array que hay al final.

      1. Lo intente pero al momento de realizar la prueba pues no detecto ninguno de estos cambios realizados, es como si hiciera uso de esas palabras pero de forma interna.

        1. La verdad tiene muchusimo tiempo que desarrolle el compilador, que ni me acuerdo como lo hice, creo que te tocará debugear y analizar el funcionamiento.

      2. Justo iba a preguntar lo mismo, explicas en tu post solo partiendo del análisis sintáctico con los archivos .jj pero no mencionas en que parte defines los tokens o la parte del análisis léxico, sería interesante si lo recordaras, desde los 2000 ya pasó uf de tiempo

  4. Oscar Nuevamente yo, que diferencia existe entre codigo intermedio y codigo objeto, veo que existe una clase llamada generadorcodigoobjeto pero no se que funcion tiene y como se le da uso a dicha clase.

    1. Hola Jhon, el código intermedio es un código que se genera previo al resultado final, por ello se llama intermedio, y su objetivo es tener una base sobre la cual ejecutar procedimientos de obtimización, una vez que el código intermedio es optimizado, se convierte en código objeto, el cual es el resultado final del compilador, el cual puede ser código máquina, u otro código intermedio como es el caso de los bytecode de Java o MSI de .NET

      1. Hola Oscar, osea en este caso tu compilador tiene una pestaña llamada código intermedio, podemos decir que esta pestaña muestra el código intermedio ya optimizado, que ya esta convertido a objeto?

  5. Hola Oscar nuevamente soy yo, Jhon Meza, disculpa de casualidad recuerdas en base a que realizaste el código objeto de tu compilador, me piden en la U que explique como ejecuta un compilador el código objeto, en otra palabras como el código insertado por el usuario se convierte en dicho código, quisiera aprender a generarlo de forma escrita para revisar que el código objeto generado esta correcto.
    algun apartado, foro, alguna serie de tutoriales en el cual pueda aprender a realizarlo, si fueras tan amable de ayudarme con ello.

    1. Hola Jhon, el código objeto generado creo que era para una versión de Lisp, pero la verdad ya no recuerdo, hace varios años que lo hice, respecto a como ejecutarlo, lo que faltaría es crear una máquina virtual que interprete el código y lo ejecute, a lo cual yo ya no llegue, pero estoy seguro que si googleas un poco podrás encontrar como crear una Máquina virtual, pero te garantizo que no será nada fácil.

  6. Vale gracias, oye y cuando dices que el código Intermedio se ve solo para gramática chica, ese código si sabes como se genera verdad?
    es que deseo saber ese código como explicarlo, es decir ejemplo:

    // Declaracion de Variable
    ADDI $T1, $ZERO, $ZERO
    SW $T1, 0($ESP)
    ADDI $ESP, $ESP, -4

    ejemplo que significa ADDI y demás que significado tiene.
    siento mucho molestarte tanto.

          1. Si señor muchas gracias por tus grandes aportes, he podido comprender gracias a ti el proceso de un compilador, todos los créditos te los he dado a ti en mi curso de compiladores, por tus aportes y demás, muchas gracias.

  7. Lo estuvimos realizando por la plataforma Microsoft Teams, el cual expusimos los procesos por los que pasa el código fuente para ser interpretado, buscare si puedo encontrar la manera de hacerte llegar el enlace de la clase.

  8. Hola Oscar soy apenas un ingenuo y feliz estudiante y me es muy interesante como funcionan los compiladores por suerte encontré tu blog, gracias por compartir tus conocimientos. Bueno estoy aquí para preguntarte como ejecutar tu compilador ya lo descargue en mi pc y a la hora de darle “Run” me dice que no encuentra o no se carga la clase principal oscarMain busque por todas partes pero no se como solucionarlo podrías ayudarme con este problema ?

  9. Excelente Oscar. Tengo el mismo problema de que no hay un oscar.main en tus archivos, imagino que Axel es mi compañero, pues estamos estudiando cómo hacer compiladores por igual. Si llegamos aquí ambos es porque haces un gran trabajo. Saludos!

    Error: Could not find or load main class oscar.Main
    Caused by: java.lang.ClassNotFoundException: oscar.Main

Deja un comentario

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