2010-02-09 2 views
5

Ich versuche ANTLR zu lernen und gleichzeitig für ein aktuelles Projekt zu verwenden.Wie kann ich den Text von Tokens in einem CommonTokenStream mit ANTLR ändern?

Ich bin zu dem Punkt gekommen, wo ich den Lexer auf einem Stück Code ausführen und es an einen CommonTokenStream ausgeben kann. Dies funktioniert einwandfrei, und ich habe überprüft, dass der Quelltext in die entsprechenden Token aufgeteilt wird.

Jetzt möchte ich in der Lage sein, den Text bestimmter Token in diesem Stream zu ändern und den jetzt geänderten Quellcode anzuzeigen.

Zum Beispiel habe ich versucht:

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()); 
    } 
} 

Ich versuche, alle Identifier Token Text auf die Zeichenfolge zu setzen wörtliche "V".

  1. Warum werden meine Änderungen am Token-Text nicht wiedergegeben, wenn ich tokens.toString() aufruft?

  2. Wie soll ich die verschiedenen Token Type IDs kennen? Ich ging mit meinem Debugger durch und sah, dass die ID für die IDENTIFIER Tokens "4" war (daher meine Konstante an der Spitze). Aber wie hätte ich das sonst wissen sollen? Gibt es eine andere Möglichkeit, Token-Typ-IDs dem Token-Namen zuzuordnen?


EDIT:

Eine Sache, die mir wichtig ist, ist wünsche ich für die Tokens ihren ursprünglichen Start- und Zielzeichenpositionen haben. Das heißt, ich möchte nicht, dass sie ihre neuen Positionen mit den auf "V" geänderten Variablennamen widerspiegeln. So weiß ich, wo sich die Token im ursprünglichen Quelltext befanden.

+0

Einfach fragen - ist es erforderlich, dass Sie ANTLR verwenden dafür? – cowboydan

Antwort

5

ANTLR hat eine Möglichkeit, dies in seiner Grammatikdatei zu tun.

Angenommen, Sie analysieren einen String, der aus Zahlen und Strings besteht, die durch Kommas voneinander getrennt sind. Eine Grammatik würde so aussehen:

grammar Foo; 

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

value 
    : Number 
    | String 
    ; 

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

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

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

Dies sollte Ihnen allen bekannt vorkommen. Nehmen wir an, Sie möchten eckige Klammern um alle ganzzahligen Werte legen. Hier ist, wie das zu tun:

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();} 
    ; 

Wie Sie sehen, habe ich einige options an der Spitze hinzugefügt, und hat eine Rewrite-Regel (alles nach dem ->) nach dem Number im value Parser-Regel.

Nun, es zu testen alle, kompilieren und diese Klasse laufen:

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()); 
    } 
} 

, die produziert:

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

Die andere gegebenen Beispiel das Ändern des Textes in der Lexer, wenn Sie funktioniert gut zu wollen, Ersetzen Sie den Text global in allen Situationen, jedoch möchten Sie den Text eines Tokens in bestimmten Situationen oft nur ersetzen.

Mit dem TokenRewriteStream können Sie den Text nur in bestimmten Kontexten ändern.

Dies kann mit einer Unterklasse der von Ihnen verwendeten Token-Stream-Klasse erfolgen. Anstatt die Klasse CommonTokenStream zu verwenden, können Sie TokenRewriteStream verwenden.

Sie müssten also den TokenRewriteStream den Lexer konsumieren und dann würden Sie Ihren Parser laufen lassen.

In Grammatik der Regel würden Sie den Austausch wie folgt tun:

/** 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] 
; 

Hier haben wir das Token int ersetzt, die wir mit dem Text Schwimmer abgestimmt. Die Standortinformationen bleiben erhalten, aber der Text, mit dem sie übereinstimmt, wurde geändert.

Um Ihren Token-Stream zu überprüfen, nachdem Sie den gleichen Code wie zuvor verwenden würden.

+0

Danke für die Info. Hast du eine Idee, warum setText auf den einzelnen Tokens nicht funktioniert hat? – mmcdole

+0

@Simucal, versuchen Sie es mit einem 'TokenRewriteStream' statt einem' CommonTokenStream'? –

+0

@Simucal, Ich habe nicht in die Java-Quelle für Antlr gegraben, wie ich normalerweise C++ verwende, aber ich würde mir vorstellen, dass Sie eine Kopie des Token-Stream und nicht den tatsächlichen Stream ändern. – chollida

2

In ANTLR 4 gibt es eine neue Einrichtung, die Parse Tree Listeners und TokenStreamRewriter verwendet (beachten Sie den Namensunterschied), die verwendet werden können, um Bäume zu beobachten oder zu transformieren. (Die Antworten, die TokenRewriteStream vorschlagen, gelten für ANTLR 3 und funktionieren nicht mit ANTLR 4.)

In ANTL4 wird eine XXXBaseListener-Klasse für Sie mit Callbacks für das Eingeben und Beenden von Nicht-Terminal-Knoten in der Grammatik generiert (zB enterClassDeclaration()).

Sie die Zuhörer auf zwei Arten verwendet werden:

1) Als Beobachter - Durch einfaches Überschreiben der Methoden beliebige Ausgabe zu erzeugen, um die Eingabe von Text in Verbindung stehend - z.B. überschreiben enterClassDeclaration() und geben eine Zeile für jede in Ihrem Programm deklarierte Klasse aus.

2) Als ein Transformator, der TokenRewriteStream verwendet, um den ursprünglichen Text beim Durchlaufen zu ändern. Dazu verwenden Sie den Rewriter, um in den Callback-Methoden Tokens zu ändern (hinzufügen, löschen, ersetzen) und Sie verwenden den Rewriter und das Ende, um den geänderten Text auszugeben.

Siehe die folgenden Beispiele aus dem ANTL4 Buch für ein Beispiel, wie Transformationen zu tun:

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

und

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

+0

Die Links zu GitHub Repo sind jetzt tot. –

Verwandte Themen