Documentos de Académico
Documentos de Profesional
Documentos de Cultura
INTRODUCCIN A JAVACC.
2.1. INTRODUCCIN
Como se coment en el capitulo anterior, la herramienta que vamos a usar para
definir nuestro lenguaje, y por consiguiente su intrprete, va a ser el metacompilador
JavaCC.
Un metacompilador o generador de parsers es una herramienta que, a partir de la
especificacin de un lenguaje, construye un programa o analizador que es capaz de
reconocer secuencias o elementos de dicho lenguaje. En general, la especificacin del
lenguaje abarca tanto el aspecto lxico como el sintctico, y son los que permiten la
construccin del parser, mientras que el aspecto semntico del lenguaje se deja en
manos del usuario, para que lo ensamble una vez obtenido el parser.
En este captulo vamos a desarrollar una introduccin a JavaCC a modo de breve
gua. La idea es hacer que el lector aprenda las nociones bsicas de uso y
funcionamiento de manera que este captulo pueda ser usado como un manual de apoyo
bsico para el desarrollo de compiladores mediante JavaCC. No se va a entrar en
CAPTULO 2
INTRODUCCIN A JAVACC
options {
LOOKAHEAD=1;
}
PARSER_BEGIN(Calculadora)
public class Calculadora {
public static void main(String args[]) throws ParseException {
Calc1 parser = new Calc1(System.in);
while (true) {
System.out.print("Introduzca una expresion: ");
System.out.flush();
try {
switch (parser.one_line()) {
case -1:
System.exit(0);
default:
break;
}
}
catch (ParseException x) {
System.out.println("Finalizando.");
throw x;
}
}
}
}
PARSER_END(Calculadora)
CAPTULO 2
INTRODUCCIN A JAVACC
SKIP :
{
|
|
}
""
"\r"
"\t"
TOKEN :
{
< EOL: "\n" >
}
TOKEN : /* OPERADORES */
{
< MAS: "+" >
|
< MENOS: "-" >
|
< POR: "*" >
|
< ENTRE: "/" >
}
TOKEN :
{
< CONSTANTE: ( <DIGITO> )+ >
| < #DIGITO: ["0" - "9"] >
}
int one_line() :
{}
{
suma() <EOL>{ return 1; }
| <EOL>{ return 0; }
| <EOF>{ return -1; }
}
void suma() :
{}
{
termino() (( <MAS> | <MENOS> ) termino())*
}
void termino() :
{}
{
unario() (( <POR> | <ENTRE> ) unario())*
}
void unario() :
{}
{
<MENOS> elemento()
| elemento()
}
void elemento() :
{}
{
<CONSTANTE>
| "(" suma() ")"
}
Ejemplo 2.1. Gramtica reconocedora de una Calculadora.
CAPTULO 2
INTRODUCCIN A JAVACC
mientras que los analizadores sintcticos generados por yacc son de tipo ascendente
LALR. Otra diferencia bastante importante es que las especificaciones lxicas pueden
estar incluidas dentro de la especificacin de la gramtica. Por ejemplo, podemos
escribir algo como esto:
10
CAPTULO 2
INTRODUCCIN A JAVACC
Genera por defecto un parser LL(1). sin embargo, puede haber porciones de la
gramtica que no son LL(1). JavaCC ofrece la posibilidad de resolver las
ambigedades shift-shift localmente al punto del conflicto. En otras palabras,
permite que el parser se vuelva LL(k) slo en tales puntos; pero se conserva
LL(1) en el resto de las producciones para obtener una mejor actuacin.
Permite el uso de Tokens Especiales que son ignorados por el parser; pero estn
disponibles para poder ser procesados por el desarrollador.
Las especificaciones lxicas pueden definir tokens de manera tal que a nivel
global no se diferencien las maysculas de las minsculas en la especificacin
lxica completa o en una especificacin lxica individual
De entre los generadores de parsers, JavaCC se halla entre los que tienen mejor
manejo de errores. Los parsers generados por JavaCC son capaces de localizar
exactamente la ubicacin de los errores, proporcionando informacin
diagnstica completa.
JavaCC incluye una herramienta llamada JJDoc que convierte los archivos de la
gramtica en archivos de documentacin.
11
CAPTULO 2
INTRODUCCIN A JAVACC
12
CAPTULO 2
INTRODUCCIN A JAVACC
ASCII_CharStream.java,
TokenMgrError.java),
pero
estos
ficheros son los mismos para cualquier gramtica y pueden conservarse de una para
otra.
Gramatica.java
GramaticaConstants.java
GramaticaTokenManager.java
Gramatica.jj
ASCII_CharStream.java
JavaCC
Token.java
Errores
ParserException.java
TokenMgrError.java
Los archivos Token.java , ParseException.java, ASCII_CharStream.java y TokenMgrError.java son los mismos para cualquier
gramtica y pueden conservarse de una para otra.
13
CAPTULO 2
INTRODUCCIN A JAVACC
2.4. OPCIONES
La seccin options, si est presente, comienza con la palabra reservada options
seguida de una lista de una o ms opciones entre llaves. La misma opcin no debe
establecerse ms de una vez. En el ejemplo 2.1. slo se ha usado una de las opciones
(LOOKAHEAD=1).
Las opciones pueden ser especificadas tanto en el archivo de la gramtica como
en la lnea de comandos. En caso de estar presentes en el archivo , stas deben aparecer
al principio precedidas por la palabra options. Y en el caso en que se especifiquen en
la lnea de comandos, stas tienen mayor prioridad que las especificadas en el cdigo.
Para ver al completo las Reglas de Produccin de especificaciones JavaCC consulte el apndice
respectivo.
14
CAPTULO 2
INTRODUCCIN A JAVACC
ser
usados
simultneamente
15
en
threads
diferentes.
CAPTULO 2
INTRODUCCIN A JAVACC
16
CAPTULO 2
INTRODUCCIN A JAVACC
Cuando usamos este tipo de reglas de produccin tenemos que tener muchsimo
cuidado ya que es muy fcil especificar bastante ms de lo que realmente se quiere.
Este tipo de reglas son problemticas cuando las utilizamos en lugares donde el
analizador sintctico tiene que hacer una eleccin. Supongamos que la regla de
produccin anterior es referenciada desde sta:
17
CAPTULO 2
INTRODUCCIN A JAVACC
void NT():
{}
{
skip_to_matching_brace()
|
otra_regla_de_produccion()
}
Ejemplo 2.4. Uso inadecuado de una regla de produccin Javacode
En este punto JavaCC no sabra elegir entre las dos opciones. En cambio si la
regla de produccin JAVACODE es usada en un lugar donde no hay que elegir entre
diversas reglas no hay ningn problema. Por ejemplo:
void NT():
{}
{
{ skip_to_matching_brace()
|
(lista_de_parametros() )
}
Ejemplo 2.5. Uso correcto de una regla de produccin Javacode
18
CAPTULO 2
INTRODUCCIN A JAVACC
Cada regla de produccin BNF consta de una parte izquierda la cual consiste en
la especificacin de un no terminal. Las reglas de produccin BNF definen este no
terminal en funcin de expansiones BNF que se encuentran en la parte derecha. El no
terminal se escribe exactamente de la misma forma en que se declara un mtodo en
Java, (en el ejemplo 2.6. void unario():) . Ya que cada no terminal se traduce a un
mtodo en el analizador sintctico, la forma de escribir el no terminal hace que esta
asociacin sea obvia. El nombre del no terminal es el nombre del mtodo, y los
parmetros y el valor de retorno son los medios para pasar valores arriba y abajo dentro
del rbol sintctico. parse tree- (Esto se mostrar ms adelante).
La parte derecha de una regla de produccin BNF tiene dos partes: La primera
parte es un conjunto de declaraciones y cdigo Java ( bloque Java). Este cdigo es
generado al principio del mtodo relacionado con el no terminal. Por lo tanto, cada vez
que el no terminal es usado en el proceso de anlisis, este cdigo es ejecutado. En el
ejemplo 2.6. esta parte aparece vaca (ms adelante se mostrar un ejemplo que hace uso
de ella). La segunda parte est formada por las expansiones BNF.
expansion ::=(expansion_unit)*
Esquema 2.6. Estructura de una expansin BNF.
19
CAPTULO 2
INTRODUCCIN A JAVACC
expansin <MENOS> y elemento() -. Para que una entrada sea reconocida con xito,
debe comenzar por -, y finalizar con una expresin que se ajuste a elemento().
Una de la mayores ventajas que aporta JavaCC es el hecho de que se puede
hacer uso de bloques de cdigo java incrustados en cada una de la expansiones BNF.
Este aspecto se ver claramente en el apartado dedicado a la gestin de atributos. Por
medio del empleo de cdigo java, se nos permite la capacidad de realizar acciones
semnticas que puede actuar sobre las entidades sintcticas que se han ido generando.
Combinando esto con el uso de los parmetros de entrada y de retorno de los no
terminales, se puede tener un control completo sobre el paso de valores, tanto hacia
arriba como hacia abajo, en el rbol sintctico.
regular_expr_production::= [lista_de_estados_lexicos]
regexpr_kind[ [ IGNORE CASE ] ] :
{ regexpr_spec ( | regexpr_spec )* }
Esquema 2.7. Estructura de una regla de produccin mediante expresiones regulares.
CAPTULO 2
INTRODUCCIN A JAVACC
21
CAPTULO 2
INTRODUCCIN A JAVACC
// Comentarios
SKIP:
{
/*:EntreComentarios
}
<EntreComentarios> SKIP:
{
*/ : DEFAULT
}
<EntreComentarios> MORE:
{
<~ [ ] >
}
Ejemplo 2.7. Definicin de entidades lxicas mediante el empleo de expresiones regulares.
Como puede verse en los ejemplos 2.7. y 2.8., la combinacin del uso de los
distintos estados lxicos y de los tipos de expresiones regulares, aporta una gran
versatilidad en la definicin de las distintas entidades lxicas. En el ejemplo 2.7. se est
haciendo uso de dos tipos de entidades lxicas diferentes: SKIP y MORE, y se emplean
tambin dos estados lxicos: <EntreComentarios> y DEFAULT. Por su parte el ejemplo
22
CAPTULO 2
INTRODUCCIN A JAVACC
2.8. hace lo propio, usando dos tipos de entidades: TOKEN y MORE, y otros dos
estados lxicos: <EntreComillas> y DEFAULT.
Tras haber aclarado cmo se definen tanto el tipo de entidad lxica como la listas
de estados lxicos en que sta tiene sentido, pasemos a ver realmente cmo se definen
haciendo uso de expresiones regulares.
2.5.3.1. EXPRESIONES REGULARES
La especificacin de una expresin regular inicia la descripcin de las entidades
lxicas que forman parte de esta regla de produccin. Cada regla de produccin puede
contener cualquier numero de especificaciones de expresiones regulares.
regexpr_espec::=expresion_regular[bloque_java] [: identificador_java]
Esquema 2.9. Estructura de una especificacin de expresin regular.
regular_expression::=java_string_literal
| < [ [ # ] java_identifier : ] complex_regular_expresssion_choices >
| < java_identifier >
| < EOF >
23
CAPTULO 2
INTRODUCCIN A JAVACC
24
CAPTULO 2
INTRODUCCIN A JAVACC
Los tokens regulares son los tokens normales que maneja el parser.
25
CAPTULO 2
INTRODUCCIN A JAVACC
Los tokens especiales son otros tokens muy usados que aunque no tienen
relevancia sintctica no son descartados, tales como los comentarios.
Token next; Es una referencia al siguiente token regular del stream de entrada.
Si es el ltimo token de la entrada, o si el token manager no ha ledo tokens
despus de ste, el campo toma valor null.
La descripcin anterior es vlida slo si el token es un token regular.
Token specialToken; Este campo se utiliza para acceder a los tokens especiales
que ocurren antes del token, pero despus del token regular (no especial) que lo
precede inmediatamente. Si no existen tales tokens especiales, este campo toma
el valor null. Cuando hay ms de un token especial, este campo se refiere al
ltimo de dichos tokens especiales, el cual se refiere al anterior, y as
sucesivamente hasta llegar al primer token especial, cuyo campo specialToken
es null. Los siguientes campos de tokens especiales se refieren a otros tokens
especiales que lo suceden inmediatamente (sin la intervencin de un token
regular). Si no existe tal token, el campo toma valor null.
26
CAPTULO 2
INTRODUCCIN A JAVACC
{
switch(ofKind){
default: return new Token();
}
}
Por defecto, su comportamiento consiste en retornar un nuevo objeto Token. Si
quieren ejecutarse acciones especiales cuando se construye un token o se crean
subclases de la clase Token y se instancian, puede redefinirse este mtodo
apropiadamente. La nica restriccin es que este mtodo retorna un nuevo
objeto de tipo Token (o una subclase de Token).
27
CAPTULO 2
INTRODUCCIN A JAVACC
options {
LOOKAHEAD=1;
}
PARSER_BEGIN(Calculadora )
public class Calculadora {
public static Integer[] variables=new Integer[27];
public static void main(String args[]) throws ParseException {
Calculadora parser = new Calculadora(System.in);
while (true) {
System.out.print("Introduzca una expresion: ");
System.out.flush();
try {
switch (parser.one_line()) {
case -1:
System.exit(0);
default:
break;
}
} catch (ParseException x) {
System.out.println("Finalizando.");
throw x;
}
}
}
public static void insertaValor(Character indice, Integer valor){
int i=Character.getNumericValue(indice.charValue()) Character.getNumericValue('A');
variables[i]=valor;
}
public static int valorDeLaVariable(Character var){
int i=Character.getNumericValue(var.charValue()) Character.getNumericValue('A');
int resultado=variables[i].intValue();
return resultado;
}
}
PARSER_END(Calculadora )
SKIP :
{
|
|
}
""
"\r"
"\t"
TOKEN :
{
< EOL: "\n" >
}
28
CAPTULO 2
INTRODUCCIN A JAVACC
TOKEN : /* OPERADORES */
{
< MAS: "+" >
|
< MENOS: "-" >
|
< POR: "*" >
|
< ENTRE: "/" >
|
< IGUAL: "=" >
}
TOKEN :
{
< CONSTANTE: ( <DIGITO> )+ >
|
< #DIGITO: ["0" - "9"] >
}
TOKEN :
{
<VARIABLE: <LETRA>>
|
<#LETRA:["A"-"Z"]>
}
int one_line() :
{int resultado;}
{
LOOKAHEAD(2)
resultado=suma(){System.out.println("El resultado es: "+resultado);}
<EOL>{ return 1; }
| decl_var() <EOL>{return 0;}
| <EOL>{ return 0; }
| <EOF>
{ return -1; }
}
int suma() :
{int resultado;
int aux; }
{
resultado=termino()
( <MAS> aux=termino(){resultado=resultado+aux;}
| <MENOS> aux=termino(){resultado=resultado-aux;}
)*
{return resultado;}
}
int termino() :
{ int resultado;
int aux;}
{
resultado=unario()
( <POR> aux=unario(){resultado=resultado*aux;}
| <ENTRE> aux=unario(){resultado=resultado/aux;}
)*
{return resultado;}
}
29
CAPTULO 2
INTRODUCCIN A JAVACC
int unario() :
{ int resultado;}
{
(
<MENOS> resultado=elemento(){resultado=0-resultado;}
|
resultado=elemento()
)
{return resultado;}
}
int elemento() :
{Integer valor;
int resultado;
Character aux;}
{
( <CONSTANTE>
{valor=new Integer(token.image);
resultado=valor.intValue();
}
| <VARIABLE>
{aux=new Character(token.image.charAt(0));
resultado=valorDeLaVariable(aux);
}
| "(" resultado=suma() ")"
)
{return resultado;}
}
void decl_var() :
{Integer valor;
Character indice;}
{
<VARIABLE>{indice=new Character(token.image.charAt(0));}
<IGUAL>
<CONSTANTE>{valor=new Integer(token.image);}
{insertaValor(indice,valor);}
}
30
CAPTULO 2
INTRODUCCIN A JAVACC
31
CAPTULO 2
INTRODUCCIN A JAVACC
32
CAPTULO 2
INTRODUCCIN A JAVACC
que realicen aquello que se desee. Dentro de estos bloques se puede hacer uso tanto de
las variables y mtodos definidos dentro del No Terminal, en la zona dedicada a tal fin,
como de mtodos propios de la clase principal, declarados dentro de la zona delimitada
por PARSER_BEGIN ...... PARSER_END. Esta posibilidad asociada al uso de la clase
token es el principal mecanismo de gestin de atributos. De hecho la mayora de
acciones semnticas se van a realizar de esta forma. Las posibilidades que se nos
brindan ahora son enormes. En el ejemplo 2.10. gran parte de la gestin de atributos se
ha desarrollado de esta forma. Se combinan el uso de mtodos declarados en la clase
Calculadora, insertaValor y valorDeLaVariable, con empleo de token.image. Veamos
un trozo en concreto:
int elemento() :
{Integer valor;
int resultado;
Character aux;}
{
( <CONSTANTE>
{valor=new Integer(token.image);
resultado=valor.intValue();
}
| <VARIABLE>
{aux=new Character(token.image.charAt(0));
resultado=valorDeLaVariable(aux);
}
| "(" resultado=suma() ")"
)
{return resultado;}
}
Ejemplo 2.13.Empleo de bloques de cdigo Java en las expansiones BNF
33