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.