Apéndice IV. Operadores '+' y '*'

Lectura Adicional Recomendada

🔎 Este apéndice no forma parte del material a evaluar

Cuando a un símbolo se le aplica alguno de los operadores '+' y '*', si se accede a dicho símbolo en una acción Java, ¿qué valor se obtiene?

s: a* { ... $a ... };   // ¿Qué contiene $a?

En este apéndice se verán los valores obtenidos en el acceso a símbolos a los que se haya aplicado dichos operadores.

El objetivo de este apéndice es de servir de referencia presentando un análisis de todas las combinaciones para su consulta, aunque no será necesario conocer todas ellas para la realización de la práctica.

Repeticiones en ANTLR

Cuando se accede a un símbolo que tenga el operador '+' o '*', el valor obtenido dependerá de que dicho símbolo sea un token o un no-terminal.

Por tanto, a continuación se verá el comportamiento del:

Valor de '+' en Tokens

Si se accede a un token al que se le ha aplicado '+', se está accediendo sólo al último token de la serie. Supóngase la siguiente entrada:

2 a b c 3

La siguiente acción imprimiría el lexema c:

a: INT IDENT+ INT { System.out.println($IDENT.text); }; // Imprime "c"

Si se quisieran obtener todos los IDENT habría que hacer que el operador '+' también se aplique a la acción. Para ello, se agruparían con paréntesis tanto el símbolo a repetir coma la acción y, ahora, el '+' se aplicaría a ambos. De esta manera la acción se ejecutará cada vez que se repita el elemento:

a: INT (IDENT { System.out.println($IDENT.text); })+ INT;
    // Imprime "a b c"

Esto, sin embargo, hace que la regla sea más difícil de leer, ya que mezcla los símbolos de la regla con el código Java.

Otra opción sería indicar a ANTLR que guarde automáticamente todos los tokens de la repetición en una lista (List<Token>) y que deje dicha lista en el contexto de la regla. Para ello, habría que poner, antes del token que se repite, el nombre que se le quiere dar a la lista seguido del operador '+='.

a: INT ids+=IDENT+ INT { System.out.println($ids); } // $ids es una List<Token>

Si se quisiera acceder a cada uno de los elementos, se recorrería como cualquier List de Java. Por ejemplo:

a: INT ids+=IDENT+ INT
        { for (Token tok : $ids) { System.out.println(tok.getText()); }};

Esta opción permite unas reglas más limpias al tener todo el código Java al final de las mismas (y no intercaladas entre los símbolos).

Valor de '*' en Tokens

Si se accede a un token al que se le ha aplicado '*', el valor obtenido será:

  • Si se ha usado el operador '+=', siempre devolverá una List<Token> (aunque no tenga elementos).
    a: INT ids+=IDENT* INT      // $ids siempre será una List<Token>
    
  • Si no se usa '+=', depende de cuántas veces se halle dicho elemento en la entrada:
    a: INT IDENT* INT           // $IDENT puede ser null o Token
    
    • Si hay algún elemento en la entrada, el '*' se comporta igual que el operador '+' (es decir, $IDENT se refiere sólo al último token).
    • Si hay cero elementos, el '*' se comporta igual que el operador '?' con los terminales (y, por tanto, $IDENT y $IDENT.text devuelven null y $IDENT.getText() lanza una excepción).

Valor de '+' en No-Terminales

Si se accede a un no-terminal al que se le ha aplicado '+', al igual que ocurría con los terminales, se está accediendo sólo al contexto del último elemento de la serie. Supóngase la siguiente entrada:

2 a b c 3

La siguiente acción imprimiría el lexema c:

a: INT b+ INT { System.out.println($b.val); };     // Imprime "c"

b returns[String val]
	: IDENT { $val = $IDENT.text; };

Si se quisieran obtener todos los valores devueltos por a, habría que mover la acción dentro del operador '+', para que se ejecute así con cada elemento que se encuentre:

a: INT (b {System.out.println($b.val); })+ INT; // a b c

Otra opción sería indicar a ANTLR que guarde automáticamente todos los contextos de los elementos de la repetición en una lista (List<ParserRuleContext>) y que deje dicha lista en el contexto de la regla actual. Para ello, habría que poner, antes del token que se repite, el nombre que se le quiere dar a la lista seguido del operador '+='.

a: INT v+=b+ INT { System.out.println($v); }    // $v es una List<BContext>

b returns[String val]
	: IDENT { $val = $IDENT.text; };

Si se quisiera acceder a cada uno de los elementos, se recorrería como cualquier List de Java. Por ejemplo:

a: INT v+=b+ INT { for (BContext ctx : $v) { System.out.println(ctx.val); } }

Valor de '*' en No-Terminales

Si se accede a un no-terminal al que se le ha aplicado '*', el valor obtenido será:

  • Si se ha usado el operador '+=', al igual que con '+', siempre devolverá una lista de contextos (aunque tenga cero elementos).
    a: INT v+=b* INT    // $v siempre será una List<BContext>
    
  • Si no se usa '+=', depende de cuántas veces se halle dicho elemento en la entrada:
    a: INT b* INT       // $b.val puede lanzar excepción
    
    b returns[String val]
        : IDENT { $val = $IDENT.text; };
    
    • Si hay algún elemento en la entrada, se comporta igual que el operador '+' (es decir, con $b.val se está accediendo sólo valor del último elemento de la serie).
    • Si hay cero elementos, se comporta igual que el operador '?' con los no-terminales (y, por tanto, $b.ctx valdrá null y $b.val lanzará una excepción).