当出现某些语法时,ANTLR 不会遍历整棵树
ANLTR not walking whole tree when certain syntax appears
我正在做一个读取各种语言源代码的项目。该项目本身是用 scala 编写的,但如果您知道 antlr,那么我所做的应该很熟悉。我在 github 上使用 scala.g4
语法为 antlr4 生成解析器、词法分析器等。我写了一个 ScalaBaseListener 的子类,它只是打印覆盖的 Enter 方法
例如
override def enterClassDef(ctx: ScalaParser.ClassDefContext): Unit = {
println(ctx.getText)
}
在我的应用程序的主体中,我试图从文件源遍历整棵树,如下所示:
import ScalaLexer._
import org.antlr.v4.runtime._
import org.antlr.v4.runtime.tree._
import scala.io.Source
object Main extends App {
val fileContents = Source.fromFile(args(0)).getLines.mkString
val charStream = new ANTLRInputStream(fileContents)
val lexer = new ScalaLexer(charStream)
val tokens = new CommonTokenStream(lexer)
val parser = new ScalaParser(tokens)
val tree = parser.compilationUnit
ParseTreeWalker.DEFAULT
.walk(new ScalaMySubclassListener(), tree)
}
我发现如果源文件是 say,只需几个 类:
class Foo {
def bar = {
1
}
def baz = 1
}
class Foo1 {
def bar = {
1
}
def baz = 1
}
我可以从程序的输出中看到树中的每一片叶子都被走过了。
但是,如果我要在文件顶部添加一个 import 语句(因为在 scala 源文件中通常会有)
import Thing._
class Foo {
def bar = {
1
}
def baz = 1
}
class Foo1 {
def bar = {
1
}
def baz = 1
}
只有 import
语句中的叶子会被遍历。文件的其余部分将被忽略。
当我使用 antlr4 GUI 解析源文件时,整个树都是可见的。
当解析树似乎被切断时,首先要做的是检查是否存在任何语法错误,因为这将是最常见的原因。由于您的代码中根本没有错误处理,这意味着任何语法错误都应该打印到 stderr。由于 none 是,显然没有任何语法错误。
但是我们不要放弃存在语法错误的想法。当涉及到 ANTLR 中的语法错误时,一个常见的陷阱是如果您的开始规则没有以 EOF
结尾。如果是这种情况,ANTLR 将简单地尝试找到语法上有效的输入前缀并忽略其余部分。也就是说,它将在第一个语法错误处停止,而不会实际产生错误消息(只要存在导致该错误的有效程序——因为许多语法接受空程序,这种情况经常发生)。果然:如果我们查看 Scala.g4
,语法中的任何地方都没有 EOF(无论如何在撰写本文时)。因此,让我们在 compilationUnit
规则的末尾添加 EOF
。现在,如果我们重新编译所有内容并再次 运行 您的代码,我们最终会得到一个语法错误:
line 1:20 mismatched input 'Foo' expecting {<EOF>, '.', ',', 'implicit', 'lazy', 'case', '@', 'override', 'abstract', 'final', 'sealed', 'private', 'protected', 'import', 'class', 'object', 'trait', 'package'}
现在有两件事可能会让您感到好奇:
- 为什么当 运行 来自您的代码时 ANTLR 会检测到语法错误,而不是来自 TestRig GUI(即使在添加
EOF
之后,GUI 仍会显示正确的树)。
- 为什么错误消息声称
Foo
出现在第 1 行的第 20 列,而实际上它在第 3 行?
这两个问题的答案是相同的:您提供给 ANTLR 的输入不是您的测试文件中的内容。要验证这一点,请在读入后尝试打印 fileContents
。您会看到所有输入都在一行中,以 import Thing._class Foo
开头,这显然是不正确的语法。
发生这种情况的原因是 getLines
为您提供了一个没有行尾的行列表,并且 mkString
将它们连接在一起而没有任何分隔符。快速解决方法是简单地将 "\n"
作为分隔符传递给 mkString
,但更好的解决方案是根本不自己读取文件。
相反,您可以通过使用 CharStreams.fromFileName
创建输入流来让 ANTLR 做到这一点。这也将消除关于 ANTLRInputStream
被弃用的警告。
我正在做一个读取各种语言源代码的项目。该项目本身是用 scala 编写的,但如果您知道 antlr,那么我所做的应该很熟悉。我在 github 上使用 scala.g4
语法为 antlr4 生成解析器、词法分析器等。我写了一个 ScalaBaseListener 的子类,它只是打印覆盖的 Enter 方法
例如
override def enterClassDef(ctx: ScalaParser.ClassDefContext): Unit = {
println(ctx.getText)
}
在我的应用程序的主体中,我试图从文件源遍历整棵树,如下所示:
import ScalaLexer._
import org.antlr.v4.runtime._
import org.antlr.v4.runtime.tree._
import scala.io.Source
object Main extends App {
val fileContents = Source.fromFile(args(0)).getLines.mkString
val charStream = new ANTLRInputStream(fileContents)
val lexer = new ScalaLexer(charStream)
val tokens = new CommonTokenStream(lexer)
val parser = new ScalaParser(tokens)
val tree = parser.compilationUnit
ParseTreeWalker.DEFAULT
.walk(new ScalaMySubclassListener(), tree)
}
我发现如果源文件是 say,只需几个 类:
class Foo {
def bar = {
1
}
def baz = 1
}
class Foo1 {
def bar = {
1
}
def baz = 1
}
我可以从程序的输出中看到树中的每一片叶子都被走过了。
但是,如果我要在文件顶部添加一个 import 语句(因为在 scala 源文件中通常会有)
import Thing._
class Foo {
def bar = {
1
}
def baz = 1
}
class Foo1 {
def bar = {
1
}
def baz = 1
}
只有 import
语句中的叶子会被遍历。文件的其余部分将被忽略。
当我使用 antlr4 GUI 解析源文件时,整个树都是可见的。
当解析树似乎被切断时,首先要做的是检查是否存在任何语法错误,因为这将是最常见的原因。由于您的代码中根本没有错误处理,这意味着任何语法错误都应该打印到 stderr。由于 none 是,显然没有任何语法错误。
但是我们不要放弃存在语法错误的想法。当涉及到 ANTLR 中的语法错误时,一个常见的陷阱是如果您的开始规则没有以 EOF
结尾。如果是这种情况,ANTLR 将简单地尝试找到语法上有效的输入前缀并忽略其余部分。也就是说,它将在第一个语法错误处停止,而不会实际产生错误消息(只要存在导致该错误的有效程序——因为许多语法接受空程序,这种情况经常发生)。果然:如果我们查看 Scala.g4
,语法中的任何地方都没有 EOF(无论如何在撰写本文时)。因此,让我们在 compilationUnit
规则的末尾添加 EOF
。现在,如果我们重新编译所有内容并再次 运行 您的代码,我们最终会得到一个语法错误:
line 1:20 mismatched input 'Foo' expecting {<EOF>, '.', ',', 'implicit', 'lazy', 'case', '@', 'override', 'abstract', 'final', 'sealed', 'private', 'protected', 'import', 'class', 'object', 'trait', 'package'}
现在有两件事可能会让您感到好奇:
- 为什么当 运行 来自您的代码时 ANTLR 会检测到语法错误,而不是来自 TestRig GUI(即使在添加
EOF
之后,GUI 仍会显示正确的树)。 - 为什么错误消息声称
Foo
出现在第 1 行的第 20 列,而实际上它在第 3 行?
这两个问题的答案是相同的:您提供给 ANTLR 的输入不是您的测试文件中的内容。要验证这一点,请在读入后尝试打印 fileContents
。您会看到所有输入都在一行中,以 import Thing._class Foo
开头,这显然是不正确的语法。
发生这种情况的原因是 getLines
为您提供了一个没有行尾的行列表,并且 mkString
将它们连接在一起而没有任何分隔符。快速解决方法是简单地将 "\n"
作为分隔符传递给 mkString
,但更好的解决方案是根本不自己读取文件。
相反,您可以通过使用 CharStreams.fromFileName
创建输入流来让 ANTLR 做到这一点。这也将消除关于 ANTLRInputStream
被弃用的警告。