parsel 0.2.0

Zero-code parser generation by using AST node types as the grammar
Documentation
use proc_macro2::Span;
use anyhow::{ensure, Result};
use syn::{Ident, Lit, LitBool, LitInt, LitFloat, LitStr, Token};
use parsel::Parse;


/// Return `Err` rather than panicking when the assertion fails.
macro_rules! test_assert_eq {
    ($actual:expr, $expected:expr) => {
        ensure!(
            $actual == $expected,
            "equality assertion failed\nactual:\n{actual:#?}\nexpected:\n{expected:#?}",
            actual = $actual,
            expected = $expected,
        );
    }
}

#[test]
fn parse_struct_named_field() -> Result<()> {
    #[derive(PartialEq, Eq, Debug, Parse)]
    struct Assignment {
        name: Ident,
        eq: Token![=],
        value: Lit,
    }

    let actual: Assignment = syn::parse_str("foo = 13.37")?;
    let expected = Assignment {
        name: Ident::new("foo", Span::call_site()),
        eq: <Token![=]>::default(),
        value: Lit::Float(syn::LitFloat::new("13.37", Span::call_site())),
    };

    test_assert_eq!(actual, expected);

    Ok(())
}

#[test]
fn parse_struct_indexed_field() -> Result<()> {
    #[derive(PartialEq, Eq, Debug, Parse)]
    struct Assignment(Ident, Token![=], Lit);

    let actual: Assignment = syn::parse_str("quux = 'X'")?;
    let expected = Assignment(
        Ident::new("quux", Span::call_site()),
        <Token![=]>::default(),
        Lit::Char(syn::LitChar::new('X', Span::call_site())),
    );

    test_assert_eq!(actual, expected);

    Ok(())
}

#[test]
fn parse_enum() -> Result<()> {
    #[derive(PartialEq, Eq, Debug, Parse)]
    enum Foo {
        Qux {
            first: Lit,
            comma: syn::token::Comma,
            second: Lit,
        },
        Bar(Ident, Lit),
    }

    let f1: Foo = syn::parse_str("foo1 42")?;
    test_assert_eq!(
        f1,
        Foo::Bar(
            Ident::new("foo1", Span::call_site()),
            Lit::Int(LitInt::new("42", Span::call_site()))
        )
    );

    let f2: Foo = syn::parse_str("1337, 0.5")?;
    test_assert_eq!(
        f2,
        Foo::Qux {
            first: Lit::Int(LitInt::new("1337", Span::call_site())),
            comma: Default::default(),
            second: Lit::Float(LitFloat::new("0.5", Span::call_site())),
        }
    );

    Ok(())
}

#[test]
fn parse_generic_type() -> Result<()> {
    /// For testing whether generic trait bouds are correctly placed
    /// on field types, and not just blindly on type parameters
    trait Dummy {
        type Assoc;
    }

    /// It must be `Eq + Debug`, though, because `std` derive macros aren't
    /// smart enough, and they do blindly apply bounds to type parameters.
    #[derive(PartialEq, Eq, Debug)]
    struct DoesNotImplParse;

    impl Dummy for DoesNotImplParse {
        type Assoc = LitStr;
    }

    #[derive(PartialEq, Eq, Debug, Parse)]
    struct GenericStruct<T: Dummy, U> {
        namespace: U,
        colon2: syn::token::Colon2,
        name: T::Assoc,
    }

    let actual_struct: GenericStruct<DoesNotImplParse, Ident>
        = syn::parse_str(r#"my_ns :: "lorem ipsum""#)?;
    let expected_struct = GenericStruct {
        namespace: Ident::new("my_ns", Span::call_site()),
        colon2: Default::default(),
        name: LitStr::new("lorem ipsum", Span::call_site()),
    };
    test_assert_eq!(actual_struct, expected_struct);

    #[derive(PartialEq, Eq, Debug, Parse)]
    enum GenericEnum<X, Y: Dummy, Q> {
        X1(X),
        /// for an ambiguous prefix
        Y2(Y::Assoc, Y::Assoc),
        YQ {
            y: Y::Assoc,
            semi: syn::token::Semi,
            q: Q,
        },
    }

    let actual_enum_1: GenericEnum<LitInt, DoesNotImplParse, Token![=>]>
        = syn::parse_str("42_usize")?;
    let expected_enum_1 = GenericEnum::X1(LitInt::new("42_usize", Span::call_site()));
    test_assert_eq!(actual_enum_1, expected_enum_1);

    let actual_enum_2: GenericEnum<LitInt, DoesNotImplParse, Token![=>]>
        = syn::parse_str(r#""foo bar! #qux"; =>"#)?;
    let expected_enum_2 = GenericEnum::YQ {
        y: LitStr::new("foo bar! #qux", Span::call_site()),
        semi: Default::default(),
        q: Default::default(),
    };
    test_assert_eq!(actual_enum_2, expected_enum_2);

    Ok(())
}

#[test]
fn parse_recursive_type() -> Result<()> {
    #[derive(PartialEq, Eq, Debug)]
    struct Parenthesized<T> {
        parens: syn::token::Paren,
        expr: T,
    }

    impl<T: Parse> Parse for Parenthesized<T> {
        fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
            let inner;
            let parens = syn::parenthesized!(inner in input);
            let expr: T = inner.parse()?;

            Ok(Parenthesized { parens, expr })
        }
    }

    #[derive(PartialEq, Eq, Debug, Parse)]
    enum Expr {
        Add {
            lhs: MulExpr,
            op: syn::token::Add,
            rhs: MulExpr,
        },
        Sub {
            lhs: MulExpr,
            op: syn::token::Sub,
            rhs: MulExpr,
        },
        Mul(MulExpr),
    }

    #[derive(PartialEq, Eq, Debug, Parse)]
    enum MulExpr {
        Mul {
            lhs: Term,
            op: syn::token::Star,
            rhs: Term,
        },
        Div {
            lhs: Term,
            op: syn::token::Div,
            rhs: Term,
        },
        Term(Term),
    }

    #[derive(PartialEq, Eq, Debug, Parse)]
    enum Term {
        Lit(Lit),
        Var(Ident),
        Paren(
            #[parsel(recursive)]
            Box<Parenthesized<Expr>>
        ),
    }

    let actual_expr: Expr = syn::parse_str("(pq * 3.14) - 738291")?;
    let expected_expr = Expr::Sub {
        lhs: MulExpr::Term(Term::Paren(
            Box::new(Parenthesized {
                parens: Default::default(),
                expr: Expr::Mul(MulExpr::Mul {
                    lhs: Term::Var(Ident::new("pq", Span::call_site())),
                    op: Default::default(),
                    rhs: Term::Lit(Lit::Float(LitFloat::new("3.14", Span::call_site()))),
                }),
            }),
        )),
        op: Default::default(),
        rhs: MulExpr::Term(Term::Lit(Lit::Int(LitInt::new("738291", Span::call_site())))),
    };
    test_assert_eq!(actual_expr, expected_expr);

    #[derive(PartialEq, Eq, Debug, Parse)]
    enum Cond {
        Or {
            lhs: AndExpr,
            op: syn::token::Or,
            rhs: AndExpr,
        },
        And(AndExpr),
    }

    #[derive(PartialEq, Eq, Debug, Parse)]
    enum AndExpr {
        And {
            lhs: Predicate,
            op: syn::token::And,
            rhs: Predicate,
        },
        Term(Predicate),
    }

    #[derive(PartialEq, Eq, Debug, Parse)]
    enum Predicate {
        Lit(LitBool),
        Var(Ident),
        Neg {
            bang: syn::token::Bang,
            #[parsel(recursive)]
            subexpr: Box<Predicate>,
        },
        Paren(
            #[parsel(recursive)]
            Box<Parenthesized<Cond>>
        ),
    }

    let actual_cond: Cond = syn::parse_str("true & (a_var | !false)")?;
    let expected_cond = Cond::And(AndExpr::And {
        lhs: Predicate::Lit(LitBool::new(true, Span::call_site())),
        op: Default::default(),
        rhs: Predicate::Paren(Box::new(Parenthesized {
            parens: Default::default(),
            expr: Cond::Or {
                lhs: AndExpr::Term(Predicate::Var(Ident::new("a_var", Span::call_site()))),
                op: Default::default(),
                rhs: AndExpr::Term(Predicate::Neg {
                    bang: Default::default(),
                    subexpr: Box::new(Predicate::Lit(LitBool::new(false, Span::call_site()))),
                }),
            }
        })),
    });
    test_assert_eq!(actual_cond, expected_cond);

    Ok(())
}