使用ANTLR4计数令牌

mit*_*nia 3 java antlr antlr4

我需要编写一个Java程序,使用ANTLR4它,给定一个带有单个方法的源文件,可以计算变量,运算符,标点符号和保留字的数量.

我如何ANTLR4根据其类型使用计数令牌?

mit*_*nia 5

经过一些研究,基于ÖzhanDüz,我意识到我需要的是两种技术:

  • 操作符,保留字和标点符号可以使用ANTLR4词法分析器进行计数,因为这些可以在源代码中识别,而不必将它们放入上下文中.
  • 可以使用ANTLR4解析器计算变量(以及常量,方法,类......),因为识别它们需要解析和理解这些标识符出现的上下文.

为了所有需要在将来做类似事情的人,以下是我的确切方式:

1)使用ANTLR命令行工具为您的语言生成Lexer,Parser和BaseListener.有关如何操作的说明,请访问ANTLR官方网站.在这个例子中,我创建了这些用于分析Java语言的类.

2)创建一个新的Java项目.添加JavaLexer.java,JavaListener.java,JavaParser.javaJavaBaseListener.java你的项目,以及ANTLR库添加到项目的构建路径.

3)创建一个扩展JavaBaseListener基类的新类.查看JavaBaseListener.java您可以覆盖的所有方法的文件.扫描源代码的AST时,将在相应事件发生时调用每个方法(例如 - enterMethodDeclaration()每次解析器到达新方法声明时都会调用).

例如,每次找到新方法时,此侦听器都会将计数器加1:

public static final AtomicInteger count = new AtomicInteger();

/**
 * Implementation of the abstract base listener
 */
public static class MyListener extends JavaBaseListener {
    /**
     * Overrides the default callback called whenever the walker has entered a method declaration.
     * This raises the count every time a new method is found
     */
    @Override
    public void enterMethodDeclaration(JavaParser.MethodDeclarationContext ctx) {
        count.incrementAndGet();
    }
}
Run Code Online (Sandbox Code Playgroud)

4)创建一个Lexer,一个Parser,一个ParseTree和一个ParseTreeWalker:

  • Lexer - 从头到尾遍历您的代码,并将其拆分为"标记" - 标识符,文字,运算符等.每个标记都有一个名称和类型.类型列表可以在词法分析器文件的开头找到(在我们的例子中JavaLexer.java)
  • 解析器 - 使用词法分析器的输出来构建表示代码的AST(抽象语法树).除了标记源代码之外,这还允许了解每个标记出现在哪个上下文中.
  • ParseTree - 您的整个代码的AST或其子树
  • ParseTreeWalker - 允许"遍历"树的对象,这基本上意味着分层扫描代码而不是从头到尾

然后,最后,实例化您的侦听器并走ParseTree.

例如:

public static void main(String... args) throws IOException {
    JavaLexer lexer = new JavaLexer(new ANTLRFileStream(sourceFile, "UTF-8"));
    JavaParser parser = new JavaParser(new CommonTokenStream(lexer));
    ParseTree tree = parser.compilationUnit();

    ParseTreeWalker walker = new ParseTreeWalker();
    MyListener listener = new MyListener();
    walker.walk(listener, tree);
}
Run Code Online (Sandbox Code Playgroud)

这是基础.接下来的步骤取决于您想要实现的目标,这让我回到使用LexerParser之间的区别:

对于代码的基本词法分析,例如识别运算符和保留字,使用词法分析器迭代您的标记并通过检查Token.type字段确定其类型.使用此代码计算方法内保留字的数量:

private List<Token> tokenizeMethod(String method) {
    JavaLexer lex = new JavaLexer(new ANTLRInputStream(method));
    CommonTokenStream tokStream = new CommonTokenStream(lex);
    tokStream.fill();

    return tokStream.getTokens();
}


/**
 * Returns the number of reserved words inside the given method, using lexical analysis
 * @param method The method text
 */
private int countReservedWords(String method) {
    int count = 0;

    for(Token t : tokenizeMethod(method)) {
        if(t.getType() <= JavaLexer.WHILE) {
            count++;
        }
    }

    return count;
}
Run Code Online (Sandbox Code Playgroud)

对于需要解析AST的任务,比如标识变量,方法,注释等,请使用Parser.使用此代码计算方法内的变量声明的数量:

/**
 * Returns the number of variable declarations inside the given method, by parsing the method's AST
 * @param method The method text
 */
private int countVariableDeclarations(String method) {
    JavaLexer lex = new JavaLexer(new ANTLRInputStream(method));
    JavaParser parse = new JavaParser(new CommonTokenStream(lex));
    ParseTree tree = parse.methodDeclaration();

    ParseTreeWalker walker = new ParseTreeWalker();
    final AtomicInteger count = new AtomicInteger();
    walker.walk(new JavaBaseListener() {
        @Override public void enterLocalVariableDeclaration(JavaParser.LocalVariableDeclarationContext ctx) {
            count.incrementAndGet();
        }
    }, tree);

    return count.get();
}
Run Code Online (Sandbox Code Playgroud)