JavaCC - JJTree

JJTree


JJTree es un preprocesador para JavaCC que inserta acciones de construcción de árboles abstractos de parsing (abstract syntax tree, o AST) en el archivo fuente JavaCC. Para que ello sea posible, es necesario modificar y/o agregar algunos detalles en la especificación del lenguaje, aunque el "cuerpo" global del mismo conserva la estructura básica de las especificaciones JavaCC. La salida de JJTree actúa como entrada de JavaCC para generar el parser JJTree deseado.


Consideraciones a tener en cuenta para las especificaciones JJTree

La estructura general de las especificaciones generadas por la gramática de las especificaciones de lenguajes que pueden ser procesadas por JJTree, básicamente es la misma que la de las especificaciones generadas por la gramática de JavaCC. A continuación se detallan las diferencias generales que poseen las especificaciones JJTree respecto de las especificaciones JavaCC:


bnf_production ::= node_return_type java_identifier "(" java_parameter_list ")" ":"
java_block
"{" expansion_choices"{" return jjtThis; "}" "}"

Para apreciar mejor las diferencias, pueden consultarse las siguientes especificaciones, que corresponden al mismo lenguaje, un lenguaje muy sencillo, el lenguaje de las llaves balanceadas, que puede describirse mediante la expresión regular ({})+.

llaves.jj, es el archivo que contiene la especificación JavaCC.
llaves.jjt, es el archivo que contiene la especificación JJTree.

y las siguientes, correspondientes a un lenguaje de expresiones aritméticas:

ea.jj., es el archivo que contiene la especificación JavaCC.
ea.jjt, es el archivo que contiene la especificación JJTree.


A continuación se describen los elementos necesarios para que JJTree pueda construir los árboles sintácticos:


Nodos

Por defecto, JJTree genera código para construir cada uno de los nodos del árbol de parsing correspondientes a los no terminales del lenguaje. Este comportamiento puede ser modificado para que no se generen nodos para algunos no terminales, o para que una parte de una expansión de una producción sea representada por un nodo. JJTree define una interface de Nodo Java que todos los nodos de los árboles de parsing deben implementar. La interface provee métodos para realizar operaciones tales como setear el padre de un nodo, o agregar hijos y recuperarlos.
Existen diferentes tipos de nodos, y cada nodo posee un ciclo de vida durante el cual tiene un alcance determinado.


Construcción del Arbol

Aunque Java es un parser top-down, JJTree construye el árbol de parsing bottom up. Para ello usa una pila en la que va apilando los nodos a medida que son creados. Cuando encuentra un padre para los nodos, desapila los hijos de la pila, y tras asociarlos con el padre, apila al nuevo nodo padre. La pila está disponible para ser accededida desde las acciones de la gramática: pueden apilarse y/o desapilarse los elementos, dependiendo de lo que sea necesario realizar.


Modos de Operación

JJTree puede operar en dos modos distintos: simpley multi.
En el modo simple, cada nodo del árbol de parsing es de tipo SimpleNode concreto.
En modo multi, el tipo de los nodos se deriva del nombre del nodo.

Si no se proveen implementaciones para las clases nodo de JJTree, por defecto se generarán implementaciones basadas en SimpleNode. Luego, dichas implementaciones podrían sufrir modificaciones.


Estado JJTree

JJTree mantiene su estado en un campo de la clase parser, llamado jjtree.
El estado del parser implementa una pila en la que se mantienen los nodos hasta que pueden ser asociados a su nodo padre. Este objeto, provee métodos para manipular los elementos de la pila de nodos.

    final class JJTreeState {


      void reset();
      /* Reinicializa la pila de nodos.  */

      Node rootNode();
      /* Retorna el nodo raiz del AST. */

      boolean nodeCreated();
      /* Determina si el nodo actual fue cerrado y apilado */

      int arity();
      /* Retorna el número de nodos apilados actualmente en 
      el stack en el alcance de nodo actual. */

      void pushNode(Node n);
      /* Apila un nodo en la pila. */

      Node popNode();
      /* Retorna el tope de la pila y lo elimina.  */

      Node peekNode();
      /*  Retorna el tope de la pila. */
    }

Manejo de Excepciones

Las excepciones lanzadas por la expansión de un nodo que no son atrapadas en el alcance del nodo, son capturadas por el mismo JJTree. Cuando esto ocurre, los nodos que habían sido apilados son desapilados y eliminados, y de esta manera, la excepción vuelve a dispararse.

La intención es hacer posible para los parsers implementar la recuperación de errores y continuar con la pila de nodos en un estado conocido.

Actualmente, JJTree no puede detectar si las excepciones son disparadas por acciones de usuario dentro del alcance del nodo. Por tal motivo, puede darse el caso en que una excepción sea manejada incorrectamente.