理解Scala解析器组合器中的波浪号

时间:2011-07-25 15:23:08

标签: scala parser-combinators

我对Scala很新,在阅读解析器组合器(The Magic Behind Parser CombinatorsDomain-Specific Languages in Scala)时,我遇到了这样的方法定义:

def classPrefix = "class" ~ ID ~ "(" ~ formals ~ ")"

我一直在阅读scala.util.parsing.Parsers的API文档,该文档定义了一个名为(tilde)的方法,但我仍然不能理解它在上面示例中的用法。 在该示例中(代字号)是在java.lang.String上调用的方法,该方法没有该方法并导致编译器失败。 我知道(代字号)被定义为

case class ~ [+a, +b] (_1: a, _2: b)

但是在上面的例子中这有什么用呢?

如果有人能给我一些暗示,了解这里发生了什么,我会很高兴。 非常感谢你提前!

3 个答案:

答案 0 :(得分:30)

这里的结构有点棘手。首先,请注意,您始终在内部定义某些解析器的子类,例如 class MyParser extends RegexParsers。现在,您可能会注意到RegexParsers中的两个隐式定义:

implicit def literal (s: String): Parser[String]
implicit def regex (r: Regex): Parser[String]

这些将做什么是获取任何字符串或正则表达式并将它们转换为匹配该字符串或该正则表达式作为标记的解析器。它们是隐含的,因此它们将在需要时随时应用(例如,如果您在Parser[String](或String)没有的Regex上调用方法。< / p>

但是这个Parser是什么东西?它是Parsers内定义的内部类,RegexParser的超级代码:

class Parser [+T] extends (Input) ⇒ ParseResult[T]

看起来它是一个接受输入并将其映射到结果的函数。嗯,这很有道理!你可以看到它的文档here

现在我们可以查看~方法:

def ~ [U] (q: ⇒ Parser[U]): Parser[~[T, U]]
  A parser combinator for sequential composition
  p ~ q' succeeds if p' succeeds and q' succeeds on the input left over by p'.

所以,如果我们看到像

这样的东西
def seaFacts = "fish" ~ "swim"

首先,"fish"没有~方法,因此隐式转换为Parser[String]。然后,~方法需要Parser[U]类型的参数,因此我们将"swim"隐式转换为Parser[String](即U == String) 。现在我们有一些匹配输入"fish"的内容,输入中剩下的内容应与"swim"匹配,如果两者都匹配,则seaFacts将成功匹配。< / p>

答案 1 :(得分:13)

解析器上的~方法将两个解析器组合在一起,连续应用两个原始解析器并返回两个结果。这可能很简单(在Parser[T]

def ~[U](q: =>Parser[U]): Parser[(T,U)]. 

如果你从未组合过两个以上的解析器,那就没关系。但是,如果您链​​接其中三个,p1p2p3,返回类型为T1T2T3,则{ {1}},表示p1 ~ p2 ~ p3的类型为p1.~(p2).~(p3)。如果你按照你的例子中的五个组合,那就是Parser[((T1, T2), T3)]。然后,当您对结果进行模式匹配时,您也将拥有所有这些parantheses:

Parser[((((T1, T2), T3), T4), T5)]

这很不舒服。

然后是一个聪明的句法技巧。当案例类有两个参数时,它可以显示在中缀而不是模式中的前缀位置。也就是说,如果你有 case ((((_, id), _), formals), _) => ... ,您可以与case class X(a: A, b: B)匹配,也可以与case X(a, b)匹配。 (这是使用模式case a X b来匹配非空列表,x::xs是一个案例类)。 当您编写案例::时,它意味着a ~ b ~ c,但是比case ~(~(a,b), c)更令人愉快,更愉快,这对于正确而言是棘手的。

因此,Parser中的case ((a,b), c)方法会返回~而不是Parser[~[T,U]],因此您可以轻松地对多个〜的结果进行模式匹配。除此之外,Parser[(T,U)]~[T,U]几乎是一样的,就像你可以得到同构一样。

为解析器和结果类型中的组合方法选择相同的名称,因为生成的代码很自然。人们可以立即看到结果处理中的每个部分如何与语法规则的项目相关。

(T,U)

选择Tilda是因为它的优先级(紧密绑定)与解析器上的其他运算符很好地匹配。

最后一点,有辅助运算符parser1 ~ parser2 ~ parser3 ^^ {case part1 ~ part2 ~ part3 => ...} ~>,它们丢弃其中一个操作数的结果,通常是规则中不包含有用数据的常量部分。所以人们宁愿写

<~

并仅获取结果中ID和形式的值。

答案 2 :(得分:3)

你应该结帐Parsers.Parser。 Scala有时会定义具有相同名称的方法和案例类以帮助模式匹配等,如果您正在阅读Scaladoc,它会有点混乱。

特别是,"class" ~ ID"class".~(ID)相同。 ~是一种将解析器与另一个解析器顺序组合在一起的方法。

RegexParsers中定义的an implicit conversion会自动从String值创建解析器。因此,"class"会自动成为Parser[String]的实例。

val ID = """[a-zA-Z]([a-zA-Z0-9]|_[a-zA-Z0-9])*"""r

RegexParsers还定义了另一个隐式转换,它自动从Regex值创建解析器。因此,ID也会自动成为Parser[String]的实例。

通过组合两个解析器,"class" ~ ID返回与文字“class”匹配的Parser[String],然后依次出现正则表达式ID。还有其他方法,例如||||。有关详细信息,请阅读Programming in Scala