5

Using the nom crate, I'm trying to write a parser that can recognize signed i32 number from a String, i.e. can transform the string -42 into the i32 representation.

So far I've come up with the following, but I'm failing to parse negative numbers:

use nom::types::CompleteStr;
use std::str::FromStr;

named!(
    i32_parser<CompleteStr, i32>,
    map_res!(nom::digit, |CompleteStr(s)| i32::from_str(s))
);

#[test]
fn parse_i32_positive() {
    assert_eq!(
        i32_parser(CompleteStr::from("42")),
        Ok((CompleteStr::from(""), 42))
    );
}

#[test]
fn parse_i32_negative() {
    assert_eq!(
        i32_parser(CompleteStr::from("-42")),
        Ok((CompleteStr::from(""), -42))
    );
}

I've also tried the following, but with a cryptic compilation error:

named!(
     i32_parser<CompleteStr, i32>,
     map_res!(alt!(char!('-') | nom::digit), |CompleteStr(s)| i32::from_str(s))
 );
^ expected char, found struct `nom::types::CompleteStr`

Any suggestion on how to fix it? Or a simpler way to achieve that with nom?

I'm explicitly looking to implement this with nom because I'm trying to parse a more complex structure. i32::from_str(s) works for simple strings but it's not what I'm looking for.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Nicolas C
  • 1,584
  • 2
  • 17
  • 33

3 Answers3

5

The recognize! macro can help you. It returns the parsed input string instead of the parser output, which can then be converted as usual. For example:

named!(i32_parser<&str, i32>,
    map_res!(
        recognize!(tuple!(opt!(char!('-')), digit)),
        FromStr::from_str)
);
starblue
  • 55,348
  • 14
  • 97
  • 151
  • Isn't it kinda weird that there are so many functions to parse numbers but nothing for something so basic as this? https://docs.rs/nom/6.0.1/nom/number/complete/index.html – Alper Dec 05 '20 at 10:38
3

Updated this for modern non-macro nom:

fn parse_isize(input: &str) -> IResult<&str, isize> {
    let (i, number) = map_res(recognize(preceded(opt(tag("-")), digit1)), |s| {
        isize::from_str(s)
    })(input)?;

    Ok((i, number))
}
Alper
  • 3,424
  • 4
  • 39
  • 45
0

alt!(char!('-') | nom::digit) "returns" a char, so your lambda needs to accept a char as argument. And it is possibly '-', so calling i32::from_str on it will fail at runtime.

Instead of handle both the sign and digits in a single step, you should decompose your problem in two, eg. using do_parse.

named!(
    i32_parser<CompleteStr, i32>,
    do_parse!(
        minus: opt!(char!('-')) >>
        digits: many1!(digit) >>
        ({
            let sign = if minus.is_some() { -1 } else { 1 };
            let mut number = 0;
            for digit in digits {
                number = number*10 + i32::from_str(digit.0).unwrap();
            }
            sign * number
        })
    )
);
Valentin Lorentz
  • 9,556
  • 6
  • 47
  • 69
  • "alt!(char!('-') | nom::digit) "returns" a char, so your lambda needs to accept a char as argument. And it is possibly '-', so calling i32::from_str on it will fail at runtime.", the code will not compile, what are you saying ? rust is a strong type language. Just use `take_while` combinator from nom and this code will not "fail at runtime" – Stargateur Feb 17 '19 at 18:18