6

我正在尝试学习 ANTLR,同时将其用于当前项目。

我已经到了可以在一段代码上运行词法分析器并将其输出到 CommonTokenStream 的地步。这工作正常,我已经验证源文本被分解成适当的标记。

现在,我希望能够修改此流中某些标记的文本,并显示现在修改的源代码。

例如我试过:

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

我正在尝试将所有标识符标记的文本设置为字符串文字“V”。

  1. 为什么当我调用 tokens.toString() 时我对令牌文本的更改没有反映出来?

  2. 我怎么知道各种令牌类型 ID?我带着我的调试器走过去,发现 IDENTIFIER 令牌的 ID 是“4”(因此我的常量在顶部)。但否则我怎么会知道呢?是否有其他方法可以将令牌类型 ID 映射到令牌名称?


编辑:

对我来说很重要的一件事是我希望标记具有其原始的开始和结束字符位置。也就是说,我不希望他们通过将变量名称更改为“V”来反映他们的新位置。这样我就知道标记在原始源文本中的位置。

4

4 回答 4

4

ANTLR 在它的语法文件中有办法做到这一点。

假设您正在解析一个由数字和逗号分隔的字符串组成的字符串。语法看起来像这样:

grammar Foo;

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

value
  :  Number
  |  String
  ;

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

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

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

这对你来说应该很熟悉。假设您想在所有整数值周围加上方括号。以下是如何做到这一点:

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

如您所见,我options在顶部添加了一些,并在解析器规则之后添加了重写规则(在 之后的所有内容->) 。Numbervalue

现在来测试一下,编译并运行这个类:

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

产生:

parsing: 12, "34", 56, "a\"b", 78
tokens: [12],"34",[56],"a\"b",[78]
于 2010-02-09T13:50:15.697 回答
4

在 ANTLR 4 中有一个使用解析树侦听器和 TokenStreamRewriter(注意名称差异)的新工具,可用于观察或转换树。(建议 TokenRewriteStream 的回复适用于 ANTLR 3,不适用于 ANTLR 4。)

在 ANTL4 中,会为您生成一个 XXXBaseListener 类,其中包含用于进入和退出语法中每个非终端节点的回调(例如 enterClassDeclaration() )。

您可以通过两种方式使用侦听器:

  1. 作为观察者 - 通过简单地重写方法以产生与输入文本相关的任意输出 - 例如重写 enterClassDeclaration() 并为程序中声明的每个类输出一行。

  2. 作为转换器,使用 TokenRewriteStream 在原始文本通过时对其进行修改。为此,您使用重写器在回调方法中进行修改(添加、删除、替换)标记,并使用重写器和 end 输出修改后的文本。

有关如何进行转换的示例,请参见 ANTL4 书中的以下示例:

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

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

于 2015-04-05T17:19:59.303 回答
3

如果您想在所有情况下全局替换文本,则另一个在词法分析器中更改文本的给定示例效果很好,但是您通常只想在某些情况下替换标记的文本。

使用 TokenRewriteStream 可以让您灵活地仅在某些上下文中更改文本。

这可以使用您正在使用的令牌流类的子类来完成。CommonTokenStream您可以使用TokenRewriteStream.

所以你会让 TokenRewriteStream 使用词法分析器,然后你会运行你的解析器。

在您的语法中,您通常会像这样进行替换:

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

在这里,我们用文本浮点数替换了我们匹配的令牌 int。位置信息被保留,但它“匹配”的文本已更改。

在您使用与以前相同的代码之后检查您的令牌流。

于 2010-02-09T18:36:21.360 回答
0

我已经使用示例 Java 语法创建了一个 ANTLR 脚本来处理R.java文件,并在反编译的 Android 应用程序中使用 、 等形式的值重写所有十六R.string.*进制R.id.*R.layout.*

密钥TokenStreamRewriter用于处理令牌,然后输出结果。

该项目(Python)称为RestoreR

用于重写的修改后的 ANTLR 监听器

我使用侦听器解析以读取 R.java 文件并创建从整数到字符串的映射,然后替换十六进制值,因为我使用包含重写器实例的不同侦听器解析程序 java 文件。

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
于 2019-11-15T10:50:59.810 回答