今天是大阅兵,祝祖国繁荣富强
今天看了看大阅兵,感觉不错,新装备不少,看了看国外那群汉奸反应,都快被气红温了,笑死我了,这回够美国佬喝一壶了哈哈哈。
今天学了学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解析器重写一下😁