谢谢
谢谢
就像我说的那样,它用于为解析器参数化,但让我们通过一个示例来说明清楚。
让我们从一个简单的解析器开始,该解析器解析一个数字后跟一个单词:
def numberAndWord = number ~ word
def number = "\\d+".r
def word = "\\w+".r
使用RegexParsers,可以解析"3 fruits"之类的内容。
现在,如果你想要这些“n个物品”的列表。例如,“3个水果:香蕉、苹果、橙子”。我们尝试解析一下,看看情况如何。
首先,如何解析“N”个物品?碰巧有一个repN
方法:
def threeThings = repN(3, word)
这将解析 "banana apple orange",但不会解析 "banana, apple, orange"。我需要一个分隔符。repsep
提供了这个功能,但它不能让我指定我想要的重复次数。因此,让我们自己提供分隔符:
def threeThings = word ~ repN(2, "," ~> word)
好的,那就这样吧。我们现在可以按照以下三个事情,写出完整的例子:
def listOfThings = "3" ~ word ~ ":" ~ threeThings
def word = "\\w+".r
def threeThings = word ~ repN(2, "," ~> word)
这种方法有点可行,但我正在将 3 固定为 "N"。我希望用户可以自己指定数量。这就是 >>
,也被称为 into
(是的,对于 Parser
,它确实是 flatMap
)发挥作用的地方。首先,让我们更改 threeThings
:
def things(n: Int) = n match {
case 1 => word ^^ (List(_))
case x if x > 1 => word ~ repN(x - 1, "," ~> word) ^^ { case w ~ l => w :: l }
case x => err("Invalid repetitions: "+x)
}
这比你预期的要稍微复杂一些,因为我强制它返回Parser[List[String]]
。但是我如何向它传递参数呢?我的意思是,这样做行不通:
def listOfThings = number ~ word ~ ":" ~ things(/* what do I put here?*/)
但我们可以这样重写:
def listOfThings = (number ~ word <~ ":") >> {
case n ~ what => things(n.toInt)
}
那几乎够好了,除了现在我失去了n
和what
: 它只返回"List(banana, apple, orange)",而不是应该有多少个,以及它们是什么。我可以这样做:def listOfThings = (number ~ word <~ ":") >> {
case n ~ what => things(n.toInt) ^^ { list => new ~(n.toInt, new ~(what, list)) }
}
def number = "\\d+".r
def word = "\\w+".r
def things(n: Int) = n match {
case 1 => word ^^ (List(_))
case x if x > 1 => word ~ repN(x - 1, "," ~> word) ^^ { case w ~ l => w :: l }
case x => err("Invalid repetitions: "+x)
}
最后一句话。你可能会想知道,自问自答说:"flatMap是什么意思?不是一个单子/for-comprehension的东西吗?" 是的,没错! :-) 这是另一种编写listOfThings
的方式:
def listOfThings = for {
nOfWhat <- number ~ word <~ ":"
n ~ what = nOfWhat
list <- things(n.toInt)
} yield new ~(n.toInt, new ~(what, list))
我不使用 Scala 中的 filter
或 withFilter
,因为这在 Parsers
中没有实现,所以我不执行 n ~ what <- number ~ word <~ ":"
。但是,以下是另一种编写方式,它并没有完全相同的语义,但会产生相同的结果:
def listOfThings = for {
n <- number
what <- word
_ <- ":" : Parser[String]
list <- things(n.toInt)
} yield new ~(n.toInt, new ~(what, list))
这甚至可能让人想到,也许声称 "单子无所不在" 的说法有些道理。 :-)
>>
接受一个函数,该函数给出解析器的结果并使用它来构造一个新的解析器。正如所述,这可以用于将解析器参数化为先前解析器的结果。n + 1
个整数值的行。第一个值n
表示要跟随的值的数量。首先解析此第一个整数,然后使用此解析的结果构造解析器,以解析n
个其他整数。parseInt:Parser [Int]
解析整数。它首先解析一个整数值n
,然后使用>>
解析n
个附加整数,这些整数形成解析器的结果。因此,初始n
不会由解析器返回(尽管它是返回列表的大小)。def intLine: Parser[Seq[Int]] = parseInt >> (n => repN(n,parseInt))
1 42
3 1 2 3
0
0 1
1
3 42 42
into
的别名。根据源代码,>>
调用into
,而into
又调用了flatMap
。 - ziggystar
~
运算符解析某个东西时,结果将是~
类的一个实例。我本可以——或者说我应该——返回一个元组,因为这样做更容易。但是,我想保留示例代码中的原始返回值。 - Daniel C. Sobral”类和“Parser”的“”运算符是有意设计成相同的名称,“~”运算符返回一个Parser [〜[T,U]]
,其中T
和U
是此运算符应用于的解析器的类型。但是,类和运算符可以完全不同的名称。顺便说一下,这种二元运算符/类的二重性也可以在“::”中找到。 - Daniel C. Sobral