use proc_macro2::Span;
use anyhow::{ensure, Result};
use syn::{Ident, Lit, LitBool, LitInt, LitFloat, LitStr, Token};
use parsel::Parse;
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<()> {
trait Dummy {
type Assoc;
}
#[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),
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(())
}