2011-07-25 50 views
21

我是相当新的Scala和一边念叨解析器组合(The Magic Behind Parser CombinatorsDomain-Specific Languages in Scala)我整个的方法定义出来是这样的:了解在Scala的解析器组合子波浪号

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

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

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

但是,这如何帮助在上面的例子?

如果有人能给我一个提示,让我知道这里发生了什么,我会很高兴。 非常感谢您提前!

1月

回答

30

这里的结构有点棘手。首先,请注意,您总是在某个解析器的子类中定义了这些东西,例如class MyParser extends RegexParsers。现在,你可能会注意到两个隐含的定义里面RegexParsers

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

什么这些都会做的是采取任何字符串或正则表达式,并将其转换成一个解析器,该字符串或正则表达式作为标记相匹配。它们是隐含的,所以它们会在需要时随时应用(例如,如果您调用Parser[String]String(或Regex)不具备的方法)。

但这是什么Parser的事?它的内部Parsers限定的内班,supertrait为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将匹配成功。

+1

非常感谢您的解释。我对scala的“隐式转换”功能并不熟悉。 关于该主题的一篇很好的文章可以在这里找到:http://scalada.blogspot.com/2008/03/implicit-conversions-magical-and.html – Jano

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返回Parser[String],它与文字“class”匹配,然后按顺序出现正则表达式ID。还有其他方法,如||||。欲了解更多信息,请阅读Programming in Scala

13

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

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

如果你从来没有合并两个以上的解析器,这将是确定。但是,如果您将他们三人,p1p2p3,与返回类型T1T2T3,然后p1 ~ p2 ~ p3,这意味着p1.~(p2).~(p3)Parser[((T1, T2), T3)]类型。如果你结合其中的五个,如你的例子,那将是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),但是也比case ((a,b), c)更愉快,更愉快,这很难得到正确的。

因此,解析器中的~方法返回Parser[~[T,U]]而不是Parser[(T,U)],因此您可以轻松地对多个〜的结果进行模式匹配。除此之外,~[T,U](T,U)几乎是相同的东西,因为你可以得到同构。

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

parser1 ~ parser2 ~ parser3 ^^ {case part1 ~ part2 ~ part3 => ...} 

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

最后一点,有辅助运算符~><~,它们丢弃操作数之一的结果,通常是规则中不携带有用数据的常量部分。所以人们宁愿写

"class" ~> ID <~ ")" ~ formals <~ ")" 

并只得到在结果中的ID和形式的值。

+0

对不起,我错过了关于String的部分问题。 @Rex Kerr正确回答了它。所以〜既是Parser上的一种方法,也是它返回的数据类型。当作为方法应用于String时,它会触发将字符串隐式转换为解析器。 –

相关问题