如何解析直到在一行上找到一个令牌

时间:2018-05-04 21:22:05

标签: scala parser-combinators

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

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)

谁能看到我出错的地方?

1 个答案:

答案 0 :(得分:1)

错误的原因是lineeol被定义为普通类字段val,但它们在定义之前在lines中使用。将值分配给类字段的代码在构造函数中按顺序执行,lineeolnull分配时仍为lines

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

解析器本身也存在一些问题。默认情况下,Scala解析器会自动忽略所有空格,包括EOL。考虑到没有任何标志的正则表达式.* 包含EOL,line自然意味着&#34;整行直到换行符#34;所以你不要#39 ; t必须分析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定义为.*而不是.+。像这样,如果输入中有任何空行,解析器就不会失败。