Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recognize floating point from input with nom

I'm trying to use nom to parse a text based protocol. This protocol can have floating point values in it of the form:

[-]digit+[.digit+]

Examples of which are:

  • -10.0
  • 10.0
  • 10

The nom parser I've built to recognize this is... not pretty. It also doesn't quite typecheck. What I've got so far:

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() }
       )
);

The first alternative parser recognizes digit+, the second is an attempt to recognize digit+.digit+ but

<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 (

Recognizing -digit etc is not addressed in the above. There's a significant amount of duplication and the intent is very much obscured. Is there a better way to parse [-]digit+[.digit+] that I'm not seeing?

like image 779
troutwine Avatar asked Sep 07 '25 12:09

troutwine


1 Answers

Just because nom heavily uses macros to do it's dirty work, don't forget that you can still apply normal programming best practices. Specifically, break down the problem into smaller parts and compose them.

In nom, this can be achieved with the chain! macro, which allows you to build up component parsers, and named!, to give them useful names.

I'd recommend creating sub-parsers for the three parts of the number - the optional sign, the required integral part, and the optional decimal part. Note that digit already pulls in multiple sequential numeric characters.

The main tricky thing with this code was the need to use complete! to force the decimal parser to be all-or-nothing.

#[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"[..]));
}

I've left off any conversion of the bytes to something interesting, it would clutter up the code and I don't really know how I'd do something useful with it anyway. ^_^

like image 194
Shepmaster Avatar answered Sep 10 '25 07:09

Shepmaster