parser 上的~
方法将两个解析器合二为一,依次应用两个原始解析器并返回两个结果。那可能只是(在Parser[T]
)
def ~[U](q: =>Parser[U]): Parser[(T,U)].
如果您从未组合过两个以上的解析器,那也没关系。但是,如果将其中三个 , , , 与返回类型 , , , 链接p1
起来p2
,p3
则T1
,T2
这T3
意味着p1 ~ p2 ~ p3
是p1.~(p2).~(p3)
type 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
匹配非空列表所做的,::
是一个案例类)。当你写 casea ~ b ~ c
时,它的意思是case ~(~(a,b), c)
, but 更令人愉快,也比case ((a,b), c)
too 更令人愉快,这很难正确。
因此~
Parser 中的方法返回 aParser[~[T,U]]
而不是 a Parser[(T,U)]
,因此您可以轻松地对多个 ~ 的结果进行模式匹配。除此之外, 它们几乎是一样的~[T,U]
,(T,U)
尽可能同构。
为解析器中的组合方法和结果类型选择相同的名称,因为结果代码易于阅读。可以立即看到结果处理中的每个部分如何与语法规则的项目相关联。
parser1 ~ parser2 ~ parser3 ^^ {case part1 ~ part2 ~ part3 => ...}
选择 Tilda 是因为它的优先级(它紧密绑定)与解析器上的其他运算符配合得很好。
最后一点,辅助运算符~>
会<~
丢弃其中一个操作数的结果,通常是规则中不携带有用数据的常量部分。所以宁愿写
"class" ~> ID <~ ")" ~ formals <~ ")"
并在结果中仅获取 ID 和形式的值。