本教程旨在解决使用antlr解析完整java源文件时常见的“extraneous input”错误。核心问题在于选择了不匹配文件内容的语法入口规则。我们将详细阐述如何通过使用`compilationunit`作为解析入口,并演示如何正确地获取完整的解析树和逐个令牌的详细信息,确保java代码能够被antlr准确识别和处理。
ANTLR(ANother Tool for Language Recognition)是一个强大的解析器生成器,广泛用于构建语言、工具和框架。它能够从语法文件(.g4)生成词法分析器(Lexer)和语法分析器(Parser),用于将输入的文本流转换为结构化的解析树。在处理Java源代码时,我们常常需要对整个文件进行语法分析,以实现代码分析、重构或转换等功能。
然而,初次使用ANTLR解析Java文件时,一个常见的陷阱是选择错误的起始规则,导致解析失败并抛出“extraneous input”错误。本文将深入探讨这一问题,并提供一个完整的解决方案,包括如何正确选择解析规则以及如何提取详细的令牌信息。
当尝试使用ANTLR解析一个完整的Java源文件(包含import语句、类定义、字段等)时,如果将解析器的入口规则设置为expression(),通常会遇到类似如下的错误:
line 1:0 extraneous input 'import' expecting {'boolean', 'byte', 'char', 'double', 'float', 'int', 'long', 'new', 'short', 'super', 'this', 'void', IntegerLiteral, FloatingPointLiteral, BooleanLiteral, CharacterLiteral, StringLiteral, 'null', '(', '!', '~', '++', '--', '+', '-', Identifier, '@'}这个错误信息清晰地表明,解析器在文件的第一行遇到了import关键字,但其当前期望的是一个表达式的起始元素,例如基本类型、字面量、运算符或标识符。这说明expression规则设计用于解析Java语法中的单个表达式,而不是一个完整的Java编译单元。一个完整的Java文件通常以包声明、导入语句、类或接口定义等开始,这些都不是简单的表达式。
对于一个完整的Java源文件,正确的解析入口规则通常是compilationUnit。在ANTLR的Java语法(例如Java8.g4)中,compilationUnit规则被定义为表示一个Java源文件的最高层级结构,它能够识别包声明、导入声明以及类型声明(如类、接口、枚举等)。
通过将解析器的调用从parser.expression()更改为parser.compilationUnit(),我们可以确保解析器从正确的语法层面开始分析整个文件,从而正确处理import语句、类定义及其他高级结构。
以下是一个使用ANTLR解析Java文件并获取解析树及令牌信息的完整Java示例:
import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.tree.ParseTree; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.io.InputStream; public class JavaParserTutorial { public static void main(String[] args) throws IOException { // 示例Java源代码内容 String sourceCode = "import org.antlr.v4.runtime.Lexer;\n" + "import org.antlr.v4.runtime.ParserRuleContext;\n" + "import org.antlr.v4.runtime.atn.PredictionMode;\n" + "\n" + "import java.io.File;\n" + "import java.lang.System;\n" + "import java.util.ArrayList;\n" + "import java.util.List;\n" + "import java.util.concurrent.BrokenBarrierException;\n" + "import java.util.concurrent.CyclicBarrier;\n" + "\n" + "class Test {\n" + " public static boolean profile = false;\n" + " public static boolean notree = false;\n" + " public static boolean gui = false;\n" + " public static boolean printTree = false;\n" + "}"; // 1. 初始化词法分析器和语法分析器 // 可以从文件或字符串创建CharStream // InputStream inputStream = Files.newInputStream(Paths.get("/xxx/java-antler-parser/src/main/java/Test.java")); // Java8Lexer lexer = new Java8Lexer(CharStreams.fromStream(inputStream)); Java8Lexer lexer = new Java8Lexer(CharStreams.fromString(sourceCode)); CommonTokenStream tokens = new CommonTokenStream(lexer); Java8Parser parser = new Java8Parser(tokens); // 2. 使用正确的入口规则进行解析 System.out.println("--- 解析树输出 ---"); ParseTree root = parser.compilationUnit(); System.out.println(root.toStringTree(parser)); // 打印解析树的文本表示 // 3. 提取并打印所有令牌信息 System.out.println("\n--- 令牌信息输出 ---"); // 重置词法分析器,以便重新遍历令牌流 lexer.reset(); // 重新填充令牌流,确保获取所有令牌 CommonTokenStream commonTokenStream = new CommonTokenStream(lexer); commonTokenStream.fill(); for (Token t : commonTokenStream.getTokens()) { // 获取令牌类型符号名和令牌文本 System.out.printf( "type=%-25s text='%s'%n", Java8Lexer.VOCABULARY.getSymbolicName(t.getType()), t.getText() ); } } }
代码说明:
执行上述代码后,你将得到两部分输出:
type=IMPORT text='import'
type=Identifier text='org'
type=DOT text='.'
...
type=CLASS text='class'
type=Identifier text='Test'
type=LBRACE text='{'
...
type=EOF text='' 这个列表满足了获取令牌类型和文本的需求,可以用于进一步的词法分析或工具开发。
通过本教程,我们解决了ANTLR解析Java文件时常见的“extraneous input”错误,其核心在于选择了不匹配文件内容的语法入口规则。我们强调了使用compilationUnit作为完整Java源文件的正确解析入口,并提供了详细的Java代码示例,演示了如何获取完整的解析树以及如何提取每一个令牌的类型和文本信息。掌握这些技巧将使你能够更有效地利用ANTLR进行Java代码的分析和处理。