Skip to content

Commit 054f229

Browse files
authored
Merge pull request #112 from GTxx/main
add missing paragrahps for parser-combinator
2 parents 10fb751 + 6d62267 commit 054f229

File tree

1 file changed

+75
-0
lines changed

1 file changed

+75
-0
lines changed

src/chapter_6/parser-combinator.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,6 +1045,81 @@ fn parent_element<'a>() -> impl Parser<'a, Element> {
10451045
}
10461046
```
10471047

1048+
Oops。现在我们要怎么把参数传给`close_element`?我想我们还缺少最后一个组合子。
1049+
1050+
我们已经很接近了。一旦我们解决了最后这个问题以让`parent_element`工作,我们就能把`element`解析器的`open_element`替换成新的`parent_element`,这就意味着,我们就有一个完整可用的XML解析器。
1051+
1052+
还记得我前面说过我们后面会回到`and_then`吗?好的,后面就是这里了。我们所需要的组合子,实际上,就是`and_then`:我们需要一个东西能接收一个解析子,还要一个函数,这个函数能接收解析子的返回结果并返回一个**解析子,这个解析子我们后面再用。这有点像`pair`,但不是仅仅收集tuple里的两个结果,我们把结果传入一个函数。这也是`and_then`怎么作用在`Result``Options`的方式,除了这个更好理解,因为`Result``Options`基本上什么也不做,它们只是包含了一些数据而已(或者不包含,在某一些情况下)。
1053+
1054+
所以让我们试着实现一下它。
1055+
1056+
```rust
1057+
fn and_then<'a, P, F, A, B, NextP>(parser: P, f: F) -> impl Parser<'a, B>
1058+
where
1059+
P: Parser<'a, A>,
1060+
NextP: Parser<'a, B>,
1061+
F: Fn(A) -> NextP,
1062+
{
1063+
move |input| match parser.parse(input) {
1064+
Ok((next_input, result)) => f(result).parse(next_input),
1065+
Err(err) => Err(err),
1066+
}
1067+
}
1068+
```
1069+
1070+
看看这些类型,这里有很多类型变量,但是我们认识`P`,我们的输入解析器,`P`有一个结果类型`A`。我们的函数`F`,本来是一个从`A``B``map`,然而,关键的不同在于`and_then`接收一个函数,这个函数把`A`转化为一个新的解析器`NextP``NextP`有一个返回类型`B`。最终的结果类型是`B`,因此我们可以假设不管从`NextP`返回的是什么,将是最后的结果。
1071+
1072+
这段代码没类型那么复杂:我们一开始运行输入解析器,如果失败,这段code也失败,但是如果输入解析器成功,接下来我们调用函数`f`作用于result(为类型`A`),从`f(result)`返回的是个新的解析器, 类型是`B`。我们把返回的解析器作用在下一段输入,然后直接返回结果。如果失败,就挂在这里,如果成功,我们就得到类型为`B`的值。
1073+
1074+
再来一次:首先我们运行类型为`P`的解析器,如果成功,我们调用函数`F`作用在解析器`P`的结果上,得到下一个类型为`NextP`的解析器,然后我们运行,得到最终的结果。
1075+
1076+
让我们也把它直接加到`Parser`特质上,因为这个就像`map`,肯定这样读起来更好。
1077+
1078+
```rust
1079+
fn and_then<F, NextParser, NewOutput>(self, f: F) -> BoxedParser<'a, NewOutput>
1080+
where
1081+
Self: Sized + 'a,
1082+
Output: 'a,
1083+
NewOutput: 'a,
1084+
NextParser: Parser<'a, NewOutput> + 'a,
1085+
F: Fn(Output) -> NextParser + 'a,
1086+
{
1087+
BoxedParser::new(and_then(self, f))
1088+
}
1089+
1090+
```
1091+
1092+
OK,现在,这样做有什么好处?
1093+
1094+
首先,我们几乎可以用这个实现`pair`:
1095+
1096+
```rust
1097+
fn pair<'a, P1, P2, R1, R2>(parser1: P1, parser2: P2) -> impl Parser<'a, (R1, R2)>
1098+
where
1099+
P1: Parser<'a, R1> + 'a,
1100+
P2: Parser<'a, R2> + 'a,
1101+
R1: 'a + Clone,
1102+
R2: 'a,
1103+
{
1104+
parser1.and_then(move |result1| parser2.map(move |result2| (result1.clone(), result2)))
1105+
}
1106+
```
1107+
1108+
看上去很整洁,但是这里有个问题:`parser2.map()`消耗了`parser2`来创建包装解析器,而且函数是`Fn`,不是`FnOnce`,因此不允许消费`parser2`,只能取它的引用。Rust的问题,换句话说。在一个这些不是问题的更高级的语言中,这可以是一个很整洁的定义`pair`的方法。
1109+
1110+
尽管如此,我们还是能在Rust用这个函数懒生成`close_element`解析器的正确版本,或者,换句话,我们可以把参数传入它。
1111+
1112+
回想一下我们失败的尝试:
1113+
1114+
```rust
1115+
fn parent_element<'a>() -> impl Parser<'a, Element> {
1116+
pair(
1117+
open_element(),
1118+
left(zero_or_more(element()), close_element(…oops)),
1119+
)
1120+
}
1121+
```
1122+
10481123
通过使用 `and_then`,我们现在可以通过使用该函数构建正确版本的 `close_element` 来获得正确的结果。
10491124

10501125
```rust

0 commit comments

Comments
 (0)