1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
extern crate proc_macro;

use proc_macro::TokenStream;
use proc_macro_hack::proc_macro_hack;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::{self, parenthesized, parse_macro_input, Attribute, Expr, ExprPath, Result, Token};

mod expressions;

#[derive(Debug)]
struct ArsertExpression {
    failure_function: Option<Expr>,
    assertion: expressions::Assertion,
    format_expr: Punctuated<Expr, Token![,]>,
}

impl Parse for ArsertExpression {
    fn parse(input: ParseStream) -> Result<Self> {
        let failure_function = if input.peek(Token![#]) {
            let attrs = input.call(Attribute::parse_inner)?;
            if let Some(attr) = attrs
                .into_iter()
                .find(|a| a.path.is_ident("failure_function"))
            {
                let expr: FailureAttr = syn::parse2(attr.tts)?;
                Some(expr.0)
            } else {
                None
            }
        } else {
            None
        };
        let assertion = expressions::Assertion::parse(input)?;
        let format_expr: Punctuated<Expr, Token![,]> = Punctuated::parse_terminated(input)?;
        Ok(ArsertExpression {
            assertion,
            format_expr,
            failure_function,
        })
    }
}

impl Into<TokenStream> for ArsertExpression {
    fn into(self) -> TokenStream {
        let default: Expr = syn::parse_str("arsert::panic_on_failed_assertion").unwrap();
        self.assertion
            .into_expression(self.failure_function.unwrap_or(default))
    }
}

#[derive(Debug)]
struct FailureAttr(Expr);

impl Parse for FailureAttr {
    fn parse(input: ParseStream) -> Result<Self> {
        let args;
        parenthesized!(args in input);
        Ok(FailureAttr(args.parse::<Expr>()?))
    }
}

#[proc_macro_hack]
pub fn arsert(tokens: TokenStream) -> TokenStream {
    let input: ArsertExpression = parse_macro_input!(tokens as ArsertExpression);
    input.into()
}