Skip to content

Implementación de los Nodos

A la hora de implementar los nodos de la especificación, se presentan dos opciones: implementarlo de forma manual o usar una herramienta. A continuación se verán ambas opciones.

Implementación Manual

El método para obtener la implementación de los nodos de un AST en Java a partir de la gramática abstracta es el siguiente:

  • Cada categoría sintáctica se implementa como un interfaz.
  • Cada nodo se implementa como una clase en Java. Dicha clase implementará los interfaces de las categorías sintácticas a las que pertenezca el nodo.
  • Cada hijo es un atributo de la clase del padre. El tipo de dicho atributo está especificado en la gramática abstracta. Si el hijo es multievaluado (tiene un asterisco detrás del tipo) entonces el atributo será una List de Java.

Además, dado que el árbol se va a recorrer utilizando el patrón Visitor, habría que implementar también los requisitos que supone este patrón:

  • Un interfaz AST que sea implementado por todos los nodos.
  • Un interfaz Visitor con un método visit por cada nodo del árbol.
  • Un método accept en cada nodo que invoque al método visit del Visitor que le corresponda.

Y, aunque no es obligatorio, también es conveniente que cada nodo del árbol guarde información sobre su posición original en el fichero de entrada (línea y columna) para poder ofrecer más información en los mensajes de error y en la generación de código.

Implementación con Herramienta (VGen)

VGen es una herramienta que, a partir de una gramática abstracta, genera, entre otras muchas cosas, las clases que formarán los nodos del AST y las clases necesarias para recorrerlos mediante el patrón de diseño Visitor. Dicha herramienta está ya incluida y configurada en el código fuente de este tutorial.

El uso de VGen en este tutorial es opcional (aunque recomendable, ya que genera prácticamente todo el código de manera automática). Sin embargo, si se opta por realizar toda la implementación de forma manual, puede ignorarse este apartado. En ambos casos, el código obtenido debería ser el mismo y el resto de las fases no deberían verse afectadas por la elección de una u otra forma de implementación.

Definición de la Gramática Abstracta

Para que VGen genere los nodos del AST hay que describirlos mediante una gramática abstracta. Dicha gramática, siguiendo la estructura del proyecto del tutorial, se escribiría en el fichero /specifications/AbstractGrammar.g4:

bnf
CATEGORIES
expression;
statement;
type;

NODES
program -> varDefinition* statement*;

varDefinition -> type string;

intType:type -> ;
floatType:type -> ;

print:statement -> expression;
assignment:statement -> left:expression right:expression;

arithmetic:expression -> left:expression operator:string right:expression;
variable:expression -> string;
intLiteral:expression -> int;
floatLiteral:expression -> float;

Como puede observarse, la sintaxis de VGen es básicamente igual a la de la gramática abstracta del capítulo anterior.

Las únicas diferencias son:

  • Las categorías sintácticas tienen que definirse expresamente en la sección CATEGORIES. Esto permite detectar más errores en la declaración de los nodos.

  • La gramática abstracta propiamente dicha se define en la sección NODES.

  • VGen asigna automáticamente un nombre a cada hijo. Pero, si se quiere asignar un nombre expresamente, se puede poner antes de su tipo (separándolos por dos puntos). Puede verse que eso es lo que se ha hecho, por ejemplo, en la regla anterior arithmethic. De esta manera se ayuda a la comprensión de la función de cada hijo.

  • Además del operador * para indicar que un hijo es multivaluado (lista), se puede usar el operador ? para indicar que el hijo es opcional (si no se pone nada, se asume que el hijo es obligatorio).

    Por ejemplo, la siguiente regla define un nodo example_node con un hijo obligatorio de tipo int y un hijo opcional de tipo string:

    example_node -> int string?;
  • Aunque en la anterior gramática no se hace uso de ello, se pueden definir tambien hijos en las categorías (ya que tienen la misma sintaxis que los nodos) y éstos se heredarán en todos los nodos de la categoría. De la misma forma, se permite herencia, tanto simple como múltiple, de categorías (tanto en nodos como en categorías).

Ejecución de VGen

VGen se ejecuta desde línea de comando. Aunque se puede invocar desde una ventana, se recomienda usar la línea de comandos para poder comprobar que no haya errores en el fichero de entrada:

bash
c:\mlang> vgen.bat

2.028 lines of AST classes in 23 files.
159 lines of visitor skeleton in 1 file.
43 lines generated in 1 Antlr4 grammar file.
292 HTML lines generated in 2 specification files skeletons.
191 línes generated in 2 plantUML files.
=== TOTAL: 2.713 lines generated in 29 files.

Una vez hecho esto, se habrán generado clases en dos carpetas del proyecto: en el package ast y en el package visitor.

Package ast

En el paquete ast se han generado las clases que formarán los nodos del árbol. Se habrá generado una clase por cada nodo de la gramática abstracta y un interfaz con su clase abstracta por cada categoría sintáctica.

TIP

Se recomienda abrir la imagen siguiente en una pestaña nueva (botón derecho del ratón) para poder verla más cómodamente.

alt

TIP

Este diagrama ha sido generado siguiendo los pasos de este apartado.

Como muestra de una de las clases generadas, se incluye a continuación la clase Print:

java
package ast.statement;

import ast.expression.*;
import visitor.Visitor;

//	print: statement -> expression

public class Print extends AbstractStatement  {

	private Expression expression;

	public Print(Expression expression) {
		if (expression == null)
			throw new IllegalArgumentException("Parameter 'expression' can't be null. Pass a non-null value or use 'expression?' in the abstract grammar");
		this.expression = expression;

		updatePositions(expression);
	}

	public Print(Object expression) {
        if (expression == null)
            throw new IllegalArgumentException("Parameter 'expression' can't be null. Pass a non-null value or use 'expression?' in the abstract grammar");
		this.expression = (Expression) expression;

		updatePositions(expression);
	}

    // ----------------------------------
	// Child 'expression'

	public void setExpression(Expression expression) {
		if (expression == null)
			throw new IllegalArgumentException("Parameter 'expression' can't be null. Pass a non-null value or use 'expression?' in the abstract grammar");
		this.expression = expression;
	}

    public Expression getExpression() {
        return expression;
    }

    // ----------------------------------
    // Helper methods

    public Object accept(Visitor v, Object param) {
        return v.visit(this, param);
    }

    public String toString() {
        return "Print{" + " expression=" + this.getExpression() + "}";
    }

}

En la implementación anterior puede observarse:

  • El primer comentario es la regla de la gramática abstracta por la cual se ha generado esta clase.
  • La clase Print deriva de AbstractStatement, ya que pertenece a la categoría sintáctica statement.
  • El constructor con parámetro Object permitirá eliminar los cast en las acciones de Antlr (se verá en breve).
  • Las llamadas a updatePositions en los constructores son las que se encargan de manera transparente de calcular la posición de este nodo en el fichero (línea y columna).
  • Se han definido los hijos como atributos de la clase (en este caso un solo hijo llamado expression) y se han añadido los métodos de acceso (get y set) correspondientes.

Package visitor

En el paquete visitor se han generado las clases que formarán la base del patrón del mismo nombre. Esto incluye el interfaz Visitor así como varias clases auxiliares para facilitar la implementación de futuros recorridos.

Aunque son varias clases, se incluye sólo a modo de muestra el interfaz principal del patrón:

java
package visitor;

import ast.*;
import ast.type.*;
import ast.statement.*;
import ast.expression.*;

public interface Visitor {
	public Object visit(Program program, Object param);
	public Object visit(VarDefinition varDefinition, Object param);
	public Object visit(IntType intType, Object param);
	public Object visit(FloatType floatType, Object param);
	public Object visit(Print print, Object param);
	public Object visit(Assignment assignment, Object param);
	public Object visit(Arithmetic arithmetic, Object param);
	public Object visit(Variable variable, Object param);
	public Object visit(IntLiteral intLiteral, Object param);
	public Object visit(FloatLiteral floatLiteral, Object param);
}

Se recomienda detenerse en este momento para revisar el todo código generado por VGen en las carpetas ast y visitor y familiarizarse con él. Si se conoce el patrón Visitor, el código será el esperado. En cualquier caso, es importante entender que, de no haber utilizado la herramienta, el código a generar de forma manual debería haber sido muy similar.