使用 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.java
、JavaListener.java
、JavaParser.java
和 JavaBaseListener.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);
}
这是基础。接下来的步骤取决于您想要实现的目标,这让我回到了使用 Lexer 和 Parser:[=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();
}
我需要编写一个 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.java
、JavaListener.java
、JavaParser.java
和 JavaBaseListener.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);
}
这是基础。接下来的步骤取决于您想要实现的目标,这让我回到了使用 Lexer 和 Parser:[=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();
}