2010-02-09 16 views
5

Estoy tratando de aprender ANTLR y al mismo tiempo usarlo para un proyecto actual.¿Cómo puedo modificar el texto de los tokens en un CommonTokenStream con ANTLR?

He llegado al punto en el que puedo ejecutar el lexer en un fragmento de código y enviarlo a CommonTokenStream. Esto está funcionando bien, y he verificado que el texto fuente está dividido en los tokens apropiados.

Ahora, me gustaría poder modificar el texto de ciertos tokens en esta secuencia y mostrar el código fuente ahora modificado.

Por ejemplo, yo he probado:

import org.antlr.runtime.*; 
import java.util.*; 

public class LexerTest 
{ 
    public static final int IDENTIFIER_TYPE = 4; 

    public static void main(String[] args) 
    { 
    String input = "public static void main(String[] args) { int myVar = 0; }"; 
    CharStream cs = new ANTLRStringStream(input); 


     JavaLexer lexer = new JavaLexer(cs); 
     CommonTokenStream tokens = new CommonTokenStream(); 
     tokens.setTokenSource(lexer); 

     int size = tokens.size(); 
     for(int i = 0; i < size; i++) 
     { 
      Token token = (Token) tokens.get(i); 
      if(token.getType() == IDENTIFIER_TYPE) 
      { 
       token.setText("V"); 
      } 
     } 
     System.out.println(tokens.toString()); 
    } 
} 

Estoy tratando de establecer texto de todo símbolo identificador de la cadena literal "V".

  1. ¿Por qué mis cambios en el texto del token no se reflejan cuando llamo a tokens.toString()?

  2. ¿Cómo se supone que debo conocer los diversos ID de tipo de token? Caminé con mi depurador y vi que la ID para los tokens del IDENTIFICADOR era "4" (de ahí mi constante en la parte superior). ¿Pero cómo habría sabido eso? ¿Hay alguna otra forma de asignar identificadores de tipo de token al nombre del token?


EDIT:

Una cosa que es importante para mí es que desear para las fichas tengan sus posiciones de inicio y final de caracteres originales. Es decir, no quiero que reflejen sus nuevas posiciones con los nombres de variables cambiados a "V". Esto es para que sepa dónde estaban los tokens en el texto fuente original.

+0

preguntaba - ¿es un requisito que utilice antlr ¿para esto? – cowboydan

Respuesta

5

ANTLR tiene una forma de hacer esto en su archivo de gramática.

Digamos que está analizando una cadena que consta de números y cadenas delimitadas por comas. Una gramática se vería así:

grammar Foo; 

parse 
    : value (',' value)* EOF 
    ; 

value 
    : Number 
    | String 
    ; 

String 
    : '"' (~('"' | '\\') | '\\\\' | '\\"')* '"' 
    ; 

Number 
    : '0'..'9'+ 
    ; 

Space 
    : (' ' | '\t') {skip();} 
    ; 

Todo esto debería resultarle familiar. Supongamos que quiere ajustar corchetes alrededor de todos los valores enteros. He aquí cómo hacerlo:

grammar Foo; 

options {output=template; rewrite=true;} 

parse 
    : value (',' value)* EOF 
    ; 

value 
    : n=Number -> template(num={$n.text}) "[<num>]" 
    | String 
    ; 

String 
    : '"' (~('"' | '\\') | '\\\\' | '\\"')* '"' 
    ; 

Number 
    : '0'..'9'+ 
    ; 

Space 
    : (' ' | '\t') {skip();} 
    ; 

Como se puede ver, he añadido un poco de options en la parte superior, y ha añadido una regla de reescritura (todo después de la ->) después de la Number en la regla value analizador.

ahora para probar todo, compilar y ejecutar esta clase:

import org.antlr.runtime.*; 

public class FooTest { 
    public static void main(String[] args) throws Exception { 
    String text = "12, \"34\", 56, \"a\\\"b\", 78"; 
    System.out.println("parsing: "+text); 
    ANTLRStringStream in = new ANTLRStringStream(text); 
    FooLexer lexer = new FooLexer(in); 
    CommonTokenStream tokens = new TokenRewriteStream(lexer); // Note: a TokenRewriteStream! 
    FooParser parser = new FooParser(tokens); 
    parser.parse(); 
    System.out.println("tokens: "+tokens.toString()); 
    } 
} 

que produce:

parsing: 12, "34", 56, "a\"b", 78 
tokens: [12],"34",[56],"a\"b",[78] 
2

El otro ejemplo dado de cambiar el texto en el léxico funciona bien si quieres reemplaza el texto de forma global en todas las situaciones, sin embargo, a menudo solo deseas reemplazar el texto de un token durante ciertas situaciones.

El uso de TokenRewriteStream le permite la flexibilidad de cambiar el texto solo en determinados contextos.

Esto se puede hacer utilizando una subclase de la clase de secuencia de tokens que estaba utilizando. En lugar de utilizar la clase CommonTokenStream, puede usar el TokenRewriteStream.

Así que tendría el TokenRewriteStream consumir el lexer y luego ejecutaría su analizador.

En su gramática general que harías la sustitución de esta manera:

/** Convert "int foo() {...}" into "float foo();" */ 
function 
: 
{ 
    RefTokenWithIndex t(LT(1)); // copy the location of the token you want to replace 
    engine.replace(t, "float"); 
} 
type id:ID LPAREN (formalParameter (COMMA formalParameter)*)? RPAREN 
    block[true] 
; 

Aquí hemos reemplazado el int token que nos emparejaron con el flotador de texto. La información de ubicación se conserva pero el texto que "coincide" ha sido cambiado.

Para verificar el flujo de tokens después de usar el mismo código que antes.

+0

Gracias por la información. ¿Tienes alguna idea de por qué no funcionó llamar a setText en los tokens individuales? – mmcdole

+0

@Simucal, id ¿intenta usar un 'TokenRewriteStream' en lugar de' CommonTokenStream'? –

+0

@Simucal, no he profundizado en el origen de java para antlr, ya que normalmente uso C++, pero me imagino que está modificando una copia de la secuencia de token y no la secuencia real. – chollida

2

En ANTLR 4 hay una nueva instalación que usa oyentes de árbol de análisis sintáctico y TokenStreamRewriter (tenga en cuenta la diferencia de nombre) que se puede utilizar para observar o transformar árboles. (Las respuestas que sugieren TokenRewriteStream se aplican a ANTLR 3 y no funcionarán con ANTLR 4.)

En ANTL4 se genera una clase XXXBaseListener con devoluciones de llamada para ingresar y salir de cada nodo no terminal en la gramática (por ejemplo, enterClassDeclaration())

Puede utilizar el Listener de dos maneras:

1) como un observador - Por simplemente sustituyendo los métodos para producir una salida arbitraria en relación con el texto de entrada - por ejemplo, anula enterClassDeclaration() y genera una línea para cada clase declarada en tu programa.

2) Como un transformador que usa TokenRewriteStream para modificar el texto original a medida que pasa. Para hacer esto, usa el reescritor para hacer modificaciones (agregar, eliminar, reemplazar) tokens en los métodos de devolución de llamada y usa el reescritor y el final para producir el texto modificado.

Consulte los siguientes ejemplos del libro ANTL4 para un ejemplo de cómo hacer transformaciones:

https://github.com/mquinn/ANTLR4/blob/master/book_code/tour/InsertSerialIDListener.java

y

https://github.com/mquinn/ANTLR4/blob/master/book_code/tour/InsertSerialID.java

+0

Los enlaces al repositorio de GitHub están muertos ahora. –

Cuestiones relacionadas