Notación de ANTLR

Aspectos Generales

A la hora de hacer una especificación léxica en ANTLR, hay que tener en cuenta varios aspectos:

  • La cabecera debe indicar, detrás de la palabra reservada grammar, el nombre de la clase Java a generar a partir de la especificación (MiLex en este caso). Debe indicarse también, mediante lexer, que esta es una especificación léxica, ya que ANTLR también admite especificaciones sintácticas.
    grammar lexer MiLex;
    
  • Los nombres de los tokens deben empezar en mayúscula (de hecho, lo habitual será ponerlo completamente en mayúscula).
  • Las reglas acaban en punto y coma.
  • ANTLR admite comentarios. Su sintaxis es la misma que en Java (// y /* ... */).
  • Si una vez formado un lexema válido, en vez de devolverlo como valor de retorno, se quiere ignorar y pasar al siguiente, hay que poner -> skip. Esto se utilizará, fundamentalmente, para ignorar espacios en blanco y saltos de línea.
    WHITESPACE: [ \t\r\n]+ -> skip;
    
  • Todo patrón debe generar lexemas de al menos un carácter (no se permiten reglas que puedan generar la cadena vacía).
  • Se permiten espacios entre los componentes de las expresiones regulares.
    LITREAL: [0-9]+  '.'  [0-9]+;
    
  • Los literales se delimitan con comillas simples.
    RETURN: 'return';
    
    • Si se ponen sin comillas (return), ANTLR lo interpreta erróneamente como un no-terminal de una gramática (los no-terminales se verán en el siguiente capítulo).
    • Si se ponen comillas dobles ("return"), ANTLR producirá un error.
  • Si la entrada no cumple con ninguno de los patrones del fichero de ANTLR, el método nextToken imprimirá un mensaje de error en la salida estandar de error (System.err), descartará dicha entrada y continuará intentando reconocer el resto de la entrada. Es decir, ante un error, el léxico no devuelve un token ERROR ni produce una excepción.

Ejemplo de un fichero completo de especificación de ANTLR:

grammar lexer MiLex;

/* Esto es un comentario de varias líneas */

LITENT: [0-9]+; // Constante entera

WS: [ \t\r\n]+ -> skip;

A partir de estas reglas, ANTLR determinará la implementación del método nextToken que en el capítulo anterior se hizo a mano.

Lectura Adicional Recomendada

🔎 Para más detalle sobre la notación de ANTLR, ver el apartado Grammar Lexicon de la documentación.

Operadores de ANTLR

Los operadores más usados de ANTLR, además de los básicos de las expresiones regulares, son los siguientes:

OperadorDescripciónEjemploLexemas válidos
?El operando al que se aplique será opcionalpa?bpb y pab
.Representa a cualquier carácter (incluido salto de línea)a.baCb, a$b, a b, ...
[conjunto]Representa a uno de los caracteres en dicho conjunto. Se permiten rangos de caracteres[a-c4]a, b, c y 4
~Un carácter que no sea el operandop~a0pb0, p$0, p60, p 0, ...
+?Versión non-greedy del '+'
*?Versión non-greedy del '*'

La versión non-greedy de los operadores '+' y '*' se forma añadiendo una '?' detrás de los mismos. Nótese que estos dos son nuevos operadores y, por ejemplo, el operador '+?' no tiene nada que ver con hacer opcional el '+' (el carácter '?', que se usa también en el operador de opcionalidad, puede causar confusión ya que se está utilizando el mismo carácter para dos operadores distintos).

La versión normal (greedy) y la versión non-greedy se diferencian en el momento en el que paran de reconocer una cadena. La versión non-greedy dejar de reconocer en cuanto ha encontrado un lexema válido. La versión normal (o greedy) sigue reconociendo por si puede formar un lexema más largo que también cumpla el patrón (si no lo consigue, devolvería lo mismo que la versión non-greedy). Es decir, la versión normal intenta formar el lexema más largo que cumpla el patrón.

Por ejemplo, supóngase el siguiente patrón con la versión normal del '*':

T: '@' .* '@';

Ante una entrada como la siguiente:

@hola@@adios@

En una única llamada devolvería el lexema "@hola@@adios@", ya que cumple el ser una secuencia de caracteres que empieza y acaba con '@' (no importa que haya '@' en medio).

Sin embargo, usando la versión non-greedy del operador:

T: '@' .*? '@';

En la primera llamada a nextToken se formaría sólo el lexema "@hola@" (deja de leer más caracteres en cuanto la entrada cumple el patrón) y en una segunda llamada a nextToken ya se devolvería "@adios@".

Lectura Adicional Recomendada

🔎 Para más detalle sobre los operadores que se pueden usar en las expresiones regulares en ANTLR, ver el apartado Lexer Rule Elements de la documentación.

Prioridades de las Reglas

Cuando una entrada sólo casa con un patrón, es fácil prever el comportamiento de ANTLR. Pero ¿qué pasa si una misma entrada casa con más de un patrón? En ese caso, hay que conocer las reglas de prioridad de ANTLR.

Supóngase la situación con el fichero de ANTLR de la izquierda y la entrada "32954".

Cumpliendo esas reglas, se podría reconocer esa entrada de las tres formas que se muestra en la imagen: la primera devolvería dos lexemas, la segunda tres y la última sólo uno. ¿Cuál de ellas produce el método nextToken generado por ANTLR?

Norma 1

📖 Cuando una entrada puede ser reconocida por más de una regla, se elige aquella que forme el lexema más largo

Siguiendo la norma anterior, la solución sería la tercera. ANTLR elegiría la regla T2 ya que forma el lexema "32954" que es más largo que el lexema "32" que formaría T1.

Supóngase ahora la siguiente situación en la que se cambia la cadena de entrada y pasa a ser "32ALJ".

En este caso ambas reglas forman un lexema del mismo tamaño, por lo que la norma 1 no es de ayuda.

Norma 2

📖 Cuando varias reglas forman un lexema del mismo tamaño, se elige la regla que se haya definido primero

Siguiendo la norma 2, nextToken devolvería ahora el token T1 con el lexema "32".

Estas son las únicas dos normas que hay que saber para predecir qué hará ANTLR ante cualquier situación de solapamiento de reglas léxicas.