6 Stimmen

Wie kann ich den Text von Token in einem CommonTokenStream mit ANTLR ändern?

Ich versuche, ANTLR zu erlernen und es gleichzeitig für ein aktuelles Projekt zu verwenden.

Ich habe den Punkt erreicht, an dem ich den Lexer auf einem Stück Code laufen lassen und ihn in einen CommonTokenStream ausgeben kann. Dies funktioniert gut, und ich habe überprüft, dass der Quelltext in die entsprechenden Token aufgebrochen wird.

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

Ich habe es zum Beispiel 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, den Text aller Identifier-Token auf das String-Literal "V" zu setzen.

  1. Warum werden meine Änderungen am Text des Tokens nicht berücksichtigt, wenn ich tokens.toString() aufrufe?

  2. Woher soll ich die verschiedenen Token-Typ-IDs kennen? Ich bin mit meinem Debugger durchgegangen und habe gesehen, dass die ID für die IDENTIFIER-Token "4" ist (daher meine Konstante oben). Aber woher hätte ich das sonst wissen sollen? Gibt es eine andere Möglichkeit der Zuordnung von Token-Typ-IDs zum Token-Namen?


EDIT:

Eine Sache, die mir wichtig ist, ist, dass ich möchte, dass die Token ihre ursprüngliche Start- und Endposition behalten. Das heißt, ich möchte nicht, dass sie ihre neuen Positionen widerspiegeln, wenn die Variablennamen in "V" geändert werden. Auf diese Weise weiß ich, wo sich die Zeichen im ursprünglichen Ausgangstext befanden.

0 Stimmen

Ich frage mich nur - ist es eine Voraussetzung, dass Sie ANTLR für diese verwenden?

4voto

Bart Kiers Punkte 160101

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

Nehmen wir an, Sie analysieren eine Zeichenkette, die aus Zahlen und Zeichenfolgen besteht, die durch Kommas getrennt sind. Eine Grammatik würde wie folgt aussehen:

grammar Foo;

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

value
  :  Number
  |  String
  ;

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

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

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

Das alles sollte Ihnen bekannt vorkommen. Nehmen wir an, Sie möchten eckige Klammern um alle ganzzahligen Werte setzen. So geht's:

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 am Anfang und fügte eine Rewrite-Regel hinzu (alles nach der -> ) nach der Number im value Parser-Regel.

Um das Ganze zu testen, kompilieren Sie die Klasse und führen Sie sie aus:

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]

4voto

Pat Niemeyer Punkte 5040

In ANTLR 4 gibt es eine neue Funktion, die Parse-Baum-Listener und TokenStreamRewriter (beachten Sie den Namensunterschied) verwendet, 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 mit Callbacks für das Betreten und Verlassen jedes nicht-terminalen Knotens in der Grammatik generiert (z.B. enterClassDeclaration() ).

Sie können den Listener auf zwei Arten verwenden:

  1. Als Beobachter - Durch einfaches Überschreiben der Methoden, um beliebige Ausgaben in Bezug auf den Eingabetext zu erzeugen - z.B. enterClassDeclaration() überschreiben und eine Zeile für jede in Ihrem Programm deklarierte Klasse ausgeben.

  2. Als Transformator, der TokenRewriteStream verwendet, um den Originaltext zu ändern, während er durchläuft. Dazu verwenden Sie den Rewriter, um Änderungen (Hinzufügen, Löschen, Ersetzen) an Token in den Callback-Methoden vorzunehmen, und Sie verwenden den Rewriter und das Ende, um den geänderten Text auszugeben.

Die folgenden Beispiele aus dem ANTL4-Buch zeigen, wie man Transformationen durchführt:

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

3voto

chollida Punkte 7656

Das andere Beispiel für das Ändern des Textes im Lexer funktioniert gut, wenn Sie den Text in allen Situationen global ersetzen wollen, aber Sie wollen oft nur den Text eines Tokens in bestimmten Situationen ersetzen.

Die Verwendung des TokenRewriteStream ermöglicht Ihnen die Flexibilität, den Text nur in bestimmten Kontexten zu ändern.

Dies kann mit einer Unterklasse der Token-Stream-Klasse geschehen, die Sie verwendet haben. Anstelle der Verwendung der CommonTokenStream Klasse können Sie die TokenRewriteStream .

Sie würden also den TokenRewriteStream den Lexer konsumieren lassen und dann Ihren Parser ausführen.

In Ihrer Grammatik würden Sie die Ersetzung normalerweise so vornehmen:

/** 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, das wir mit dem Text float abgeglichen haben, ersetzt. Die Ortsangaben bleiben erhalten, aber der Text, der "passt", wurde geändert.

Um Ihren Token-Stream zu überprüfen, verwenden Sie denselben Code wie zuvor.

0voto

Ich habe die Java-Beispielgrammatik verwendet, um ein ANTLR-Skript zur Verarbeitung einer R.java Datei und schreiben Sie alle Hex-Werte in einer dekompilierten Android-App mit Werten der Form R.string.* , R.id.* , R.layout.* und so weiter.

Der Schlüssel ist die Verwendung von TokenStreamRewriter um die Token zu verarbeiten und dann das Ergebnis auszugeben.

Das Projekt (Python) heißt WiederherstellenR

Der geänderte ANTLR-Listener zum Umschreiben

Ich parse mit einem Listener, um die R.java-Datei einzulesen und ein Mapping von Integer zu String zu erstellen und dann die Hex-Werte durch ein Ich parse die Java-Dateien der Programme mit einem anderen Listener, der eine Rewriter-Instanz enthält.

class RValueReplacementListener(ParseTreeListener):
    replacements = 0
    r_mapping = {}
    rewriter = None

    def __init__(self, tokens):
        self.rewriter = TokenStreamRewriter(tokens)

    // Code removed for the sake of brevity

    # Enter a parse tree produced by JavaParser#integerLiteral.
    def enterIntegerLiteral(self, ctx:JavaParser.IntegerLiteralContext):
        hex_literal = ctx.HEX_LITERAL()
        if hex_literal is not None:
            int_literal = int(hex_literal.getText(), 16)
            if int_literal in self.r_mapping:
                # print('Replace: ' + ctx.getText() + ' with ' + self.r_mapping[int_literal])
                self.rewriter.replaceSingleToken(ctx.start, self.r_mapping[int_literal])
                self.replacements += 1

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X