4. 具有自定义返回类型的解析器
到目前为止,我们看到的大多数函数都接受一个 &str,并返回一个 IResult<&str, &str>。将字符串拆分成更小的字符串当然很有用,但这并不是 Nom 唯一能做的事情!
解析时的一个有用操作是在类型之间进行转换;例如从 解析为 &str 另一个原语,如 bool。
要使解析器返回不同的类型,我们需要做的就是将 IResult 的第二个类型参数更改为所需的返回类型。例如,要返回布尔值,则返回 IResult<&str, bool>。
回想一下, 的第一个类型参数 IResult 是输入类型,因此即使您返回不同的东西,如果您的输入是 &str, 的第一个类型参数 IResult 也应该是。
在您阅读有关 Errors 的章节之前,我们强烈建议您避免使用 Rust 内置的解析器(如 str.parse);因为它们需要特殊处理才能与 Nom 很好地配合。
也就是说,进行类型转换的一种 Nom 原生方法是使用 value 组合器将成功解析转换为特定值。
以下代码将包含 "true" 或 "false" 的字符串转换为相应的 bool。
use nom::IResult;
use nom::bytes::complete::tag;
use nom::combinator::value;
use nom::branch::alt;
fn parse_bool(input: &str) -> IResult<&str, bool> {
    // either, parse `"true"` -> `true`; `"false"` -> `false`, or error.
    alt((
      value(true, tag("true")),
      value(false, tag("false")),
    ))(input)
}
fn main() -> Result<(), Box<dyn Error>> {
    // Parses the `"true"` out.
    let (remaining, parsed) = parse_bool("true|false")?;
    assert_eq!(parsed, true);
    assert_eq!(remaining, "|false");
   
    // If we forget about the "|", we get an error.
    let parsing_error = parse_bool(remaining);
    assert!(parsing_error.is_err());
    
    // Skipping the first byte gives us `false`!
    let (remaining, parsed) = parse_bool(&remaining[1..])?;
    assert_eq!(parsed, false);
    assert_eq!(remaining, "");
}
Nom 的内置解析器函数
nom 内置了大量的解析器。这是 识别特定字符的解析器 的列表。我花了点时间转写成了简体中文的 markdown 如下
| 函数 | 描述 | 
|---|---|
| alpha0 | 识别零个或多个小写和大写 ASCII 字母字符:a-z, A-Z | 
| alpha1 | 识别一个或多个小写和大写 ASCII 字母字符:a-z, A-Z | 
| alphanumeric0 | 识别零个或多个 ASCII 数字和字母字符:0-9, a-z, A-Z | 
| alphanumeric1 | 识别一个或多个 ASCII 数字和字母字符:0-9, a-z, A-Z | 
| anychar | 匹配一个字节作为字符。注意,输入类型将接受 str,但不接受 &[u8],与其他许多 nom 解析器不同。 | 
| char | 识别一个字符。 | 
| crlf | 识别字符串 “\r\n”。 | 
| digit0 | 识别零个或多个 ASCII 数字字符:0-9 | 
| digit1 | 识别一个或多个 ASCII 数字字符:0-9 | 
| hex_digit0 | 识别零个或多个 ASCII 十六进制数字字符:0-9, A-F, a-f | 
| hex_digit1 | 识别一个或多个 ASCII 十六进制数字字符:0-9, A-F, a-f | 
| i8 | 将文本形式的数字解析为数字 | 
| i16 | 将文本形式的数字解析为数字 | 
| i32 | 将文本形式的数字解析为数字 | 
| i64 | 将文本形式的数字解析为数字 | 
| i128 | 将文本形式的数字解析为数字 | 
| line_ending | 识别行尾(‘\n’ 和 ‘\r\n’)。 | 
| multispace0 | 识别零个或多个空格、制表符、回车符和换行符。 | 
| multispace1 | 识别一个或多个空格、制表符、回车符和换行符。 | 
| newline | 匹配换行符 ‘\n’。 | 
| none_of | 识别不在提供的字符中的一个字符。 | 
| not_line_ending | 识别除 ‘\r\n’ 或 ‘\n’ 之外的任何字符的字符串。 | 
| oct_digit0 | 识别零个或多个八进制字符:0-7 | 
| oct_digit1 | 识别一个或多个八进制字符:0-7 | 
| one_of | 识别提供的字符中的一个字符。 | 
| satisfy | 识别一个字符并检查它是否满足谓词。 | 
| space0 | 识别零个或多个空格和制表符。 | 
| space1 | 识别一个或多个空格和制表符。 | 
| tab | 匹配制表符 ‘\t’。 | 
| u8 | 将文本形式的数字解析为数字 | 
| u16 | 将文本形式的数字解析为数字 | 
| u32 | 将文本形式的数字解析为数字 | 
| u64 | 将文本形式的数字解析为数字 | 
| u128 | 将文本形式的数字解析为数字 | 
其中有些我们在第 2 章中已经见过,但现在我们还可以尝试返回不同类型的解析器,例如i32。下一节将展示此解析器的一个示例。
构建更复杂的例子
解析自定义类型的一个更复杂的例子可能是解析二维坐标。
让我们尝试弄清楚如何设计它。
- 我们知道我们想要获取一个字符串,比如 
"(3, -2)",并将其转换为一个Coordinate结构体。 - 我们可以将其分为三个部分:
 
(vvvvvvvvvvvvv) # 外部的括号
  vvvv , vvvv   # 逗号,这是分隔符
    3     -2    # 真实的数字
- 因此,我们需要三个解析器来解决这个问题: 
- 整数解析器,用于处理原始数字。
 - 逗号分隔对的解析器,将其拆分为整数。
 - 外括号的解析器。
 
 - 下面我们可以看到我们如何实现这个目标:
 
extern crate nom;
use std::error::Error;
use nom::IResult;
use nom::bytes::complete::tag;
use nom::sequence::{separated_pair, delimited};
// 这是我们将解析成的类型。
#[derive(Debug,PartialEq)]
pub struct Coordinate {
  pub x:   i32,
  pub y:   i32,
}
// 1. Nom 内置了 i32 解析器。
use nom::character::complete::i32;
// 2. 使用 `separated_pair` 解析器来组合两个解析器(在本例中,两个 `i32`),忽略中间的内容。
fn parse_integer_pair(input: &str) -> IResult<&str, (i32, i32)> {
    separated_pair(
        i32,
        tag(", "),
        i32
    )(input)
}
// 3. 使用 `delimited` 解析器来应用一个解析器,忽略两个包围解析器的结果。
fn parse_coordinate(input: &str) -> IResult<&str, Coordinate> {
    let (remaining, (x, y)) = delimited(
        tag("("),
        parse_integer_pair,
        tag(")")
    )(input)?;
    
    // Note:我们可以通过在 `Coordinate` 上实现 `From` 来构造这个结构体,
    // 但我们没有这样做,只是为了让它更明显地展示发生了什么。
    Ok((remaining, Coordinate {x, y}))
    
}
fn main() -> Result<(), Box<dyn Error>> {
    let (_, parsed) = parse_coordinate("(3, 5)")?;
    assert_eq!(parsed, Coordinate {x: 3, y: 5});
   
    let (_, parsed) = parse_coordinate("(2, -4)")?;
    assert_eq!(parsed, Coordinate {x: 2, y: -4});
    
    let parsing_error = parse_coordinate("(3,)");
    assert!(parsing_error.is_err());
    
    let parsing_error = parse_coordinate("(,3)");
    assert!(parsing_error.is_err());
    
    let parsing_error = parse_coordinate("Ferris");
    assert!(parsing_error.is_err());
    
  Ok(())
}
作为练习,您可能想探索如何让这个解析器优雅地处理输入中的空格。
