Skip to content

Commit

Permalink
amt! macro proof of concept
Browse files Browse the repository at this point in the history
  • Loading branch information
sam0x17 committed Apr 17, 2024
1 parent a675882 commit 7b785cb
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 1 deletion.
1 change: 1 addition & 0 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ uint = "0.9"
serde_json = "1"

[features]
default = []
serde = ["dep:serde"]
std = []
parsing = ["serde", "std", "dep:quoth"]
3 changes: 2 additions & 1 deletion macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ proc-macro = true
quote = "1"
proc-macro2 = "1"
syn = "2"
currencies-core = { version = "0.3.2", path = "../core" }
currencies-core = { version = "0.3.2", path = "../core", features = ["parsing"] }
derive-syn-parse = "0.2"
66 changes: 66 additions & 0 deletions macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1 +1,67 @@
use currencies_core::{
currency::USD,
safety::{Checked, Unchecked},
Currency, ParsedAmount,
};
use derive_syn_parse::Parse;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{parse2, Error, Ident, LitStr, Result, Token};

#[derive(Parse)]
struct AmountInput {
currency: Ident,
_comma: Token![,],
amount: LitStr,
}

#[proc_macro]
pub fn amt(input: TokenStream) -> TokenStream {
match amt_internal::<false>(input) {
Ok(tokens) => tokens.into(),
Err(err) => err.to_compile_error().into(),
}
}

#[proc_macro]
pub fn amt_checked(input: TokenStream) -> TokenStream {
match amt_internal::<true>(input) {
Ok(tokens) => tokens.into(),
Err(err) => err.to_compile_error().into(),
}
}

fn filter_error(err: impl ToString) -> String {
format!("invalid amount: {}", err.to_string().trim_start_matches("error: "))
}

fn parse_amount<C: Currency, const SAFE: bool>(amount: &LitStr, currency_ident: &Ident) -> Result<TokenStream2> {
let krate = quote!(::currencies);
if SAFE {
let amount: ParsedAmount<C, Checked> = amount.value().parse().map_err(|err| Error::new(amount.span(), filter_error(err)))?;
let amount = amount.amount;
let amount: TokenStream2 = format!("{:?}", amount.raw_backing()).parse().unwrap();
Ok(quote! {
#krate::Amount::<#currency_ident, #krate::safety::Checked>::from_raw(#amount)
})
} else {
let amount: ParsedAmount<C, Unchecked> = amount.value().parse().map_err(|err| Error::new(amount.span(), filter_error(err)))?;
let amount = amount.amount;
let amount: TokenStream2 = format!("{:?}", amount.raw_backing()).parse().unwrap();
Ok(quote! {
#krate::Amount::<#currency_ident, #krate::safety::Unchecked>::from_raw(#amount)
})
}
}

fn amt_internal<const SAFE: bool>(tokens: impl Into<TokenStream2>) -> Result<TokenStream2> {
let input = parse2::<AmountInput>(tokens.into())?;
let currency = input.currency;
let amount = input.amount;
let output = match currency.to_string().to_uppercase().as_str() {
"USD" => parse_amount::<USD, SAFE>(&amount, &currency)?,
_ => todo!(),
};
Ok(output)
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@
#![warn(missing_docs)]

pub use currencies_core::*;
pub use currencies_macros::*;
17 changes: 17 additions & 0 deletions tests/macro_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use currencies::amt;
use currencies::amt_checked;
use currencies::currency::*;
use currencies::safety::Checked;
use currencies::Amount;

#[test]
fn test_amt_usd_unchecked() {
let amount = amt!(USD, "$3.24");
assert_eq!(format!("{}", amount), "$3.24");
}

#[test]
fn test_amt_usd_checked() {
let amount: Amount<USD, Checked> = amt_checked!(USD, "$3.24");
assert_eq!(format!("{}", amount), "$3.24");
}

0 comments on commit 7b785cb

Please sign in to comment.