diff --git a/decimal-macros/.gitignore b/decimal-macros/.gitignore new file mode 100644 index 00000000..b83d2226 --- /dev/null +++ b/decimal-macros/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/decimal-macros/Cargo.toml b/decimal-macros/Cargo.toml new file mode 100644 index 00000000..eaf8db54 --- /dev/null +++ b/decimal-macros/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "decimal-macros" +version = "0.1.0" +authors = [ + "Callum Tolley ", + "Peter Marheine ", +] + +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +decimal = { path = "../" } + diff --git a/decimal-macros/src/lib.rs b/decimal-macros/src/lib.rs new file mode 100644 index 00000000..3dd4a805 --- /dev/null +++ b/decimal-macros/src/lib.rs @@ -0,0 +1,98 @@ +use std::iter::once; + +use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree}; + +/// Get a [`TokenStream`] of `compile_error!("message")` +fn error(span: Span, message: &str) -> TokenStream { + [ + TokenTree::from(Ident::new("compile_error", span)), + TokenTree::from(Punct::new('!', Spacing::Alone)), + TokenTree::from(Group::new( + Delimiter::Parenthesis, + TokenTree::from(Literal::string(message)).into(), + )), + ] + .into_iter() + .collect() +} + +#[proc_macro] +pub fn d128(args: TokenStream) -> TokenStream { + let arg = args.to_string(); + + // parse argument as d128, error if invalid + let num = match from_str(&arg) { + Ok(num) => num, + Err(s) => { + return error(Span::call_site(), s); + } + }; + + // literal array of the parsed value's bytes + let bytes = num + .to_raw_bytes() + .iter() + .flat_map(|&byte| { + [ + TokenTree::from(Literal::u8_suffixed(byte)), + TokenTree::from(Punct::new(',', Spacing::Alone)), + ] + .into_iter() + }) + .collect::(); + let array = TokenTree::from(Group::new(Delimiter::Bracket, bytes)); + + let double_colons = || { + [ + TokenTree::from(Punct::new(':', Spacing::Joint)), + TokenTree::from(Punct::new(':', Spacing::Alone)), + ] + .into_iter() + }; + let ident = |s| once(TokenTree::from(Ident::new(s, Span::call_site()))); + + // ::decimal::d128::from_raw_bytes(array) + let mut from_raw_bytes = TokenStream::new(); + from_raw_bytes.extend(double_colons()); + from_raw_bytes.extend(ident("decimal")); + from_raw_bytes.extend(double_colons()); + from_raw_bytes.extend(ident("d128")); + from_raw_bytes.extend(double_colons()); + from_raw_bytes.extend(ident("from_raw_bytes")); + from_raw_bytes.extend(once(TokenTree::from(Group::new( + Delimiter::Parenthesis, + array.into(), + )))); + + // unsafe { from_raw_bytes(...) } + // TODO: is this correct when host endianness differs from target, or do we + // need to do a conversion of the bytes in some configurations? + let mut out = TokenStream::new(); + out.extend(ident("unsafe")); + out.extend(once(TokenTree::from(Group::new( + Delimiter::Brace, + from_raw_bytes, + )))); + out +} + +fn from_str(s: &str) -> Result { + use decimal::{d128, Status}; + use std::str::FromStr; + + d128::set_status(Status::empty()); + let res = d128::from_str(s); + + let status = d128::get_status(); + if status.contains(Status::CONVERSION_SYNTAX) { + Err("not a valid d128 number") + } else if status.contains(Status::OVERFLOW) { + Err("too large for a d128 number") + } else if status.contains(Status::UNDERFLOW) { + Err("too small for a d128 number") + } else if !status.is_empty() { + Err("not a valid d128 number") + } else { + Ok(res.unwrap()) + } +} diff --git a/decimal-macros/tests/test.rs b/decimal-macros/tests/test.rs new file mode 100644 index 00000000..9757b00b --- /dev/null +++ b/decimal-macros/tests/test.rs @@ -0,0 +1,8 @@ +use decimal::d128; +use decimal_macros::d128 as proc_d128; +use std::str::FromStr; + +#[test] +fn equivalent_to_from_str() { + assert_eq!(proc_d128!(0.3), d128::from_str("0.3").unwrap()); +} diff --git a/src/dec128.rs b/src/dec128.rs index 4d6faab0..d6dba984 100644 --- a/src/dec128.rs +++ b/src/dec128.rs @@ -508,12 +508,12 @@ impl d128 { } /// Creates a d128 from raw bytes. Endianess is host dependent. - pub unsafe fn from_raw_bytes(bytes: [u8; 16]) -> d128 { + pub unsafe const fn from_raw_bytes(bytes: [u8; 16]) -> d128 { d128 { bytes: bytes } } /// Returns raw bytes for this d128. Endianess is host dependent. - pub fn to_raw_bytes(&self) -> [u8; 16] { + pub const fn to_raw_bytes(&self) -> [u8; 16] { self.bytes } @@ -549,7 +549,7 @@ impl d128 { // Utilities and conversions, extractors, etc. /// Returns the d128 representing +0. - pub fn zero() -> d128 { + pub const fn zero() -> d128 { unsafe { let mut res: MaybeUninit = MaybeUninit::uninit(); *decQuadZero(res.as_mut_ptr()) @@ -557,12 +557,12 @@ impl d128 { } /// Returns the d128 representing +Infinity. - pub fn infinity() -> d128 { + pub const fn infinity() -> d128 { d128!(Infinity) } /// Returns the d128 representing -Infinity. - pub fn neg_infinity() -> d128 { + pub const fn neg_infinity() -> d128 { d128!(-Infinity) }