rust_decimal_macros/lib.rs
1//!
2//! A helpful macro for instantiating `Decimal` numbers.
3//!
4//! By default, this requires `rust_decimal` to be available at the project root. e.g. the macro
5//! will effectively produce:
6//!
7//! ```ignore
8//! ::rust_decimal::Decimal::from_parts(12345, 0, 0, false, 4)
9//! ```
10//!
11//! While this is convenient for most use cases, it is sometimes not desired behavior when looking
12//! to reexport the library. Consequently, this behavior can be modified by enabling the feature
13//! `reexportable`. When this feature is enabled, the macro will instead reproduce the functional
14//! equivalent of:
15//!
16//! ```ignore
17//! Decimal::from_parts(12345, 0, 0, false, 4)
18//! ```
19//!
20//! # Examples
21//!
22//! ```rust
23//! use rust_decimal_macros::dec;
24//!
25//! // If the reexportable feature is enabled, `Decimal` needs to be in scope
26//! #[cfg(feature = "reexportable")]
27//! use rust_decimal::Decimal;
28//!
29//! let number = dec!(1.2345);
30//! assert_eq!("1.2345", number.to_string());
31//! let number = dec!(-5.4321);
32//! assert_eq!("-5.4321", number.to_string());
33//! ```
34//!
35
36mod str;
37
38use proc_macro::TokenStream;
39use quote::quote;
40use syn::{
41 parse::{Parse, ParseStream},
42 parse_macro_input, Expr, Ident, LitInt, Result, Token,
43};
44
45/// Transform a literal number directly to a `Decimal` at compile time.
46///
47/// Any Rust number format works, for example:
48///
49/// - `dec!(1)`, `dec!(-1)`, `dec!(1_999)`, `dec!(- 1_999)`
50/// - `dec!(0b1)`, `dec!(-0b1_1111)`, `dec!(0o1)`, `dec!(-0o1_777)`, `dec!(0x1)`, `dec!(-0x1_Ffff)`
51/// - `dec!(1.)`, `dec!(-1.111_009)`, `dec!(1e6)`, `dec!(-1.2e+6)`, `dec!(12e-6)`, `dec!(-1.2e-6)`
52///
53/// ### Option `radix`
54///
55/// You can give it integers (not float-like) in any radix from 2 to 36 inclusive, using the letters too:
56/// `dec!(100, radix 2) == 4`, `dec!(-1_222, radix 3) == -53`, `dec!(z1, radix 36) == 1261`,
57/// `dec!(-1_xyz, radix 36) == -90683`
58///
59/// ### Option `exp`
60///
61/// This is the same as the `e` 10's exponent in float syntax (except as a Rust expression it doesn't accept
62/// a unary `+`.) You need this for other radixes. Currently, it must be between -28 and +28 inclusive:
63/// `dec!(10, radix 2, exp 5) == 200_000`, `dec!( -1_777, exp -3, radix 8) == dec!(-1.023)`
64///
65/// # Example
66///
67/// ```rust
68/// use rust_decimal_macros::dec;
69///
70/// // If the reexportable feature is enabled, `Decimal` needs to be in scope
71/// #[cfg(feature = "reexportable")]
72/// use rust_decimal::Decimal;
73///
74/// let number = dec!(1.2345);
75/// assert_eq!("1.2345", number.to_string());
76/// let number = dec!(-5.4321);
77/// assert_eq!("-5.4321", number.to_string());
78/// let number = dec!(-0o1_777);
79/// assert_eq!("-1023", number.to_string());
80/// let number = dec!(-1_777, radix 8);
81/// assert_eq!("-1023", number.to_string());
82/// ```
83///
84#[proc_macro]
85pub fn dec(input: TokenStream) -> TokenStream {
86 // Parse the input using our custom parser
87 let dec_input = parse_macro_input!(input as DecInputParser);
88 let value = dec_input.value.as_str();
89
90 // Process the parsed input
91 let result = if let Some(radix) = dec_input.radix {
92 str::parse_decimal_with_radix(value, dec_input.exp.unwrap_or_default(), radix)
93 } else {
94 str::parse_decimal(value, dec_input.exp.unwrap_or_default())
95 };
96
97 let unpacked = match result {
98 Ok(d) => d,
99 Err(e) => panic!("{}", e),
100 };
101
102 expand(
103 unpacked.lo(),
104 unpacked.mid(),
105 unpacked.hi(),
106 unpacked.negative(),
107 unpacked.scale,
108 )
109}
110
111#[cfg(not(feature = "reexportable"))]
112fn expand(lo: u32, mid: u32, hi: u32, negative: bool, scale: u32) -> TokenStream {
113 let expanded = quote! {
114 ::rust_decimal::Decimal::from_parts(#lo, #mid, #hi, #negative, #scale)
115 };
116 expanded.into()
117}
118
119#[cfg(feature = "reexportable")]
120fn expand(lo: u32, mid: u32, hi: u32, negative: bool, scale: u32) -> TokenStream {
121 let expanded = quote! {
122 Decimal::from_parts(#lo, #mid, #hi, #negative, #scale)
123 };
124 expanded.into()
125}
126
127/// Custom parser for the dec! macro input
128struct DecInputParser {
129 radix: Option<u32>,
130 exp: Option<i32>,
131 value: String,
132}
133
134impl Parse for DecInputParser {
135 fn parse(input: ParseStream) -> Result<Self> {
136 let mut radix = None;
137 let mut exp = None;
138 let mut value = None;
139
140 // Parse the first item which could be a value or a parameter
141 if !input.is_empty() {
142 // Try to parse as an identifier (parameter name)
143 if input.peek(Ident) {
144 let ident = input.parse::<Ident>()?;
145 let ident_str = ident.to_string();
146
147 match ident_str.as_str() {
148 "radix" => {
149 if let Some(value) = parse_radix(input)? {
150 radix = Some(value);
151 }
152 }
153 "exp" => {
154 if let Some(value) = parse_exp(input)? {
155 exp = Some(value);
156 }
157 }
158 _ => {
159 // This is not a parameter but a value
160 value = Some(ident_str);
161 }
162 }
163 } else {
164 // It's a value
165 let expr = input.parse::<Expr>()?;
166 value = Some(quote!(#expr).to_string());
167 }
168 }
169
170 // Parse the remaining tokens
171 while !input.is_empty() {
172 // We expect a comma between tokens
173 if input.peek(Token![,]) {
174 let _ = input.parse::<Token![,]>()?;
175 }
176
177 // Check if we're at the end
178 if input.is_empty() {
179 break;
180 }
181
182 // Parse the next token
183 if input.peek(Ident) {
184 let ident = input.parse::<Ident>()?;
185 let ident_str = ident.to_string();
186
187 match ident_str.as_str() {
188 "radix" => {
189 if radix.is_some() {
190 panic!("Duplicate radix parameter");
191 }
192 if let Some(value) = parse_radix(input)? {
193 radix = Some(value);
194 }
195 }
196 "exp" => {
197 if exp.is_some() {
198 panic!("Duplicate exp parameter");
199 }
200 if let Some(value) = parse_exp(input)? {
201 exp = Some(value);
202 }
203 }
204 _ => {
205 // This is not a parameter but a value
206 if value.is_none() {
207 value = Some(ident_str);
208 } else {
209 panic!("Unknown parameter or duplicate value: {}", ident_str);
210 }
211 }
212 }
213 } else {
214 // Parse as an expression (value)
215 if value.is_none() {
216 let expr = input.parse::<Expr>()?;
217 value = Some(quote!(#expr).to_string());
218 } else {
219 panic!("Duplicate value found");
220 }
221 }
222 }
223
224 // Ensure we have a value
225 let value = value.unwrap_or_else(|| panic!("Expected a decimal value"));
226
227 Ok(DecInputParser { radix, exp, value })
228 }
229}
230
231fn parse_radix(input: ParseStream) -> Result<Option<u32>> {
232 // Parse the value after the parameter name
233 if input.peek(LitInt) {
234 let lit_int = input.parse::<LitInt>()?;
235 return Ok(Some(lit_int.base10_parse::<u32>()?));
236 }
237 let expr = input.parse::<Expr>()?;
238 match expr {
239 Expr::Lit(lit) => {
240 if let syn::Lit::Int(lit_int) = lit.lit {
241 return Ok(Some(lit_int.base10_parse::<u32>()?));
242 }
243 }
244 _ => panic!("Expected a literal integer for radix"),
245 }
246
247 Ok(None)
248}
249
250fn parse_exp(input: ParseStream) -> Result<Option<i32>> {
251 // Parse the value after the parameter name
252 if input.peek(LitInt) {
253 let lit_int = input.parse::<LitInt>()?;
254 return Ok(Some(lit_int.base10_parse::<i32>()?));
255 }
256 let expr = input.parse::<Expr>()?;
257 match expr {
258 Expr::Lit(lit) => {
259 if let syn::Lit::Int(lit_int) = lit.lit {
260 return Ok(Some(lit_int.base10_parse::<i32>()?));
261 }
262 }
263 Expr::Unary(unary) => {
264 if let Expr::Lit(lit) = *unary.expr {
265 if let syn::Lit::Int(lit_int) = lit.lit {
266 let mut val = lit_int.base10_parse::<i32>()?;
267 if let syn::UnOp::Neg(_) = unary.op {
268 val = -val;
269 }
270 return Ok(Some(val));
271 }
272 }
273 }
274 _ => panic!("Expected a literal integer for exp"),
275 }
276 Ok(None)
277}