理解Scala中的瓷砖';s解析器组合子

理解Scala中的瓷砖';s解析器组合子,scala,parser-combinators,Scala,Parser Combinators,我对Scala相当陌生,在阅读有关解析器组合器(,)的文章时,我遇到了如下方法定义: def classPrefix = "class" ~ ID ~ "(" ~ formals ~ ")" 我已经通读了scala.util.parsing.Parsers的API文档,它定义了一个名为(tilde)的方法,但是我仍然不太理解它在上面示例中的用法。 在该示例中(tilde)是一个在java.lang.String上调用的方法,该方法没有该方法,并导致编译器失败。 我知道(tilde)的定义是 c

我对Scala相当陌生,在阅读有关解析器组合器(,)的文章时,我遇到了如下方法定义:

def classPrefix = "class" ~ ID ~ "(" ~ formals ~ ")"
我已经通读了scala.util.parsing.Parsers的API文档,它定义了一个名为(tilde)的方法,但是我仍然不太理解它在上面示例中的用法。 在该示例中(tilde)是一个在java.lang.String上调用的方法,该方法没有该方法,并导致编译器失败。 我知道(tilde)的定义是

case class ~ [+a, +b] (_1: a, _2: b)
但是在上面的例子中,这有什么帮助呢

如果有人能给我一个暗示,让我明白这里发生了什么,我会很高兴的。 提前非常感谢

一月

你应该结账。Scala有时使用相同的名称定义方法和case类,以帮助模式匹配等,如果您正在阅读Scaladoc,则会有点混乱

特别是,
“类”~ID
“类”~(ID)
相同
~
是一种按顺序将解析器与另一个解析器组合在一起的方法

RegexParsers
中定义了从
字符串
值自动创建解析器。因此,
“类”
自动成为
解析器[String]
的实例

val ID = """[a-zA-Z]([a-zA-Z0-9]|_[a-zA-Z0-9])*"""r
RegexParsers
还定义了另一个隐式转换,该转换从
Regex
值自动创建解析器。因此,
ID
也自动成为
Parser[String]
的一个实例


通过组合两个解析器,
“class”~ID
返回一个与文本“class”匹配的
解析器[String]
,然后正则表达式
ID
依次出现。还有其他方法,如
|
| |
。更多信息,请阅读。

这里的结构有点复杂。首先,请注意,您总是在某个解析器的子类中定义这些内容,例如,
classmyparser扩展RegexParsers
。现在,您可能会注意到
RegexParsers
中的两个隐式定义:

implicit def literal (s: String): Parser[String]
implicit def regex (r: Regex): Parser[String]
它们将做的是将任何字符串或正则表达式转换成一个解析器,该解析器将该字符串或正则表达式作为标记匹配。它们是隐式的,因此可以在需要时随时应用它们(例如,如果您在
Parser[String]
上调用
String
(或
Regex
)没有的方法)

但这是什么
解析器
呢?它是
解析器
中定义的一个内部类,是
RegexParser
的超级特性:

class Parser [+T] extends (Input) ⇒ ParseResult[T]
看起来它是一个接受输入并将其映射到结果的函数。嗯,这是有道理的!你可以看到它的文档

现在我们只需查找
~
方法:

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”
没有
~
方法,因此它被隐式转换为
解析器[String]
,后者有。
~
方法需要类型为
Parser[U]
的参数,因此我们将
“swim”
隐式转换为
Parser[String]
(即
U
==
String
)。现在我们有了一个匹配输入的东西
“fish”
,输入中剩下的任何东西都应该匹配
“swim”
,如果两者都匹配,然后
sefacts
将成功匹配。

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

如果您从未组合过两个以上的解析器,那就可以了。但是,如果将其中三个链接起来,
p1
p2
p3
,返回类型为
T1
T2
T3
,那么
p1~p2~p3
,这意味着
p1.~(p2)。~(p3)
属于
解析器[(T1,T2),T3)]
。如果您像在您的示例中那样将它们中的五个组合起来,那将是
解析器[((T1,T2),T3,T4),T5)]
。然后,当你对结果进行模式匹配时,你也会有所有这些妄想:

case ((((_, id), _), formals), _) => ...
这很不舒服

然后是一个巧妙的语法技巧。当案例类有两个参数时,它可以显示在中缀中,而不是模式中的前缀位置。也就是说,如果你有
案例类别X(a:a,b:b)
,您可以与
案例X(a,b)
进行模式匹配,也可以与
案例a X b
进行模式匹配。(这就是使用模式
x::xs
来匹配非空列表所做的,
是一个case类)。 当你写case
a~b~c
时,它的意思是
case~(~(a,b),c)
,但它比
case((a,b),c)
更令人愉快,也比
case((a,b),c)
更令人愉快,这是很难理解的

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

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

parser1 ~ parser2 ~ parser3 ^^ {case part1 ~ part2 ~ part3 => ...}
之所以选择Tilda,是因为它的优先级(绑定紧密)和解析器上的其他操作符配合得很好


最后一点,有辅助运算符
~>
ID对不起,我错过了问题中关于字符串的部分@雷克斯·克尔正确地回答了这个问题。So~既是解析器上的一个方法,也是它返回的数据类型。当作为一种方法应用于字符串时,它会触发字符串到解析器的隐式转换。非常感谢您的解释。我不熟悉scala的“隐式转换”特性。在这里可以找到关于该主题的一篇好文章:
parser1 ~ parser2 ~ parser3 ^^ {case part1 ~ part2 ~ part3 => ...}
"class" ~> ID <~ ")" ~ formals <~ ")"