m4rw3r/chomp

Accessing numbering::InputPosition::position via map_err

dashed opened this issue · 2 comments

I have a usecase where I'd like to somehow pass numbering::InputPosition::position to an Error type as a way of reporting parsing errors at a location (e.g. line/column location).

The issue is that I'm unable to access numbering::InputPosition::position from within chomp::types::ParseResult::map_err function.

I adapted map_err into map_err2 as follows: dashed@3f1998b

This enables me to do this:

type ESParseResult<I, T> = ParseResult<I, T, ParseError>;

fn some_parser<I: U8Input>(i: InputPosition<I, CurrentPosition>)
    -> ESParseResult<InputPosition<I, CurrentPosition>, ()> {
    parse!{i;

        let _var = (i -> {
            string(i, b"var")
                .map_err2(|_, i| {
                    let loc = i.position();
                    ParseError::Expected(loc, "Expected var here.")
                })
        });

        // ...

        ret {()}
    }
}

I'd love to hear any feedback on this, especially for any better alternative approaches. 👍


Appendix

CurrentPosition type for reference:

#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub struct CurrentPosition(
    // The current line, zero-indexed.
    u64,
    // The current col, zero-indexed.
    u64
);

impl CurrentPosition {
    // Creates a new (line, col) counter with zero.
    pub fn new() -> Self {
        CurrentPosition(0, 0)
    }
}

impl Numbering for CurrentPosition {
    type Token  = u8;

    fn update<'a, B>(&mut self, b: &'a B)
        where B: Buffer<Token=Self::Token> {
            b.iterate(|c| if c == b'\n' {
                self.0 += 1; // line num
                self.1 = 0;  // col num
            } else {
                self.1 += 1; // col num
            });
    }

    fn add(&mut self, t: Self::Token) {
        if t == b'\n' {
            self.0 += 1; // line num
            self.1 = 0;  // col num
        } else {
            self.1 += 1; // col num
        }
    }
}
pub trait Input: Sized {

    // ...

    #[inline]
    pub fn map_err2<V, F>(self, f: F) -> ParseResult<I, T, V>
      where F: FnOnce(E, &I) -> V {
        match self {
            ParseResult(i, Ok(t))  => ParseResult(i, Ok(t)),
            ParseResult(i, Err(e)) => {
                let err = f(e, &i);
                ParseResult(i, Err(err))
            },
        }
    }

    // ...
}

I might settle on a higher-order function as follows:

fn on_error<I: Input, T, E, F, V, G>(i: I, f: F, g: G) -> ParseResult<I, T, V>
    where F: FnOnce(I) -> ParseResult<I, T, E>,
          G: FnOnce(E, &I) -> V {

    match f(i).into_inner() {
        (i, Ok(t))  => i.ret(t),
        (i, Err(e)) => {
            let err_val = g(e, &i);
            i.err(err_val)
        }
    }
}

Usage:

fn some_parser<I: U8Input>(i: InputPosition<I, CurrentPosition>)
    -> ESParseResult<InputPosition<I, CurrentPosition>, ()> {
    parse!{i;

        let var = on_error(
            |i| string(i, b"var"),
            |_err, i| {
                let loc = i.position();
                ParseError::Expected(loc, "Expected var keyword.".to_string())
            }
        );

        // ...

        ret {()}
    }
}

That looks pretty good, when I have the time I will see if I can include something like that in Chomp proper. Would be useful to have a higher-order function and/or error type to carry position-information for errors.