JavaCC - 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:
- existen opciones de personalización extras: JJTree soporta varias opciones de personalización para la construcción del AST, que pueden ser incluídas tanto en las sentencias de opciones de JavaCC, como en la línea de comandos.
- la unidad de compilación java debe cambiarse
- el símbolo inicial de la gramática del lenguaje debe ser de tipo Node: el método que se define en correspondencia al símbolo inicial de la gramática del lenguaje, debe ser del tipo de nodos con los que se desee trabajar. Por defecto, el tipo debe ser SimpleNode.
- el símbolo inicial debe tener asociada una acción para mostrar el AST: para que el stream de entrada pueda ser visualizado, debe agregarse una sentencia de retorno del nodo correspondiente al símbolo inicial:
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.