nom学习

2025-09-03

今天是大阅兵,祝祖国繁荣富强

今天看了看大阅兵,感觉不错,新装备不少,看了看国外那群汉奸反应,都快被气红温了,笑死我了,这回够美国佬喝一壶了哈哈哈。

今天学了学nom库,感觉还不错,就是有点麻烦,不过听说性能可以,记录笔记

版本

nom="8.0.0"

我用的版本是8.0版本的,目前网上找到的资料都是以前版本的有的函数已经废弃了,需要看官方的英文文档来,英语不好是真难受 文档地址

使用

IResult

nom的错误处理枚举,具体类型是

pub type IResult<I, O, E = error::Error<I>> = Result<(I, O), Err<E>>;
// 其中I是解析后的剩余部分,o是被解析目标字符串,E是错误
// 比如
fn parse_sth(input:&str)->IResutl<&str,&str>{
    todo!()
}
//其中nom的解析哲学是组合子和零拷贝
// 我目前理解大概是一种消耗形式

tag

pub fn tag<T, Input, Error: ParseError<Input>>(
    tag: T
) -> impl Fn(Input) -> IResult<Input, Input, Error> where
    Input: InputTake + Compare<T>,
    T: InputLength + Clone,

这个函数返回一个解析器,大致使用方式是这样

fn parse_test(input:&str)->IResult<&str,&str>
{
    tag("aaa").parse(input)
}
// 返回一个IResult枚举
let (res,match_str)=parse_test("aaabbb").unwrap();
//res是bbb,match_str是aaa
//8.0以前的版本是可以省略掉parse
// 我用的现在版本似乎有点问题,不能这样用,会报错,有点怪

除次之外还有

alpha0识别零个或多个小写和大写字母字符a-zA-Z]
alpha1 执行相同操作但返回至少一个字符
alphanumeric0识别零个或多个数字和字母字符[0-9a-zA-Z]
alphanumeric1 执行相同操作但返回至少一个字符
digit0识别零个或多个数字字符/[0-9]/.
digit1执行相同操作但返回至少一个字符
multispace0识别零个或多个空格制表符回车符和换行符
multispace1 执行相同操作但返回至少一个字符
space0识别零个或多个空格和制表符
space1执行相同操作但返回至少一个字符
line_ending识别行尾(\n  \r\n
newline匹配换行符\n
tab匹配制表符 \t

一些组合函数

use nom::{
    IResult, Parser,
    branch::alt,
    bytes::{complete::is_not, tag},
    character::{
        anychar,
        complete::{alpha0, char as char_parse, digit0, not_line_ending},
    },
    sequence::{delimited, pair, preceded, separated_pair, terminated},
};

// ========================================
// nom 组合子教学示例
// 每个函数演示一个核心组合子的用法
// ========================================

/// alt: 尝试多个解析器,返回**第一个成功**的结果
/// - 适用于“多种可能之一”的场景(如关键字、协议版本等)
/// - 一旦某个子解析器成功,就不再尝试后面的
/// - 如果全部失败,返回最后一个错误
fn parse_alt(input: &str) -> IResult<&str, &str> {
    alt((
        tag("aaa"), // 尝试匹配字面量 "aaa"
        tag("bbb"), // 若失败,尝试 "bbb"
        tag("ccc"), // 若再失败,尝试 "ccc"
    ))
    .parse(input)
}

/// delimited: 解析“被包围”的内容
/// 结构:前缀 + 主体 + 后缀
/// 常用于括号、引号、块结构等
fn parens(input: &str) -> IResult<&str, &str> {
    delimited(
        char_parse('('), // 前缀:左括号 '('
        is_not(")"),     // 主体:任意不是 ')' 的字符(即括号内的内容)
        char_parse(')'), // 后缀:右括号 ')'
    )
    .parse(input)
}

/// tuple: 顺序解析多个独立部分,返回一个元组
/// 所有部分必须连续且全部成功
fn parse_tuple(input: &str) -> IResult<&str, (&str, &str, &str)> {
    (
        tag("aaa"), // 先匹配 "aaa"
        tag("bbb"), // 然后匹配 "bbb"
        tag("ccc"), // 最后匹配 "ccc"
    )
        .parse(input)
    // 返回 ((剩余输入), ("aaa", "bbb", "ccc"))
}

/// delimited 示例:与 parens 相同,用于对比命名
/// 解析被括号包围的内容,返回括号内的字符串
fn parse_delimited(input: &str) -> IResult<&str, &str> {
    delimited(
        char_parse('('), // 前缀 '('
        is_not(")"),     // 内容:非 ')' 字符
        char_parse(')'), // 后缀 ')'
    )
    .parse(input)
}

/// preceded: 解析“前缀之后的内容”
/// 结构:前缀 + 目标内容 → 返回目标内容
/// 常用于提取 @ 之后的域名、冒号后的值等
fn parse_preced(input: &str) -> IResult<&str, &str> {
    preceded(
        (digit0, char_parse('@')), // 前缀:任意数字 + '@' 符号
        not_line_ending,           // 目标内容:直到行尾之前的所有字符(即域名)
    )
    .parse(input)
    // 示例: "2779753429@qq.com" → 返回 "qq.com"
}

/// terminated: 解析“以某内容结尾”的结构
/// 结构:目标内容 + 后缀 → 返回目标内容
/// 常用于提取冒号前的键、分号前的语句等
fn parse_terminated(input: &str) -> IResult<&str, &str> {
    terminated(
        is_not(":"),        // 目标内容:任意非冒号字符
        char_parse(':'),    // 后缀:冒号 ':'
    )
    .parse(input)
    // 示例: "email:password" → 返回 "email"
}

/// pair: 解析两个连续的部分,返回二元组 (part1, part2)
/// 不跳过分隔符,需要手动处理
/// 这里我们手动用 `preceded` 处理 ": " 分隔符
fn parse_pair(input: &str) -> IResult<&str, (&str, &str)> {
    pair(
        is_not(":"),                         // 第一部分:冒号前的内容(如键名)
        preceded(tag(": "), not_line_ending), // 第二部分:": " 之后到行尾的内容(如值)
    )
    .parse(input)
}

/// separated_pair: 解析“被分隔符分开”的两个部分
/// 结构:part1 + separator + part2 → 返回 (part1, part2)
/// 是 `pair` + `preceded` 的更简洁版本
fn parse_separated_pair(input: &str) -> IResult<&str, (&str, &str)> {
    separated_pair(
        is_not(":"),    // 第一部分:非冒号字符(键)
        tag(": "),      // 分隔符:": "(注意空格)
        is_not(":"),    // 第二部分:非冒号字符(值)
        // 注意:这里假设值中不含 ':'
    )
    .parse(input)
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 可在此处添加实际解析示例
    Ok(())
}

#[cfg(test)]
mod test {
    const POST: &str = r#"POST /api/users HTTP/1.1
Host: example.com
Content-Type: application/json
Content-Length: 45
Connection: close

{"name": "Alice", "age": 30, "city": "Beijing"}"#;

    use super::*;

    #[test]
    fn test_alt() {
        let test_str = "aaa,bbb,ccc";
        let (_, match_str) = parse_alt(test_str).unwrap();
        assert_eq!("aaa", match_str); // alt 匹配第一个成功的 "aaa"
    }

    #[test]
    fn test_tuple() {
        let test_str = "aaabbbccc";
        let (_, (a, b, c)) = parse_tuple(test_str).unwrap();
        assert_eq!("aaa", a);
        assert_eq!("bbb", b);
        assert_eq!("ccc", c);
    }

    #[test]
    fn test_delimited() {
        let test_str = "(aaa,bbbccc)";
        let (_, match_str) = parse_delimited(test_str).unwrap();
        assert_eq!(match_str, "aaa,bbbccc"); // 返回括号内的内容
    }

    #[test]
    fn test_preceed() {
        let test_str = "2779753429@qq.com";
        let (_, domain) = parse_preced(test_str).unwrap();
        assert_eq!("qq.com", domain); // 提取 @ 之后的域名
    }

    #[test]
    fn test_terminated() {
        let test_str = "2779753429@qq.com:VR2050";
        let (_, email) = parse_terminated(test_str).unwrap();
        assert_eq!(email, "2779753429@qq.com"); // 提取 : 之前的内容
    }

    #[test]
    fn test_pari() {
        let test_str = "User-Agent: Mozilia Fire Fox 1.1";
        // 注意:原函数名 typo,应为 `parse_separated_pair`
        let (_, (k, v)) = parse_separated_pair(test_str).unwrap();
        assert_eq!("User-Agent", k);
        assert_eq!("Mozilia Fire Fox 1.1", v);
    }
}

大致就这些 感觉用这个有点麻烦但是吧,他相比regex性能要更强一点 抽时间把我那个shit request解析器重写一下😁