Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions decimal-macros/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/target/
16 changes: 16 additions & 0 deletions decimal-macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "decimal-macros"
version = "0.1.0"
authors = [
"Callum Tolley <cgtrolley@gmail.com>",
"Peter Marheine <peter@taricorp.net>",
]

edition = "2021"

[lib]
proc-macro = true

[dependencies]
decimal = { path = "../" }

98 changes: 98 additions & 0 deletions decimal-macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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::<TokenStream>();
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<decimal::d128, &'static str> {
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())
}
}
8 changes: 8 additions & 0 deletions decimal-macros/tests/test.rs
Original file line number Diff line number Diff line change
@@ -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());
}
10 changes: 5 additions & 5 deletions src/dec128.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -549,20 +549,20 @@ 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<d128> = MaybeUninit::uninit();
*decQuadZero(res.as_mut_ptr())
}
}

/// 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)
}

Expand Down