Nom -- how to raise an error / convert other errors to nom errors?

When calling partial functions like str::from_utf8 and str::parse from nom
parsers how do I convert the errors returned by those functions to nom errors
and raise them as parse errors? I tried making my parsers return Result, but
then combinators like many0 returns Vec<Result<...>> instead of Vec<...>
which doesn't make any sense -- any errors raised when parsing a sequence of
things should be raised as parse errors. How do I do this with nom?

Thanks

2 Likes

It's not clear to me exactly what you're asking.

If you are using nom's named! macro to create your parse functions, the types should match up in a sane way. If you're inserting your own functions at some points (sometimes necessary), then you have to do some of the plumbing work that is otherwise handled by the macros. You may wish to use return_error! to cause earlier returns in the error case. Normally, nom will backtrack through parsers in an attempt to find one which succeeds in parsing. return_error! should prevent this backtracking. I'm not sure it will help if you aren't using nom's IResult type.

Note that the vector of results makes a lot of sense if you are returning just Result<T,Err> from your functions - you haven't given many0! anything else to work with, so it's acting like a simple vec! macro, except it will accept zero successful arguments. nom returns IResult<I,O>, which is an alias for Result<(I, O), nom::Err<I, E>>. It returns this from parsers, because it passes the remaining input through the parse chain. If you aren't using nom's IResult type, then you have to write out the signature I provided above yourself to get more-idiomatic results from nom.

Without code, I'm not sure we can provide very specific help.

1 Like

Funny how 9 months later I googled the exact question and found my own question.

I still don't know the answer to this, and none of the nom exampes (as far as I
can see) show how to do this.

The idea is simple: in a nom parser I sometimes want to run some action that can
fail. If it fails then I want the parse to fail.

Here's a simple example:

use ::nom::bytes::complete as nom;

fn parse_decimal(input: &[u8]) -> ::nom::IResult<&[u8], u32> {
    let (input, digits) = nom::take(10usize)(input)?;
    match parse_decimal_digits(digits) {
        None => {
            unimplemented!()
        }
        Some(num) =>
            Ok((input, num)),
    }
}

fn parse_decimal_digits(input: &[u8]) -> Option<u32> {
    std::str::from_utf8(input).ok().and_then(|s| s.parse().ok())
}

How should I implement the unimplemented! part in this code?

Note: Please don't suggest replacing my parse_decimal_digits function with some nom function -- imagine having much more complicated, domain-specific, failable action there. I want to turn any function that can fail into a parse error.

1 Like

The best way I found to solve this was to create my own error type, you can do this by implementing ParseError. But if you want something quick and dirty, you can use make_error.

If you do create your own error type, you can specify it using the nom::IResult<Input, Output, Error> typedef that nom provides. Usually Error is defaulted to a simple error type, but you can change it. You can then create the Err directly.

The problem is that if you create a custom error type in a custom parse function, the custom error type will be propagated all the way to the top of your parser combinator, which means you need to make some kind of all-encompassing enum type that covers every case you encounter. It seems there isn't a way to integrate custom error types into the default (I, ErrorKind) error type. Otherwise, the author would have integrated it into

pub fn map_res<I: Clone, O1, O2, E: ParseError<I>, E2, F, G>(first: F, second: G) -> impl Fn(I) -> IResult<I, O2, E>
where
  F: Fn(I) -> IResult<I, O1, E>,
  G: Fn(O1) -> Result<O2, E2>,
{
  move |input: I| {
    let i = input.clone();
    let (input, o1) = first(input)?;
    match second(o1) {
      Ok(o2) => Ok((input, o2)),
      Err(_) => Err(Err::Error(E::from_error_kind(i, ErrorKind::MapRes))),
    }
  }
}

instead of just ignore the error. The only solution I can think of is to use dynamically dispatch on all Error types.

Note the generic parameter E: ParseError<I>, this is a custom error type. All nom' combinators do this, so it is really easy to plug in your own error type.


You could just create an error like this,

struct Error<I> {
    pub kind: ErrorKind<I>,
    backtrace: Vec<ErrorKind<I>>
}

enum ErrorKind<I> {
     Nom(I, nom::ErrorKind),
     // your error types as the rest of the variants
}

And implement ParseError for this like so:

impl<I> ParseError<I> for Error<I> {
    fn from_error_kind(input: I, kind: nom::ErrorKind) -> Self {
        Self {
            kind: ErrorKind::Nom(inpput, kind),
            backtrace: Vec::new()
        }
    }
    
    fn append(input: I, kind: nom::ErrorKind, mut other: Self) -> Self {
        other.backtrace(Self::from_error_kind(input, kind));
        other
    }
}

And you are set to use it in nom. I would also define the typedef

type Result<I, T> = nom::Result<I, T, Error<I>>;

This isn''t a lot of boiler plate, and it gives the same ergonomics as normal nom errors.

1 Like

In case anyone finds this while searching (as I did), there's now an example in the nom repo for defining and instantiating custom errors:

https://github.com/Geal/nom/blob/master/examples/custom_error.rs

7 Likes

I must be doing something wrong, because I tried this tact and failed:


#[derive(Debug, PartialEq)]
pub enum PesNomError<I> {
  InvalidKey(String),
  Nom(I, ErrorKind),
}

impl<I> ParseError<I> for PesNomError<I> {
  fn from_error_kind(input: I, kind: ErrorKind) -> Self {
    PesNomError::Nom(input, kind)
  }

  fn append(_: I, _: ErrorKind, other: Self) -> Self {
    other
  }
}

pub type PNResult<I, T> = nom::IResult<I, T,PesNomError<I>>;

And later using it

let result = self.get(variable).ok_or_else(|| PesNomError::<&str>::InvalidKey(variable.to_string()))?;

Which results in:

error[E0277]: `?` couldn't convert the error to `nom::Err<PesNomError<&str>>`
   --> src/parser.rs:268:109
    |
268 |         let result = self.get(variable).ok_or_else(|| PesNomError::<&str>::InvalidKey(variable.to_string()))?;
    |                                                                                                             ^ the trait `From<PesNomError<&str>>` is not implemented for `nom::Err<PesNomError<&str>>`
    |
    = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
    = note: required by `std::convert::From::from`

I had to add the following impl


impl<'a> From<PesNomError<&'a str>> for nom::Err<PesNomError<&'a str>> {
    fn from(err: PesNomError<&'a str>) -> Self {
        nom::Err::Error(err)
    }
}

to get this to compile, although I am uncertain if this is "correct"

That looks similar to what I did in the end. I didn't add a From implementation though, instead I wrapped my custom error type with nom::Err::Failure() directly when constructing it - the result is the same though, the custom error is wrapped in the nom:Err type which is what nom seems to require. I used nom::Failure rather than nom::Error to wrap my custom error as I wanted the error to be treated as non-recoverable in this case.

This is where you deviated from the nom example. Rather than simply using your error you need to wrap it in nom's error type - according to the nom example.

You're From implementation simply tells the compiler how to do this for you using ?

The alternative is:

let result = self.get(variable).ok_or_else(|| nom::Error(PesNomError::<&str>::InvalidKey(variable.to_string())))

(I haven't tested this!)

There's nothing wrong with implementing From to convert between different error types. It is idiomatic in fact. It's used in rust by example Wrapping errors - Rust By Example and in the standard library: Error in std::io - Rust just scroll all the way down to the implementations.

You're doing great basically :slight_smile:

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.