Reducing boilerplate when wrapping LALRPOP parsers

I'm trying to wrap parsers produced by LALRPOP which do not implement a parser trait but just provide a parse function.

The code below works fine but is there a way to reduce the amount of boilerplate?

For example, is there a way to avoid spelling out the types in the trait implementation here?

pub type DefsParserEx = ParserEx<grammar::DefsParser, Vec<ast::Definition>>;

impl Parser for DefsParserEx {
    type T = grammar::DefsParser;
    type U = Vec<ast::Definition>;
...

Also, is there a way to provide a blanket parse implementation rather than copy the same code for each trait implementation?

Thanks, Joel
pub struct ParserEx<T, U> {
    parser: T,
    marker: std::marker::PhantomData<U>,
}

pub trait Parser {
    type T;
    type U;

    fn new() -> Self;

    fn parse<'input>(
        &self,
        file_id: FileId,
        lexer: lexer::Lexer<'input>,
    ) -> Result<Self::U, error::LalrParseError<'input>>;
}

pub fn parse<'input, T>(
    parser: impl Parser<U = T>,
    s: &'input str,
) -> Result<T, error::LalrParseError<'input>> {
    let lexer = lexer::Lexer::new(s);
    parser.parse(FileId::empty(), lexer)
}

pub type DefsParserEx = ParserEx<grammar::DefsParser, Vec<ast::Definition>>;

impl Parser for DefsParserEx {
    type T = grammar::DefsParser;
    type U = Vec<ast::Definition>;

    fn new() -> Self {
        ParserEx {
            parser: Self::T::new(),
            marker: std::marker::PhantomData,
        }
    }

    fn parse<'input>(
        &self,
        file_id: FileId,
        lexer: lexer::Lexer<'input>,
    ) -> Result<Self::U, error::LalrParseError<'input>> {
        self.parser.parse(file_id, lexer)
    }
}

pub type CtrStartParserEx =
    ParserEx<grammar::ConstructorStartParser, (Loc<ast::Ident>, ast::Display, bool)>;

impl Parser for CtrStartParserEx {
    type T = grammar::ConstructorStartParser;
    type U = (Loc<ast::Ident>, ast::Display, bool);

    fn new() -> Self {
        ParserEx {
            parser: Self::T::new(),
            marker: std::marker::PhantomData,
        }
    }

    fn parse<'input>(
        &self,
        file_id: FileId,
        lexer: lexer::Lexer<'input>,
    ) -> Result<Self::U, error::LalrParseError<'input>> {
        self.parser.parse(file_id, lexer)
    }
}

In theory a generic implementation should work, something like:

impl<T, U> Parser for ParserEx<T, U> {
    type T = T;
    type U = U;

    fn new() -> Self {
        Self {
            parser: Self::T::new(),
            marker: std::marker::PhantomData,
        }
    }

    fn parse<'input>(
        &self,
        file_id: FileId,
        lexer: lexer::Lexer<'input>,
    ) -> Result<Self::U, error::LalrParseError<'input>> {
        self.parser.parse(file_id, lexer)
    }
}

But since specialization isn't a thing, that also prevents you from having a more specific implementation.

You will need some bound on T at least, but I can't see what it would be from the example code?

There are no bounds on T, unfortunately.

I’ll probably just patch up LALRPOP to generate the parser trait and make sure every generated parser implements it.

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.