使用 ANTLR4 计算令牌

Counting tokens using ANTLR4

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

如何使用 ANTLR4 根据令牌的类型计算令牌?

您可以像这样使用 hashmap 来跟踪所有单词类型

@header {
import java.util.HashMap;
}

@members {
// Map variable name to Integer object holding value 
HashMap memory = new HashMap();
}

Identifier 
:   IdentifierNondigit(  IdentifierNondigit |   Digit )* {
    if(memory.containsKey(getText())){
        memory.put(getText(),(((Integer)memory.get(getText()))+1));     
    }
    else {
        memory.put(getText(),1);
    }
    System.out.println(getText()+" : "+memory.get(getText()));
} 
// { getText().length()<=3}?{ String str=getText(); while(str.length()<=3){ str=str+str;} setText(str);}
    |   IdentifierNondigit (   IdentifierNondigit |   Digit)* 
    ;

像这样,代替 getToken(),您可以直接说出 "reserved" 键并在每次递增后存储计数

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

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

为了所有将来需要做类似事情的人,我就是这样做的:

1) 使用 ANTLR 命令行工具为您的语言生成 Lexer、Parser 和 BaseListener。可以在 ANTLR 官方网站上找到有关如何操作的说明。在这个例子中,我创建了这些 classes 用于分析 Java 语言。

2) 创建一个新的 Java 项目。将 JavaLexer.javaJavaListener.javaJavaParser.javaJavaBaseListener.java 添加到您的项目,并将 ANTLR 库添加到您的项目的构建路径。

3) 创建一个新的 class 扩展 JavaBaseListener 基础 class。查看 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();
    }
}

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

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

最后,实例化您的侦听器并遍历 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);
}

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

对于代码的基本词法分析,例如识别运算符和保留字,请使用词法分析器迭代标记并通过检查 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;
}

对于需要解析 AST 的任务,例如识别变量、方法、注释等,请使用解析器。使用此代码计算方法内变量声明的数量:

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