Marwes / combine

A parser combinator library for Rust
https://docs.rs/combine/*/combine/
MIT License
1.3k stars 95 forks source link

Having trouble with `choice` parser #299

Closed mainrs closed 4 years ago

mainrs commented 4 years ago

Hey! I am new to the library and not sure where to ask this question. I wanted to write a comment line parser. However, I get a compilation error that I do not understand. Inside of begin_of_line_comment, when using the comment_start_token parser, the compilation fails. If I pass down the choices parser instead of calling comment_start_token, the function compiles. Looks like Rust can't correctly deduce the type. I tried to manually set the Output type but to be honest I miserably failed to somehow specify the correct one.

use combine::{*, parser::char::space, parser::choice::Choice, parser::token::Token, stream::position};

fn comment_start_token<I>() -> impl Parser<I>
where
    I: Stream<Token=char>,
    I::Error: ParseError<I::Token, I::Range, I::Position>
{
    choice([
        token(';'),
        token('%'),
        token('|'),
        token('*'),
    ])
}

fn begin_of_line_comment<Input>() -> impl Parser<Input, Output=()>
where
    Input: Stream<Token=char>,
    Input::Error: ParseError<Input::Token, Input::Range, Input::Position>
{
    let comment = (comment_start_token(), skip_many(satisfy(|c| c != '\n'))).map(|_| ());
    skip_many(skip_many1(space()).or(comment))
}

mod tests {
    use super::{begin_of_line_comment, comment_start_token};
    use combine::Parser;

    #[test]
    fn test_comment_start_token() {
        let i = [";", "|", "%", "*"];
        for case in i.iter() {
            let result = comment_start_token().parse(*case);
            assert_eq!(result, Ok(((), ""))); // <---- error
        }
    }

    #[test]
    fn test_comment() {
        let i = "; This is a comment";
        let result = begin_of_line_comment().parse(i);
        assert_eq!(Ok(((), "")), result);
    }
}

Error:

error[E0369]: binary operation `==` cannot be applied to type `std::result::Result<(<impl combine::Parser<&str> as combine::Parser<&str>>::Output, &str), combine::error::StringStreamError>`
  --> parser/src/parser.rs:46:13
   |
46 |             assert_eq!(result, Ok((_, "")));
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |             |
   |             std::result::Result<(<impl combine::Parser<&str> as combine::Parser<&str>>::Output, &str), combine::error::StringStreamError>
   |             std::result::Result<_, _>
   |
   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0277]: `<impl combine::Parser<&str> as combine::Parser<&str>>::Output` doesn't implement `std::fmt::Debug`
  --> parser/src/parser.rs:46:13
   |
46 |             assert_eq!(result, Ok((_, "")));
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `<impl combine::Parser<&str> as combine::Parser<&str>>::Output` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug`
   |
   = help: the trait `std::fmt::Debug` is not implemented for `<impl combine::Parser<&str> as combine::Parser<&str>>::Output`
   = note: required because of the requirements on the impl of `std::fmt::Debug` for `(<impl combine::Parser<&str> as combine::Parser<&str>>::Output, &str)`
   = note: required because of the requirements on the impl of `std::fmt::Debug` for `std::result::Result<(<impl combine::Parser<&str> as combine::Parser<&str>>::Output, &str), combine::error::StringStreamError>`
   = note: required because of the requirements on the impl of `std::fmt::Debug` for `&std::result::Result<(<impl combine::Parser<&str> as combine::Parser<&str>>::Output, &str), combine::error::StringStreamError>`
   = note: required by `std::fmt::Debug::fmt`
   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
Marwes commented 4 years ago

You need to set the Output type of comment_start_token for the current error at least, I think it should be

fn comment_start_token<I>() -> impl Parser<I, Output = char>

Hard to say at a glance what any errors are other than that but this line

assert_eq!(result, Ok(((), ""))); // <---- error

Would then fail since the result will now contain a char so you need to change the () to a char

mainrs commented 4 years ago

I didn't set an output because I wanted the function to consume the token and not bother with it anymore. I mainly used nom up until now but wanted to give combine a try, since the error messages are more user friendly.

That being said, I thought that token would be similar to nom::take: It consumes the output. I will try your suggestion. If there isn't something like take I can simply map over it and ignore the value I guess :)

Marwes commented 4 years ago

If you don't care about the output you should specify () and map the output to () (like you do in begin_of_line_comment). Otherwise, if you do not specify it, rust will assume that the output is an "opaque" type and it will consider that "opaque" type to only be identical to itself and you can't compare, debug print or do anything else with it.

mainrs commented 4 years ago

Ah ok, thanks you very much!