Marwes / combine

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

How to use take_until_range() as part of your own parser? #192

Closed svenstaro closed 6 years ago

svenstaro commented 6 years ago

So I have a parser that looks like this:

fn mt940_message<I>() -> impl Parser<Input = I, Output = String>
where
    I: Stream<Item = char>,
    I::Error: ParseError<I::Item, I::Range, I::Position>,
{
    take_until_range("\r\n")
}

But that gives me a lot of errors:

error[E0271]: type mismatch resolving `<combine::range::TakeUntilRange<I> as combine::Parser>::Output == std::string::String`
  --> src/lib.rs:30:26
   |
30 | fn mt940_message<I>() -> impl Parser<Input = I, Output = String>
   |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected associated type, found struct `std::string::String`
   |
   = note: expected type `<I as combine::StreamOnce>::Range`
              found type `std::string::String`
   = note: the return type of a function must have a statically known size

error[E0271]: type mismatch resolving `<I as combine::StreamOnce>::Range == &str`
  --> src/lib.rs:35:5
   |
35 |     take_until_range("\r\n")
   |     ^^^^^^^^^^^^^^^^^^^^^^^^ expected associated type, found &str
   |
   = note: expected type `<I as combine::StreamOnce>::Range`
              found type `&str`

error[E0277]: the trait bound `<I as combine::StreamOnce>::Range: combine::stream::Range` is not satisfied
  --> src/lib.rs:30:26
   |
30 | fn mt940_message<I>() -> impl Parser<Input = I, Output = String>
   |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `combine::stream::Range` is not implemented for `<I as combine::StreamOnce>::Range`
   |
   = help: consider adding a `where <I as combine::StreamOnce>::Range: combine::stream::Range` bound
   = note: required because of the requirements on the impl of `combine::Parser` for `combine::range::TakeUntilRange<I>`
   = note: the return type of a function must have a statically known size

error[E0277]: the trait bound `I: combine::RangeStreamOnce` is not satisfied
  --> src/lib.rs:35:5
   |
35 |     take_until_range("\r\n")
   |     ^^^^^^^^^^^^^^^^ the trait `combine::RangeStreamOnce` is not implemented for `I`
   |
   = help: consider adding a `where I: combine::RangeStreamOnce` bound
   = note: required because of the requirements on the impl of `combine::RangeStream` for `I`
   = note: required by `combine::range::take_until_range`

error: aborting due to 4 previous errors

Some errors occurred: E0271, E0277.
For more information about an error, try `rustc --explain E0271`.
error[E0271]: type mismatch resolving `<combine::range::TakeUntilRange<I> as combine::Parser>::Output == std::string::String`
  --> src/lib.rs:30:26
   |
30 | fn mt940_message<I>() -> impl Parser<Input = I, Output = String>
   |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected associated type, found struct `std::string::String`
   |
   = note: expected type `<I as combine::StreamOnce>::Range`
              found type `std::string::String`
   = note: the return type of a function must have a statically known size

error[E0271]: type mismatch resolving `<I as combine::StreamOnce>::Range == &str`
  --> src/lib.rs:35:5
   |
35 |     take_until_range("\r\n")
   |     ^^^^^^^^^^^^^^^^^^^^^^^^ expected associated type, found &str
   |
   = note: expected type `<I as combine::StreamOnce>::Range`
              found type `&str`

error[E0277]: the trait bound `<I as combine::StreamOnce>::Range: combine::stream::Range` is not satisfied
  --> src/lib.rs:30:26
   |
30 | fn mt940_message<I>() -> impl Parser<Input = I, Output = String>
   |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `combine::stream::Range` is not implemented for `<I as combine::StreamOnce>::Range`
   |
   = help: consider adding a `where <I as combine::StreamOnce>::Range: combine::stream::Range` bound
   = note: required because of the requirements on the impl of `combine::Parser` for `combine::range::TakeUntilRange<I>`
   = note: the return type of a function must have a statically known size

error[E0277]: the trait bound `I: combine::RangeStreamOnce` is not satisfied
  --> src/lib.rs:35:5
   |
35 |     take_until_range("\r\n")
   |     ^^^^^^^^^^^^^^^^ the trait `combine::RangeStreamOnce` is not implemented for `I`
   |
   = help: consider adding a `where I: combine::RangeStreamOnce` bound
   = note: required because of the requirements on the impl of `combine::RangeStream` for `I`
   = note: required by `combine::range::take_until_range`

I really only want a parser that recognizes something until "\r\n".

Marwes commented 6 years ago

There are actually only 4 errors here, it is just that cargo outputs them twice since I believe you are compiling in test mode (which is rather annoying).

error[E0271]: type mismatch resolving `<combine::range::TakeUntilRange<I> as combine::Parser>::Output == std::string::String`
  --> src/lib.rs:30:26
   |
30 | fn mt940_message<I>() -> impl Parser<Input = I, Output = String>
   |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected associated type, found struct `std::string::String`
   |
   = note: expected type `<I as combine::StreamOnce>::Range`
              found type `std::string::String`
   = note: the return type of a function must have a statically known size

take_until_range outputs I::Range, not a String. If you want to return a String and not rely on RangeStream you can use https://docs.rs/combine/3.5.1/combine/parser/repeat/fn.take_until.html (not that it takes a parser, and not a range)

error[E0271]: type mismatch resolving `<I as combine::StreamOnce>::Range == &str`
  --> src/lib.rs:35:5
   |
35 |     take_until_range("\r\n")
   |     ^^^^^^^^^^^^^^^^^^^^^^^^ expected associated type, found &str
   |
   = note: expected type `<I as combine::StreamOnce>::Range`
              found type `&str`

"\r\n" forces take_until_range to have <I as combine::StreamOnce>::Range == &str but you have not declared that in the signature, It should be I: RangeStream<Item = char, Range = &'a str>

error[E0277]: the trait bound `<I as combine::StreamOnce>::Range: combine::stream::Range` is not satisfied
  --> src/lib.rs:30:26
   |
30 | fn mt940_message<I>() -> impl Parser<Input = I, Output = String>
   |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `combine::stream::Range` is not implemented for `<I as combine::StreamOnce>::Range`
   |
   = help: consider adding a `where <I as combine::StreamOnce>::Range: combine::stream::Range` bound
   = note: required because of the requirements on the impl of `combine::Parser` for `combine::range::TakeUntilRange<I>`
   = note: the return type of a function must have a statically known size

take_until_range also requires I::Range: Range https://docs.rs/combine/3.5.1/combine/stream/trait.Range.html

error[E0277]: the trait bound `I: combine::RangeStreamOnce` is not satisfied
  --> src/lib.rs:35:5
   |
35 |     take_until_range("\r\n")
   |     ^^^^^^^^^^^^^^^^ the trait `combine::RangeStreamOnce` is not implemented for `I`
   |
   = help: consider adding a `where I: combine::RangeStreamOnce` bound
   = note: required because of the requirements on the impl of `combine::RangeStream` for `I`
   = note: required by `combine::range::take_until_range`

error: aborting due to 4 previous errors

You need to bound I: RangeStream (not just I: Stream)

That will fix the errors to make take_until_range work but you could instead use https://docs.rs/combine/3.5.1/combine/parser/repeat/fn.take_until.html if you don't want to restrict your self to RangeStream (implemented by&str and &[T] and wrappers around those) https://docs.rs/combine/3.5.1/combine/trait.RangeStreamOnce.html#foreign-impls

svenstaro commented 6 years ago

Following your suggestions in gitter, I came up with this:

fn mt940_tag<I>() -> impl Parser<Input = I, Output = String>
where
    I: Stream<Item = char>,
    I::Error: ParseError<I::Item, I::Range, I::Position>,
{
    (
        token(':'),
        char::digit(),
        many::<String, _>(char::alpha_num()),
        token(':'),
    )
        .map(|(_, x, y, _)| format!("{}{}", x, y))
}

fn mt940_record_start<I>() -> impl Parser<Input = I, Output = (String, String)>
where
    I: Stream<Item = char>,
    I::Error: ParseError<I::Item, I::Range, I::Position>,
{
    (mt940_tag(), take_until(byte::bytes(&b"\r\n"[..])))
}

Which gives me

error[E0271]: type mismatch resolving `<I as combine::StreamOnce>::Item == u8`
  --> src/lib.rs:28:31
   |
28 | fn mt940_record_start<I>() -> impl Parser<Input = I, Output = (String, String)>
   |                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected char, found u8
   |
   = note: required because of the requirements on the impl of `combine::Parser` for `combine::byte::Bytes<I>`
   = note: required because of the requirements on the impl of `combine::Parser` for `combine::combinator::TakeUntil<std::string::String, combine::byte::Bytes<I>>`
   = note: the return type of a function must have a statically known size

error[E0271]: type mismatch resolving `<I as combine::StreamOnce>::Range == &[u8]`
  --> src/lib.rs:28:31
   |
28 | fn mt940_record_start<I>() -> impl Parser<Input = I, Output = (String, String)>
   |                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected associated type, found &[u8]
   |
   = note: expected type `<I as combine::StreamOnce>::Range`
              found type `&[u8]`
   = note: required because of the requirements on the impl of `combine::Parser` for `combine::byte::Bytes<I>`
   = note: required because of the requirements on the impl of `combine::Parser` for `combine::combinator::TakeUntil<std::string::String, combine::byte::Bytes<I>>`
   = note: the return type of a function must have a statically known size

error[E0271]: type mismatch resolving `<I as combine::StreamOnce>::Item == u8`
  --> src/lib.rs:33:19
   |
33 |     (mt940_tag(), take_until(byte::bytes(&b"\r\n"[..])))
   |                   ^^^^^^^^^^ expected char, found u8
   |
   = note: required because of the requirements on the impl of `combine::Parser` for `combine::byte::Bytes<I>`

error[E0271]: type mismatch resolving `<I as combine::StreamOnce>::Range == &[u8]`
  --> src/lib.rs:33:19
   |
33 |     (mt940_tag(), take_until(byte::bytes(&b"\r\n"[..])))
   |                   ^^^^^^^^^^ expected associated type, found &[u8]
   |
   = note: expected type `<I as combine::StreamOnce>::Range`
              found type `&[u8]`
   = note: required because of the requirements on the impl of `combine::Parser` for `combine::byte::Bytes<I>`

error[E0271]: type mismatch resolving `<I as combine::StreamOnce>::Item == u8`
  --> src/lib.rs:33:30
   |
33 |     (mt940_tag(), take_until(byte::bytes(&b"\r\n"[..])))
   |                              ^^^^^^^^^^^ expected char, found u8
   |
   = note: required by `combine::byte::bytes`

error[E0271]: type mismatch resolving `<I as combine::StreamOnce>::Range == &[u8]`
  --> src/lib.rs:33:30
   |
33 |     (mt940_tag(), take_until(byte::bytes(&b"\r\n"[..])))
   |                              ^^^^^^^^^^^ expected associated type, found &[u8]
   |
   = note: expected type `<I as combine::StreamOnce>::Range`
              found type `&[u8]`
   = note: required by `combine::byte::bytes`

I tried giving it various types and mapping it but to no avail. Help appreciated. :)

Marwes commented 6 years ago

These all boil down to the same error in the end and the only real way to solve it is to slowly read through the error to understand what rustc fails at.

The second from the bottom is probably the clearerest (by a slight margin).

error[E0271]: type mismatch resolving `<I as combine::StreamOnce>::Item == u8`
  --> src/lib.rs:33:30
   |
33 |     (mt940_tag(), take_until(byte::bytes(&b"\r\n"[..])))
   |                              ^^^^^^^^^^^ expected char, found u8
   |
   = note: required by `combine::byte::bytes`

Here it says that it expected a char but found a u8, more specifically it also says type mismatch resolving <I as combine::StreamOnce>::Item == u8 so apparently I::Item were expected to be a char but it was actually an u8. So somewhere you have specified that I::Item == char but elsewhere it is specified as u8.

In this case you have specified I: Stream<Item = char>, in the function signature so I assume that u8 is the error here so we must change that to use char as well. Helpfully rustc points directly at the culprit, byte::bytes is, as it name suggests, a parser that works with bytes (that is, u8) so that parser needs to be replaced with a char based one. In the char module we can find https://docs.rs/combine/3.5.1/combine/parser/char/fn.string.html which is the equivalent to bytes which will do the trick.

Any other of the errors could lead us to the same conclusion in a slight less clear way as they all boil down to trying to use u8 or &[u8] where a string parser were expected. It is all about taking the time to dig into the error enough to understand and fix it, rustc --explain E0271 could be helpful as well if the error is confusing.

svenstaro commented 6 years ago

Oh, thanks! Well, reading the error is one thing but figuring out exactly what it means in relation to my code is another. I suggest adding an example for char::string to the take_until docs.

svenstaro commented 6 years ago

So right now I'm here:

fn mt940_tag<I>() -> impl Parser<Input = I, Output = String>
where
    I: Stream<Item = char>,
    I::Error: ParseError<I::Item, I::Range, I::Position>,
{
    (
        token(':'),
        char::digit(),
        many::<String, _>(char::alpha_num()),
        token(':'),
    )
        .map(|(_, x, y, _)| format!("{}{}", x, y))
}

fn mt940_record_start<I>() -> impl Parser<Input = I, Output = (String, String)>
where
    I: Stream<Item = char>,
    I::Error: ParseError<I::Item, I::Range, I::Position>,
{
    (mt940_tag(), take_until(char::string("\r\n")))
}

fn mt940_record<I>() -> impl Parser<Input = I, Output = String>
where
    I: Stream<Item = char>,
    I::Error: ParseError<I::Item, I::Range, I::Position>,
{
    (
        mt940_record_start(),
        many((not_followed_by(look_ahead(mt940_tag())), char::string("\r\n"))),
    ).map(|(_, _)| "test".to_string())
}

and it's telling me

error[E0277]: the trait bound `combine::error::Info<_, _>: std::convert::From<std::string::String>` is not satisfied
  --> src/lib.rs:42:15
   |
42 |         many((not_followed_by(look_ahead(mt940_tag())), char::string("\r\n"))),
   |               ^^^^^^^^^^^^^^^ the trait `std::convert::From<std::string::String>` is not implemented for `combine::error::Info<_, _>`
   |
   = help: the following implementations were found:
             <combine::error::Info<T, R> as std::convert::From<&'static str>>
             <combine::error::Info<char, R> as std::convert::From<char>>
             <combine::error::Info<u8, R> as std::convert::From<u8>>
   = note: required because of the requirements on the impl of `std::convert::Into<combine::error::Info<_, _>>` for `std::string::String`
   = note: required by `combine::not_followed_by`

The problem is that I can't just define that for String for combine::error::Info as that's not part of my own crate.

So I tried mapping that output to &str:

fn mt940_record<I>() -> impl Parser<Input = I, Output = String>
where
    I: Stream<Item = char>,
    I::Error: ParseError<I::Item, I::Range, I::Position>,
{
    (
        mt940_record_start(),
        many((not_followed_by(look_ahead(mt940_tag()).map(|x| x.as_str())), char::string("\r\n"))),
    ).map(|(_, _)| "lol".to_string())
}

which then resulted in

error[E0283]: type annotations required: cannot resolve `_: std::iter::Extend<((), &'static str)>`
  --> src/lib.rs:42:9
   |
42 |         many((not_followed_by(look_ahead(mt940_tag()).map(|x| x.as_str())), char::string("\r\n"))),
   |         ^^^^
   |
   = note: required by `combine::many`

error: aborting due to previous error

Ok so far so good. Now I needed annotations on many as before. I want to end up with a String if I can help it, so:

fn mt940_record<I>() -> impl Parser<Input = I, Output = String>
where
    I: Stream<Item = char>,
    I::Error: ParseError<I::Item, I::Range, I::Position>,
{
    (
        mt940_record_start(),
        many::<String, _>((not_followed_by(look_ahead(mt940_tag()).map(|x| x.as_str())), char::string("\r\n"))),
    ).map(|(_, _)| "lol".to_string())
}

gets me

error[E0277]: the trait bound `std::string::String: std::iter::Extend<((), &'static str)>` is not satisfied
  --> src/lib.rs:42:9
   |
42 |         many::<String, _>((not_followed_by(look_ahead(mt940_tag()).map(|x| x.as_str())), char::string("\r\n"))),
   |         ^^^^^^^^^^^^^^^^^ the trait `std::iter::Extend<((), &'static str)>` is not implemented for `std::string::String`
   |
   = help: the following implementations were found:
             <std::string::String as std::iter::Extend<&'a char>>
             <std::string::String as std::iter::Extend<&'a str>>
             <std::string::String as std::iter::Extend<char>>
             <std::string::String as std::iter::Extend<std::borrow::Cow<'a, str>>>
             <std::string::String as std::iter::Extend<std::string::String>>
   = note: required by `combine::many`

Now where does my static lifetime come from? I don't quite yet and frankly, I think I'm going down the wrong road on this. Please help. :)

Marwes commented 6 years ago

Now where does my static lifetime come from? I don't quite yet and frankly, I think I'm going down the wrong road on this. Please help. :)

The static lifetime comes from what string outputs (see impl Parser at https://docs.rs/combine/3.5.1/combine/parser/char/struct.Str.html, not exactly easy to discover :/ )

the trait bound std::string::String: std::iter::Extend<((), &'static str)> is not satisfied

You are trying to extend with a tuple here as you are passing a tuple of parsers to many((a, b)). You could either map over the tuple to only take the second field of it perhaps.

The problem is that I can't just define that for String for combine::error::Info as that's not part of my own crate. So I tried mapping that output to &str:

Just using as_str will run you afoul of the borrow checker but you could give it your own &'static str. Since not_followed_by only use it to display an error message if the parser succeeds doing not_followed_by(look_ahead(mt940_tag()).map(|_| "mt940 tag")) and errors will say "unexpected mt940 tag".

many::<String, _>

As an aside, do you really want a String here? Or do you want a Vec<String>, just thought I'd mention it (if it is just a String then it will collect all of the lines into the same String).

svenstaro commented 6 years ago

Thanks, that was the last bit I needed to actually make it compile. Types are magical. And I believe I want a String here. See, a Record is a tag and a message and then a Statement (which is not actually a type in my code) is just a Vec<Record>. My idea was to compose a bunch of parsers (that's the whole idea, right?).

Anyway, I believe the problem that I originally asked for help with in Gitter is not actually solved and that's how to parse the line format properly. For reference, I'm putting my whole bunch of code there.

The problem is that the parse_mt940_record (and therefore also parse_mt940_statement) test doesn't work. The problem must be with how I currently parse until \r\n if the line doesn't start with a tag. I'll try around a bit but I don't quite grok how to make the combinators behave so that I can express this need.

extern crate combine;

use combine::parser::char;
use combine::parser::repeat::take_until;
use combine::*;

#[derive(Debug, PartialEq)]
pub struct Record {
    pub tag: String,
    pub message: String,
}

fn mt940_tag<I>() -> impl Parser<Input = I, Output = String>
where
    I: Stream<Item = char>,
    I::Error: ParseError<I::Item, I::Range, I::Position>,
{
    (
        token(':'),
        char::digit(),
        many::<String, _>(char::alpha_num()),
        token(':'),
    )
        .map(|(_, x, y, _)| format!("{}{}", x, y))
}

fn mt940_record_start<I>() -> impl Parser<Input = I, Output = (String, String)>
where
    I: Stream<Item = char>,
    I::Error: ParseError<I::Item, I::Range, I::Position>,
{
    (mt940_tag(), take_until(char::string("\r\n")))
}

fn mt940_record<I>() -> impl Parser<Input = I, Output = Record>
where
    I: Stream<Item = char>,
    I::Error: ParseError<I::Item, I::Range, I::Position>,
{
    (
        mt940_record_start(),
        many::<String, _>(
            (
                not_followed_by(look_ahead(mt940_tag()).map(|_| "mt940_tag")),
                char::string("\r\n"),
            )
                .map(|(_, x)| x),
        ),
    )
        .map(|(start, rest)| Record {
            tag: start.0,
            message: format!("{start}{rest}", start = start.1, rest = rest),
        })
}

/// A Statement is just a list of Records.
fn mt940_statement<I>() -> impl Parser<Input = I, Output = Vec<Record>>
where
    I: Stream<Item = char>,
    I::Error: ParseError<I::Item, I::Range, I::Position>,
{
    many1::<Vec<_>, _>(mt940_record())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parse_mt940_tag() {
        let expected = Ok(("20".to_string(), ""));
        let result = mt940_tag().easy_parse(":20:");
        assert_eq!(expected, result);
    }

    #[test]
    fn parse_mt940_record_start() {
        let expected = Ok((("20".to_string(), "3996-11-11111111".to_string()), "\r\n"));
        let result = mt940_record_start().easy_parse(":20:3996-11-11111111\r\n");
        assert_eq!(expected, result);
    }

    #[test]
    fn parse_mt940_record() {
        let expected = Ok((
            Record {
                tag: "20".to_string(),
                message: "3996-11-11111111\r\nTESTTEST\r\nMORETEST\r\n".to_string(),
            },
            "",
        ));
        let result = mt940_record().easy_parse(":20:3996-11-11111111\r\nTESTTEST\r\nMORETEST\r\n");
        assert_eq!(expected, result);
    }

    #[test]
    fn parse_mt940_statement() {
        let test_data = "\
            :20:3996-1234567890\r\n\
            :25:DABADKKK/1234567890\r\n\
            :28C:00014/001\r\n\
            :60F:C091019DKK3859701,48\r\n\
            :86:For your inform. IBAN no.: DK5030001234567890\r\n\
            :86:DABADKKK                                                 \r\n\
            :86:1234567890\r\n\
            :86:DANSKE BANK                        HOLMENS KANAL 2-12\r\n\
            :61:0910201020DK5312,50NMSCDBT.teste kunden\r\n\
            :86:F.M.T.\r\n\
            V/TESTE KUNDEN\r\n\
            HOLMENS KANAL 2-12\r\n\
            1192  KOBENHAVN H\r\n\
            :61:0910201020DK3009,51NMSCDBT.Pet Van Park\r\n\
            :86:DBTS 1111272333/Bnf. PET VAN PARK AMSTERDAM/Bnf.acc. NL47ABNAXXXX\r\n\
            558756/Our fee DKK 40,00/Foreign fee DKK 200,00\r\n\
            :62F:C091020DKK3851379,47\r\n\
            :64:C091020DKK3851379,47\r\n\
            \r\n
        ";

        let expected = Ok((
            vec![
                Record {
                    tag: "20".to_string(),
                    message: "3996-1234567890".to_string(),
                },
                Record {
                    tag: "25".to_string(),
                    message: "DABADKKK/1234567890".to_string(),
                },
                Record {
                    tag: "28C".to_string(),
                    message: "00014/001".to_string(),
                },
                Record {
                    tag: "60F".to_string(),
                    message: "C091019DKK3859701,48".to_string(),
                },
                Record {
                    tag: "86".to_string(),
                    message: ")
                    For your inform. IBAN no.: DK5030001234567890\n\
                    DABADKKK\n\
                    1234567890\n\
                    DANSKE BANK                        HOLMENS KANAL 2-12\n\
                ".to_string(),
                },
                Record {
                    tag: "61".to_string(),
                    message: "0910201020DK5312,50NMSCDBT.teste kunden".to_string(),
                },
                Record {
                    tag: "86".to_string(),
                    message: "F.M.T.\n\
                              V/TESTE KUNDEN\n\
                              HOLMENS KANAL 2-12\n\
                              1192  KOBENHAVN H\n\
                              ".to_string(),
                },
                Record {
                    tag: "61".to_string(),
                    message: "0910201020DK3009,51NMSCDBT.Pet Van Park".to_string(),
                },
                Record {
                    tag: "86".to_string(),
                    message: "DBTS 1111272333/Bnf. PET VAN PARK AMSTERDAM/Bnf.acc. NL47ABNAXXXX\n\
                              558756/Our fee DKK 40,00/Foreign fee DKK 200,00"
                        .to_string(),
                },
                Record {
                    tag: "62F".to_string(),
                    message: "C091020DKK3851379,47".to_string(),
                },
                Record {
                    tag: "64".to_string(),
                    message: "C091020DKK3851379,47".to_string(),
                },
            ],
            "",
        ));

        let result = mt940_statement().easy_parse(test_data);
        assert_eq!(expected, result);
    }
}
Marwes commented 6 years ago
(
                not_followed_by(look_ahead(mt940_tag()).map(|_| "mt940_tag")),
                char::string("\r\n"),
            )

I think you are just missing a take_until wrapper around char::string("\r\n") :)

Marwes commented 6 years ago

I believe we worked this out on gitter, so closing (feel free to re-open if necessary).