You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 19:11:31