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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#![feature(proc_macro_span)]
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::spanned::Spanned;
#[proc_macro]
pub fn check(tokens: TokenStream) -> TokenStream {
match check_impl(syn::parse_macro_input!(tokens), false) {
Ok(x) => x.into(),
Err(e) => e.to_compile_error().into(),
}
}
#[proc_macro]
pub fn assert(tokens: TokenStream) -> TokenStream {
match check_impl(syn::parse_macro_input!(tokens), true) {
Ok(x) => x.into(),
Err(e) => e.to_compile_error().into(),
}
}
fn check_impl(expression: syn::Expr, instant_panic: bool) -> syn::Result<proc_macro2::TokenStream> {
match expression {
syn::Expr::Binary(expr) => check_binary_op(expr, instant_panic),
expr => check_bool_expr(expr, instant_panic),
}
}
fn check_binary_op(expr: syn::ExprBinary, instant_panic: bool) -> syn::Result<proc_macro2::TokenStream> {
let syn::ExprBinary { left, right, op, .. } = &expr;
let left_str = spanned_to_string(&left);
let right_str = spanned_to_string(&right);
let op_str = spanned_to_string(&op);
match op {
syn::BinOp::Eq(_) => (),
syn::BinOp::Lt(_) => (),
syn::BinOp::Le(_) => (),
syn::BinOp::Ne(_) => (),
syn::BinOp::Ge(_) => (),
syn::BinOp::Gt(_) => (),
_ => return check_bool_expr(syn::Expr::Binary(expr), instant_panic),
}
if instant_panic {
Ok(quote! {
if !(left #op right) {
::assert2::print::binary_failure("assert", &left, &right, #op_str, #left_str, #right_str, file!(), line!(), column!());
panic!("assertion failed");
}
})
} else {
Ok(quote! {
let left = #left;
let right = #right;
let guard;
if !(left #op right) {
::assert2::print::binary_failure("check", &left, &right, #op_str, #left_str, #right_str, file!(), line!(), column!());
guard = Some(::assert2::FailGuard(|| panic!("assertion failed")));
} else {
guard = None;
}
})
}
}
fn check_bool_expr(expr: syn::Expr, instant_panic: bool) -> syn::Result<proc_macro2::TokenStream> {
let expr_str = spanned_to_string(&expr);
if instant_panic {
Ok(quote! {
let value : bool = #expr;
if !value {
::assert2::print::bool_failure("assert", &value, #expr_str, file!(), line!(), column!());
panic!("assertion failed");
}
})
} else {
Ok(quote! {
let value : bool = #expr;
let guard;
if !value {
::assert2::print::bool_failure("check", &value, #expr_str, file!(), line!(), column!());
eprintln!();
guard = Some(::assert2::FailGuard(|| panic!("assertion failed")));
} else {
guard = None;
}
})
}
}
fn spanned_to_string<T: quote::ToTokens + ?Sized>(node: &T) -> String {
node.span().unwrap().source_text().unwrap_or_else(|| node.to_token_stream().to_string())
}