Sintaxis para la construccion de archivos de

Anuncio
Sintaxis para la construcción de archivos de gramática para JavaCC
Los Tokens en los archivos de gramática siguen las mismas convenciones que para el
lenguaje de programación Java. Por tanto identificadores, cadenas, caracteres, etc.
Utilizada en las gramáticas son iguales que los identificadores de Java, cadenas de Java,
caracteres de Java, etc.
Los espacios en blanco en los archivos de gramática también siguen las mismas
convenciones para el lenguaje de programación Java. Este incluye la sintaxis para
comentarios. La mayor parte de los comentarios presentes en los archivos de gramáticas
son generados dentro del analizador léxico/sintáctico generado.
Los archivos de gramática son preprocesados por códigos de escape Unicode tal como
aparecen en los archivos Java (las ocurrencias de cadenas tales como \uxxxx – donde
xxxx es un valor hex – son convertidos al carácter Unicode correspondiente antes de
efectuar el análisis léxico).
Las excepciones a las reglas antes descritas: Los operadores de Java: "<<", ">>", ">>>",
"<<=", ">>=", y ">>>=" son retiradas por la izquierda de la lista de tokens de entrada de
JavaCC para permitir un uso conveniente de la especificación de tokens. Finalmente, las
siguientes son las palabras reservadas adicionales en los archivos de gramática de
JavaCC.
EOF IGNORE_CASE JAVACODE LOOKAHEAD
MORE options PARSER_BEGIN PARSER_END
SKIP SPECIAL_TOKEN TOKEN TOKEN_MGR_DECLS
Cualquier entidad de Java utilizada en las reglas de la gramática que a continuación
aparezca con el prefijo java_ (Ej., java_compilation_unit).
-------------------------------------------------------------------------------javacc_input ::= javacc_options
"PARSER_BEGIN" "(" <IDENTIFIER> ")"
java_compilation_unit
"PARSER_END" "(" <IDENTIFIER> ")"
( production )*
<EOF>
Los archivos de gramática comienzan con una lista de opciones (las cuales son
opcionales). Esto es entonces seguido por la unidad de compilación de Java encerrado
entre "PARSER_BEGIN(name)" y "PARSER_END(name)". Despues de esto hay una
lista de producciones de la gramática. Las opciones y producciones son descritas
posteriormente.
El nombre que sigue a "PARSER_BEGIN" y "PARSER_END" debe ser el mismo e
identifica el nombre del parser generado. Por ejemplo, si el nombre es "MiParser",
entonces se generan los siguientes archivos:
MiParser.java: El analizador generado.
MiParserTokenManager.java: El manejador de tokens generado (o analizador léxico).
MiParserConstants.java: Un conjunto de constantes útiles.
Otros archivos como "Token.java", "ParseError.java", etc. También son generados.
Estos archivos contienen código modelo y son el mismo para cualquier gramática y
pueden ser reutilizadas en otras gramáticas.
Entre las construcciones PARSER_BEGIN y PARSER_END se encuentra una unidad
regular de compilación de Java (una unidad de compilación en java puede ser el
contenido completo de un archivo en Java). Esto puede ser cualquier unidad de
compilación arbitraria tan grande como contenga una declaración de clase y cuyo
nombre sea el mismo que el nombre del parser generado ("MiParser" en el ejemplo
anterior). Por tanto, en general, esta parte del archivo de la gramática se vera como:
PARSER_BEGIN(parser_name)
. . .
clase parser_name . . . {
. . .
}
. . .
PARSER_END(parser_name)
JavaCC no realiza ninguna revisión detallada en la unidad de compilación, así esto es
posible para un archivo de gramática pasar a través de JavaCC y generar los archivos
que producen errores cuando estos son compilados.
Si la unidad de compilación incluye un paquete de declaración, esto es incluido en todos
los archivos generados. Si la unidad de compilación importa declaraciones, estas se
incluyen en el parser generado y en los archivos del manejador de tokens.
Si el archivo de parser generado contiene todo en la unida de compilación y en adición,
contiene el código generado del parser que se incluye al fin de la clase del parser. Para
el ejemplo anterior, el parser generado se podría ver como sigue:
. . .
clase parser_name . . . {
. . .
// El parser generado se incluye aqui.
}
. . .
Los parsers generados incluyen la declaración de un método público para cada noterminal (ver javacode_production y bnf_production) en el archivo de gramática. El
Análisis Sintáctico con respecto a un no-terminal se logra invocando el método
correspondiente a ese no-terminal. A diferencia de yacc, no hay ningún símbolo inicial
en JavaCC – uno puede iniciar el análisis sintáctico con respecto a cualquier no-terminal
en la gramática.
El manejador de tokens provee un método público:
Token getNextToken() throws ParseError;
Para más detalles en como puede ser utilizado este método, lea la documentación de
JavaCC.
javacc_options ::= [ "options" "{" ( option_binding )* "}" ]
Si la palabra “options” está presente, inicia con la palabra reservada “options” seguida
de una lista de más opciones encerradas con corchetes. Cada opción encerrada
especifica el establecimiento de una opción. La misma opción no debe ser indicada
varias veces.
Las opciones pueden ser especificadas tanto aquí en el archivo de gramática o desde la
línea de comandos. Si la opción se coloca desde la línea de comandos, esta toma
precedencia.
Los nombres de opciones pueden ser mayúsculas o minúsculas.
-------------------------------------------------------------------------------option_binding ::= "LOOKAHEAD" "=" java_integer_literal ";"
| "CHOICE_AMBIGUITY_CHECK" "=" java_integer_literal ";"
| "OTHER_AMBIGUITY_CHECK" "=" java_integer_literal ";"
| "STATIC" "=" java_boolean_literal ";"
| "DEBUG_PARSER" "=" java_boolean_literal ";"
| "DEBUG_LOOKAHEAD" "=" java_boolean_literal ";"
| "DEBUG_TOKEN_MANAGER" "=" java_boolean_literal ";"
| "OPTIMIZE_TOKEN_MANAGER" "=" java_boolean_literal ";"
| "ERROR_REPORTING" "=" java_boolean_literal ";"
| "JAVA_UNICODE_ESCAPE" "=" java_boolean_literal ";"
| "UNICODE_INPUT" "=" java_boolean_literal ";"
| "IGNORE_CASE" "=" java_boolean_literal ";"
| "USER_TOKEN_MANAGER" "=" java_boolean_literal ";"
| "USER_CHAR_STREAM" "=" java_boolean_literal ";"
| "BUILD_PARSER" "=" java_boolean_literal ";"
| "BUILD_TOKEN_MANAGER" "=" java_boolean_literal ";"
| "TOKEN_MANAGER_USES_PARSER" "=" java_boolean_literal ";"
| "SANITY_CHECK" "=" java_boolean_literal ";"
| "FORCE_LA_CHECK" "=" java_boolean_literal ";"
| "COMMON_TOKEN_ACTION" "=" java_boolean_literal ";"
| "CACHE_TOKENS" "=" java_boolean_literal ";"
| "OUTPUT_DIRECTORY" "=" java_string_literal ";"
LOOKAHEAD: El numero de tokens a observar por delante antes de tomar una
decisión en un punto de opciones durante el Análisis Sintáctico. El valor por default es
1. Mientras más pequeño sea dicho numero, el Análisis será más rápido. Este número
puede ser sobre-encimado por producciones específicas dentro de la gramática como se
describirá posteriormente. Ver la descripción del algoritmo lookahead para los detalles
completos en como trabaja lookahead.
CHOICE_AMBIGUITY_CHECK: Esta es una opción entera la cual tiene un valor por
default de 2. Este es el numero de tokens considerados en la revisión de ambigüedad de
opciones desde “A|B…”. Por ejemplo, si hay dos prefijos de tokens comunes tanto para
A y B, pero no hay tres tokens prefijos comunes, (asumiendo que esta opción se pone a
3) entonces JavaCC puede informarte que debes usar un lookahead de 3 para propósitos
de eliminar la ambigüedad. Y si A y B tienen tres tokens prefijos comunes, entonces
JavaCC solo te dice que puedes necesitar tener un lookahead de 3 o más. Al incrementar
este valor te puede dar más información respecto a la ambigüedad al costo de más
tiempo de procesamiento. Para gramáticas más grandes como la gramática de Java,
incrementar este número mas allá causa que la revisión tome mucho más tiempo.
OTHER_AMBIGUITY_CHECK: Esta es una opción de un número entero el cual su
valor por defecto es 1. Este es el numero de tokens considerados en la revisión de todos
los otros tipos de opciones de ambigüedad (ej. de las formas "(A)*", "(A)+", y "(A)?").
Esto toma más tiempo en hacerse que revisar la opción, y por tanto el valor por default
es colocado en 1 más bien que 2.
STATIC: Esta es una opción tipo lógica la cual su valor por default es verdadero. Si es
verdadero, todos los métodos y variables de clases son especificados como estáticas en
el analizador generado y el manejador de tokens. Esto solo permite que un objeto parser
esté presente, pero mejora el rendimiento del analizador. Para realizar varios
analizadores durante una ejecución del programa Java, tienes que invocar el método
ReInit() para reinicializar el parser si éste es estático. Si el parser es no-static puedes
utilizar el operador “new” para construir tantos parsers como necesites. Estos pueden
ser utilizados simultáneamente desde diferentes threads.
DEBUG_PARSER: Esta es una opción tipo lógica la cual establece un valor por default
de falso. Esta opción es utilizada para obtener información de depuración desde los
analizadores generados. Al establecer estas opciones en verdadero ocasiona que los
parsers generen el seguimiento de sus acciones. El seguimiento puede ser deshabilitado
invocando el método disable_tracing() en la clase del parser generado. El seguimiento
puede ser posteriormente habilitado al invocar el método enable_tracing() en la clase del
parser generado.
DEBUG_LOOKAHEAD: Esta es una opción tipo lógica la cual tiene un valor por
default de falso. Al establecer esta opción en verdadero (true) causa que el parser genere
toda la información de seguimiento que se hace cuando se establece la opción
DEBUG_PARSER en verdadero, en adición, esto también ocasiona que se genere la
ruta de las acciones realizadas durante las operaciones de lookahead
DEBUG_TOKEN_MANAGER: Esta es una opción de tipo lógica la cual por default se
establece en false. Esta opción es utilizada para obtener información de depuración
desde el manejador de tokens. Al establecer esta opción en true ocasiona que el
manejador de tokens genere un seguimiento de sus acciones. Este seguimiento es
bastante extenso y solo debe usarse cuando tengas un error léxico que haya sido
reportado y no comprendas porque. Típicamente, en esta situación, puedes determinar el
problema observando las últimas cuantas líneas del seguimiento.
ERROR_REPORTING: Esta es una opción la cual tiene un valor por default de
verdadero “true”. Si se establece en falso causará que los errores sean reportados con
menos detalles. La única razón para poner ésta opción en falso es mejorar su eficiencia.
JAVA_UNICODE_ESCAPE: Esta es una opción tipo lógica la cual tiene un valor por
default de falso. Cuando se establece en “true”, el parser generado utiliza un objeto de
flujo de entrada que procesa los códigos de escape Unicode de Java (\u...) antes de
enviar los caracteres al manejador de tokens. Por default, los códigos de escape Unicode
de Java no son procesados. Esta opción se ignora si cualquiera de las opciones
USER_TOKEN_MANAGER, USER_CHAR_STREAM se establece en true.
UNICODE_INPUT: Esta es una opción tipo lógica la cual su valor por default es falso.
Cuando se establece en true, el parser generado utiliza un objeto de flujo de entrada que
lee los archivos Unicode. Por default se asume que los archivos son ASCII. Esta opción
se ignora si se establece en true cualquiera de las opciones
USER_TOKEN_MANAGER, USER_CHAR_STREAM.
IGNORE_CASE: Esta es una opción de tipo lógica la cual el valor default es falso. Al
poner esta opción causa que el manejador de tokens generado ignore la diferencia entre
mayúsculas y minúsculas en la especificación de tokens y los archivos de entrada. Esto
es útil para escribir gramáticas para lenguajes tales como HTML.
USER_TOKEN_MANAGER: Esta es una opción tipo lógica la cual tiene por default el
valor falso. La acción por default es generar un token manager que trabaja en aceptar
tokens desde cualquier token manager de tipo “TokenManager” – esta interfase es
generada dentro del directorio del parser generado.
USER_CHAR_STREAM: Esta es una opción tipo lógica la cual por default se establece
en falso. La acción por default es generar un lector de flujo de caracteres tal como se
especifica en las opciones JAVA_UNICODE_ESCAPE y UNICODE_INPUT. El
analizador léxico generado recibe los caracteres de este lector de flujo. Si esta opción se
establece en verdadero (true), entonces el analizador léxico generado para leer los
caracteres desde cualquier lector de flujo de tipo "CharStream.java". El archivo es
generado dentro del directorio del analizador sintáctico generado
Esta opción se ignora si USER_TOKEN_MANAGER se establece en verdadero.
BUILD_PARSER: Esta es una opción de tipo lógica la cual tiene un valor por default
de verdadero. La acción por default es generar el archivo de parser (("MiParser.java" en
el ejemplo anterior). Cuando se establece en falso, el archivo del parser no es generado.
Típicamente esta opción se pone en falso cuando solamente se desea generar el
analizador léxico y utilizarlo sin el analizador léxico asociado (parser).
BUILD_TOKEN_MANAGER: Esta es una opción de tipo lógico la cual se establece
por default al valor “true”. La acción por default es generar el archivo del Manejador de
Tokens (Analizador léxico - "MiParserTokenManager.java" en el ejemplo anterior).
Cuando se establece en falso el archivo del analizador léxico no se genera. La única
razón para colocar esta opción en falso es ahorrar tiempo durante la generación del
analizador léxico cuando se arregla un problema en la parte de la gramática del
analizador y se deja las especificaciones sin modificación alguna.
TOKEN_MANAGER_USES_PARSER: Esta es una opción de tipo lógico la cual tiene
un valor por default de “false”. Cuando se establece en true, el manejador de token
(analizador léxico) generado incluirá un campo llamado parser que referenciará el
objeto instanciado del Parser (de tipo MiParser en el ejemplo anterior). La principal
razón de contar con una referencia al parser en un analizador léxico es el utilizar
algunas de su lógica en las acciones léxicas. Esta opción no tiene efecto si la opción
STATIC se establece en true.
SANITY_CHECK: Esta es una opción lógica la cual su valor por default es true.
JavaCC realiza muchas revisiones sintácticas y semánticas en el archivo de la gramática
durante la generación del analizador sintáctico. Algunas revisiones tales como la
detección de recursividad por la izquierda, detección de ambigüedad y mal uso de
expansiones vacías pueden ser suprimidas para la generación de un parser más rápido al
colocar ésta opción en falso. Note que la presencia de estos errores (aun cuando estas no
son detectadas y reportadas al establecer ésta opción a Falso) pueden ocasionar un
comportamiento inesperado desde el parser generado.
FORCE_LA_CHECK: Esta es una opción de tipo lógica la cual por default se encuentra
en falso. Esta opción controla la revisión de búsqueda adelantada de ambigüedad que se
realiza para todos los puntos de opción donde se utiliza el valor por default de 1 para la
búsqueda adelantada (o LookAhead)
La búsqueda adelantada de ambigüedad no se realiza en puntos de opción donde hay
una especificación explícita de búsqueda adelantada, o si la opción LOOKAHEAD se
establece en otro valor distinto de 1. Al colocar esta opción en “true” se realiza una
búsqueda adelantada de ambigüedad revisando todas las opciones a pesar de la
especificación de búsqueda adelantada en el archivo de la gramática
COMMON_TOKEN_ACTION: Esta es una opción lógica la cual tiene el valor por
default de falso. Cuando se establece en true, cada llamada al método getNextToken del
analizador léxico originara una llamada al método "CommonTokenAction" después de
que el token ha sido reconocido por el analizador léxico. El usuario debe definir éste
método dentro de la sección TOKEN_MGR_DECLS. El prototipo de éste método es:
void CommonTokenAction(Token t)
CACHE_TOKENS: Esta es una opción de valor lógico la cual por default es falso. Al
establecer esta opción en true ocasiona que el parser generado revise por tokens extra de
forma anticipada. Esto facilita alguna mejora de eficiencia. Sin embargo, en este caso
(cuando la opción se establece en true), las aplicaciones interactivas podrían no trabajar
ya que el parser necesita trabajar en sincronía con la disponibilidad de tokens desde el
flujo de entrada. En tales casos, es mejor dejar esta opción con su valor por default.
OUTPUT_DIRECTORY: Esta es una opción para un valor de cadena de caracteres la
cual el valor por default es el directorio actual. Esta opción controla donde se generan
los archivos de salida.
-------------------------------------------------------------------------------production ::=
|
|
|
javacode_production
regular_expr_production
bnf_production
token_manager_decls
Hay cuatro tipos de producciones en JavaCC. javacode_production y bnf_production
son utilizadas para definir la gramática desde la cual se genera el Parser.
regular_expr_production se utiliza para definir la gramática de tokens – el token
manager es generado a partir de ésta información (también desde la especificación de
tokens en línea en la gramática del parser). token_manager_decls se utiliza para
introducir declaraciones que se insertan dentro del analizador léxico generado.
-------------------------------------------------------------------------------javacode_production ::= "JAVACODE"
java_access_modifier java_return_type java_identifier "("
java_parameter_list ")"
java_block
La producción JAVACODE es una manera de escribir código Java para algunas
producciones en lugar de utilizar la expansión usual EBNF. Esto es útil cuando hay
necesidad de reconocer algo en un contexto no libre o por cualquier otra razón que sea
difícil para la cual sea difícil escribir una gramática. Un ejemplo del uso de
JAVACODE se muestra a continuación. En éste ejemplo, el no terminal
"skip_to_matching_brace" consume todos los tokens en el flujo de entrada hasta hacer
coincidir con el corchete que cierra (se asume que el corchete izquierdo ya fue
reconocido):
JAVACODE
void skip_to_matching_brace() {
Token tok;
int nesting = 1;
while (true) {
tok = getToken(1);
if (tok.kind == LBRACE) nesting++;
if (tok.kind == RBRACE) {
nesting--;
if (nesting == 0) break;
}
tok = getNextToken();
}
}
Se debe tener mucho cuidad en utilizar las producciones JAVACODE.
Mientras que puedes decir bastante de lo que se desea con estas producciones, JavaCC
simplemente las considera como una caja negra (algo que se realiza dentro de la tarea
del análisis). Esto puede ser un problema cuando las producciones JAVACODE
aparecen en puntos de selección. Por ejemplo, si la producción JAVACODE de arriba
estuviera referenciada desde la siguiente producción:
void NT() :
{}
{
skip_to_matching_brace()
|
some_other_production()
}
Entonces JavaCC no podría saber como escoger entre las dos opciones. De otra manera,
si la producción JAVACODE es utilizada en un punto que no hay opciones, como se
muestra en el siguiente ejemplo, no habrá problemas:
void NT() :
{}
{
"{" skip_to_matching_brace()
|
"(" parameter_list() ")"
}
Cuando las producciones JAVACODE son utilizadas en puntos de elección, JavaCC
imprimirá un mensaje de advertencia. Tendrás entonces que insertar algunas
especificaciones explícitas LOOKAHEAD para ayudar al JavaCC.
El modificador por default para producción JAVACODE es un paquete privado.
-------------------------------------------------------------------------------bnf_production ::= java_access_modifier java_return_type
java_identifier "(" java_parameter_list ")" ":"
java_block
"{" expansion_choices "}"
La producción BNF es la producción estándar utilizada en la especificación de
gramáticas JavaCC. Cada producción BNF tiene un lado izquierdo el cual es una
especificación no-terminal. La producción BNF entonces define este no-terminal en
términos de expansiones BNF en el lado derecho. El no-terminal es escrito exactamente
como la declaración de un método en Java. Dado que cada no-termina es traducido
dentro de un método en el analizador generado, este estilo de escribir el no-terminal
hace obvia esta asociación. El nombre del no-terminal es el nombre del método, y los
parámetros y valores de retorno declarados son los medios para pasar los valores hacia
arriba y abajo en el árbol sintáctico. Tal como se verá posteriormente, el paso de valores
hacia arriba y abajo del árbol se realiza utilizando exactamente el mismo paradigma que
la invocación y retorno a métodos. El modificador de acceso para las producciones BNF
es public.
Hay dos partes en el lado derecho de la producción BNF. La primer parte es el conjunto
arbitrario de declaraciones de Java y código (el bloque Java). Este código es generado al
inicio del método generado por el no-terminal Java. Por tanto, cada vez que se utilice
este no-terminal en el proceso de análisis sintáctico, estas declaraciones y código son
ejecutados. Las declaraciones en esta parte son visibles para todo el código Java en las
acciones en las expansiones BNF. JavaCC no realiza cualquier procesamiento de estas
declaraciones y código, excepto el salto hasta el corchete final, colectando todo el texto
encontrado en el camino. Por tanto el compilador de Java puede detectar los errores en
este código que ha sido procesado por JavaCC.
La segunda parte del lado derecho está en formato de expansión BNF. Esta se describe
posteriormente.
-------------------------------------------------------------------------------regular_expr_production ::= [ lexical_state_list ]
regexpr_kind [ "[" "IGNORE_CASE" "]" ] ":"
"{" regexpr_spec ( "|" regexpr_spec )* "}"
Una producción de expresión regular se utiliza para definir entidades léxicas a ser
procesadas por el manejador de tokens. Esta sección describe los aspectos sintácticos de
la especificación de entidades léxicas.
Una producción de expresión regular comienza con una especificación del l estado
léxico para las cuales aplica (la lista de estados léxicos). Hay un estado léxico estándar
llamado “DEFAULT”. Si se omite la lista de estados léxicos, las producciones de
expresión regular aplican el estado léxico "DEFAULT".
Siguiendo esto es una descripción de que tipo de producción de expresión regular que
es.
Despues de esto hay un "[IGNORE_CASE]" opcional. Si esta presente, la producción
de expresión regular no distingue de mayúsculas y minúsculas – que es el mismo efecto
que la opción IGNORE_CASE, excepto que en este caso aplica localmente a esta
producción de expresión regular.
Esto es entonces seguido por la especificación de una lista de expresiones que describen
con más detalle las entidades léxicas de esta producción de expresión regular.
-------------------------------------------------------------------------------token_manager_decls ::= "TOKEN_MGR_DECLS" ":" java_block
Las declaraciones del manejador de tokens comienza con la palabra reservada
"TOKEN_MGR_DECLS" seguida por ":" y entonces un conjunto de declaraciones y
sentencias Java (el bloque Java). Estas declaraciones y sentencias están escritas dentro
del manejador de tokens y están accesibles desde las acciones léxicas.
Solo puede haber una declaración de manejador de tokens en una declaración en un
archivo de gramáticas de JavaCC.
-------------------------------------------------------------------------------lexical_state_list ::= "<" "*" ">"
| "<" java_identifier ( "," java_identifier )* ">"
La lista de estados léxicos describe el conjunto de estados léxicos para los cuales
corresponda la aplicación de una producción de expresión regular. Si esta escrita "<*>",
la producción de la expresión regular aplica a todos los estados léxicos. De otra forma,
este aplica a todos los estados léxicos en la lista de identificadores dentro de los
paréntesis angulares.
-------------------------------------------------------------------------------regexpr_kind ::= "TOKEN"
| "SPECIAL_TOKEN"
| "SKIP"
| "MORE"
Este especifica el tipo de producción de expresión regula. Existen cuatro tipos:
TOKEN: La expresión regular en esta producción de expresión regular describe
los componentes léxicos (tokens) en la gramática. El manejador de tokens
(analizador léxico) crea un objeto Token para cada coincidencia de cada
expresión regular y la retorna al analizador sintáctico.
SPECIAL_TOKEN: La expresión regular en esta producción de expresión
regular describe tokens especiales. Los tokens especiales son como los tokens
normales, excepto que no tienen significado durante el análisis sintáctico – tal
que las producciones BNF las ignoran. Los tokens especiales son pasados al
parser enlazándolos a los tokens vecinos reales utilizando el campo
“specialToken” en la clase Token. Los tokens especiales son útiles en el
procesamiento de entidades léxicas tales como comentarios en los cuales no se
realizar un análisis significativo, pero son una parte importante del archivo de
entrada.
SKIP: Las coincidencias a una expresión regular en ésta producción de
expresión regular son simplemente ignoradas por el manejador de tokens
(Analizador léxico).
MORE: Algunas veces es útil construir gradualmente un token a ser pasado al
parser. Las coincidencias de este tipo de expresiones regulares son almacenadas
en un buffer hasta que coincida con TOKEN o SPECIAL_TOKEN. Entonces
todas las coincidencias encontradas en el buffer y las coincidencias
TOKEN/SPECIAL_TOKEN son concatenadas en la forma
TOKEN/SPECIAL_TOKEN que es pasada al analizador sintáctico. Si a una
coincidencia a la expresión regular SKIP sigue una secuencia de coincidencias
de tipo MORE, el contenido del buffer se descarta.
-------------------------------------------------------------------------------regexpr_spec ::= regular_expression [ java_block ] [ ":" java_identifier ]
La especificación de expresiones regulares inicia la descripción actual de las entidades
léxicas que son parte de la producción de la expresión regular. Cada producción de
expresión regular puede contener cualquier número de especificaciones de expresiones
regulares.
Cada especificación de expresión regular contiene una expresión regular seguida de un
bloque de Java (acción léxica) la cual es opcional. Esta es entonces seguida de un
identificador de un estado lógico (la cual es opcional). Donde quiera que coincida ésta
expresión regular, la acción léxica (si la hay) se ejecuta, seguida por cualquier cantidad
de acciones comunes de los tokens. Entonces la acción depende del tipo de producción
de la expresión regular seleccionada. Finalmente, si se especifica un estado léxico, el
manejador de tokens (analizador léxico), se mueve a ese estado léxico para futuro
procesamiento (el analizador léxico inicia con el estado “DEFAULT”).
-------------------------------------------------------------------------------expansion_choices ::= expansion ( "|" expansion )*
Las selecciones de expansión se escriben como un alista de una o más expansiones
separadas por "|"’s. El conjunto de analizadores legales seguidos por una selección de
expansión es un análisis legal de cualquier de las expansiones contenidas.
-------------------------------------------------------------------------------expansion ::= ( expansion_unit )*
Una expansión esta escrita como una secuencia de unidades de expansion. La
concatenación de unidades de expansión correctamente analizadas es una expansión de
analizador sintáctico correcto.
Por ejemplo, la expansión "{" decls() "}" consiste de tres unidades de expansión - "{",
decls(), y "}". Una coincidencia para la expansión es una concatenación de las
coincidencias individuales de las unidades de expansión – en este caso, que podría ser
una cadena que inicie con “{“, termine con “}” y contenga una coincidencia intermedia
para decls()
-------------------------------------------------------------------------------expansion_unit ::= local_lookahead
| java_block
| "(" expansion_choices ")" [ "+" | "*" | "?" ]
| "[" expansion_choices "]"
| [ java_assignment_lhs "=" ] regular_expression
| [java_assignment_lhs "="] java_identifier "("
java_expression_list")"
Una unidad de expansión puede ser una especificación local LOOKAHEAD. Esto
instruye al parser generado en como tomar las decisiones en los puntos de opciones.
Una unidad de expansion puede ser un conjunto de declaraciones de Java y código
encerrado entre corchetes (bloque Java). Estas también se denominan acciones del
analizador sintáctico. Este es generado dentro del método de análisis del no-terminal en
el lugar apropiado. Este bloque se ejecuta cada vez que el proceso de análisis cruza este
punto de forma exitosa. Cuando JavaCC procesa el bloque en Java, este no realiza
ninguna revisión de la sintaxis o semántica. Por lo tanto es posible que el compilador de
Java pueda encontrar errores en las acciones que han sido procesadas por JavaCC. Las
acciones no se ejecutan durante la evaluación de adelantada de tokens.
Una unidad de expansión puede ser un conjunto de una o más opciones entre paréntesis.
En cuyo caso, un análisis válido de una unidad de expansión es cualquier análisis legal
de las expresiones anidadas. El conjunto de opciones de expansión entre paréntesis
puede ser opcionalmente acompañada a su derecha por:
"+": Aquí cualquier coincidencia valida de la unidad es una o más repeticiones de la
coincidencia valida del conjunto de opciones entre paréntesis.
"*": Aquí cualquier coincidencia valida de la unidad es cero o más repeticiones de una
coincidencia valida del conjunto de opciones entre paréntesis.
"?": Aquí cualquier coincidencia valida de la unidad es tanto la cadena vacía como
cualquier coincidencia valida de las opciones anidadas.
Una sintaxis alterna para esta construcción es para encerrar las opciones de expansión
dentro de los paréntesis rectangulares "[...]".
Una unidad de expresión puede ser una expresión regular. Entonces un análisis válido
de la unidad de expansión es cualquier token que coincide esta expresión regular.
Cuando coincide una expresión regular, este crea un objeto de tipo Token. Este objeto
puede ser accesado asignándolo a una variable con el prefijo de la expresión regular con
"variable =". En general, puedes tener cualquier asignación valida de Java en el lado
izquierdo del signo de igual "=". Esta asignación no se realiza durante la evaluación de
la búsqueda anticipada (lookahead).
Una unidad de expansión puede ser un no-terminal (la última opción en la sintaxis
descrita anteriormente). En cuyo caso, toma la forma de una invocación a un método
con el nombre del no-terminal utilizado como el nombre del método. Un análisis
exitoso del no-terminal origina que los parámetros colocados en la llamada del método
sean empleados para las operaciones y retornados sus valores (en el caso de que un noterminal no sea declarado del tipo “void”). El valor retornado puede ser asignado
(opcionalmente) a una variable colocando como prefijo de la expresión regular con
“variable=”. En general, puedes colocar cualquier asignación valida de Java del lado
izquierdo del signo “=”. Esta asignación no se efectúa durante la evaluación adelantada
de tokens. Los no-terminales no deben ser utilizados en una expansión de manera tal
que introduzca una recursión por la izquierda. JavaCC realiza esta revisión.
-------------------------------------------------------------------------------local_lookahead ::= "LOOKAHEAD" "(" [ java_integer_literal ] [ "," ]
[ expansion_choices ] [ "," ] [ "{" java_expression "}" ] ")"
Una especificación local de búsqueda anticipada de tokens (lookahead) se utiliza para
influenciar la manera en que el analizador sintáctico hace sus elecciones en varios
puntos de opciones en la gramática. Una especificación local lookahead inicia con la
palabra reservada "LOOKAHEAD" seguido por un conjunto de restricciones de
lookahead entre paréntesis. Hay tres tipos de restricciones – un límite de lookahead, un
lookahead sintáctico (elección de expansiones) y un lookahead semántico (la expresión
entre paréntesis). Al menos debe estar presente una restricción de lookahead. Si está
presente más de una restricción lookahead, estas deben ser separadas por comas.
A continuación se proporciona una breve descripción de cada tipo de restricción del
algoritmo LookAhead:
Lookahead Limit (Limite LookAhead): Este es el máximo número de tokens
(componentes léxicos) de búsqueda adelantada que pueden ser utilizados para
propósitos de determinación de selección de opciones. Esto sobre-escribe el valor por
default el cual se especifica por la opción LOOKAHEAD. Este limite lookahead aplica
solo al punto de selección de opción de la especificación local de lookahead. Si la
especificación local de lookahead no esta en éste punto de selección, el limite de
lookahead (si lo hay) es ignorado.
Syntactic Lookahead: Esta es una expansión (o expansión de opciones) que se utiliza
con el propósito de determinar si tiene que ser tomada o no una selección particular que
aplica a esta especificación local de lookahead. Si no se provee esto, el parser utilizará
la expansión a ser seleccionada durante la determinación lookahead. Si la especificación
local de lookahead no está en un punto de selección, la búsqueda sintáctica anticipada
(si la hay) es ignorada.
Semantic Lookahead: Esta es una expresión de tipo lógica que se evalúa cuando el
parser cruza este punto durante el análisis sintáctico. Si la expresión se evalúa a
verdadero, el análisis sintáctico continúa normalmente. Si la expresión se evalúa como
falso y la especificación local de lookahead se encuentra en un punto de elección, la
opción actual no se toma y se considera la siguiente opción. Si la expresión se evalúa
con falso y la especificación local de búsqueda anticipado no se encuentra en un punto
de elección, entonces se aborta el análisis sintáctico con un error de tipo Sintáctico. A
diferencia de las otras dos restricciones de lookahead que son ignoradas en puntos que
no tienen opciones, el lookahead semántico siempre es evaluado. En efecto, el
lookahead semántico es evaluado aun si esta se encuentra durante la evaluación de
alguna otra evaluación sintáctica.
El valor default para las restricciones lookahead: Si fue provista una especificación local
de lookahead, pero no se incluyeron todas las restricciones lookahead, entonces las que
fueron omitidas se asignan con los valores que se indica a continuación:
Si el límite de lookahead no fue provisto y se provee un lookahead sintáctico,
entonces el límite de lookahead se pone por default al valor entero más grande
(2147483647). Esto esencialmente implementa lo que se conoce como
“lookahead infinito” – esto es buscar de forma adelantada todos los tokens que
sean necesarios para hacer coincidir el lookahead sintáctico que se haya
establecido.
Si no se ha provisto de un limite de lookahead o un lookahead sintáctico, el valor
limite de lookahead por default es 0. Esto significa que no se realizará un
lookahead sintáctico, y solo se realizará un lookahead semántico.
Si no se provee de un lookahead sintáctico, se hace default la selección en la
cual se aplica la especificación lookahead local. Si la especificación lookahead
no está en un punto de elección, entonces se ignora el lookahead sintáctico – por
tanto el valor por default no es relevante.
Si no se provee de una búsqueda anticipada de la semántica, este se aplica con la
expresión lógica "true".
-------------------------------------------------------------------------------regular_expression ::= java_string_literal
| "<" [ [ "#" ] java_identifier ":" ]
complex_regular_expression_choices ">"
| "<" java_identifier ">"
| "<" "EOF" ">"
Hay dos lugares en el archivo de la gramática en la cual se pueden escribir expresiones
regulares:
Dentro de la especificación de una expresión regular (parte de una producción de una
expresión regular),
Como una unidad de expansión con una expansión. Cuando una expresión regular se
utiliza de ésta manera, esto es como si la expresión regular estuviera definida en la
siguiente manera en ésta localidad y entonces referenciada por su etiqueta desde la
unidad de expansión:
<DEFAULT> TOKEN :
{
Expresión regular
}
Esto es, el uso de expresiones regulares puede ser re-escritas utilizando otro tipo de
forma.
La descripción de la construcción sintáctica sigue a continuación.
El primer tipo de expresión regular es una cadena de caracteres literal. La entrada a ser
analizada coincide con la expresión regular si el manejador de tokens esta en un estado
léxico por el cual esta expresión regular aplica y el siguiente conjunto de caracteres en
el flujo de entradas es el mismo (posiblemente se ignore la diferencia entre mayúsculas
y minúsculas) como esta literal de cadena de caracteres.
Una expresión regular puede también ser una expresión regular compleja utilizando mas
expresiones regulares relacionadas (mas que la definición de la cadena de caracteres).
Tal expresión regular es colocada dentro de paréntesis angulares "<...>", y
opcionalmente puede ser etiquetada con un identificador. Esta etiqueta puede ser
utilizada para referir a esta expresión regular desde unidades de expansión o desde otras
expresiones regulares. Si la etiqueta es precedida por el símbolo “#”, entonces esta
expresión regular no puede ser referenciada desde unidades de expansión y solo desde
otras expresiones regulares. Cuando esta presente el símbolo “#”, las expresiones
regulares son referenciadas como “expresiones regulares privadas”
Una expresión regular puede referenciar a otras expresiones regulares etiquetadas, en
cuyo caso está escrita como la etiqueta encerrada entre paréntesis angulares "<...>".
Finalmente, una expresión regular puede ser una referencia a una expresión regular
predefinida "<EOF>" la cual es la coincidencia con el fin del archivo.
Las expresiones regulares privadas no son hechas coincidir como tokens por el
manejador de tokens (analizador léxico). Su propósito solamente es para facilitar la
definición de otras expresiones regulares más complejas.
Considere el siguiente ejemplo de definición de las literales de números flotantes en
Java:
TOKEN :
{
< FLOATING_POINT_LITERAL:
(["0"-"9"])+ "." (["0"-"9"])* (<EXPONENT>)?
(["f","F","d","D"])?
| "." (["0"-"9"])+ (<EXPONENT>)? (["f","F","d","D"])?
| (["0"-"9"])+ <EXPONENT> (["f","F","d","D"])?
| (["0"-"9"])+ (<EXPONENT>)? ["f","F","d","D"]
>
|
< #EXPONENT: ["e","E"] (["+","-"])? (["0"-"9"])+ >
}
En este ejemplo, el token FLOATING_POINT_LITERAL es definido utilizando la
definición de otro token llamado EXPONENT. El símbolo "#" previo a la etiqueta que
este existe solamente para propósito de definir otros tokens
(FLOATING_POINT_LITERAL en este caso). La definición de
FLOATING_POINT_LITERAL no es afectado por la presencia o ausencia de "#". Sin
embargo, el comportamiento del manejador de tokens es: si se omite el símbolo "#" el
manejador de tokens erróneamente reconocerá una cadena como E123 como un token
valido del tipo EXPONENT (en lugar del IDENTIFIER en la gramática de Java).
-------------------------------------------------------------------------------complex_regular_expression_choices ::= complex_regular_expression ( "|"
complex_regular_expression )*
Las expresiones regulares complejas están hechas de una liste de una o mas expresiones
regulares complejas separadas por "|"s. Una coincidencia de una opción de expresión
regular compleja es la coincidencia de cualquiera de sus expresiones regulares
complejas que la constituyen.
-------------------------------------------------------------------------------complex_regular_expression ::= ( complex_regular_expression_unit )*
Una expresión regular compleja es una secuencia de unidades de expresiones regulares
complejas. Una coincidencia de una expresión regular compleja es una concatenación
de las coincidencias de las unidades de expresiones regulares.
-------------------------------------------------------------------------------complex_regular_expression_unit ::= java_string_literal
| "<" java_identifier ">"
| character_list
| "(" complex_regular_expression_choices ")" [ "+" | "*" | "?" ]
Una unidad de expresión regular compleja puede ser una literal de cadena de caracteres,
en la cual existe solamente una coincidencia para esta unidad, denominada por la cadena
de caracteres en si misma.
Una unidad de expresión regular puede ser una referencia a otra expresión regular. La
otra expresión regular tiene que ser etiquetada de tal manera que pueda ser referenciada.
La coincidencia de ésta unidad son todas las coincidencias de esta otra expresión
regular. Tal referencia en expresiones regulares no puede inducir ciclos en la
dependencia entre tokens.
Una unidad de expresión regular compleja puede ser una lista de caracteres. Una lista de
caracteres es la manera de definir un conjunto de caracteres. Una coincidencia de este
tipo de expresiones regulares complejas es cualquier carácter permitido por la lista de
caracteres.
Una expresión regular compleja puede ser un conjunto de opciones como expresiones
regulares entre paréntesis. En este caso, una coincidencia valida de la unidad es
cualquiera de las coincidencias anidadas. El conjunto de opciones entre paréntesis puede
opcionalmente seguir por:
"+": Aquí cualquier coincidencia valida de la unidad es una o más repeticiones de la
coincidencia valida del conjunto de opciones entre paréntesis.
"*": Aquí cualquier coincidencia valida de la unidad es cero o más repeticiones de una
coincidencia valida del conjunto de opciones entre paréntesis.
"?": Aquí cualquier coincidencia valida de la unidad es tanto la cadena vacía como
cualquier coincidencia valida de las opciones anidadas.
Nótese que a pesar de las expansiones BNF, la expresión regular "[...]" no es
equivalente a la expresión regular "(...)?". Esto es porque la construcción [...] es
utilizada para describir la lista de caracteres en expresiones regulares.
-------------------------------------------------------------------------------character_list ::= [ "~" ] "[" [ character_descriptor ( "," character_descriptor )* ] "]"
Una lista de caracteres describe el conjunto de caracteres. Una coincidencia legal para
una lista de caracteres es cualquier carácter en este conjunto. Una lista de caracteres es
una lista de descriptores de caracteres separados por comas y dentro de paréntesis
rectangulares. Cada descriptor de carácter describe un solo carácter o un rango de
caracteres (ver la descripción a continuación) y esta es agregada al conjunto de
caracteres de la lista de caracteres. Si la lista de caracteres comienza con el símbolo "~"
el conjunto de caracteres es representado por cualquier carácter UNICODE no
especificado en el conjunto especificado.
-------------------------------------------------------------------------------character_descriptor::= java_string_literal ["-" java_string_literal]
Un descriptor de un carácter puede ser una cadena de solo caracter literal, en cuyo caso
describe al mismo carácter; o si son dos caracteres separados por un guión “-“, en cuyo
caso, describe el conjunto de todos los caracteres en el rango entre e incluyendo estos
dos caracteres.
Descargar