Implementación Multivisitor
Estructura
En una implementación multivisitor, se crea una clase visitor por cada función de código que se haya definido en la misma. Cada uno de estos visitors implementará sólo las plantillas de código de su función.
Así, en la especificación anterior, habrá que crear cinco visitors: Run.java, Execute.java, Value.java, Address.java y Metadata.java.
run = new Run();
execute = new Execute();
value = new Value();
address = new Address();
metadata = new Metadata();
A la hora de implementar una plantilla, cuando se quiera invocar a una función, habrá que indicar qué visitor de los anteriores se quiere que recorra el nodo indicado.
Esto cambia la forma de usar el método accept
. Es decir, hasta ahora, la forma habitual de recorrer un nodo era usando this
como primer parámetro. Por tanto, se usaba el visitor actual (el único que había) para recorrer el nodo.
node.accept(this, param);
Pero ahora, que hay varios visitors, a la hora de aplicar una función a un nodo, habrá que sustituir this
por el visitor correspondiente. Por ejemplo, supóngase la siguiente plantilla:
address⟦left⟧
value⟦right⟧
STORE
Habría que implementar esta plantilla, suponiendo los visitors creados anteriormente, de la siguiente forma (nótese que se sustituyen los this
por los visitors correspondientes address
y value
):
left.accept(address, null);
right.accept(value, null);
out("store")
Creación de la Infraestructura
A la hora de crear la infraestructura multivisitor, aunque se puede hacer a mano, en este capítulo se usará de nuevo VGen. En cualquier caso, como ha ocurrido en todas las situaciones anteriores, el resultado final será similar al que se hubiera hecho a mano.
Para indicar a VGen que genere la infraestructura multivisitor para una especificación de código, se añade lo siguiente a AbstractGrammar.txt.
CATEGORIES
... // ommited for brevity
NODES
...
ATTRIBUTE GRAMMAR Identification
...
ATTRIBUTE GRAMMAR TypeChecking
...
ATTRIBUTE GRAMMAR MemoryAllocation
...
CODE SPECIFICATION Mapl
run[program]
execute[statement]
value[expression]
address[expression]
metadata[program]
metadata[varDefinition]
En el fichero anterior, se ha creado una nueva sección CODE SPECIFICATION
en la que se definen las funciones de la especificación. Nótese que es simplemente copiar la columna de la derecha de la especificación de código.
Con lo anterior, después de pasar por VGen, se obtendrán las siguientes clases en /vgen.output/skeletons.codefunctions/
codegeneration
└── mapl
├─ MaplCodeSpecification.java
├─ AbstractCodeFunction.java
└── codefunctions
├─ Address.java
├─ Execute.java
├─ Metadata.java
├─ Run.java
└─ Value.java
Estas clases forman el esqueleto de la infraestructura multivisitor para la especificación de código. Para aprovecharse de todo el código generado, el primer paso es copiar estas clases a su sitio definitivo en el proyecto. Para ello, hay que copiar la carpeta /vgen.output/skeletons.codefunctions/codegeneration/ a su nueva ubicación en /src/codegeneration/, sobreescribiendo las clases que haya con el mismo nombre.
A continuación se verá brevemente el contenido de cada una de las clases copiadas. Aunque, si se desea, se puede ignorar el resto de este capítulo y pasar directamente a la implementación en el siguiente.
TIP
Usando varias secciones CODE SPECIFICATION
se podría generar código para distintas especificaciones de código. Se podría, así, generar código para distintas arquitecturas destino (cada una se estaría en su propio package).
Clases de las Funciones
Las clases con el mismo nombre que una función de código son el lugar donde habrá que implementar las plantillas de la función correspondiente.
Como muestra de todas ellos, se incluye aquí el código de la clase Execute.java.
public class Execute extends AbstractCodeFunction {
public Execute(MaplCodeSpecification specification) {
super(specification);
}
// class Print(Expression expression)
public Object visit(Print print, Object param) {
// value(print.getExpression());
// address(print.getExpression());
out("<instruction>");
return null;
}
// class Assignment(Expression left, Expression right)
public Object visit(Assignment assignment, Object param) {
// value(assignment.getLeft());
// address(assignment.getLeft());
// value(assignment.getRight());
// address(assignment.getRight());
out("<instruction>");
return null;
}
}
Para agilizar la implementación, VGen ha generado en cada clase:
- Un método
visit
por cada nodo que pertenezca al dominio de la función. Por ejemplo, en Execute.java se han generados métodosvisit
solo paraPrint
y paraAssignment
. No son necesarios los demásvisit
. De hecho, si se invocan, lanza automáticamente una excepción. - Dentro de cada método
visit
, se recuerda, mediante comentarios, qué funciones se podrían aplicar a los hijos del nodo, es decir, a los dominios de qué funciones pertenecen los hijos. Por ejemplo, en el métodovisit
deAssignment
se recuerda que se podrían aplicar las funciones value y address a los hijos del nodo. - Se añade también un ejemplo de invocación del método
out
para escribir en el fichero de salida.
Clase AbstractCodeFunction.java
La clase AbstractCodeFunction es la clase padre de todas las clases de las funciones de código, es decir, de las clases del apartado anterior. En ella está el código común a todas ellas. Principalmente:
- El método
out
para escribir en el fichero de salida. - Un método para invocar a cada función de código (métodos run, execute, value, address y metadata).
public abstract class AbstractCodeFunction extends ExceptionThrowerVisitor {
// Métodos auxiliares para la escritura de código
protected void out(String line) {
specification.getPrintWriter().println(line);
}
//# Métodos específicos para cada función de código
protected Object run(AST node, Object param) {
return node.accept(specification.getVisitor(MaplCodeSpecification.CodeFunction.RUN), param);
}
protected Object execute(AST node, Object param) {
return node.accept(specification.getVisitor(MaplCodeSpecification.CodeFunction.EXECUTE), param);
}
protected Object value(AST node, Object param) {
return node.accept(specification.getVisitor(MaplCodeSpecification.CodeFunction.VALUE), param);
}
protected Object address(AST node, Object param) {
return node.accept(specification.getVisitor(MaplCodeSpecification.CodeFunction.ADDRESS), param);
}
protected Object metadata(AST node, Object param) {
return node.accept(specification.getVisitor(MaplCodeSpecification.CodeFunction.METADATA), param);
}
// ... omitted for brevity
}
Esta clase es el lugar donde, además del código común a todos los visitors ya generado, se podrá ubicar el código adicional común que surja en la implementación.
Clase MaplCodeSpecification.java
La clase MaplCodeSpecification, que en la versión monovisitor implementaba todas las funciones de código, aquí simplemente es la coordinadora de todos los visitors. Los crea en el constructor y, mediante su método process(AST)
, lanza el primero de ellos.
public class MaplCodeSpecification {
enum CodeFunction { RUN, EXECUTE, VALUE, ADDRESS, METADATA }
private Map<CodeFunction, Visitor> functions;
public MaplCodeSpecification(String sourceFile, Writer writer) {
// ...
this.functions = new EnumMap<>(CodeFunction.class);
functions.put(CodeFunction.RUN, new Run(this));
functions.put(CodeFunction.EXECUTE, new Execute(this));
functions.put(CodeFunction.VALUE, new Value(this));
functions.put(CodeFunction.ADDRESS, new Address(this));
functions.put(CodeFunction.METADATA, new Metadata(this));
}
//$ Entry point ---------
public void process(AST ast) {
ast.accept(functions.get(CodeFunction.RUN), null);
}
// Auxiliary method for the visitors of the functions
public Visitor getVisitor(CodeFunction function) {
return functions.get(function);
}
...
}
Cuando los distintos visitors necesiten acceder a otro, utilizarán esta clase para localizarlo a través del método getVisitor
.
Una vez que se tiene la infraestructura, en los siguientes apartados se irán completando cada uno de estos métodos visit
siguiendo la especificación de codigo.