Compiler construction 如何使用ANTLR修改CommonTokenStream中的令牌文本?

Compiler construction 如何使用ANTLR修改CommonTokenStream中的令牌文本?,compiler-construction,antlr,antlr3,lexical-analysis,Compiler Construction,Antlr,Antlr3,Lexical Analysis,我试图学习ANTLR,同时将其用于当前项目 我已经到了可以在代码块上运行lexer并将其输出到CommonTokenStream的地步。这工作正常,并且我已经验证了源文本被分解成适当的标记 现在,我希望能够修改这个流中某些令牌的文本,并显示现在修改的源代码 例如,我尝试过: import org.antlr.runtime.*; import java.util.*; public class LexerTest { public static final int IDENTIFIER

我试图学习ANTLR,同时将其用于当前项目

我已经到了可以在代码块上运行lexer并将其输出到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());
    }  
}
import org.antlr.runtime.*;
导入java.util.*;
公共类词汇测试
{
公共静态最终整数标识符_TYPE=4;
公共静态void main(字符串[]args)
{
String input=“publicstaticvoidmain(String[]args){int myVar=0;}”;
CharStream cs=新的AntlStringStream(输入);
JavaLexer-lexer=新的JavaLexer(cs);
CommonTokenStream令牌=新的CommonTokenStream();
setTokenSource(lexer);
int size=tokens.size();
对于(int i=0;i
我正在尝试将所有标识符标记的文本设置为字符串文字“V”

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

  • 我应该如何知道各种令牌类型ID?我带着调试器浏览了一遍,发现标识符标记的ID是“4”(因此我的常量位于顶部)。但如果不是这样,我怎么会知道呢?是否有其他方法将令牌类型ID映射到令牌名称


  • 编辑:


    对我来说重要的一点是,我希望代币有其原始的开始和结束字符位置。也就是说,我不希望它们在变量名更改为“V”时反映它们的新位置。这就是我知道标记在原始源文本中的位置。

    ANTLR在其语法文件中有一种方法可以做到这一点

    假设您正在解析一个由数字和逗号分隔的字符串组成的字符串。语法如下所示:

    grammar Foo;
    
    parse
      :  value ( ',' value )* EOF
      ;
    
    value
      :  Number
      |  String
      ;
    
    String
      :  '"' ( ~( '"' | '\\' ) | '\\\\' | '\\"' )* '"'
      ;
    
    Number
      :  '0'..'9'+
      ;
    
    Space
      :  ( ' ' | '\t' ) {skip();}
      ;
    
    /** 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]
    ;
    
    这对你来说应该很熟悉。假设您希望将所有整数值用方括号括起来。以下是如何做到这一点:

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

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

    如果您希望在所有情况下全局替换文本,那么在lexer中更改文本的另一个给定示例效果很好,但是您通常只希望在某些情况下替换令牌的文本

    使用TokenRewriteStream允许您仅在特定上下文中灵活地更改文本

    这可以使用您使用的令牌流类的子类来完成。您可以使用
    TokenRewriteStream
    而不是使用
    CommonTokenStream

    因此,您可以让令牌流使用lexer,然后运行解析器

    在你的语法中,你通常会这样做:

    grammar Foo;
    
    parse
      :  value ( ',' value )* EOF
      ;
    
    value
      :  Number
      |  String
      ;
    
    String
      :  '"' ( ~( '"' | '\\' ) | '\\\\' | '\\"' )* '"'
      ;
    
    Number
      :  '0'..'9'+
      ;
    
    Space
      :  ( ' ' | '\t' ) {skip();}
      ;
    
    /** 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]
    ;
    
    这里,我们用文本float替换了匹配的标记int。位置信息被保留,但其“匹配”的文本已被更改


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

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

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

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

    1) 作为观察者—只需重写方法以生成与输入文本相关的任意输出—例如,重写enterClassDeclaration(),并为程序中声明的每个类输出一行

    2) 作为转换器,使用令牌重写流在原始文本通过时修改原始文本。为此,您可以使用重写器在回调方法中修改(添加、删除、替换)标记,并使用重写器和结束输出修改后的文本

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


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

    关键是使用
    TokenStreamRewriter
    处理令牌,然后输出结果

    该项目(Python)称为

    修改后的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
    

    谢谢你的信息。你知道为什么调用单个令牌上的SETTY不起作用吗?@ SimuCUL,你尝试使用<代码> TokReReWestRestRAM< <代码>代替“<代码>通俗英语流> /Cord>?@ Simucal,我还没有为ANTLR挖掘java源代码,因为我通常使用C++,但我可以想象,您正在修改令牌流的副本,而不是实际的流。只是想知道-您是否需要为此使用ANTLR?到GitHub repo的链接现在已经不存在了。我在这里找到了一个示例: