Как анализировать, пока токен не найден в строке сам по себе - PullRequest
0 голосов
/ 05 мая 2018

Я пытаюсь разобрать следующий документ:

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 голосов
/ 05 мая 2018

Причина ошибки в том, что line и eol определены как обычные поля класса val s, но они используются в lines до их определения. Код, который присваивает значения полям класса, выполняется последовательно в конструкторе, и line и eol оба по-прежнему null, когда присваивается lines.

Чтобы решить эту проблему, определите line и eol как lazy val s или def s, или просто поместите их перед lines в коде.


Сам парсер также имеет некоторые проблемы. По умолчанию парсеры Scala автоматически игнорируют все пробелы, включая EOL. Учитывая, что регулярное выражение .* без каких-либо флагов не включает в себя EOL, line естественно означает "всю строку до разрыва строки", поэтому вам не нужно анализировать 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 определяется здесь как .* вместо .+. Таким образом, парсер не выйдет из строя, если на входе есть пустые строки.

...