use proc_macro2::TokenStream;
use quote::quote;
use syn::parse::{Parse, ParseStream, Result};
use syn::token::Comma;
use syn::{Expr, Ident, Type};
use whippyunits_core::{
calculate_unit_conversion_factors, get_unit_info, Dimension, EvaluationMode, UnitExpr,
};
use crate::utils::shared_utils::generate_unit_documentation_for_expr;
pub struct ValueMacroInput {
quantity: Expr,
unit_expr: UnitExpr,
storage_type: Option<Type>,
brand_type: Option<Type>,
}
impl Parse for ValueMacroInput {
fn parse(input: ParseStream) -> Result<Self> {
let quantity: Expr = input.parse()?;
let _comma: Comma = input.parse()?;
let unit_expr: UnitExpr = input.parse()?;
let storage_type = if input.peek(Comma) {
let _comma: Comma = input.parse()?;
Some(input.parse()?)
} else {
None
};
let brand_type = if storage_type.is_some() && input.peek(Comma) {
let _comma: Comma = input.parse()?;
Some(input.parse()?)
} else {
None
};
Ok(ValueMacroInput {
quantity,
unit_expr,
storage_type,
brand_type,
})
}
}
impl ValueMacroInput {
pub fn expand(self) -> TokenStream {
let doc_structs = generate_unit_documentation_for_expr(&self.unit_expr, true);
let quantity = &self.quantity;
let is_affine = if let UnitExpr::Unit(unit) = &self.unit_expr {
if let Some(unit_info) = get_unit_info(&unit.name.to_string()) {
unit_info.affine_offset != 0.0
} else {
false
}
} else {
false };
let result = self.unit_expr.evaluate_with_mode(EvaluationMode::Tolerant);
let (mass_exp, length_exp, time_exp, current_exp, temp_exp, amount_exp, lum_exp, angle_exp) = (
result.dimension_exponents.0[0],
result.dimension_exponents.0[1],
result.dimension_exponents.0[2],
result.dimension_exponents.0[3],
result.dimension_exponents.0[4],
result.dimension_exponents.0[5],
result.dimension_exponents.0[6],
result.dimension_exponents.0[7],
);
let (p2, p3, p5, pi) = (
result.scale_exponents.0[0],
result.scale_exponents.0[1],
result.scale_exponents.0[2],
result.scale_exponents.0[3],
);
let (storage_type_ty, rescale_fn) = if let Some(ref ty) = self.storage_type {
let ty_str = quote!(#ty).to_string();
match ty_str.as_str() {
"f32" => (quote!(f32), quote!(rescale_f32)),
"i8" => (quote!(i8), quote!(rescale_i8)),
"i16" => (quote!(i16), quote!(rescale_i16)),
"i32" => (quote!(i32), quote!(rescale_i32)),
"i64" => (quote!(i64), quote!(rescale_i64)),
"i128" => (quote!(i128), quote!(rescale_i128)),
"u8" => (quote!(u8), quote!(rescale_u8)),
"u16" => (quote!(u16), quote!(rescale_u16)),
"u32" => (quote!(u32), quote!(rescale_u32)),
"u64" => (quote!(u64), quote!(rescale_u64)),
"u128" => (quote!(u128), quote!(rescale_u128)),
_ => (quote!(#ty), quote!(rescale)),
}
} else {
(quote!(f64), quote!(rescale))
};
let brand_type_ty = self
.brand_type
.as_ref()
.map(|t| quote! { #t })
.unwrap_or_else(|| quote! { () });
let target_unit_type = quote! {
whippyunits::quantity::Quantity<
whippyunits::quantity::Scale<whippyunits::quantity::_2<#p2>, whippyunits::quantity::_3<#p3>, whippyunits::quantity::_5<#p5>, whippyunits::quantity::_Pi<#pi>>,
whippyunits::quantity::Dimension<whippyunits::quantity::_M<#mass_exp>, whippyunits::quantity::_L<#length_exp>, whippyunits::quantity::_T<#time_exp>, whippyunits::quantity::_I<#current_exp>, whippyunits::quantity::_Θ<#temp_exp>, whippyunits::quantity::_N<#amount_exp>, whippyunits::quantity::_J<#lum_exp>, whippyunits::quantity::_A<#angle_exp>>,
#storage_type_ty,
#brand_type_ty
>
};
if is_affine {
let unit = match &self.unit_expr {
UnitExpr::Unit(u) => u,
_ => unreachable!(), };
let unit_name = unit.name.to_string();
let unit_info = get_unit_info(&unit_name).unwrap();
let affine_offset = unit_info.affine_offset;
let storage_unit_symbol = if let Some((unit, dimension)) =
Dimension::find_unit_by_symbol(&unit_name)
{
if let Some(storage_unit) = dimension.units.iter().find(|u| {
u.scale == unit.scale && u.conversion_factor == 1.0 && u.affine_offset == 0.0
}) {
storage_unit.symbols[0]
} else {
dimension
.units
.iter()
.find(|u| u.conversion_factor == 1.0 && u.affine_offset == 0.0)
.map(|u| u.symbols[0])
.unwrap_or("K") }
} else {
"K" };
let storage_unit_ident = Ident::new(storage_unit_symbol, unit.name.span());
quote! {
{
const _: () = {
#doc_structs
};
let storage_value = (whippyunits::api::#rescale_fn(#quantity) as whippyunits::unit!(#storage_unit_ident, #storage_type_ty, #brand_type_ty)).unsafe_value;
(storage_value as f64 - #affine_offset) as #storage_type_ty
}
}
} else {
let (conversion_factor, affine_offset) =
calculate_unit_conversion_factors(&self.unit_expr);
let is_nonstorage = conversion_factor != 1.0 || affine_offset != 0.0;
if is_nonstorage {
quote! {
{
const _: () = {
#doc_structs
};
let scaled_value = (whippyunits::api::#rescale_fn(#quantity) as #target_unit_type).unsafe_value;
((scaled_value as f64 / #conversion_factor) - #affine_offset) as #storage_type_ty
}
}
} else {
quote! {
{
const _: () = {
#doc_structs
};
(whippyunits::api::#rescale_fn(#quantity) as #target_unit_type).unsafe_value
}
}
}
}
}
}