JJTree - inicio

Download Report

Transcript JJTree - inicio

PROGRAMACION DE SISTEMAS
JJTREE y JavaCC
Introducción
• JJTree, es una herramienta para construir
un árbol de representación del mismo
parser.
• JJTree se acciona para emitir un parser el
cual su principal trabajo durante su
ejecución no es ejecutar acciones
incrustadas de java, sino construir una
representación independiente de árbol del
análisis sintáctico de la expresión que se
analiza por el compilador resultante.
¿Qué hacer con el árbol que
produce JJTree?
• JJTree permite capturar el estado de
la sesión del análisis sintáctico en un
solo árbol que es fácil de recorrer y
verificar en tiempo de ejecución,
independientemente del código de
análisis sintáctico que lo produce.
Arbol de análisis sintáctico
• Al árbol generado por JJTree, se le
denomina formalmente como Árbol de
análisis sintáctico.
• Para trabajar con JJTree se requiere lo
siguiente:
– Crear un script con extensión .jjt el cual
JJTree toma como entrada
– Escribir el código del lado cliente para
recorrer y evaluar el árbol sintáctico que
se produce en la ejecución.
Aspectos básicos de JJTree
• JJTree es un preprocesador y genera
un parser para un BNF particular.
• Es un proceso fácil de dos pasos:
– Ejecutar JJTree contra un archivo
denominado .jjt; este producirá un
archivo .jj
– Compilar el archivo .jj con JavaCC
– Y por último, compilar el archivo .java
resultante
Sintaxis del archivo .jjt
• La estructura de un archivo .jjt es de
menor extensión que el formato .jj
• La principal diferencia es que JJTree
agrega un nuevo constructor
sintáctico (node-constructor) el cual
permite especificar donde y bajo que
condiciones se tienen que generar los
nodos del árbol sintáctico durante el
análisis sintáctico.
Una gramática simple
SKIP:{“ “|”\t”|”\n”|”\r” }
TOKEN:{<INT : ([“0”-”9”])+ > }
void simpleLeng():{} {addExpr() <EOF>}
void addExpr():{}
{integerLit() (“+” integerLit() ) ? }
void integerLit():{} {<INT>}
•
En ésta gramática se establece que una expresión válida consiste de:
–
–
•
Una sola literal entera o
Una literal entera seguida del signo más y seguido por otra literal entera.
A continuación, ligeramente modificada para emplear JJTree (se indican los cambios)
SKIP:{“ “|”\t”|”\n”|”\r” }
TOKEN:{<INT : ([“0”-”9”])+ > }
SimpleNode simpleLeng():#Root{} {addExpr()<EOF> {return jjtThis;}}
void addExpr():{}
{integerLit()
(“+” integerLit() #Add(2) ) ? }
void integerLit():#IntLit{}
{<INT>}
Paso a paso por la gramática
JJTree
• La invocación a un parser sin emplear
JJTree sería como:
SimpleParser parser = new SimpleParser(new
StringReader(expresion));
parser.simpleLeng();
• Ahora la invocación a un parser
empleando JJTree:
SimpleParser parser = new SimpleParser(new
StringReader(expresion));
SimpleNode rootNode = parser.simpleLeng();
Descripción del ejemplo
• Las etiqueta insertada de la forma
#Root, indican que se insertará un
nodo raíz (de tipo SimpleNode) en el
árbol creado y referenciado en todo
momento por jjtThis.
SimpleNode simpleLeng():#Root{}
{addExpr()<EOF> {return jjThis;}}
Descripción del ejemplo
• La etiqueta #Add(2) dentro de la producción
addExpr() difiere de la etiqueta #Root en varios
aspectos:
– Es parametrizada. El constructor del árbol utiliza una pila
de nodos durante la construcción; el comportamiento
normal para un constructor de nodos sin parámetros es
colocarse en el tope del árbol sintáctico bajo construcción,
sacando todos los nodos de la pila de nodos que fueron
creados en su mismo alcance y colocando a dicho nodo
como el nodo padre.
– El argumento 2 indica que el nuevo nodo padre (en éste
caso #Add) adoptará exactamente dos nodos hijo, los dos
sub nodos #IntLit descritos en la siguiente
construcción.
Descripción del ejemplo
• De igual importancia, la colocación de la directiva
#Root fuera del cuerpo de la producción significa
que el nodo #Root es generado cada vez que se
cumple la producción (lo cual en éste caso sólo
sucede una vez).
• Sin embargo, la colocación de la directiva #Add(2)
dentro de un término “cero o uno” significa que un
nodo #Add se crea condicionalmente si la cláusula
opcional se cumple, es decir si ésta producción
representa una verdadera operación de suma.
Descripción del ejemplo
• Siguiendo las reglas de producción,
integerLit() se cumple dos veces,
agregando al árbol un nodo #IntLit en
cada invocación. Ambos nodos #IntLit se
convierten en nodos hijos del nodo #Add
que los invoca. Sin embargo si la expresión
a analizar sintácticamente es de un solo
entero, entonces el nodo resultante se
convierte directamente en nodo hijo de
#Root
Representación del árbol
• Árbol sintáctico para una expresión de un solo
entero.
• Árbol sintáctico para una operación de suma.
Trabajando sobre el árbol
resultante
• Cada nodo que se declara en un archivo .jjt
instruye al parser a generar una subclase
del tipo SimpleNode de JJTree.
• SimpleNode en turno, implementa una
interface Java denominada Node.
• Los archivos fuente para estas clases son
generadas automáticamente por cada
archivo JJTree, acompañando al archivo .jj
• JJTree también emite archivos fuente para
sus propias clases – Root, Add y IntLit del
ejemplo – así como otras clases de apoyo.
Desplegando el contenido del
árbol.
• Todas las subclases de SimpleNode
heredan un comportamiento útil. El
método dump() de SimpleNode es
uno de estos.
• Las siguientes tres líneas de código
inician el parser, lo invocan, obtienen
en árbol sintáctico e imprimen
directamente una representación
textual del árbol en la consola.
Trabajando sobre el árbol
resultante
• Código dentro del método main() o método de la clase
“Cliente” del parser.
SimpleParser parser = new SimpleParser(new
StringReader(expresion));
SimpleNode rootNode = parser.simpleLeng();
rootNode.dump(“ “);
• Representación del árbol de acuerdo al ejemplo
Root
Add
IntLit
IntLit
Navegando a través del árbol
• Otro método útil de SimpleNode es
jjtGetChild(int).
• De acuerdo al ejemplo, cuando se
navega a través del árbol sintáctico y
se encuentre un nodo #Add,
seguramente se buscará obtener sus
nodos hijos tipo #IntLit, extraer sus
valores enteros que representan y
sumarlos.
Navegando a través del árbol
• Asumiendo que addNode, mostrado en el
siguiente fragmento de código, es una
variable que representa un nodo de tipo
Add, podemos acceder a los dos nodos
hijos de addNode.
SimpleNode izq = addNode.jjtGetChild(0);
SimpleNode der = addNode.jjtGetChild(1);
Es importante aclarar que los dos nodos IntLit todavía no
contienen los valores enteros que pretenden
representar.
Almacenando y recuperando el
estado.
• Para almacenar el valor de los tokens
identificados en los nodos apropiados,
hay que realizar las siguientes
modificaciones a SimpleNode:
public class SimpleNode extends Node{
String m_text;
public void setText(String text) {m_text = text;}
public String getText() { return m_text; }
}
Almacenar y recuperar el
estado.
• Cambiar la siguiente producción en el
archivo .jjt
void integerLit(): #IntLit {} {<INT>}
• por ésto
void integerLit(): #IntLit {Token t}
{
t=<INT>{ jjtThis.setText(t.image);}
}
Esta producción obtiene el texto crudo del valor del entero
encontrado en t.image y usa el método setText() para
almacenar la cadena en el nodo actual.
Desplegando los cambios
• Fácilmente se puede modificar el método
SimpleNode.dump() para emitir el valor
de m_text para cualquier nodo que sea
almacenado durante el análisis.
• Por ejemplo, se puede modificar para
visualizar la salida de dump(), para la
expresión “42 + 1” y obtener el siguiente
resultado:
Root
Add
IntLit[42]
IntLit[1]
Implementando la precedencia de
operadores.
• Ordenando correctamente las reglas
de producción, colocando en la parte
inferior aquellas reglas que tienen
mayor prioridad, se cubre la
precedencia de los operadores ya que
la representación del árbol sintáctico
controlará efectivamente el orden
correcto de evaluación.
Ejemplo completo.
• Revisar el archivo Calc.jjt y realizar las
siguientes modificaciones: