//! provides some ([`map`](array::map) and [`from_fn`](core::array::from_fn)) [`core::array`] fn related functions as const macros. //! ``` //! const X: [usize; 6] = car::map!(car::from_fn!(|x| x), |x| x * 24); //! ``` #![forbid(unsafe_code)] use quote::quote; use syn::{parse::Parse, punctuated::Punctuated, spanned::Spanned, *}; fn lib() -> proc_macro2::TokenStream { quote! { // dont worry about this whole area const fn uninit_array() -> [::core::mem::MaybeUninit; N] { unsafe { ::core::mem::MaybeUninit::<[::core::mem::MaybeUninit; N]>::uninit().assume_init() } } const fn uninit_array_copied(/* steal the length */ _len: &[U; N]) -> [::core::mem::MaybeUninit; N] { uninit_array() } const unsafe fn aai(array: [::core::mem::MaybeUninit; N]) -> [T; N] { transmute_unchecked(array) } const unsafe fn transmute_unchecked(value: T) -> U { unsafe { #[repr(C)] union Transmute { t: ::core::mem::ManuallyDrop, u: ::core::mem::ManuallyDrop } ::core::mem::ManuallyDrop::into_inner(Transmute { t: ::core::mem::ManuallyDrop::new(value) }.u) } } } } /// [From fn](std::array::from_fn) in const. /// ``` /// const OUT: [u8; 8] = car::from_fn!(|x| (x as u32 * 611170012 >> 24) as u8); /// assert_eq!(OUT, [0, 36, 72, 109, 145, 182, 218, 255]); /// ``` #[proc_macro] pub fn from_fn(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let ExprClosure { inputs, body, .. } = parse_macro_input!(input as ExprClosure); if inputs.len() > 1 { let n = inputs.len(); return Error::new_spanned(inputs, format!("expected one (or 0) inputs, found {n}")) .into_compile_error() .into(); }; let inputs = inputs.into_iter(); let lib = lib(); quote! { { #lib let mut __out = uninit_array(); let mut i = 0usize; while i < __out.len() { __out[i] = ::core::mem::MaybeUninit::new({ // disable mutation (cant shadow) let i = i; let __out = (); #(let #inputs = i)*; #body }); i += 1; } unsafe { aai(__out) } } } .into() } /// [Map](array::map) in const. /// ``` /// assert_eq!(car::map!([1, 2, 3], |x| x * 2), [2, 4, 6]); /// assert_eq!(car::map!([1, 2, 3], std::convert::identity), [1, 2, 3]); /// ``` #[proc_macro] pub fn map(input: proc_macro::TokenStream) -> proc_macro::TokenStream { struct Args { array: Expr, f: Expr, } impl Parse for Args { fn parse(input: parse::ParseStream) -> Result { let mut args = Punctuated::::parse_terminated(input)?; if args.len() != 2 { return Err(input.error(format!( "arg count mismatch, expected 2 (array expression) found {}", args.len() ))); } let f = args.pop().unwrap().into_value(); let array = args.pop().unwrap().into_value(); Ok(Self { array, f }) } } let Args { array, f } = parse_macro_input!(input as Args); let lib = lib(); let b = match f { Expr::Closure(ExprClosure { body, inputs, .. }) => { let n = inputs.len(); let ns = inputs.span(); let mut inputs = inputs.into_iter(); let Some(binding) = inputs.next() else { return Error::new(ns, format!("expected one (or 2) inputs, found {n} inputs")) .into_compile_error() .into(); }; let index = inputs.next().into_iter(); quote! { let i = i; let #binding = unsafe { __ap.add(i).read() }; let __out = (); let __arr = (); #(let #index = i;)* #body } } Expr::Path(e) => quote! { let i = i; let __out = (); let __arr = (); #e(unsafe { __ap.add(i).read() }) }, e => { return Error::new(e.span(), format!("mismatched types: expected function")) .into_compile_error() .into() } }; quote! { { #lib let __arr = #array; let mut __out = uninit_array_copied(&__arr); let size = __arr.len(); let __ap = __arr.as_ptr(); let __arr = ::core::mem::ManuallyDrop::new(__arr); let mut i = 0usize; while i < size { __out[i] = ::core::mem::MaybeUninit::new({ #b }); i += 1; } unsafe { aai(__out) } }} .into() } fn try_from_fn( input: proc_macro::TokenStream, good: proc_macro2::TokenStream, pat: proc_macro2::TokenStream, then: proc_macro2::TokenStream, ) -> proc_macro::TokenStream { let ExprClosure { inputs, body, .. } = parse_macro_input!(input as ExprClosure); if inputs.len() > 1 { let n = inputs.len(); return Error::new_spanned(inputs, format!("expected one (or 0) inputs, found {n}")) .into_compile_error() .into(); }; let inputs = inputs.into_iter(); let lib = lib(); quote! { { #lib let mut __out = uninit_array(); let mut i = 0usize; loop { if i >= __out.len() { break #good(unsafe { aai(__out) }) } __out[i] = ::core::mem::MaybeUninit::new({ // disable mutation (cant shadow) let i = i; let __out = __out; #(let #inputs = i)*; match #body { #good(x) => x, #pat => { let mut j = 0; while j < i { unsafe { __out[j].assume_init() }; j += 1; } #then } } }); i += 1; } } } .into() } #[proc_macro] /// [Try from fn](std::array::try_from_fn) in const, for options. /// ``` /// let array: Option<[_; 4]> = car::try_from_fn_option!(|i| i.checked_add(100)); /// assert_eq!(array, Some([100, 101, 102, 103])); /// /// let array: Option<[_; 4]> = car::try_from_fn_option!(|i| i.checked_sub(100)); /// assert_eq!(array, None); /// ``` pub fn try_from_fn_option(input: proc_macro::TokenStream) -> proc_macro::TokenStream { try_from_fn(input, quote!(Some), quote!(None), quote!(break None)) } #[proc_macro] /// [Try from fn](std::array::try_from_fn) in const, for results. /// ``` /// let array: Result<[u8; 5], _> = car::try_from_fn_result!(|i| i.try_into()); /// assert_eq!(array, Ok([0, 1, 2, 3, 4])); /// let array: Result<[i8; 200], _> = car::try_from_fn_result!(|i| i.try_into()); /// assert!(array.is_err()); /// ``` pub fn try_from_fn_result(input: proc_macro::TokenStream) -> proc_macro::TokenStream { try_from_fn( input, quote!(Ok), quote!(Err(e)), quote!(break Err(e /* .into() */)), ) }