将组合子解析器的列表/序列转换为单个

时间:2011-10-08 21:19:27

标签: scala parser-combinators

我有一个值列表,我可以从中构建一个解析器列表,它通过映射依赖于这些值(参见示例)。那么我想做的是通过连接将解析器列表转换为单个解析器。

一种可能性是使用foldLeft~

parsers.foldLeft(success(Nil)){case (ps,p) => rs ~ p ^^ {case xs ~ x => x ::xs}} ^^ (_.reverse)

效率这么高吗?

我不知道组合器解析器是如何工作的;会有一个深度为列表长度的调用堆栈吗?因此,我可能会遇到很长时间串联的SO错误吗?

更好的方式

是否有更不可读的方式?

实施例

假设您有一个包含两行的文件。第一行包含n个整数x_1到x_n。第二行包含根据第一行属于组的x_1 + x_2 + ... x_n整数。我想从第一行获取整数序列并创建n个解析器p_1到p_n,其中p_i解析x_i整数。

假设我有第一行的整数列表l = List(1,2,3)。对于每个整数n,我创建一个解析n整数的解析器:parsers = l.map(repN(_,integer))

2 个答案:

答案 0 :(得分:5)

您所描述的内容(以及您在foldLeft~的实施中或多或少重新发明的内容)实际上是Haskell的sequence对于monad(实际上您只需要一个应用仿函数,但这与此无关)。 sequence获取monadic值列表并返回monadic值列表。 Parser是一个monad,因此sequence Parser会将List[Parser[A]]更改为Parser[List[A]]

Scalaz为您提供了sequence,但我不知道是否有一种很好的方法来获取Applicative的必要Parser实例。幸运的是,你可以轻松地自己动手(我直接翻译the Haskell definition):

import scala.util.parsing.combinator._

object parser extends RegexParsers {
  val integer = """\d+""".r

  val counts = List(1, 2, 3)
  val parsers = counts.map(repN(_, integer))

  val line = parsers.foldRight(success(Nil: List[List[String]])) {
    (m, n) => for { x <- m ; xs <- n } yield (x :: xs)
  }

  def apply(s: String) = parseAll(line, s)
}

根据需要,这为List(List(1), List(2, 3), List(4, 5, 6))提供了parser("1 2 3 4 5 6")

(请注意,我在这里使用RegexParsers作为一个方便的完整示例,但该方法更常用。)

如果我们贬低for理解,那么发生的事情可能会更清楚一些:

val line = parsers.foldRight(success(Nil: List[List[String]])) {
  (current, acc) => current.flatMap(x => acc.map(x :: _))
}

我们可以将flatMap写为into,将map写为^^

val line = parsers.foldRight(success(Nil: List[List[String]])) {
  (current, acc) => current into (x => acc ^^ (x :: _))
}

这与你的表述相差不远,只是我们使用了正确的折叠而不是倒转,并且没有构建并打破~


关于效率:我们的两个实现都会导致令人不快的调用堆栈。根据我的经验,这只是Scala解析器组合器的生活现实。引用another Stack Overflow answer,例如:

  

Scala的解析器组合器效率不高。他们不是   旨在成为。它们适合做相对较小的小任务   小投入。

我的sequence - y方法解决了问题中“更具可读性”的部分,几乎可以肯定是使用Scala解析器组合器解决问题的最简洁方法。它的效率略高于您的实施,对于几千个左右的团体来说应该没问题。如果您需要处理更多内容,则必须在scala.util.parsing.combinator之外查看。我建议如下:

def parse(counts: Seq[Int], input: String): Option[Seq[Seq[Int]]] = {
  val parsed = try {
    Some(input.split(" ").map(_.toInt))
  } catch {
    case _ : java.lang.NumberFormatException => None
  }

  parsed.flatMap { ints =>
    if (ints.length != counts.sum) None
    else Some(counts.foldLeft((Seq.empty[Seq[Int]], ints)) {
      case ((collected, remaining), count) => {
        val (m, n) = remaining.splitAt(count)
        (m.toSeq +: collected, n)
      }
    }._1.reverse)
  }
}

没有保证,但在我的系统上,它不会在具有100k整数组的行上溢出。


答案 1 :(得分:1)

您是否考虑过使用RegexParsersscala.util.parsing.combinator)?然后你可以使用正则表达式作为解析器,它可以非常快速地计算并且易于编写。

例如,如果您使用解析器组合器来解析简单算术的AST,则可以使用正则表达式来解释引用对象的标记,以便您可以解析appleList.size + 4之类的表达式。

这是一个相当简单的例子,但它显示了解析器组合器如何组合正则表达式。

object MyParser extends RegexParsers {
  val regex1 = """[abc]*""".r
  val regex2 = """[def]*""".r
  val parse = regex1 ~ regex2

  def apply(s: String) = parseAll(parse, s)
}