Подтвердить что ты не робот

Scala комбинаторы парсеров и текст с разделителями строк

Я пишу грамматику комбинатора парсера Scala, которая читает списки слов с разделителями строк, в которых списки разделены одной или несколькими пустыми строками. Учитывая следующую строку:

cat
mouse
horse

apple
orange
pear

Я хотел бы вернуть его List(List(cat, mouse, horse), List(apple, orange, pear)).

Я написал эту основную грамматику, которая рассматривает списки слов как слова, разделенные линией. Обратите внимание, что мне пришлось переопределить определение whitespace по умолчанию.

import util.parsing.combinator.RegexParsers

object WordList extends RegexParsers {

    private val eol = sys.props("line.separator")

    override val whiteSpace = """[ \t]+""".r

    val list: Parser[List[String]] = repsep( """\w+""".r, eol)

    val lists: Parser[List[List[String]]] = repsep(list, eol)

    def main(args: Array[String]) {
        val s =
          """cat
            |mouse
            |horse
            |
            |apple
            |orange
            |pear""".stripMargin

        println(parseAll(lists, s))
    }
}

Это неправильно обрабатывает пустые строки как пустые списки слов, т.е. возвращает

[8.1] parsed: List(List(cat, mouse, horse), List(), List(apple, orange, pear))

(Обратите внимание на пустой список посередине.)

Я могу поместить необязательный конец строки в конце каждого списка.

val list: Parser[List[String]] = repsep( """\w+""".r, eol) <~ opt(eol)

Это обрабатывает случай, когда есть одна пустая строка между списками, но имеет ту же проблему с несколькими пустыми строками.

Я попытался изменить определение lists, чтобы разрешить несколько ограничителей конца строки:

val lists:Parser[List[List[String]]] = repsep(list, rep(eol))

но это зависает на указанном выше входе.

Какова правильная грамматика, которая будет обрабатывать несколько пустых строк в качестве разделителей?

4b9b3361

Ответ 1

Вы должны попытаться установить skipWhitespace на false вместо переопределения определения пробела. Проблема с пустым списком вызвана тем, что repsep не использует разрыв строки в конце списка. Вместо этого вы должны разобрать разрыв строки (или, возможно, конец ввода) после каждого элемента:

import util.parsing.combinator.RegexParsers

object WordList extends RegexParsers {

  private val eoi = """\z""".r // end of input
  private val eol = sys.props("line.separator")
  private val separator = eoi | eol
  private val word = """\w+""".r

  override val skipWhitespace = false

  val list: Parser[List[String]] = rep(word <~ separator)

  val lists: Parser[List[List[String]]] = repsep(list, rep1(eol))

  def main(args: Array[String]) {
    val s =
      """cat
        |mouse
        |horse
        |
        |apple
        |orange
        |pear""".stripMargin

    println(parseAll(lists, s))
  }

}

Затем опять комбинаторы парсеров здесь немного перегружены. Вы можете получить практически то же самое (но с массивами вместо списков) с чем-то гораздо более простым:

s.split("\n{2,}").map(_.split("\n"))