使用nom识别输入的浮点数

时间:2016-06-09 20:32:25

标签: rust parser-combinators

我尝试使用nom来解析基于文本的协议。该协议可以具有以下形式的浮点值:

[-]digit+[.digit+]

例子如下:

  • -10.0
  • 10.0
  • 10

为了识别这个而构建的nom解析器是......不漂亮。它也没有什么特别之处。到目前为止我得到了什么:

named!(float_prs <&[u8], f64>,
       alt!(
           take_while!(nom::is_digit) => {|x| FromStr::from_str(std::str::from_utf8(x).unwrap()).unwrap()} |
           recognize!(chain!(
               take_while!(nom::is_digit) ~
                   tag!(".") ~
                   take_while!(nom::is_digit),
               || {})
           ) => {|x: &[u8]| FromStr::from_str(std::str::from_utf8(x).unwrap()).unwrap() }
       )
);

第一个替代解析器识别digit+,第二个尝试识别digit+.digit+,但

<nom macros>:5:38: 5:62 error: unable to infer enough type information about `_`; type annotations or generic parameter binding required [E0282]
<nom macros>:5 let index = ( $ i ) . offset ( i ) ; $ crate:: IResult:: Done (

上述内容未解决-digit等问题。有大量的重复,意图非常模糊。有没有更好的方法来解析我没有看到的[-]digit+[.digit+]

4 个答案:

答案 0 :(得分:8)

仅仅因为nom大量使用宏来完成它的肮脏工作,不要忘记你仍然可以应用正常的编程最佳实践。具体而言,将问题分解为更小的部分并组成它们。

在nom中,这可以通过chain!宏来实现,它可以构建组件解析器,named!可以为它们提供有用的名称。

我建议为数字的三个部分创建子解析器 - 可选符号,必需的整数部分和可选的小数部分。请注意,digit已经提取了多个连续的数字字符。

此代码的主要棘手之处在于需要使用complete!强制decimal解析器为全有或全无。

#[macro_use]
extern crate nom;

use nom::digit;

named!(negative, tag!("-"));

named!(decimal, complete!(do_parse!(
    tag!(".")  >>
    val: digit >>
    (val)
)));

named!(floating_point<(Option<&[u8]>, &[u8], Option<&[u8]>)>, tuple!(
    opt!(negative), digit, opt!(decimal)
));

fn main() {
    println!("{:?}", floating_point(&b"0"[..]));
    println!("{:?}", floating_point(&b"0."[..]));
    println!("{:?}", floating_point(&b"0.0"[..]));
    println!("{:?}", floating_point(&b"-0"[..]));
    println!("{:?}", floating_point(&b"-0."[..]));
    println!("{:?}", floating_point(&b"-0.0"[..]));
}

我没有将字节转换为有趣的东西,它会使代码混乱,我也不知道我是如何用它做一些有用的东西。 ^ _ ^

答案 1 :(得分:1)

Nom 2.1现在有浮点解析器辅助函数;您不再需要手动执行此操作:

这是added on Jan 27th

答案 2 :(得分:0)

我最后摆弄并解决了我自己的问题。

fn float_prs(input: &[u8]) -> nom::IResult<&[u8], f64> {
    let (i, name) = try_parse!(input,
                               recognize!(chain!(
                                   tag!("-")? ~
                                       take_while!(nom::is_digit) ~
                                       tag!(".")? ~
                                       take_while!(nom::is_digit)?,
                                   || {}
                               )));
    let num: &str = std::str::from_utf8(name).unwrap();
    nom::IResult::Done(i, num.parse::<f64>().unwrap())
}

Shepmaster的解决方案也很不错,并且明显缺乏unwrap()使用。 :)

答案 3 :(得分:0)

迟到的答案,但此解决方案适用于浮点数和指数。关键是要确保数字的每个子部分都使用complete!宏。这适用于nom 3.2

named!(
    exponent,
    recognize!(complete!(preceded!(
        alt!(tag!("e") => { |_| b'e' } | tag!("E") => { |_| b'e'}),
        pair!(opt!(tag!("-")), many1!(digit))
    )))
);
named!(
    decimal,
    recognize!(complete!(terminated!(tag!("."), many0!(digit))))
);
named!(
    floating<f32>,
    map!(
        recognize!(do_parse!(
            opt!(tag!("-")) >> many1!(digit) >> opt!(decimal) >> opt!(exponent) >> (())
        )),
        |v| str::from_utf8(v).unwrap().parse::<f32>().unwrap()
    )
);

#[test]
fn parse_pdl_float_test() {
    assert_eq!(decimal(b".123").to_result(), Ok(".123".as_ref()));
    assert_eq!(floating(b"-123").to_result(), Ok(-123.0));
    assert_eq!(floating(b"123").to_result(), Ok(123.0));
    assert_eq!(floating(b"-123e4").to_result(), Ok(-123e4));
    assert_eq!(floating(b"123E4").to_result(), Ok(123e4));
    assert_eq!(floating(b"-123e-4").to_result(), Ok(-123e-4));
    assert_eq!(floating(b"-123.123e-4").to_result(), Ok(-123.123e-4));
}