如何动态地组合Scala的解析器组合器功能?

3

我正在寻找一种动态组合Scala解析器组件的函数。例如,如果我想静态地完成这个任务,我可以编写以下代码:

def aDirective: Parser[String] = "a" ^^ { case _ => "a" }
def bDirective: Parser[String] = "b" ^^ { case _ => "b" }

def combinedDirective: Parser[List[String]] =
  aDirective ~ bDirective ^^ { case a ~ b => List(a, b) }

但是,我希望能够以动态方式编写此代码,以生成解析器的组合。

例如:

def aDirective: Parser[String] = "a" ^^ { case _ => "a" }
def bDirective: Parser[String] = "b" ^^ { case _ => "b" }

def combinedDirective: Parser[List[String]] =
  combine(List(aDirective, bDirective))

def combine(parsers: List[Parser[T]): Parser[List[T]] = ???

我认为我需要从一个解析器列表转换为结果列表的解析器。因此,我尝试编写了一个名为“combine”的函数的签名。
目前,我无法想出如何实现“combine”函数。无论我怎么尝试,似乎都存在一些问题,我暂时想不出如何解决。例如,如何构建折叠的初始解析器?我已经尝试过各种foldLeft和reduceLeft结构的实验,但似乎总是无法达到预期的效果。
我正在使用Scala 2.11。有什么想法吗?
1个回答

3

这是一个序列化操作,而Scalaz提供了一种快捷方式(通常情况下,你不需要使用Scalaz的显式实例定义模板,但这是一个特殊情况):

import scala.util.parsing.combinator.RegexParsers
import scalaz._, Scalaz._

object MyParser extends RegexParsers {
  implicit val pm = std.util.parsing.combinator.parser.parserMonad(this)

  def aDirective: Parser[String] = "a" ^^ { case _ => "a" }
  def bDirective: Parser[String] = "b" ^^ { case _ => "b" }

  def combine[T](parsers: List[Parser[T]]): Parser[List[T]] = parsers.sequenceU

  def combinedDirective: Parser[List[String]] =
    combine(List(aDirective, bDirective))
}

接下来:

scala> MyParser.parseAll(MyParser.combinedDirective, "ab")
res0: MyParser.ParseResult[List[String]] = [1.3] parsed: List(a, b)

你可以使用fold自定义它:
import scala.util.parsing.combinator.RegexParsers

object MyParser extends RegexParsers {
  def aDirective: Parser[String] = "a" ^^ { case _ => "a" }
  def bDirective: Parser[String] = "b" ^^ { case _ => "b" }

  def combine[T](parsers: List[Parser[T]]): Parser[List[T]] =
    parsers.foldRight(success(List.empty[T])) {
      case (p, acc) => for {
        pRes   <- p
        accRes <- acc
      } yield pRes :: accRes
    }

  def combinedDirective: Parser[List[String]] =
    combine(List(aDirective, bDirective))
}

它的功能完全相同。诀窍就是正确地设置基础,它需要是一个解析器,总是以空列表作为其值成功。


更新:如果您正在定义一个类而不是对象,则上述Scalaz方法将无法正常工作(由于许多奇怪的原因——简而言之,this不够稳定)。不过,您可以轻松地定义自己的monad实例:

class MyParser extends RegexParsers {
  implicit val pm = new Monad[Parser] {
    def point[A](a: => A): Parser[A] = success(a)
    def bind[A, B](fa: Parser[A])(f: A => Parser[B]): Parser[B] = fa flatMap f
  }

  def aDirective: Parser[String] = "a" ^^ { case _ => "a" }
  def bDirective: Parser[String] = "b" ^^ { case _ => "b" }

  def combine[T](parsers: List[Parser[T]]): Parser[List[T]] = parsers.sequenceU

  def combinedDirective: Parser[List[String]] =
    combine(List(aDirective, bDirective))
}

在这里使用sequence,你实际上不需要一个单子实例,只需要一个应用函子即可,但是定义实际上更加方便,并且单子实例可能在其他情况下很有用。


对于您提供的 Scalaz 示例,我遇到了一些编译错误。我正在使用 Scala 2.11 和 Scalaz 7.0.6。您和我的唯一区别是,我使用的是 class 而不是 object,但我不确定这是否有影响?我看到的编译错误是: - adamretter
错误:(14,71)未找到隐式:scalaz.Unapply [scalaz.Applicative,TestParser.this.Parser [T]]。 无法将类型TestParser.this.Parser [T]解构为由类型类scalaz.Applicative分类的M [_]类型构造函数。 编译“implicitly [scalaz.Applicative [type constructor]]”以检查类型类是否定义,并查看对象Unapply中的隐式内容,它仅涵盖常见类型“形状”。 def combine [T](parsers:List [Parser [T]]):Parser [List [T]] = parsers.sequenceU - adamretter
错误:(14, 71) 方法sequenceU 没有足够的参数:(隐式 G: scalaz.Unapply[scalaz.Applicative,TestParser.this.Parser[T]])G.M[List[G.A]]。 未指定值参数 G。 def combine[T](parsers: List[Parser[T]]): Parser[List[T]] = parsers.sequenceU ^ - adamretter
非Scalaz版本似乎可以编译通过,所以我现在可能会使用它。虽然代码库已经在某些地方使用了Scalaz,所以我也很想让你发布的Scalaz示例能够工作。 - adamretter
@adamretter:那个错误实际上是因为你正在使用一个类而不是一个对象 - 请参见我的更新以获取解决方法。 - Travis Brown
啊,好的。谢谢你的更新,非常感谢你的帮助。 - adamretter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接