当出现某些语法时,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'}

现在有两件事可能会让您感到好奇:

  1. 为什么当 运行 来自您的代码时 ANTLR 会检测到语法错误,而不是来自 TestRig GUI(即使在添加 EOF 之后,GUI 仍会显示正确的树)。
  2. 为什么错误消息声称 Foo 出现在第 1 行的第 20 列,而实际上它在第 3 行?

这两个问题的答案是相同的:您提供给 ANTLR 的输入不是您的测试文件中的内容。要验证这一点,请在读入后尝试打印 fileContents。您会看到所有输入都在一行中,以 import Thing._class Foo 开头,这显然是不正确的语法。

发生这种情况的原因是 getLines 为您提供了一个没有行尾的行列表,并且 mkString 将它们连接在一起而没有任何分隔符。快速解决方法是简单地将 "\n" 作为分隔符传递给 mkString,但更好的解决方案是根本不自己读取文件。

相反,您可以通过使用 CharStreams.fromFileName 创建输入流来让 ANTLR 做到这一点。这也将消除关于 ANTLRInputStream 被弃用的警告。