logoalt Hacker News

ArneCode • today at 12:58 PM • 3 replies • view on HN

I really like the animations, makes it intuitive to understand what is happening!

One disadvantage that I think parser combinators have, for example compared to parser generator libraries (where the parser is generated from a seperate grammar file), is that you have to add a lot of mapping / unwrapping code. In my opinion this makes the parsers harder to read.

As an attempt to improve this, I have been working on a parser combinator library in rust. It adds two extra combinators (capture and bind) which can store the results of parsers in buckets (bind) and use the values stored in these buckets later to construct results (capture). This allows you to eliminate the unwrapping / mapping code and seperate the grammar from the result contstruction.

If you have the time, I would really appreciate feedback on this!

You can find it here: https://github.com/ArneCode/marser


Replies

pxc • today at 6:34 PM

My first contact with parser combinators was in a parser generator library: https://github.com/engelberg/instaparse

This is (probably weirdly, since I've just learned that the approach in the OP is the original one that introduced parser combinators) the first time I've seen parser combinators where the parsers are just functions like this. If you want to be able to combine parsers and you don't want to write a bunch of mapping/unwrapping code, why not just use combinators over parsers assembled by a parser generator?

pdpi • today at 2:09 PM

I'm struggling to see the point in this. Your roll example using Winnow. Not really that much (un)wrapping logic:

    use winnow::{Result, ascii::digit1, combinator::separated_pair, prelude::*};

    #[derive(Debug, PartialEq)]
    struct Roll {
        count: u32,
        sides: u32,
    }

    fn number(input: &mut &str) -> Result<u32> {
        digit1.try_map(str::parse).parse_next(input)
    }

    fn roll(input: &mut &str) -> Result<Roll> {
        let (count, sides) = separated_pair(number, 'd', number).parse_next(input)?;
        Ok(Roll { count, sides })
    }

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

        #[test]
        fn test_roll() {
            let input = &mut "3d6";
            assert_eq!(roll(input), Ok(Roll { count: 3, sides: 6 }));
        }
    }
➕ show 1 reply
mrkeen • today at 1:37 PM

What kind of mapping/unwrapping are you being subjected to with PCs?

➕ show 1 reply