如何解析直到在一行中找到一个标记

How to parse until a token is found on a line by itself

我正在尝试解析以下文档:

val doc = """BEGIN
A Bunch
Of Text
With linebreaks
##
"""

这里的想法是,当我看到 ## 单独一行时,我应该认为解析结束。

我试过,使用下面的代码解析这个文档:

object MyParser extends RegexParsers {
    val begin: Parser[String] = "BEGIN"
    val lines: Parser[Seq[String]] = repsep(line, eol)
    val line: Parser[String] = """.+""".r
    val eol: Parser[Any] = "\n" | "\r\n" | "\r"
    val end: Parser[String] = "##"

    val document: Parser[Seq[String]] = 
      begin ~> lines <~ end 

}

MyParser.parseAll(MyParser.document, doc)

然而,当我尝试执行此操作时(在 Annonite 脚本中),我得到以下信息:

java.lang.NullPointerException
  scala.util.parsing.combinator.Parsers$class.rep1sep(Parsers.scala:771)
  ammonite.$file.vtt$minusparser$MyParser$.rep1sep(vtt-parser.sc:3)
  scala.util.parsing.combinator.Parsers$class.repsep(Parsers.scala:687)
  ammonite.$file.vtt$minusparser$MyParser$.repsep(vtt-parser.sc:3)
  ammonite.$file.vtt$minusparser$MyParser$.<init>(vtt-parser.sc:5)
  ammonite.$file.vtt$minusparser$MyParser$.<clinit>(vtt-parser.sc)
  ammonite.$file.vtt$minusparser$.<init>(vtt-parser.sc:22)
  ammonite.$file.vtt$minusparser$.<clinit>(vtt-parser.sc)

谁能看出我错在哪里?

错误的原因是lineeol被定义为正常的class字段val,但是在lines中使用了它们在他们的定义之前。给class字段赋值的代码在构造函数中是顺序执行的,lineeol在赋值lines时仍然是null

要解决此问题,请将 lineeol 定义为 lazy vals 或 defs,或者将它们放在代码中的 lines 之前。


解析器本身也存在一些问题。默认情况下,Scala 解析器会自动忽略所有空格,包括 EOL。考虑到没有任何标志的正则表达式 .* 包含 EOL,line 自然意味着 "the whole line until the line break",因此您根本不必分析 EOL .

其次,定义的lines解析器是贪婪的。它会愉快地消耗一切,包括最后的 ##。例如,要使其在 end 之前停止,您可以使用 not 组合器。

经过所有更改后,解析器如下所示:

object MyParser extends RegexParsers {
  val begin: Parser[String] = "BEGIN"
  val line: Parser[String] = """.+""".r
  val lines: Parser[Seq[String]] =  rep(not(end) ~> line)
  val end: Parser[String] = "##"

  val document: Parser[Seq[String]] =
    begin ~> lines <~ end
}

您也可以覆盖跳过空格的行为,并手动分析所有空格。这包括 BEGIN 之后和 ##:

之后的空格
object MyParser extends RegexParsers {
  override def skipWhitespace = false

  val eol: Parser[Any] = "\n" | "\r\n" | "\r"
  val begin: Parser[String] = "BEGIN" <~ eol
  val line: Parser[String] = """.*""".r
  val lines: Parser[Seq[String]] =  rep(not(end) ~> line <~ eol)
  val end: Parser[String] = "##"

  val document: Parser[Seq[String]] =
    begin ~> lines <~ end <~ whiteSpace

}

请注意,line 在这里定义为 .* 而不是 .+。这样,如果输入中有任何空行,解析器就不会失败。