use proc_macro::{TokenStream, TokenTree}; use quote::quote; /// Subtracts two numbers and calls a given callback macro with the result. Optionally, a /// additional argument (delimeted) can be given which will also be passed to the /// callback. /// /// # Input /// `$num_a - $num_b, $callback $(, $user_data)?` /// /// # Examples /// ``` /// # use std::any::TypeId; /// use util_macros::sub; /// /// macro_rules! sub_cb { /// ($num: literal) => { /// $num /// }; /// } /// /// type Foo = [u8; sub!(5 - 2, sub_cb)]; /// /// assert_eq!(TypeId::of::(), TypeId::of::<[u8; 3]>()); /// ``` ///
/// /// The callback macro can be called with extra arguments. /// ``` /// # use std::any::TypeId; /// use util_macros::sub; /// /// macro_rules! sub_cb { /// ($num: literal, $to_multiply: literal) => { /// $num * $to_multiply /// }; /// } /// /// type Foo = [u8; sub!(5 - 2, sub_cb, (20))]; /// /// assert_eq!(TypeId::of::(), TypeId::of::<[u8; 60]>()); /// ``` ///
/// /// The callback is called with the identifier `overflow` if a overflow occurs. /// ``` /// # use std::any::TypeId; /// use util_macros::sub; /// /// macro_rules! sub_cb { /// ($num: literal) => { /// $num /// }; /// /// (overflow) => { /// 128 /// }; /// } /// /// type Foo = [u8; sub!(3 - 10, sub_cb)]; /// /// assert_eq!(TypeId::of::(), TypeId::of::<[u8; 128]>()); /// ``` #[proc_macro] pub fn sub(input: TokenStream) -> TokenStream { let mut input_tt_iter = input.into_iter(); let num_a = match input_tt_iter.next().unwrap() { TokenTree::Literal(lit) => lit.to_string().parse::().unwrap(), _ => { panic!("Expected a number literal"); } }; match input_tt_iter.next().unwrap() { TokenTree::Punct(punct) if punct.as_char() == '-' => {} _ => { panic!("Expected a '-' token"); } }; let num_b = match input_tt_iter.next().unwrap() { TokenTree::Literal(lit) => lit.to_string().parse::().unwrap(), _ => { panic!("Expected a number literal"); } }; match input_tt_iter.next().unwrap() { TokenTree::Punct(punct) if punct.as_char() == ',' => {} _ => { panic!("Expected a ',' token"); } }; let cb_ident = match input_tt_iter.next().unwrap() { TokenTree::Ident(cb_ident) => { proc_macro2::Ident::new(&cb_ident.to_string(), cb_ident.span().into()) } _ => { panic!("Expected a identifier"); } }; let opt_user_data = input_tt_iter .next() .map(|comma_tt| { match comma_tt { TokenTree::Punct(punct) if punct.as_char() == ',' => {} _ => { panic!("Expected a ',' token"); } }; let user_data_tt = input_tt_iter.next().unwrap(); let TokenTree::Group(group) = user_data_tt else { panic!("User data must be a delimeted") }; let inside: proc_macro2::TokenStream = group.stream().into(); quote! {, #inside } }) .unwrap_or_default(); let Some(subtracted) = num_a.checked_sub(num_b) else { return quote! { #cb_ident!(overflow) } .into(); }; let subtracted_lit = proc_macro2::Literal::u32_unsuffixed(subtracted); quote! { #cb_ident!(#subtracted_lit #opt_user_data) } .into() }