使用nom crate解析带符号十六进制与十进制数字的优化方案咨询
基于nom实现带符号多进制数字解析的简洁方案
1. 拆分职责,用小粒度组合子构建解析器
把符号、进制前缀、数字主体拆成独立的小解析器,每个只处理单一逻辑,再通过nom的组合子(如opt、alt、tuple)组合,避免大函数堆砌逻辑。每个小解析器职责单一,可读性和扩展性更强。
示例代码片段:
use nom::{ branch::alt, character::complete::{digit1, hex_digit1, one_of, tag}, combinator::{map, opt, peek}, sequence::tuple, IResult, }; #[derive(Debug, PartialEq)] enum NumBase { Decimal, Hexadecimal, } #[derive(Debug, PartialEq)] struct Number { sign: Option<char>, base: NumBase, digits: String, } // 解析可选正负号 fn parse_sign(input: &str) -> IResult<&str, Option<char>> { opt(one_of("+-"))(input) } // 解析进制前缀,判断基数 fn parse_base(input: &str) -> IResult<&str, NumBase> { alt(( map(tag("#"), |_| NumBase::Hexadecimal), map(peek(digit1), |_| NumBase::Decimal), ))(input) } // 根据基数解析对应数字 fn parse_digits(input: &str, base: NumBase) -> IResult<&str, &str> { match base { NumBase::Decimal => digit1(input), NumBase::Hexadecimal => hex_digit1(input), } } // 组合所有解析器 fn parse_number(input: &str) -> IResult<&str, Number> { let (input, sign) = parse_sign(input)?; let (input, base) = parse_base(input)?; // 若为十六进制,先跳过#前缀 let (input, _) = if matches!(base, NumBase::Hexadecimal) { tag("#")(input)? } else { (input, "") }; let (input, digits) = parse_digits(input, base)?; Ok(( input, Number { sign, base, digits: digits.to_string(), }, )) }
2. 用recognize+map_res简化数值转换
如果最终需要将解析结果转为数值类型(如i64),可以用recognize把符号、前缀、数字合并成完整字符串,再通过map_res调用标准库的from_str_radix完成转换,避免手动处理符号与基数的组合逻辑。
示例:
fn parse_signed_num(input: &str) -> IResult<&str, i64> { let sign_part = opt(one_of("+-")); let prefix_part = alt((tag("#"), peek(digit1))); let digit_part = alt((hex_digit1, digit1)); map_res( recognize(tuple((sign_part, prefix_part, digit_part))), |s: &str| { let (sign_str, num_str) = match s.chars().next() { Some('+') | Some('-') => s.split_at(1), _ => ("", s), }; let (base, num_str) = if num_str.starts_with('#') { (16, &num_str[1..]) } else { (10, num_str) }; i64::from_str_radix(num_str, base).map(|n| match sign_str { "-" => -n, _ => n, }) }, )(input) }
3. 模块化+宏复用,避免重复代码
如果需要扩展更多进制(如八进制),可以用宏生成对应基数的解析器,减少重复代码:
macro_rules! digit_parser { ($parser_name:ident, $nom_parser:ident) => { fn $parser_name(input: &str) -> IResult<&str, &str> { nom::character::complete::$nom_parser(input) } }; } digit_parser!(parse_decimal_digits, digit1); digit_parser!(parse_hex_digits, hex_digit1); // 后续加八进制只需:digit_parser!(parse_octal_digits, oct_digit1);
4. 遵循nom"组合优先"原则
尽量使用nom提供的预定义组合子(如alt、preceded、opt)组合逻辑,不要手动遍历字符流。这种方式完全贴合nom的设计思路,后续扩展功能(比如加八进制支持)只需在alt分支里新增前缀匹配即可,代码维护成本极低。
内容的提问来源于stack exchange,提问作者DraganS
相关产品推荐
相关产品推荐

