Skip to content

Implementación de Funciones Auxiliares

La gramática atribuida obtenida en este capítulo requiere de una función auxiliar size:

NameDescription
size(type)Type size in bytes

Tal y como se comentó en el capítulo anterior, a la hora de implementar las funciones auxiliares hay básicamente dos opciones:

  1. Añadir dichas operaciones en los nodos del ast. De esta manera, se añadiría al interfaz Type un método size que devuelva un int con su tamaño.
  2. Añadirlas en el visitor de la etapa que se está implementado.

En el capítulo anterior se argumentó el porqué se optó por la segunda opción. En resumen, se pretendía seguir la filosofía del patrón visitor y que todo el código de una nueva operación no afectara a las clases a recorrer (evitando así su acoplamiento). Es por ello que el método auxiliar, en aquel caso, se implementó como un método privado.

Sin embargo aquí se presenta la situación de que la función auxiliar size es requerida por más de una etapa (se requerirá también en la siguiente). Por tanto, no puede ser implementada como un método privado del visitor actual.

En ese caso, una solución que permitiría solucionar este problema sería el uso del patrón strategy. Sin embargo, este tutorial pretende ser una introducción sencilla a la implementación de un compilador y, por tanto, se ha preferido mantener su simplicidad y no requerir conocimientos adicionales de patrones de diseño. Así que, por no distraer del objetivo del tutorial, se optará en este caso por implementar la operación size en los nodos.

Por tanto, habría que modificar los siguientes ficheros:

java
public interface Type extends AST {

    // %% User Members -------------------------

    public int getSize();

    // %% --------------------------------------
}
java
public class IntType extends AbstractType {
    ...

    // %% User Members -------------------------

    @Override
    public int getSize() {
        return 2;
    }

    // %% --------------------------------------
}
java
public class FloatType extends AbstractType {
    ...

    // %% User Members -------------------------

    @Override
    public int getSize() {
        return 4;
    }

    // %% --------------------------------------
}

Nótese que los métodos se han añadido entre unos comentarios especiales que tienen el siguiente formato:

java
// %% User Members

    <code here>

// %% -

Estos comentarios son la forma de delimitar el código añadido por el usuario. De esta manera, cuando se regeneren las clases con VGen, se mantendrá dicho código. En caso de que no se esté usando VGen, se pueden borrar dichos comentarios. En cualquier caso, aunque VGen disponga de esta funcionalidad, hay que intentar minimizar su uso, ya que el usarla puede ser un síntoma de que se estén acoplando los nodos a algún recorrido del árbol.

Patrón Strategy

TIP

Este apartado puede ser ignorado, ya que no es necesario para seguir el tutorial. Se incluye sólo para aquellos que ya conozcan el patrón strategy y tengan curiosidad por saber cómo se hubiera aplicado.

En el apartado anterior se ha optado por implementar la operación size en los nodos. Sin embargo, esta solución tiene el inconveniente de que está acoplando el AST con la generación de código para la máquina virtual MAPL (la plataforma destino que se usará en el tutorial). Esto implica que si se quisiera generar código para otra plataforma (x86, ARM, Risc V), habría que modificar los nodos para añadir más métodos (getSizeX86, getSizeRiscV, ...). Por tanto, no es flexible incorporar a los nodos elementos propios del backend del compilador.

En la solución basada en el patrón strategy, la estrategia de cómo calcular el tamaño de un tipo estaría encapsulada en un objeto que sería pasado a las distintas operaciones que la requieran.

java
public interface SizeStrategy {
    public int size(Type type);
}


public class MemoryAllocation ... {

    public MemoryAllocation(SizeStrategy strategy) {
        ...
    }
    ...
}

Así, se tendría una estrategia para cada plataforma:

java
public class X86Strategy implements SizeStrategy {
    public int size(Type type) {
        ...
    }
}

public class RiscVStrategy implements SizeStrategy {
    public int size(Type type) {
        ...
    }
}

De esta manera, incluso un mismo visitor de asignación de direcciones podría ser reutilizado para calcular direcciones en distintas plataformas simplemente pasándole una estrategia distinta.

En definitiva, la solución basada en el patrón strategy:

  • No acopla los nodos con ningún visitor.
  • Permite parametrizar varios visitor con un algoritmo común.