Skip to content

Preparación del Código Inicial

Antes de empezar a implementar las distintas fases de un compilador, para agilizar el comienzo del tutorial, se copiará un esqueleto de un compilador que sirva de punto de partida.

El esqueleto usado en esta sección no es específico para este tutorial y, por tanto, puede ser utilizado como base de cualquier otro compilador.

Creación del Proyecto

Antes de empezar los capítulos del tutorial, hay que crear un proyecto Java siguiendo estos pasos:

  1. Descargar el código fuente del esqueleto. Este se encuentra en la carpeta "0. Skeleton" de dicho zip.

  2. Crear un proyecto a partir del esqueleto. Para esto hay dos vías:

    • Importar como proyecto el esqueleto en el editor elegido (eclipse, Visual Studio Code, …). En este caso, el jar de Antlr ya estará añadido al proyecto.

    • Crear un nuevo proyecto (vacío) y, posteriormente, copiar en él todo el contenido de la carpeta "0. Skeleton". En este caso, es importante recordar que hay que añadir al proyecto el jar de Antlr (que se encuentra en /tools/antlr/antlr-xxx.jar) usando el comando adecuado en función del editor elegido.

      TIP

      Sólo es necesario importa el jar de Antlr. El jar que se encuentra en /tools/vgen no hay que añadirlo, ya que es una herramienta opcional que se utiliza únicamente en línea de comando. Esta herramienta no requiere para su uso que el proyecto incluya ninguna de sus clases.

  3. Comprobar que todo funciona correctamente ejecutando la clase main.Main. Debería aparecer el siguiente mensaje:

    bash
    Compiler starts...
    
    -> The tree has not been generated (this message will disappear when the tree is implemented)

    Es normal que el mensaje indique que el árbol (tree) aún no ha sido generado, ya que esa parte no se ha implementado aún.

Si se han seguido los pasos anteriores, ya se está en disposición de seguir los capítulos de este tutorial.

Contenido del Esqueleto

Aunque se puede saltar el resto de esta sección e ir directamente a ver el código del proyecto creado, aquí se explicará brevemente el código obtenido del esqueleto.

Funcionalidad Inicial Implementada

El código obtenido del esqueleto implementa un analizador que solo admite como entrada válida ficheros que tengan un número. Esto es debido a que en el fichero /specifications/Grammar.g4 se encuentra la siguiente gramática:

antlr
program: INT_LITERAL EOF;

Por tanto, sólo serían entradas válidas ficheros como el siguiente:

txt
32

Esta implementación simplemente pretende servir de código inicial que compile y se puede ejecutar sin errores aunque no haga nada útil aún. En futuros capítulos se irá ampliando esta funcionalidad.

Estructura General del Proyecto

El fichero src/main/Main.java dirige todo el proceso de compilación.

java
public class Main {
    public static final String TEST_FILE = "input.txt"; // Used only in development
    public static final String OUTPUT_FILE = "output.txt";

    public static void main(String[] args) throws Exception {
        ErrorManager errorManager = new ErrorManager();

        var inputFile = TEST_FILE; // TODO: Replace `TEST_FILE` with `args[0]` in the final version

        System.out.println("\nCompiler started.\n");

        AST ast = compile(inputFile, errorManager);
        if (errorManager.errorsCount() > 0)
            System.out.println("\n" + errorManager.errorsCount() + " errors detected.");

        System.out.println("\nCompiler finished.\n");

        AstPrinter.toHtml(inputFile, ast, "AST"); // Class generated by VGen (optional)
    }

    // Method that coordinates all compiler phases
    public static AST compile(String sourceName, ErrorManager errorManager) throws IOException {

        //$ 1. Lexical and Syntax analysis -----------------------------
        GrammarLexer lexer = new GrammarLexer(CharStreams.fromFileName(sourceName));
        GrammarParser parser = new GrammarParser(new CommonTokenStream(lexer));

        AST ast = null;
        // IMPORTANT: When the AST has been generated, swap the following two lines of code.
        parser.program();
        // ast = parser.program().ast;

        if (parser.getNumberOfSyntaxErrors() > 0) { // Syntax error detected (ANTLR omits lexical errors)
            errorManager.notify("Compilaton finished due to syntax errors.");
            return null;
        }

        //$ 2. Semantic Analysis -----------------------------
        var analyzer = new SemanticAnalisys(errorManager);
        analyzer.analize(ast);
        if (errorManager.errorsCount() > 0)
            return ast;

        //$ 3. Code Generation -----------------------------------
        File sourceFile = new File(sourceName);
        Writer out = new FileWriter(new File(sourceFile.getParent(), OUTPUT_FILE));

        var generator = new CodeGeneration();
        generator.generate(sourceFile.getName(), ast, out);
        out.close();

        return ast;
    }
}

La clase Main normalmente no habrá que modificarla, ya que se limita a llamar a las distintas fases del compilador.

Por tanto, los fuentes del esqueleto se dividen en dos tipos:

  • Aquellos que incluyen código rutinario que es siempre igual en todos los compiladores. Estos fuentes no serán modificados.
  • Aquellos que están vacíos y su única función es indicar dónde se deberían ubicar dichos ficheros cuando se creen en la fase apropiada. Se pretende ayudar así a mantener una estructura adecuada en el compilador. Por ejemplo, a la hora de implementar la etapa de Identificación del analizador semántico, habrá que completar los métodos de la clase /src/semantic/Identification.java (que ahora se encuentra vacía).

TIP

Se recomienda ahora mirar las clases del proyecto creado (son bastante pequeñas y con comentarios que explican su función). En cualquier caso, se irá viendo su función en capítulos posteriores a medida que se vayan necesitando en cada fase.