Sei sulla pagina 1di 39

Compiladores e Intrpretes

Una introduccin al generador JavaCC

Jos Gabriel Prez Dez Departamento L.P.S.I. Escuela Universitaria de Informtica Universidad Politcnica de Madrid

Octubre 2008

Contenido

Principios bsicos del generador JavaCC


Descripcin inicial Obtencin de un analizador lxico-sintctico Ejemplo de presentacin Analizadores generados

2 2 2 2 4 5 5 6 7 10 14 15 15 17 20 23 24 25 26 29 33 34

Forma de una especificacin JavaCC


Seccin de opciones Seccin de ejecucin Seccin de sintaxis Seccin de lexicografa

Tareas asociadas a la estructura sintctica


Bloque para un smbolo Acciones sintcticas Valor comunicado por un mtodo

Comunicacin entre los analizadores lxico y sintctico Tareas asociadas a las piezas sintcticas
Accin ligada a una pieza sintctica Bloque de declaraciones lexicogrficas Declaraciones lexicogrficas predefinidas Accin lexicogrfica comn

A modo de recapitulacin Precisiones sobre el analizador sintctico generado

Principios bsicos del generador JavaCC

Descripcin inicial El generador JavaCC (Java Compiler Compiler) es una herramienta para generar programas escritos en lenguaje Java; acepta como entrada una especificacin de un determinado lenguaje y produce como salida un analizador para ese lenguaje. En la manera ms simple de funcionamiento, la especificacin proporcionada define las caractersticas sintcticas y lexicogrficas de un lenguaje y se genera un analizador lxicosintctico del lenguaje especificado; pero tambin es posible completar una especificacin lxico-sintctica con la inclusin adecuada de cdigo para que el programa generado llegue a ser un analizador completo del lenguaje.

Obtencin de un analizador lxico-sintctico

Pasos para la generacin del analizador


1.- Edicin de la especificacin (editor de texto plano) vi | edit | NombreFichero.jj (el nombre del fichero puede tener cualquier extensin; suele usarse .jj) 2.- Ejecucin del generador javacc NombreFichero.jj Si el nombre elegido para la especificacin es NombreDeLaEspecif (ms adelante se indica la manera de dar un nombre a la especificacin), como resultado de la generacin se obtiene (adems de otros ficheros auxiliares) el fichero NombreDeLaEspecif.java 3.- Compilacin del analizador generado javac NombreDeLaEspecif.java Como resultado de la compilacin se obtiene (adems de otras clases auxiliares) el fichero NombreDeLaEspecif.class

Ejecucin del analizador generado

Si el nombre del fichero donde se encuentra el texto fuente (escrito en el lenguaje para el que se ha generado el analizador) que se pretende analizar es Programa.len java NombreDeLaEspecif < Programa.len Si se desea que los resultados del anlisis, en vez de presentarse por pantalla, queden grabados en un fichero de nombre Salida.dat java NombreDeLaEspecif < Programa.len > Salida.dat

Ejemplo de presentacin

Descripcin del lenguaje

El lenguaje L est formado por las expresiones en las que pueden aparecer: - variables - constantes - operadores + y * Las variables son nombres formados por una nica letra (minscula o mayscula); las constantes son nmeros enteros de una o ms cifras. El espacio y el tabulador pueden estar presentes, pero no tienen ningn

3 significado; los finales de lnea tampoco son significativos (una expresin puede codificarse ocupando una o ms lneas). La sintaxis de las expresiones se especifica mediante la siguiente gramtica: <Expresion> ::= <Termino> <Termino> ::= <Factor>

+ <Termino>

* <Factor>

<Factor> ::= variable | constante | ( <Expresion> )

Especificacin lxico-sintctica codificada con la notacin JavaCC

Una manera de escribir la especificacin (para la que se ha elegido el nombre ExprMin) de forma que sea aceptada por el generador es: options { Ignore_Case = true; }

PARSER_BEGIN (ExprMin) public class ExprMin { public static void main (String[] argum) throws ParseException { ExprMin anLexSint = new ExprMin (System.in); anLexSint.unaExpresion(); System.out.println("Anlisis terminado:"); System.out.println ("no se han hallado errores lxico-sintcticos"); } } PARSER_END (ExprMin) void unaExpresion() : { } { expresion() <EOF> } void expresion() : { } { termino() ( "+" } void termino() : { } { factor() ( "*" }

termino() )*

factor() )*

void factor() : { } { <constante> | <variable> | "(" expresion() ")" }

4 TOKEN: { } TOKEN: { } SKIP: < constante : ( ["0"-"9"] ) + > < variable : ["a"-"z"] >

{ " " | "\t" | "\n" | "\r" }

Obtencin del analizador

Si la especificacin precedente se tiene grabada en un fichero de nombre Ejemplo.jj, para obtener el analizador: - se ejecuta el generador: javacc Ejemplo.jj

- se compila el analizador generado: javac ExprMin.java

Ejecucin del analizador

Si se quiere analizar una expresin grabada en un fichero de nombre PruebaExp.txt:

- se ejecuta el analizador obtenido: java ExprMin < PruebaExp.txt


Analizadores generados En su funcionamiento ms sencillo y habitual, JavaCC genera un analizador sintctico, complementado con un analizador lexicogrfico, para que, conjuntamente, se pueda realizar un anlisis lxico-sintctico de un texto de entrada.

El analizador sintctico obtenido es, en general, LL(k): descendente y determinista con la consulta de k smbolos por adelantado; si la gramtica proporcionada cumple la condicin LL(1), se genera un analizador sintctico descendente-predictivo-recursivo. Ms adelante se hacen algunas precisiones sobre esta afirmacin. Si la especificacin lxico-sintctica de un lenguaje codificada en JavaCC tiene dado (como indicativo que acompaa a las palabras reservadas PARSER_BEGIN y PARSER_END) el nombre EspLexSin y se tiene grabada en un fichero de nombre Lenguaje.jj, cuando se ejecuta el generador tomando como entrada ese fichero javacc Lenguaje.jj se obtienen los siguientes ficheros (clases) con cdigo Java:

Token.java descripciones para la comunicacin entre los analizadores lxico y sintctico TokenMgrError.java tratamiento de errores para el anlisis lexicogrfico ParseException.java tratamiento de errores para el anlisis sintctico SimpleCharStream.java componentes para la realizacin de las tareas de entrada/salida del analizador EspLexSinConstants.java definicin de la representacin interna de las piezas sintcticas

EspLexSinTokenManager.java analizador lexicogrfico EspLexSin.java analizador sintctico

Puede apreciarse que hay dos categoras de nombres de ficheros generados: los cuatro primeros nombres citados no dependen del nombre de la especificacin considerada, los otros nombres de ficheros se forman a partir del nombre dado a la especificacin.

Forma de una especificacin JavaCC (versin simplificada)


La forma que se describe en lo que sigue es una versin simplificada; el generador JavaCC admite especificaciones con otras muchas posibilidades no mencionadas en esta introduccin.

Una especificacin para el generador JavaCC puede considerarse dividida en cuatro secciones:

Seccin de opciones

Seccin de ejecucin

Seccin de sintaxis

Seccin de lexicografa

Seccin de opciones En esta seccin, cuya presencia es optativa, se pueden asignar valores a diversos parmetros (llamados opciones) que sirven para configurar ciertas caractersticas del funcionamiento del generador o del analizador generado. Cada parmetro (opcin) tiene un valor por defecto, que es el que toma cuando no se le asigna explcitamente un valor. Los valores de las opciones tambin se pueden fijar en la lnea de comandos cuando se ejecuta el generador (lo indicado en la lnea de comandos tiene prioridad sobre lo especificado en esta seccin de opciones).

Algunas de las opciones para las que se puede fijar un valor son: Ignore_Case (valor por defecto: false) indica si en el texto analizado ha de distinguirse o no entre letras minsculas y maysculas Build_Parser (valor por defecto: true) indica si se genera el analizador sintctico o no

6 Build_Token_Manager (valor por defecto: true) indica si se genera el analizador lexicogrfico o no Sanity_Check (valor por defecto: true) indica si se realizan comprobaciones sobre la gramtica sintctica Debug_Parser (valor por defecto: false) indica si se genera una traza para el anlisis lxico-sintctico Error_Reporting (valor por defecto: true) indica si los mensajes de error emitidos son ms o menos explicativos Static (valor por defecto: true) con el valor por defecto, los mtodos de los analizadores lxico y sintctico se generan con el descriptor esttico (static) Los nombres de las opciones pueden escribirse con letras minsculas o maysculas; los valores de las opciones son, en la mayora de los casos, un valor entero o un valor lgico. Si se incluye la seccin, se empieza poniendo la palabra reservada options; en este caso, al menos ha de ponerse una opcin. La forma de esta seccin es: options { nombreOpcion1 = valorOpcion1; nombreOpcion2 = valorOpcion2; nombreOpcionk = valorOpcionk; } Seccin de ejecucin En esta seccin se pone el cdigo Java que contiene la llamada al analizador generado para que se realice el anlisis de un determinado texto de entrada. Tambin se establece aqu el nombre de la especificacin, que es el nombre que se toma para formar los nombres de parte de los ficheros (clases) generados.

La seccin est delimitada por dos palabras reservadas, ambas acompaadas por un mismo nombre (puesto entre parntesis); ese nombre es el que se da a la especificacin. Entre esas dos palabras ha de ponerse una clase directora para el proceso de anlisis; el nombre de esa clase directora ha de coincidir con el nombre dado a la especificacin. PARSER_BEGIN (NombreDeLaEspecif) public class NombreDeLaEspecif {

}
PARSER_END (NombreDeLaEspecif) Una posible versin sencilla de la clase directora es:

7 public class NombreDeLaEspecif { public static void main (String[] argum) throws ParseException { NombreDeLaEspecif anLeSi = new NombreDeLaEspecif (System.in); anLeSi.simboloPrograma();

} }
se crea el objeto anLeSi para poder aplicar los mtodos de anlisis, se asocia la entrada standard ( System.in ) a la entrada para el analizador generado, se aplica al objeto creado el mtodo de anlisis correspondiente al smbolo inicial de la gramtica. Seccin de sintaxis En esta seccin se describe la sintaxis del lenguaje para el que se desea generar el analizador, usndose para ello una notacin parecida a la BNF. En lo que sigue se expone la forma de las producciones tal y como se escriben en JavaCC, ponindolas en comparacin con las producciones de la notacin BNF-Ampliada.

Reglas sintcticas
<NombreSimb> ::= | | se codifica de la siguiente manera: void nombreSimb () : { } {

El conjunto de reglas que definen un smbolo no terminal

1 2

1-jcc | 2-jcc
}

| n-jcc - el nombre del smbolo no terminal (parte izquierda de las reglas) se escribe como el encabezamiento de un mtodo en Java, sin argumentos y sin valor de devolucin (mtodo de tipo void); se sigue, como en Java, la costumbre de escribir los nombres de los mtodos empezando con letra minscula, - el smbolo dos puntos representa la separacin entre las partes izquierda y derechas de las reglas (en vez del smbolo ::= de la notacin BNF),

- el smbolo barra vertical representa la separacin entre distintas partes derechas con la misma
parte izquierda (igual que en la notacin BNF), - despus del smbolo dos puntos se pone un bloque de cdigo Java; este cdigo se suele dedicar a la realizacin de tareas semnticas; si se pretende hacer slo un anlisis sintctico, el bloque quedar vaco (aunque siempre es obligada su presencia),

- el conjunto de las alternativas (partes derechas de las reglas) est delimitado mediante llaves,
como si constituyese un bloque de cdigo Java, - i-jcc representa la codificacin de una alternativa (parte derecha), emplendose una notacin que se describe a continuacin.

Smbolos no terminales en las partes derechas


nombreSimb()

En la notacin BNF se pone <NombreSimb>; en la notacin JavaCC se escribe esto es, se pone como si fuese una llamada en Java a un mtodo sin argumentos.

Smbolos terminales en las partes derechas

Se considera una clasificacin en las piezas sintcticas (smbolos terminales de una gramtica sintctica), distinguindose entre piezas sintcticas nominales y annimas: son nominales las piezas sintcticas a las que se asocia un nombre en otra parte de la especificacin (ms adelante se describe la manera de indicar esta asociacin); ese nombre asociado a la pieza es el que se usa al escribir las partes derechas de las producciones. En la notacin JavaCC un smbolo terminal nominal se escribe poniendo el nombre delimitado por los caracteres < y > <nombreTerminal> son annimas las piezas sintcticas que no tienen nombre asociado; no se precisa porque la pieza se representa por su propia secuencia de caracteres (lexema). En la notacin JavaCC, un smbolo terminal annimo se escribe poniendo su lexema delimitado por comillas; por ejemplo

";"

":="

"<"

"<="

"END"

Metasmbolos de opcionalidad y de repeticin

En la notacin BNF se emplean los corchetes para indicar opcionalidad; en la notacin JavaCC se utilizan tambin esos mismos smbolos

[ ]

en ambas notaciones

En la notacin BNF se emplean las llaves para indicar una repeticin de cero o ms veces; en la notacin JavaCC se utilizan los parntesis y un asterisco detrs del parntesis de cerrar

{ } ( )*

en notacin BNF-Ampliada en notacin JavaCC

Otros metasmbolos de la notacin JavaCC

La opcionalidad tambin se puede indicar empleando el metasmbolo de interrogacin

( )?
Para indicar repeticin de una o ms veces se utiliza el metasmbolo operador aditivo suma

( )+

Control del final de fichero de entrada

Para controlar el final del fichero en el texto analizado, ha de aadirse una regla sintctica especial que tenga (segn la notacin BNF) la forma <ProgramaCompleto> ::= <Programa> FF

9 donde <ProgramaCompleto> es un nuevo smbolo (no terminal) auxiliar aadido <Programa> es el smbolo inicial de la gramtica sintctica FF es una representacin del final del fichero En JavaCC el final de fichero se escribe con la notacin <EOF>, siendo EOF una palabra reservada; la regla sintctica especial aadida a la especificacin quedara codificada as: void programaCompleto() : { } { programa() <EOF> }

Representacin de la produccin vaca

Las producciones vacas (reglas sintcticas cuya parte derecha es la palabra vaca ), no se pueden representar explcitamente en una especificacin lxico-sintctica escrita en JavaCC. Para indicar la presencia de una produccin vaca han de aplicarse las posibilidades de la notacin BNF-Ampliada; segn esta notacin, cuando se indica la opcionalidad de un componente, se est especificando implcitamente la palabra vaca (en el caso de que el componente no est presente). As pues, si se quisiera escribir en JavaCC la construccin sintctica expresada por las reglas

<Componente> ::= |
deberan de considerarse con la notacin equivalente

<Componente> ::=
y si hubiera varias alternativas no vacas

[ ]

<Componente> ::= | |
habra que considerarlas como

<Componente> ::=
o tambin podra verse como

[ | ]

<Componente> ::= |

[ ]

Como ejemplo, sea la especificacin sintctica de una llamada a una funcin que puede tener cero o ms parmetros separados por comas; aunque no haya parmetros, los parntesis estn presentes. Las siguientes reglas definen esta estructura: <Llamada> ::= id ( <Parametros> ) <Parametros> ::= <Expresion> |

, <Expresion>

10 En JavaCC la definicin del smbolo <Parametros> se puede escribir como sigue (ntese que aqu los parntesis empleados para aplicar el operador * son metasmbolos) void parametros() : { } { [ expresion() }

( ","

expresion() )*

Seccin de lexicografa En esta seccin se indica la lexicografa del lenguaje para el que se va a generar el analizador; la notacin de JavaCC que representa la forma de cada una de las piezas sintcticas es una variante de las bien conocidas expresiones regulares.

El nombre de las piezas sintcticas nominales

Para asociar un nombre a una pieza sintctica y para, al mismo tiempo, definir la forma de la pieza se emplea la notacin

TOKEN : { < nombreTerminal : Expresin-Regular >

- TOKEN es una palabra reservada; detrs de ella se pone el separador dos puntos, - nombreTerminal representa el nombre que se quiere asociar a la pieza sintctica, - Expresin-Regular indica una expresin regular que especifica la forma de la pieza, - el nombre y la expresin se separan mediante el carcter dos puntos y, conjuntamente, se delimitan mediante los caracteres < y >; a su vez, toda la asociacin se delimita con llaves.

Expresiones regulares en JavaCC

Las expresiones regulares en una especificacin JavaCC se escriben de manera parecida a la forma utilizada en otras conocidas notaciones, aunque con ciertas peculiaridades. A continuacin se mencionan los aspectos ms significativos de las expresiones regulares JavaCC, y algunos ejemplos. Los operadores de las expresiones son:

| * + ?

unin repeticin cero o ms veces repeticin una o ms veces opcionalidad

Los parntesis permiten fijar el orden de aplicacin de los operadores. Una peculiaridad de las expresiones regulares JavaCC es que los operadores de repeticin y de opcionalidad requieren que el componente al que se aplican est en todo caso delimitado por parntesis. Se pueden citar las siguientes caractersticas bsicas de las expresiones regulares en JavaCC. Se tiene la posibilidad de definir conjuntos de caracteres poniendo entre corchetes la relacin de caracteres separados por comas y cada carcter delimitado por comillas. En un conjunto se puede fijar un rango de caracteres poniendo el primero y el ltimo, separados por el smbolo guin (-). A veces resulta ms cmodo indicar los caracteres que no pertenecen al conjunto, para ello se puede definir el complementario de un conjunto poniendo el smbolo ~ delante del corchete inicial. Para indicar que una secuencia de caracteres colocados consecutivamente constituye por s misma una expresin regular (o un componente de una expresin), se escribe la secuencia delimitada por comillas.

11 El smbolo \ se emplea como metacarcter para indicar que el smbolo que est inmediatamente a su derecha se considere como carcter del alfabeto, no como posible metacarcter (operador, delimitador). En una expresin regular se pueden incluir, como si fueran caracteres, las secuencias de escape, con la misma notacin y el mismo significado que en Java o en C. Ntese que en una produccin sintctica la opcionalidad se puede especificar de dos formas: con corchetes o con el smbolo de interrogacin; sin embargo, en una expresin regular la opcionalidad slo se puede especificar mediante el smbolo de interrogacin (los corchetes en las expresiones regulares son para definir conjuntos de caracteres). A continuacin se muestran ejemplos de expresiones regulares escritas segn la notacin JavaCC. "END" secuencia de las tres letras maysculas que conforman la palabra END "while" secuencia de las cinco letras minsculas que conforman la palabra while "\n" carcter representativo del fin de lnea (secuencia de escape) "\r" carcter representativo del retorno de carro (secuencia de escape) ":" carcter dos puntos ":=" secuencia de dos caracteres (smbolo de asignacin en Pascal) ">" carcter representativo del operador de relacin mayor ("i" | "I")("f" | "F") palabra if escrita con letras minsculas o maysculas (cuatro combinaciones) "if" | "IF" palabra if escrita slo con minsculas o slo con maysculas (dos combinaciones) "u" | "o" | "i" | "e" | "a" una cualquiera de las vocales minsculas (pero slo una de ellas) [ "A", "E", "I", "O", "U" ] una cualquiera de las vocales maysculas (pero slo una de ellas) [ "a" - "z", "" ] una cualquiera de las letras minsculas, incluyendo la letra [ "a" - "z", "A" - "Z", "0" - "9" ] una cualquiera de las letras o de las cifras decimales

12

([ "0" - "9" ])+


constante entera (nmero de una o ms cifras) ntese que es incorrecto poner [ 0" - "9" ]+

( "+" | "-" )?([ "0" - "9" ])+


constante entera, precedida opcionalmente por un signo ntese que la primera cifra ha de seguir inmediatamente al signo (si existe)

(["+", "-"])?([ "0" - "9" ])+


representa lo mismo que la expresin anterior [ "a" - "z" ] ([ "a" - "z", "0" - "9" ])* una letra minscula, seguida de cero o ms letras minsculas o cifras decimales ~["a" - "z", "", "A" - "Z", ""] cualquier carcter que no sea una letra ntese que la expresin representa un nico carcter ~["\n", "\r"] cualquier carcter (un nico carcter) excepto el fin de lnea y el retorno de carro

~[ ]
cualquier carcter del alfabeto (un nico carcter)

([ "0" - "9" ])+ ("." ([ "0" - "9" ])+)?


constante aritmtica, con parte decimal opcional "Los \"modernos\"" secuencia de catorce caracteres Los "modernos" ntese el uso de \ para imponer la condicin de carcter del smbolo comillas "\"\\n\"" secuencia de cuatro caracteres "\n" ntese que aqu no se representa la secuencia de escape

Piezas sintcticas internas (private)

Es posible definir piezas sintcticas auxiliares, llamadas internas porque no se comunican al analizador sintctico, sino que slo sirven para facilitar la escritura de la forma de las piezas sintcticas que s se comunican; con el uso de las piezas internas es posible evitar repeticiones de escritura y hacer ms legible la especificacin de las caractersticas lexicogrficas. Las piezas internas permiten dar nombre a subexpresiones regulares que, a su vez, pueden emplearse como componentes para escribir expresiones ms complejas que incorporan esos nombres. Para indicar que una pieza sintctica se considere como interna, se pone el smbolo # delante del nombre asociado a la pieza. A continuacin se muestra un ejemplo tpico de utilizacin de las piezas internas en una especificacin lexicogrfica en JavaCC. Se quieren definir tres piezas sintcticas que representan un nombre, una constante entera y una constante decimal; una posible especificacin es: TOKEN: { <nombre : ( [ "a" - "z", "A" - "Z", "", "" ] )+ > }

13 TOKEN: { <cteEntera : ( [ "0" - "9" ] )+ > } TOKEN: { <cteDecimal : ( [ "0" - "9" ] )+ "." ( [ "0" - "9" ] )+ > } Con la inclusin de tres piezas sintcticas internas auxiliares, esta misma especificacin podra escribirse de la siguiente manera: TOKEN: { < # letra : [ "a" - "z", "A" - "Z", "", "" ] > TOKEN: { < # cifra : [ "0" - "9" ] > TOKEN: { < # numero : ( <cifra> )+ > TOKEN: { < nombre : ( <letra> )+ >
TOKEN: { < cteEntera : <numero> > } }

} }

TOKEN: { < cteDecimal : <numero> "." <numero> >

Los nombres letra, cifra y numero son exclusivos (internos) del analizador lexicogrfico y no se pueden emplear como nombres de piezas en la especificacin de las reglas sintcticas; cuando esos nombres se usan en la definicin de otras piezas, han de escribirse delimitados por los smbolos < y >.

Distincin restringida entre minsculas y maysculas

Se tiene la opcin Ignore_Case para determinar si en el anlisis del texto de entrada se distingue entre las letras minsculas y las maysculas; el valor por defecto false indica que s se diferencian; si se asigna el valor true a esta opcin, entonces no se diferencian en ningn caso, con independencia de que en los patrones que se definan para las piezas sintcticas se pongan minsculas o maysculas. Pero, a veces, conviene que la diferenciacin entre minsculas y maysculas no se aplique de manera general a toda la entrada sino en particular a las secuencias de caracteres que se acoplen a ciertos patrones. Para este fin, existe la posibilidad de establecer que, en la expresin regular que define el patrn de una determinada pieza sintctica, no se diferencie entre minsculas y maysculas; ello se consigue poniendo [ IGNORE_CASE ] inmediatamente detrs de la palabra TOKEN con la que empieza la especificacin de la pieza sintctica en la que se pretende la no distincin entre minsculas y maysculas. Ntese que "ignore_case" tiene dos significados: - es el nombre de un parmetro de la seccin de opciones (puede ponerse en minsculas o maysculas), - es una palabra reservada que puede aplicarse (escribindola entre corchetes y en maysculas) a la definicin de una pieza sintctica. Por ejemplo, sea un lenguaje que, en general, distingue entre minsculas y maysculas, pero en la codificacin del exponente de una constante real exponencial puede ponerse la letra e en minscula o en mayscula. En la especificacin JavaCC para ese lenguaje la opcin Ignore_Case ha de tomar el valor false; en la definicin de una constante exponencial, son posibles las siguientes formas equivalentes:

14 1) sin hacer uso de IGNORE_CASE


TOKEN : { TOKEN : { < # cifras : ( [ "0" - "9" ] )+ > < # signo : ["+", "-"] > } }

TOKEN : { < constante : <cifras> ("." <cifras>)? }

["e",

"E"]

(<signo>)?

<cifras>

>

2) aplicando IGNORE_CASE
TOKEN : { TOKEN : { < # cifras : ( [ "0" - "9" ] )+ > < # signo : ["+", "-"] > } }

TOKEN [IGNORE_CASE] : { < constante : <cifras> ("." <cifras>)? "e" }

(<signo>)?

<cifras>

>

Secuencias de caracteres sin efecto en la estructura sintctica

En los lenguajes de programacin suele haber secuencias de caracteres que no forman parte de la estructura sintctica de un programa; esas secuencias son detectadas por el analizador lexicogrfico, pero no son comunicadas al sintctico, no han de considerarse como piezas, sino que han de saltarse. En JavaCC se pueden definir estas secuencias mediante la palabra reservada SKIP; la notacin de una especificacin con la palabra SKIP de una secuencia que ha de saltarse es la misma que la notacin usada para las especificaciones con la palabra TOKEN de las piezas que han de comunicarse. Los dos casos ms habituales de secuencias que no se comunican al analizador sintctico son: a) los caracteres separadores que no tienen significado en el texto analizado en los lenguajes de codificacin en formato libre hay caracteres del texto fuente que han de ignorarse; en la mayora de los casos, estos caracteres son: el espacio en blanco, el tabulador, el retorno de carro y el fin de lnea; esta caracterstica se especifica SKIP : { " " |"\t" | "\r" |"\n" }

b) los comentarios en el anlisis sintctico de un programa, los comentarios han de ignorarse; una manera de especificar esta circunstancia es poniendo en una definicin SKIP la expresin regular que denota la forma de los comentarios del lenguaje; por ejemplo, para los comentarios de una lnea de Java se pondra SKIP : { < "//" (~ ["\n", "\r"])* > }

Tareas asociadas a la estructura sintctica

JavaCC genera un analizador sintctico descendente recursivo constituido por un conjunto de mtodos: cada smbolo no terminal de la gramtica sintctica tiene asociado un mtodo de anlisis (el mtodo tiene el mismo nombre que el smbolo correspondiente). Un mtodo de anlisis, adems de la realizacin de la parte del anlisis sintctico que le corresponde, puede llevar a cabo otras tareas complementarias que sirvan para efectuar un anlisis ms complejo del texto de entrada.

15 Bloque para un smbolo Al describir la forma de las reglas sintcticas de un smbolo no terminal en una especificacin JavaCC, ya se ha visto que, detrs de la definicin del nombre del smbolo y antes de empezar con las diferentes alternativas que tiene asociadas, se incluye un bloque de cdigo Java (en el ejemplo de presentacin este bloque est vaco en todos los smbolos no terminales de la gramtica). Cada bloque situado en esa posicin puede considerarse ligado al smbolo no terminal en cuya descripcin est incluido; el cdigo Java del bloque se traslada literalmente al principio del mtodo generado correspondiente al smbolo no terminal y, por lo tanto, es lo primero que se considera (se ejecuta) cuando, en el transcurso del proceso de anlisis, se realiza una llamada al mtodo. As pues, cada smbolo no terminal siempre tiene asociado un nico bloque de cdigo Java bloque de presencia obligada, aunque pueda estar vaco-; en este bloque se pueden poner declaraciones y sentencias.

Acciones sintcticas Adems del bloque asociado a cada uno de los smbolos no terminales de la gramtica, se pueden incluir otros bloques de cdigo Java intercalados en cualquiera de las partes derechas de las reglas sintcticas. Los bloques incluidos en las partes derechas se denominan acciones sintcticas (parser actions) ya que son trozos de cdigo que pueden considerarse asociados a los puntos de la estructura sintctica donde estn colocados. Una accin sintctica se puede poner en cualquier posicin: al principio, al final o en el medio de la parte derecha de una regla, precedida o seguida de smbolos terminales o no terminales; el cdigo Java de una accin sintctica se traslada literalmente al mtodo asociado al smbolo no terminal de la parte izquierda de la regla, de manera que se ejecuta cada vez que el analizador transcurre por el punto de la estructura sintctica donde se ha incorporado la accin (esto es, las acciones sintcticas se incorporan al cdigo del analizador sintctico). No obstante su nombre, en los bloques de las acciones sintcticas suele ponerse cdigo dedicado a la realizacin de tareas semnticas.

Si en el bloque asociado a un smbolo no terminal se ponen declaraciones, los nombres ah definidos son accesibles en todas las acciones sintcticas intercaladas en las partes derechas de las reglas de ese smbolo; la justificacin es sencilla: el cdigo del bloque asociado al smbolo se incluye al principio de un mtodo y el cdigo de las acciones pertenece a ese mismo mtodo.
Ejemplo 1

Se considera de nuevo el ejemplo de presentacin; ahora se pretende completar el anlisis lxico-sintctico con la realizacin de las siguientes tareas: - cada vez que se analiza una expresin, un trmino o un factor, se graba el nombre del smbolo no terminal correspondiente, y - cuando se analiza un trmino en el que hay operadores multiplicativos, se cuenta su nmero y se graba el resultado (ntese que se cuentan los operadores de cada trmino, pero no se acumula la cuenta de todos los operadores de la expresin). Para la realizacin de estas tareas se aaden bloques de cdigo Java. En la especificacin que se describe a continuacin puede apreciarse: - para los smbolos no terminales expresion, termino y factor se ha incluido en los bloques correspondientes la operacin de grabacin de su nombre, que se ejecutar cada vez que se efecte una llamada al mtodo, - en el bloque asociado a termino se tiene la declaracin del contador de operadores (que ser una variable local del mtodo), - en la parte derecha de la regla que define la estructura de un trmino se han incluido dos acciones sintcticas: una para contar la cantidad de operadores (situada detrs de la pieza sintctica del operador) y otra para grabar la cantidad de operadores del trmino (situada al final de la parte derecha), - se ha definido el mtodo grabar para facilitar la escritura del cdigo Java. options { Ignore_Case = true; }

16 PARSER_BEGIN (ExprMin) public class ExprMin { public static void main (String[] argum) throws ParseException { ExprMin anLexSint = new ExprMin (System.in); anLexSint.unaExpresion(); System.out.println("Analisis terminado:"); System.out.println ("no se han hallado errores lxico-sintcticos"); } private static void grabar(String nombre) { System.out.println (" -> " + nombre + "\n"); }

PARSER_END (ExprMin) void unaExpresion() : { } { } void expresion() : { grabar("Expresion"); { } void termino() : { int nAst = 0; grabar("Termino"); } { factor() ( "*" { nAst++; } factor() )* { if (nAst > 0) System.out.println ("\n Asteriscos: " + nAst + "\n"); } termino() ( "+" } expresion() <EOF>

termino() )*

void factor() : { grabar("Factor"); {

<constante> | <variable> | "(" expresion() ")" } TOKEN: TOKEN: SKIP: { { { < < variable : ["a"-"z"] constante : > } } > }

( ["0"-"9"] ) +

" " | "\t" | "\n" | "\r"

17 Cuando se incluyen acciones sintcticas, la legibilidad del aspecto sintctico de la especificacin queda oscurecida; para paliar ese inconveniente, puede aadirse un comentario que muestre la regla sintctica escueta, sin las acciones; por ejemplo, podra escribirse: void termino() : { int nAst = 0; grabar("Termino"); } { /* factor() ( "*" { nAst++; } factor() )* { if (nAst > 0) System.out.println ("\n Asteriscos: " + nAst + "\n"); } factor() ( "*" factor() )* */

} Valor comunicado por un mtodo En todo lo visto hasta ahora, los mtodos de anlisis sintctico generados por JavaCC no devuelven valor alguno al terminar su tarea de anlisis, ya que en la especificacin siempre se ha declarado el nombre de un mtodo precedido de void (son mtodos de tipo void). Pero, en general, un mtodo de anlisis sintctico puede ser de cualquier tipo (primitivo o no); por ello, un mtodo puede devolver un valor de cualquier tipo (de cualquier clase) al mtodo desde el que se efectu la llamada.

Esta posibilidad de devolucin de valores tiene una utilidad fundamental en la implementacin de analizadores de lenguajes: permite la comunicacin de datos (atributos de las entidades analizadas) entre los diferentes mtodos que colaboran en el anlisis del programa completo. En una especificacin JavaCC, la llamada a un mtodo ocurre cuando en la parte derecha de una regla est el nombre del smbolo no terminal asociado al mtodo. Cuando un mtodo devuelve un valor (tiene un tipo distinto de void), es habitual la recepcin del valor devuelto en una variable; en la propia especificacin se puede indicar el nombre de la variable a la que se asignar el valor devuelto tras la llamada, para ello se emplea una notacin idntica a la forma que tienen las sentencias de asignacin en el lenguaje Java dato = nombreSimb() donde dato es el nombre de la variable a la que se asigna el valor devuelto por el mtodo llamado; esa variable ha de estar oportunamente declarada del mismo tipo que dicho mtodo.
Ejemplo 2

En el mismo ejemplo de presentacin se desea, adems del anlisis lxico-sintctico de la entrada, contar la cantidad de operadores que hay en la expresin. A continuacin se expone una especificacin JavaCC que es una posible solucin; slo se muestran las reglas sintcticas (el resto de la especificacin es igual que en el ejemplo de presentacin). En la especificacin que se propone puede apreciarse: - los smbolos no terminales expresion, termino y factor se han declarado de tipo int: los mtodos correspondientes devuelven un valor de tipo entero, - el valor devuelto por los mtodos sirve para propagar a travs de la estructura sintctica la cuenta de los operadores que hay en el texto analizado, - por tratarse de mtodos que devuelven un valor, es preciso incluir las correspondientes sentencias de devolucin return.

18 void unaExpresion() : { int nOper; } { nOper = expresion() <EOF> { System.out.println ("Cantidad de operadores = " + nOper); } } int expresion() : { int n, i; } { n = termino() ( "+" { n++; } i = termino() { n = n + i; } )* { return n; } } int termino() : { int n, i; } { n = factor() ( "*" { n++; } i = factor() { n = n + i; } )* { return n; } } int factor() : { int n; } { <constante> | <variable> | "(" n = expresion() ")" }

{ return 0; } { return 0; } { return n; }

La especificacin de un factor tambin podra escribirse de esta otra manera: int factor() : { int n = 0; } { ( <constante> | <variable> | "(" n = expresion() ")" { return n; }

) }

A continuacin se expone un nuevo ejemplo para ilustrar la utilidad que proporciona el valor devuelto por un mtodo de anlisis sintctico para la propagacin (ascendente) de atributos (propiedades de las entidades analizadas).
Ejemplo 3

Se pretende ampliar el analizador del ejemplo de presentacin para que se emita un mensaje indicativo cuando la expresin analizada sea una expresin en la que slo aparecen constantes; en la solucin que

19 se muestra slo se incluyen las reglas sintcticas (en el resto de la especificacin no hay modificaciones). void unaExpresion() : { boolean esCte; } { /* expresion() <EOF> */ esCte = expresion() <EOF> { if (esCte) System.out.println("\nExpresion constante\n"); }

boolean expresion() : { boolean esCte, esTambienCte; } { /* termino() ( "+" termino() )* */ esCte = termino() ( "+" esTambienCte = termino() { esCte = esCte && esTambienCte; } { return esCte; } } boolean termino() : { boolean esCte, esTambienCte; } { /* factor() ( "*" factor() )* */ esCte = factor() ( "*" esTambienCte = factor() { esCte = esCte && esTambienCte; } } { return esCte; }

)*

)*

boolean factor() : { boolean esCte; } { <constante> | <variable> | "(" esCte = expresion() ")" } { return true; } { return false; } { return esCte; }

Reconsideracin sobre las producciones vacas

Anteriormente se ha comentado que para representar una produccin vaca en una especificacin JavaCC hay que hacer uso de los metasmbolos de opcionalidad; para ilustrar esta idea se ha incluido como ejemplo la especificacin de una lista de parmetros compuesta por cero o ms expresiones, separadas entre s por el carcter coma: void parametros() : { } { [ expresion() }

( ","

expresion() )*

20 Pero si se aprovecha la posibilidad de incorporar acciones sintcticas, se tiene una manera de especificar directamente producciones vacas; para conseguirlo, basta considerar una produccin vaca (no tiene nada en su parte derecha) acompaada de una accin sintctica cuyo bloque de cdigo est vaco. Segn esta posibilidad, la especificacin anterior podra tambin escribirse as: void parametros() : { } { expresion() }

( ","

expresion() )*

En general, la accin sintctica asociada a una produccin vaca no tiene por qu ser un bloque vaco de cdigo; por ejemplo, si se quiere que el mtodo que analiza los parmetros de una llamada devuelva la cantidad de parmetros encontrados, se podra escribir la siguiente especificacin int parametros() : { int cantidad; } { ( expresion() { cantidad = 1; } expresion() { cantidad++; } )*

( "," |
) }

{ cantidad = 0; }

{ return cantidad; }

y si no quisiera ponerse explcitamente la alternativa de la produccin vaca, podra escribirse int parametros() : { int cantidad = 0; } {

expresion() { cantidad++; } ( "," expresion() { cantidad++; } )*

{ return cantidad; }
}

Comunicacin entre los analizadores lxico y sintctico

Cada vez que el analizador sintctico realiza una llamada al analizador lexicogrfico, recibe como resultado de la llamada una representacin de la pieza sintctica encontrada en el texto analizado. En los analizadores generados por JavaCC, la comunicacin de la pieza se efecta mediante un valor de clase Token. Esta clase est definida en el fichero Token.java que se obtiene en todo caso, siempre con el mismo contenido, cualquiera que sea la especificacin proporcionada como entrada al generador. Entre los campos de la clase Token se encuentran los siguientes: public int kind nmero entero que sirve de representacin interna de la pieza sintctica; estos nmeros asociados a las piezas son asignados automticamente por JavaCC; los valores considerados pueden consultarse en el fichero generado de nombre Constants.java

21 public String image cadena que contiene de la secuencia de caracteres (lexema) que constituyen la pieza sintctica comunicada public int beginLine, beginColumn, endLine, endColumn posiciones ocupadas (nmeros de lnea y columna en el fichero de entrada) por el comienzo y el final del lexema de la pieza sintctica comunicada La clase Token tiene otros campos y mtodos; en el fichero Token.java se tienen comentarios descriptivos sobre todos los campos y mtodos que forman parte de la clase; todos los campos de la clase se declaran como public. Ya se ha comentado anteriormente que, cuando un mtodo de anlisis sintctico (asociado a un smbolo no terminal) tiene tipo, el valor devuelto tras una llamada se puede almacenar en una variable, por si se desea utilizar en alguna accin sintctica; para ello, se escribe valor = nombreSimbolo() (siendo valor el nombre de la variable y nombreSimbolo el nombre del smbolo no terminal). De manera anloga, el objeto de la clase Token representativo de la pieza sintctica encontrada en la entrada por el analizador lexicogrfico puede asignarse a una variable del mismo tipo para tener accesibles las caractersticas de la pieza, por si se precisaran en alguna accin sintctica; para ello, en la parte de la especificacin sintctica donde aparezca la pieza sintctica se escribe dato = < nombrePieza > donde nombrePieza es el nombre de la pieza sintctica y dato es el nombre de una variable de tipo Token en la que se anota el valor indicativo de las propiedades de la pieza sintctica comunicada. Tambin existe otra manera de acceder a las caractersticas de la pieza sintctica que el analizador lexicogrfico comunica al analizador sintctico; en la clase del analizador sintctico se tiene declarado el campo

static

public Token token

cada vez que el analizador sintctico recibe una pieza sintctica comunicada por el analizador lexicogrfico, se deja anotada en el campo token, y ese contenido no se altera hasta que sea proporcionada la siguiente pieza de la entrada.
Ejemplo 4

Se considera una ampliacin del ejemplo de presentacin; adems del anlisis de la entrada, se pretende obtener una relacin numerada de las variables y las constantes, indicando el nmero de lnea y el nmero de columna donde estn situadas; por ejemplo, si en la entrada se tiene la expresin (grabada en dos lneas). x * (y + 11 ) + 22 en la salida se tendr 1.2.3.4.x y 11 22 linea: 1 linea: 1 linea: 1 linea: 2 columna: 1 columna: 6 columna: 10 columna: 5

En la especificacin que se propone como solucin puede apreciarse:

22
- en la clase del analizador sintctico se han incluido mtodos auxiliares en los que se usan los nom-

bres image, beginLine, beginColumn, que son campos de un objeto de clase Token, - en la especificacin sintctica de factor se emplea la variable pieza, de tipo Token, para recoger las caractersticas de las piezas sintcticas detectadas, - la variable numero hace de contador para numerar las constantes y variables. options { Ignore_Case = true; }

PARSER_BEGIN (ExprMin) public class ExprMin { static int numero = 0; private static void grabarLexema (int n, String lexema) { System.out.print(n + ".- " + lexema + " "); } private static void grabarPosicion(int nL, int nC) { System.out.print("linea: " + nL + " "); System.out.println("columna: " + nC + "\n"); } private static void grabarDatosPieza(int n, Token pieza) { grabarLexema(n, pieza.image); grabarPosicion(pieza.beginLine, pieza.beginColumn); } public static void main (String[] argum) throws ParseException { ExprMin anLexSint = new ExprMin (System.in); anLexSint.unaExpresion(); System.out.println("Analisis terminado:"); System.out.println ("no se han hallado errores lxico-sintcticos"); } } PARSER_END (ExprMin) void unaExpresion() : { } { expresion() <EOF> } void expresion() : { } { termino() ( "+" } void termino() : { } { factor() ( "*" }

termino() )*

factor() )*

23 void factor() : { Token pieza; } { pieza = <constante> { numero++; grabarDatosPieza(numero, pieza); } | pieza = <variable> { numero++; grabarDatosPieza(numero, pieza); } | "(" expresion() ")" } TOKEN: { TOKEN: { SKIP: < variable : ["a"-"z"] < constante : > } > }

( ["0"-"9"] ) +

{ " " | "\n" | "\t" | "\r" }

Si se agrupan las dos acciones sintcticas comunes, la especificacin de factor tambin puede escribirse de esta forma: void factor() : { Token pieza; } { ( pieza = <constante> | pieza = <variable> ) | } Si se accede al campo token de la clase del analizador sintctico, se puede resolver este ejemplo prescindiendo de la variable pieza: void factor() : { } { ( | ) | "(" } <constante> <variable> { numero++; grabarDatosPieza(numero, token); } expresion() ")" "(" { numero++; grabarDatosPieza(numero, pieza); } expresion() ")"

Tareas asociadas a las piezas sintcticas

Ya se ha visto que es posible asociar bloques de cdigo Java a puntos de la estructura sintctica para que se realicen ciertas operaciones cuando el analizador sintctico pase por esa zona de la estructura. Tambin es posible asociar cdigo Java a las piezas sintcticas para que el analizador lexicogrfico efecte determinadas tareas cuando detecte en la entrada analizada piezas que tengan asociado cdigo.

24 Accin ligada a una pieza sintctica Si se asocia un bloque de cdigo a una pieza sintctica; se consigue que, cada vez que se detecta en la entrada analizada la presencia de esa pieza, se ejecute en ese preciso momento el cdigo del bloque asociado a la pieza. Estas acciones se denominan acciones lexicogrficas (lexical actions); el cdigo del bloque se traslada literalmente al cdigo del analizador lexicogrfico generado para que se ejecute oportunamente cuando se detecte la pieza ligada al bloque de cdigo. La accin lexicogrfica se ejecuta inmediatamente antes que la pieza sea comunicada al analizador sintctico.

Se puede asociar una accin lexicogrfica a una pieza sintctica declarada con TOKEN y tambin a una secuencia de caracteres declarada con SKIP; adems de stas, hay otras dos formas de declaraciones lexicogrficas (no mencionadas en esta introduccin), que tambin pueden llevar una accin asociada. En una especificacin JavaCC, la forma de una pieza sintctica (o bien de una secuencia de caracteres) se define poniendo su descripcin delimitada entre llaves detrs de la indicacin TOKEN: (o bien SKIP:); si se quiere asociar una accin lexicogrfica a la pieza (o a la secuencia), hay que colocar el bloque de cdigo de la accin a continuacin de la descripcin de la forma de la pieza (o de la secuencia); el bloque (delimitado por llaves) queda situado entre la descripcin y la llave que cierra la especificacin. La pieza sintctica especial que representa el final del fichero (EOF) tambin puede llevar asociada una accin lexicogrfica; para ello, hay que poner <*> TOKEN: { < EOF > { /* cdigo de la accin asociada*/ } }

para justificar la presencia de los caracteres <*> delante de la palabra TOKEN hay que basarse en el concepto de estado lexicogrfico (cuya explicacin queda fuera del alcance de esta introduccin).
Ejemplo 5

Se considera nuevamente el ejemplo de presentacin; como resultado del anlisis de una expresin se pretende obtener una relacin de los parntesis contenidos en la expresin, en el mismo orden en que se encuentran; adems, por cada salto de lnea del texto de entrada, se grabar en la salida el carcter # y se pasar a una nueva lnea; tambin se anotar la frase Final del trabajo cuando se alcance el final del fichero de entrada. As, por ejemplo, si la entrada analizada es (a + b) * (( x )) * (y + 7) los resultados obtenidos sern () # (())() # ** Final del trabajo ** En la especificacin JavaCC que se propone como solucin, slo se muestran las reglas sintcticas y lexicogrficas (en el resto no hay cambios); en esta solucin puede apreciarse: - las piezas sintcticas correspondientes a los parntesis se han definido de manera nominal, para poder asociarles una accin lexicogrfica, - los caracteres incluidos en la pieza de tipo SKIP se han descompuesto en dos grupos ya que la accin lexicogrfica incorporada slo afecta a uno de ellos. void unaExpresion() : { } { expresion() <EOF> }

25 void expresion() : { } { termino() ( "+" } void termino() : { } { factor() ( "*" }

termino() )*

factor() )*

void factor() : { } { <constante> | <variable> | <pAbrir> expresion() } TOKEN: { } TOKEN: { < } TOKEN: { } TOKEN: { } <*> TOKEN: { < EOF > } SKIP: { } SKIP:

<pCerrar>

<

variable : ["a"-"z"]

>

constante :

( ["0"-"9"] ) +

>

< pAbrir : "(" >

{ System.out.print("("); }

< pCerrar : ")" >

{ System.out.print(")"); }

System.out.println ("** Final del trabajo **");

"\n"

{ System.out.print(" #\n"); }

{ " " | "\t" | "\r" }

Bloque de declaraciones lexicogrficas Cada definicin de una pieza sintctica puede llevar asociada una accin lexicogrfica, que se ejecuta inmediatamente despus de detectar en la entrada un lexema que se ajusta a la definicin de la pieza; cada accin lexicogrfica es un bloque de cdigo Java que puede contener sus propias declaraciones. Pero a veces, interesa tener declaraciones que sean compartidas por todas las acciones lexicogrficas, con independencia de la pieza a la que est asociada cada una de ellas.

26 Para incluir declaraciones que puedan usarse desde el cdigo asociado a cualquiera de las piezas sintcticas, se dispone de una construccin con el siguiente formato TOKEN_MGR_DECLS : {

que consta de una palabra reservada, seguida del smbolo dos puntos, seguido de un bloque de cdigo Java (delimitado por llaves); esta declaracin se puede colocar en cualquier parte de una especificacin JavaCC; si se incluye, slo puede ponerse una vez. Todas las declaraciones de variables o de mtodos puestas en el bloque de declaraciones lexicogrficas compartidas son accesibles desde el cdigo de cualquier accin lexicogrfica; la justificacin es sencilla: el bloque de declaraciones se traslada al principio del cdigo del analizador lexicogrfico generado y las acciones lexicogrficas tambin se trasladan a ese analizador. Declaraciones lexicogrficas predefinidas De lo expuesto hasta ahora, se aprecia que en una accin lexicogrfica asociada a una pieza sintctica resultan accesibles: - las declaraciones de su propio bloque (si tiene declaraciones), - los componentes del bloque de declaraciones compartidas (si est incluido en la especificacin). Adems de esto, se tienen otras declaraciones pertenecientes a la clase " TokenManager", que constituye el analizador lexicogrfico, que tambin son utilizables desde cualquiera de las acciones lexicogrficas; estas declaraciones pueden considerarse como predefinidas ya que son incorporadas automticamente por el generador. Entre estas declaraciones accesibles se encuentran las siguientes:

[ static ] StringBuffer image


contiene la cadena de caracteres representativa de la pieza sintctica actual que se acaba de reconocer en le entrada; se trata de un campo de la clase y se declara como static cuando el analizador se ha generado con la opcin (por defecto) Static = true; si se generase con la opcin Static = false, no quedara declarada como static Token matchedToken variable de clase Token que representa la pieza sintctica actual, la que se acaba de encontrar en la entrada; se trata de una variable local del mtodo al que se ha trasladado el cdigo de la accin (y, por ello, accesible desde ese cdigo) El campo image puede usarse tanto si la accin lexicogrfica est asociada a una pieza declarada con TOKEN como si lo est a una secuencia declarada con SKIP. Sin embargo, la variable matchedToken no puede usarse en una accin asociada a un patrn especificado como SKIP; el motivo es que lo declarado como SKIP, no constituye propiamente una pieza sintctica ya que no se va a comunicar al analizador sintctico.
Ejemplo 6

Se propone aqu una nueva solucin para el ejemplo n 4 expuesto anteriormente. Esta nueva solucin tal y como se expone aqu admite algunas mejoras, ya que su pretensin slo es servir de ilustracin para algunos aspectos que se acaban de citar (ms adelante se propone otra solucin alternativa). En la especificacin de la solucin es posible apreciar esto: - las acciones lexicogrficas pueden llevar sus propias declaraciones; las variables nLineaVar, nColumnaVar, nLineaCte y nColumnaCte se declaran dentro de las acciones, - la variable matchedToken a la que se hace referencia en el cdigo de la accin lexicogrfica aso-

27 ciada a una variable contiene la informacin sobre la pieza sintctica que se acaba de detectar: una variable de la expresin analizada; anlogamente con la referencia relativa a una constante, - el nombre image que aparece en las acciones lexicogrficas se refiere al campo de la clase ExprMinTokenManager (analizador lexicogrfico); ntese que, en efecto, se comunica como un parmetro de clase StringBuffer, - la variable numero se emplea para numerar las constantes y variables encontradas, - los nombres beginLine y beginColumn son campos (pblicos) de la clase Token, por ello, se pueden aplicar a un objeto de esa clase.

options {

Ignore_Case = true;

PARSER_BEGIN (ExprMin) public class ExprMin { public static void main (String[] argum) throws ParseException { ExprMin anLexSint = new ExprMin (System.in); anLexSint.unaExpresion(); System.out.println("Analisis terminado:"); System.out.println ("no se han hallado errores lxico-sintcticos"); } } PARSER_END (ExprMin) void unaExpresion() : { } { expresion() <EOF> } void expresion() : { } { termino() ( "+" } void termino() : { } { factor() ( "*" }

termino() )*

factor() )*

void factor() : { } { <constante> | <variable> | "(" expresion() }

")"

28 TOKEN_MGR_DECLS : { static int numero = 0; static void grabarLexema (int n, StringBuffer lexema) { System.out.print(n + ".- " + lexema + " "); } static void grabarPosicion(int nL, int nC) { System.out.print("linea: " + nL + " "); System.out.println("columna: " + nC + "\n"); } } TOKEN: { < variable : ["a"-"z"] > { int nLineaVar, nColumnaVar; numero++; nLineaVar = matchedToken.beginLine; nColumnaVar = matchedToken.beginColumn; grabarLexema(numero, image); grabarPosicion(nLineaVar, nColumnaVar); }

} TOKEN: { < constante : ( ["0"-"9"] ) + > { int nLineaCte, nColumnaCte; numero++; nLineaCte = matchedToken.beginLine; nColumnaCte = matchedToken.beginColumn; grabarLexema(numero, image); grabarPosicion(nLineaCte, nColumnaCte); }

} SKIP: { " " | "\n" | "\t" | "\r" }

La especificacin precedente se puede escribir de otra manera ms escueta, como se indica a continuacin (slo se muestran las modificaciones); sobre esta nueva especificacin puede mencionarse: - se han eliminado, por innecesarias, las variables declaradas dentro de las acciones lexicogrficas, - el nombre image que aparece en el bloque de las declaraciones compartidas se refiere al campo de la clase Token; ntese que, en efecto, se aplica a un objeto de esa clase y se comunica como un parmetro de tipo String. TOKEN_MGR_DECLS : { static int numero = 0; static void grabarLexema (int n, String lexema) { System.out.print(n + ".- " + lexema + " "); }

29 static void grabarPosicion(int nL, int nC) { System.out.print("linea: " + nL + " "); System.out.println("columna: " + nC + "\n"); } static void grabarDatosPieza(int n, Token pieza) { grabarLexema(n, pieza.image); grabarPosicion(pieza.beginLine, pieza.beginColumn); } } TOKEN: { } TOKEN: { } Accin lexicogrfica comn Si hubiera que realizar la misma tarea tras la deteccin de todas y cada una de las piezas sintcticas, una posibilidad incmoda sera repetir el mismo cdigo en todas las acciones lexicogrficas; para evitar tanta reiteracin, se tiene la opcin Common_Token_Action; su valor por defecto es false, pero si se le asigna el valor true, es posible definir una accin lexicogrfica comn que se escribe una nica vez y se aplica a todas las piezas sintcticas.

< variable : ["a"-"z"] {

> }

numero++; grabarDatosPieza(numero, matchedToken); ( ["0"-"9"] ) + >

< constante : {

numero++; grabarDatosPieza(numero, matchedToken);

Cuando la opcin Common_Token_Action se establece con el valor true, es obligado incluir el bloque de declaraciones lexicogrficas compartidas (TOKEN_MGR_DECLS). Adems, en ese bloque se precisa la definicin de un mtodo cuyo encabezamiento sea

static

void CommonTokenAction(Token t)

el cdigo de este mtodo constituye la accin lexicogrfica comn que se ejecutar despus de la deteccin de cualquier pieza sintctica; el mtodo tiene un parmetro de tipo Token mediante el que se puede comunicar la informacin correspondiente a la pieza que se acaba de encontrar en la entrada analizada. Si una pieza sintctica tiene asociada una accin lexicogrfica propia, la accin lexicogrfica comn (si est definida) se ejecutar inmediatamente despus de realizar la accin propia. La accin lexicogrfica comn se aplica a todas las piezas sintcticas definidas como TOKEN, bien sea nominalmente (en las reglas lexicogrficas), o bien annimamente (incorporadas dentro de las reglas sintcticas); sin embargo, no se aplica cuando la secuencia acoplada se corresponde con una expresin regular especificada como SKIP (ntese que, por el contrario, una especificacin SKIP s puede tener asociada una accin lexicogrfica propia). El cdigo del mtodo CommonTokenAction se traslada, como un componente ms del bloque de declaraciones compartidas (TOKEN_MGR_DECLS), al principio de la clase del analizador lexicogrfico.
Ejemplo 7

Se pretende ahora ampliar el ejemplo de presentacin con la obtencin de una relacin numerada de los lexemas de las piezas sintcticas encontradas en la entrada analizada; en el caso de las variables y de las constantes, antes del lexema se mostrar un indicativo (Var / Const); por ejemplo, si la expresin de la entrada es

30 ( a se grabar la salida ( #1 Var a #2 + #3 Const 987 ) #5 * #6 Var b #7 #8 + 987 ) * b

#4

En la solucin que se propone a continuacin, se muestran la definicin de las opciones y las reglas sintcticas y lexicogrficas (en el resto no hay modificaciones); puede resaltarse lo siguiente: - las llamadas al mtodo CommonTokenAction se incorporan automticamente, no hay que incluirlas en la especificacin; como parmetro de llamada a este mtodo siempre se emplea el objeto que representa la pieza que se acaba de detectar, - la accin lexicogrfica comn realiza la actualizacin del contador de piezas y la grabacin del lexema de la pieza detectada, con su nmero de orden, - la accin lexicogrfica comn tambin se aplica a las piezas sintcticas annimas (parntesis y operadores) y a la pieza especial que representa el final del fichero (EOF), - las piezas sintcticas <constante> y <variable> tienen asociada una accin lexicogrfica propia, que se ejecuta antes de la accin lexicogrfica comn, - los caracteres que se saltan (SKIP) no estn afectados por la accin lexicogrfica comn. options { Ignore_Case = true; Common_Token_Action = true; } void unaExpresion() : { } { expresion() <EOF> } void expresion() : { } { termino() ( "+" } void termino() : { } { factor() ( "*" }

termino() )*

factor() )*

void factor() : { } { <constante> | <variable> | "(" expresion() }

")"

31 TOKEN_MGR_DECLS : { static int numero = 0; static void grabarIndicativo(String indicativo) { System.out.print(indicativo + " "); } static void grabarLexema (int num, String lexema) { System.out.print(lexema + " #" + num + "\n"); } static void CommonTokenAction(Token pieza) { numero++; grabarLexema(numero, pieza.image); } }

TOKEN: { < variable : ["a"-"z"] { } TOKEN: { < constante : { } SKIP: { " " | "\n" | "\t" | "\r" } ( ["0"-"9"] ) + > } grabarIndicativo("Const"); > } grabarIndicativo("Var");

A modo de ejemplo de aplicacin de la accin lexicogrfica comn, en lo que sigue se ofrece una solucin para la grabacin (en el fichero predefinido de salida) del texto analizado, con las lneas numeradas.
Ejemplo 8

Listado del texto analizado

Se desea completar el ejemplo de presentacin con la grabacin de un listado, con las lneas numeradas, del texto analizado (ntese que una expresin puede escribirse ocupando varias lneas). No se muestra la especificacin sintctica debido a que no hay ninguna modificacin en ella. Los aspectos reseables de la solucin que se propone son: - se usa la variable numLin como contador de lneas, - en la variable linea se van yuxtaponiendo los lexemas de las piezas sintcticas de una lnea, esta yuxtaposicin se realiza mediante la accin lexicogrfica comn, - las secuencias reconocidas con los patrones especificados como SKIP no implican la ejecucin de la accin lexicogrfica comn; por ello, su lexema (almacenado en la variable image) ha de yuxtaponerse explcitamente, - hay que separar el patrn correspondiente al fin de lnea, para detectar el momento en que ha de grabarse la lnea completa cuyo final se ha alcanzado.

32 options { Ignore_Case = true; Common_Token_Action = true; }

PARSER_BEGIN (ExprMin) public class ExprMin { public static void main (String[] argum) throws ParseException { ExprMin anLexSint = new ExprMin (System.in); anLexSint.unaExpresion(); System.out.println("\n\nAnalisis terminado:"); System.out.println ("no se han hallado errores lxico-sintcticos"); } } PARSER_END (ExprMin)

TOKEN_MGR_DECLS: { static int numLin = 0; static String linea = ""; static void CommonTokenAction(Token pieza) { linea = linea + pieza.image; }

} TOKEN:

{ TOKEN: { SKIP: { } SKIP: {

< <

variable : ["a"-"z"] constante :

>

} > }

( ["0"-"9"] ) +

< " " | "\t" | "\r" > { linea = linea + image; }

"\n" {

numLin++; System.out.println(numLin + ": linea = "";

" + linea);

} }

33

A modo de recapitulacin
Accin lexicogrfica. Accin sintctica
accin lexicogrfica: bloque de cdigo Java asociado a una pieza sintctica nominal (TOKEN) o a una

secuencia de caracteres (SKIP); se ejecuta cuando se detecta en la entrada la pieza o la secuencia correspondiente; no se puede aplicar a piezas sintcticas annimas (autodefinidas con comillas), accin sintctica: bloque de cdigo Java asociado a un punto de la estructura sintctica (el punto indicado por el sitio de la produccin donde se inserta el bloque); se ejecuta cuando el anlisis de la entrada pasa por ese punto de la estructura.

Valor de una pieza sintctica comunicada


El analizador lexicogrfico comunica al analizador sintctico una pieza a travs de un valor de la clase Token (fichero Token.java); un objeto de la clase Token representa las caractersticas de la pieza comunicada. En la clase Token, entre otros, se tienen los campos String image lexema de la pieza int beginLine, beginColumn, endLine, endColumn posicin del lexema en el fichero analizado (indicada por los nmeros de fila y de columna de su comienzo y de su terminacin)

Valor asociado a un smbolo


En las producciones que especifican la sintaxis se pueden incluir asignaciones que sirven para dejar anotado el valor asociado a un smbolo:
smbolo no terminal

valor = nombreSimbolo() el mtodo de anlisis asociado a nombreSimbolo se ha declarado de un cierto tipo (distinto del tipo void); valor ha de ser una variable del mismo tipo que el mtodo dato = < nombrePieza > el valor asignado es el valor de la clase Token representativo de la pieza sintctica (nominal); dato ha de ser una variable de tipo Token

smbolo terminal

Miradas sobre la pieza sintctica comunicada


La pieza sintctica comunicada puede mirarse desde cada uno de los dos analizadores: analizador sintctico la pieza comunicada (desde el analizador lexicogrfico) se tiene anotada en un campo de la clase del analizador sintctico declarado como (static) public Token token este valor es accesible desde el cdigo de todos los mtodos de anlisis sintctico; el contenido del campo vara cuando se recibe una nueva pieza sintctica
analizador lexicogrfico

la pieza dispuesta para ser comunicada (al analizador sintctico) se tiene anotada en una variable local declarada como Token matchedToken este valor es accesible para el cdigo de todas las acciones lexicogrficas

El lexema de la pieza comunicada


El lexema de la pieza sintctica comunicada se encuentra disponible en dos sitios distintos que compar-

34 ten el mismo nombre pero que son de distinto tipo y se usan de distinta manera:
String image

es un campo de la clase TOKEN; puede consultarse en relacin con un objeto de esa clase que est disponible
StringBuffer image

es un campo de la clase del analizador lexicogrfico ( TokenManager.java); es accesible desde el cdigo de la clase y, por lo tanto, desde todas las acciones lexicogrficas (bloques de cdigo que estn incorporados al analizador lexicogrfico); se trata de un valor disponible tanto para las piezas sintcticas nominales (TOKEN) como para las secuencias que se saltan (SKIP)

Resumen relativo a la lexicografa


TOKEN pieza sintctica nominal pieza sintctica annima secuencia de caracteres que no forman pieza

" "
SKIP

accin lexicogrfica comn Commom TokenAction

accin lexicogrfica propia (accin ligada a una pieza)

bloque de declaraciones lexicogrficas TOKEN_ MGR_DECLS

declaraciones lexicogrficas predefinidas (disponibilidad de uso)

TOKEN " " SKIP

si si no

si no si

si

si

si

si

Las indicaciones "si" de las columnas 3 y 4 son consecuencia de las indicaciones de la columna 2. La accin lexicogrfica comn se ejecuta despus de la accin lexicogrfica propia.

Precisiones sobre el analizador sintctico generado

Puede decirse que, en su funcionamiento ms sencillo, cuando no se emplean las posibilidades de examen por adelantado (lookahead), JavaCC comprueba que las reglas sintcticas cumplen la condicin LL(1) y generan un analizador descendente-predictivo-recursivo. Pero esta apreciacin hay que matizarla, ya que el analizador sintctico generado no se ajusta en sentido estricto al modelo de analizador LL(1). En el modelo de anlisis LL(1), el orden del examen de las distintas alternativas de expansin de un smbolo no terminal es indiferente ya que se conocen los smbolos directores de cada una de ellas (que resultan ser conjuntos disjuntos); en la implementacin del subprograma de anlisis asociado al smbolo no terminal <NombreSimb> cuyas producciones son

35 <NombreSimb> ::=

1 2

se podra preguntar en cualquier orden si la pieza actual pertenece al conjunto de smbolos directores de las distintas reglas. Sin embargo, en el analizador sintctico generado por JavaCC, la implementacin del mtodo asociado al smbolo no terminal <NombreSimb> no sigue esa pauta. Si las anteriores reglas sintcticas se transcriben en JavaCC de la forma void nombreSimb () : { } {

1-jcc | 2-jcc
}

| n-jcc

el mtodo de anlisis generado procede de la siguiente manera: se van considerando las alternativas, sucesivamente, en el mismo orden en el que estn especificadas: primero la de 1 (1-jcc), despus la de 2 (2-jcc), etc,
para cada alternativa seleccionada, se comprueba si la pieza por adelantado coincide con alguno de los smbolos iniciales de la parte derecha, y si hay coincidencia se prosigue el anlisis con ella; si no hay coincidencia se pasa a la alternativa siguiente, siguiendo con este orden, si se llega al final de las alternativas sin haberse encontrado coincidencia alguna, se produce un error sintctico.

En el analizador generado por JavaCC hay una peculiaridad que no se tiene en los analizadores LL(1) estrictamente considerados: la palabra vaca s se considera como posible smbolo inicial de la parte derecha una regla (esto ocurre siempre que la parte derecha es anulable). De esta peculiaridad se deriva una consecuencia que ha de tenerse en cuenta al escribir la especificacin: dado que siempre es posible considerar la palabra vaca como la siguiente pieza por adelantado, si se llega a examinar la alternativa correspondiente a una produccin anulable, el analizador generado siempre seleccionar esa alternativa con independencia de la siguiente pieza que est presente en la entrada que queda por analizar. As pues, si alguna de las producciones que definen un smbolo no terminal es anulable, es importante el orden en que se escriben las alternativas en una especificacin sintctica JavaCC: el funcionamiento del analizador generado s depende de la colocacin elegida para las reglas. Para ilustrar estas peculiaridades del analizador sintctico generado por JavaCC, a continuacin se expone un ejemplo.
Ejemplo 9

Se quiere obtener un analizador lxico-sintctico de textos que se ajustan al siguiente formato: una secuencia de uno o ms nombres, seguida del smbolo igual, seguido de un nmero entero; si la secuencia tiene ms de un nombre, han de estar separados bien por una coma en todos los casos o bien por el smbolo dos puntos en todos los casos; esto es, son correctas las secuencias uno, dos, tres = 123 exclusivo = 0

36 Abajo : Arriba = 57 y son incorrectas las secuencias uno, dos : tres = 123 exclusivo inclusive = 0 fuera : dentro : = 56 Una gramtica que define las secuencias con este formato es: <Lista> ::= nombre | | <OtrosNombres> = numero : nombre <OtrosNombres> ::= , nombre

{ {

, nombre : nombre

} }

se trata de una gramtica que cumple la condicin LL(1): - el smbolo director para la alternativa vaca es el smbolo igual; es evidente, pues, que las tres reglas de <OtrosNombres> tienen smbolos directores distintos, - la decisin de abandonar la repeticin del anlisis de un nuevo nombre (en las dos primeras alternativas de <OtrosNombres>) se puede tomar ante la presencia del smbolo igual. En la implementacin de un analizador sintctico LL(1), en sentido estricto, la seleccin de la alternativa vaca para expandir el smbolo <OtrosNombres> se hara tras comprobar explcitamente que la pieza sintctica por adelantado es el smbolo igual. El funcionamiento del analizador generado por JavaCC depende del orden de colocacin de las reglas. A continuacin se exponen varias posibilidades de especificacin y se comenta el analizador generado en cada caso.
Especificacin primera

Se transcriben literalmente las reglas tal y como se han escrito antes en la notacin BNF; ntese que para poder codificar la parte derecha de la regla vaca ha de incluirse una accin sintctica vaca. PARSER_BEGIN (Igualdad) public class Igualdad { public static void main (String[] argum) throws ParseException { Igualdad analisis = new Igualdad(System.in); analisis.secuencia(); System.out.println("Analisis terminado:"); System.out.println ("no se han hallado errores lxico-sintcticos"); } }
PARSER_END (Igualdad)

void secuencia() : { } { lista() }

<EOF>

37 void lista() : { } { < nombre > }

otrosNombres() "=" < numero >

void otrosNombres() : { } { "," < nombre > | ":" < nombre > | { } } TOKEN : { TOKEN : {

( "," ( ":"

< nombre > )* < nombre > )*

< nombre : ( [ "a" - "z", "A" - "Z", "", "" ] )+ > < numero : ( [ "0" - "9" ] )+ > }

SKIP : { " " | "\r" | "\n" } El analizador generado funciona correctamente puesto que en el mtodo otrosNombres se selecciona la alternativa vaca despus de haber comprobado que la pieza por adelantado no es smbolo inicial de ninguna de las dos alternativas precedentes (no es una coma ni un smbolo dos puntos).
Especificacin segunda

Se considera la misma especificacin anterior, pero cambiando el orden de colocacin de las reglas: ahora la produccin vaca se coloca como primera alternativa (slo se muestran las partes modificadas). void otrosNombres() : { } { { } | "," < nombre > | ":" < nombre > }

( "," ( ":"

< nombre > )* < nombre > )*

Con la entrada de esta especificacin, el generador JavaCC produce un mensaje en el que se avisa de que las dos alternativas que estn colocadas detrs de la alternativa vaca nunca podrn seleccionarse; aun as, se genera un analizador. Pero es un analizador que funciona incorrectamente; as, por ejemplo, la entrada mxa , nyz = 57 se considera incorrecta: despus de visto el primer nombre, se intenta aplicar la produccin vaca (se espera encontrar el smbolo igual), pero habra que aplicar la regla cuyo smbolo director es la coma.
Especificacin tercera

Se utilizan las posibilidades de la notacin BNF-Ampliada para prescindir de la especificacin explcita de la palabra vaca; se aprovecha que JavaCC admite el operador + para indicar repeticiones de una o ms veces; la alternativa vaca est implcitamente contemplada en la posibilidad de cero repeticiones que contempla el operador * (slo se muestran las partes modificadas en la especificacin).

38 void otrosNombres() : { } { ( "," < nombre > )+ | ( ":" < nombre > )* } El mtodo otrosNombres() generado hace las comprobaciones en el siguiente orden: - se comprueba si la pieza por adelantado es una coma (smbolo inicial de la primera alternativa), y en este caso, se selecciona la primera regla, - si no es una coma, se comprueba si es un smbolo dos puntos (smbolo inicial de la segunda alternativa), y en este caso, se selecciona la segunda regla, - si no es un smbolo dos puntos, se considera la palabra vaca como smbolo inicial (siempre posible implcitamente) para la segunda alternativa; en este caso, para que no haya error, la pieza por adelantado ha de ser un smbolo igual. Se trata, pues, de un analizador que funciona correctamente.
Especificacin cuarta

Se pone la misma especificacin anterior, pero cambiando el orden de consideracin de la regla vaca: ahora se incluye implcitamente en la primera alternativa (slo se muestran las partes modificadas). void otrosNombres() : { } { ( "," < nombre > )* | ( ":" < nombre > )+ } Para esta especificacin, el generador JavaCC emite un aviso en el que se indica que la segunda alternativa nunca se seleccionar; no obstante, se genera un analizador que no funciona como se supone que debera hacerlo; por ejemplo, el anlisis de la entrada nyz : mxa = 1 produce un error: despus de tratado el primer nombre, se intenta aplicar la produccin vaca (correspondiente a la repeticin de cero veces contemplada en la primera regla) por lo que no se encuentra el esperado smbolo igual.

Potrebbero piacerti anche