use proc_macro2::TokenStream;
use quote::quote;
use syn::Ident;
use whippyunits_core::{Dimension, SiPrefix, UnitExpr};
pub fn is_valid_identifier(name: &str) -> bool {
syn::parse_str::<Ident>(name).is_ok()
}
pub fn generate_scale_name(prefix_name: &str, unit_name: &str) -> String {
let combined_name = if prefix_name.is_empty() {
unit_name.to_string()
} else {
format!("{}{}", prefix_name, unit_name)
};
whippyunits_core::CapitalizedFmt(&combined_name).to_string()
}
pub fn get_declarator_type_for_unit(unit_name: &str) -> Option<TokenStream> {
if unit_name == "dimensionless" {
return None;
}
let atomic_dimensions = whippyunits_core::Dimension::BASIS;
for dimension in atomic_dimensions {
if let Some(unit) = dimension
.units
.iter()
.find(|u| u.symbols.contains(&unit_name))
{
let type_name = whippyunits_core::CapitalizedFmt(unit.name).to_string();
let type_ident = syn::Ident::new(&type_name, proc_macro2::Span::call_site());
return Some(quote::quote! {
whippyunits::default_declarators::#type_ident
});
}
}
let (prefix_opt, base) = parse_unit_with_prefix_core(unit_name);
if let Some(prefix) = prefix_opt {
if let Some((base_unit, _)) = whippyunits_core::Dimension::find_unit_by_symbol(&base) {
let type_name = generate_scale_name(prefix.name(), base_unit.name);
let type_ident = syn::Ident::new(&type_name, proc_macro2::Span::call_site());
return Some(quote::quote! {
whippyunits::default_declarators::#type_ident
});
}
}
if let Some((_dimension, unit)) = lookup_unit_literal_direct(unit_name) {
let type_name = whippyunits_core::CapitalizedFmt(unit.name).to_string();
let type_ident = syn::Ident::new(&type_name, proc_macro2::Span::call_site());
return Some(quote::quote! {
whippyunits::default_declarators::#type_ident
});
}
None
}
pub fn get_declarator_type_for_exponents(
dimension_exponents: whippyunits_core::dimension_exponents::DynDimensionExponents,
scale_exponents: whippyunits_core::scale_exponents::ScaleExponents,
) -> Option<TokenStream> {
use whippyunits_core::dimension_exponents::DynDimensionExponents;
use whippyunits_core::{Dimension, SiPrefix, System};
if dimension_exponents == DynDimensionExponents::ZERO {
return None;
}
let matching_dimension = Dimension::ALL
.iter()
.find(|dim| dim.exponents == dimension_exponents)?;
for unit in matching_dimension.units {
if unit.scale == scale_exponents {
let type_name = if unit.system == System::Metric {
let is_base_unit = matching_dimension.units[0].name == unit.name;
if is_base_unit {
generate_scale_name("", unit.name)
} else {
whippyunits_core::CapitalizedFmt(unit.name).to_string()
}
} else {
continue;
};
let type_ident = syn::Ident::new(&type_name, proc_macro2::Span::call_site());
return Some(quote::quote! {
whippyunits::default_declarators::#type_ident
});
}
}
for unit in matching_dimension.units {
if unit.system == System::Metric {
let base_unit = &matching_dimension.units[0];
let scale_diff = scale_exponents.0[0] - base_unit.scale.0[0]; if let Some(prefix) = SiPrefix::ALL
.iter()
.find(|p| p.factor_log10() == scale_diff)
{
let type_name = generate_scale_name(prefix.name(), base_unit.name);
let type_ident = syn::Ident::new(&type_name, proc_macro2::Span::call_site());
return Some(quote::quote! {
whippyunits::default_declarators::#type_ident
});
}
}
}
None
}
fn parse_unit_with_prefix_core(
unit_name: &str,
) -> (Option<&'static whippyunits_core::SiPrefix>, String) {
if let Some((prefix, base)) = whippyunits_core::SiPrefix::strip_any_prefix_symbol(unit_name) {
if whippyunits_core::Dimension::find_unit_by_symbol(base).is_some() {
return (Some(prefix), String::from(base));
}
}
if let Some((prefix, base)) = whippyunits_core::SiPrefix::strip_any_prefix_name(unit_name) {
if whippyunits_core::Dimension::find_unit_by_name(base).is_some() {
return (Some(prefix), String::from(base));
}
}
(None, String::from(unit_name))
}
fn lookup_unit_literal_direct(
unit_name: &str,
) -> Option<(&whippyunits_core::Dimension, &whippyunits_core::Unit)> {
for dimension in whippyunits_core::Dimension::ALL {
if let Some(unit) = dimension
.units
.iter()
.find(|u| u.symbols.contains(&unit_name) || u.name == unit_name)
{
return Some((dimension, unit));
}
}
None
}
pub fn get_storage_unit_type_for_unit(unit_name: &str) -> Option<TokenStream> {
use whippyunits_core::Dimension;
let (unit, dimension) =
if let Some((unit, dimension)) = Dimension::find_unit_by_symbol(unit_name) {
(unit, dimension)
} else if let Some((unit, dimension)) = Dimension::find_unit_by_name(unit_name) {
(unit, dimension)
} else {
return None;
};
let is_nonstorage = unit.conversion_factor != 1.0;
let is_affine = unit.affine_offset != 0.0;
if !is_nonstorage && !is_affine {
return None;
}
let storage_unit = dimension
.units
.iter()
.find(|u| u.scale == unit.scale && u.conversion_factor == 1.0 && u.affine_offset == 0.0)
.or_else(|| {
dimension
.units
.iter()
.find(|u| u.conversion_factor == 1.0 && u.affine_offset == 0.0)
})?;
get_declarator_type_for_unit(storage_unit.symbols[0])
}
pub fn collect_identifiers_from_expr(expr: &UnitExpr, identifiers: &mut Vec<Ident>) {
match expr {
UnitExpr::Unit(unit) => {
identifiers.push(unit.name.clone());
}
UnitExpr::Mul(left, right) => {
collect_identifiers_from_expr(left, identifiers);
collect_identifiers_from_expr(right, identifiers);
}
UnitExpr::Div(left, right) => {
collect_identifiers_from_expr(left, identifiers);
collect_identifiers_from_expr(right, identifiers);
}
UnitExpr::Pow(base, _) => {
collect_identifiers_from_expr(base, identifiers);
}
}
}
pub fn generate_unit_doc_comment(unit_name: &str) -> TokenStream {
let doc_text = get_unit_documentation_text(unit_name);
quote! {
#[doc = #doc_text]
}
}
pub fn get_unit_documentation_text(unit_name: &str) -> String {
if let Some(unit_info) = get_unit_doc_info(unit_name) {
unit_info
} else {
format!("{} ({})", unit_name.to_uppercase(), unit_name)
}
}
pub fn get_unit_doc_info(unit_name: &str) -> Option<String> {
if let Some((unit, _dimension)) = Dimension::find_unit_by_symbol(unit_name) {
let symbol = unit.symbols.first().unwrap_or(&unit_name);
return Some(format!("{} ({})", unit.name, symbol));
}
if let Some((unit, _dimension)) = Dimension::find_unit_by_name(unit_name) {
let symbol = unit.symbols.first().unwrap_or(&unit_name);
return Some(format!("{} ({})", unit.name, symbol));
}
if let Some((prefix_symbol, _base_symbol)) = parse_prefixed_unit(unit_name) {
use whippyunits_core::to_unicode_superscript;
if let Some(prefix_info) = SiPrefix::from_symbol(&prefix_symbol) {
let (base_unit_name, base_unit_symbol) =
if let Some((base_unit, _)) = Dimension::find_unit_by_symbol(&_base_symbol) {
(
base_unit.name,
base_unit.symbols.first().unwrap_or(&base_unit.name),
)
} else if let Some((base_unit, _)) = Dimension::find_unit_by_name(&_base_symbol) {
(
base_unit.name,
base_unit.symbols.first().unwrap_or(&base_unit.name),
)
} else {
(_base_symbol.as_str(), &_base_symbol.as_str())
};
let scale_text = if prefix_info.factor_log10() == 0 {
"10⁰".to_string()
} else {
format!(
"10{}",
to_unicode_superscript(prefix_info.factor_log10(), false)
)
};
let prefixed_unit_name = format!("{}{}", prefix_info.name(), base_unit_name);
let prefixed_symbol = format!("{}{}", prefix_info.symbol(), base_unit_symbol);
return Some(format!(
"{} ({}) - Prefix: {} ({}), Base: {}",
prefixed_unit_name,
prefixed_symbol,
prefix_info.name(),
scale_text,
base_unit_name
));
}
}
None
}
pub fn parse_prefixed_unit(unit_name: &str) -> Option<(String, String)> {
if let Some((prefix, base)) = SiPrefix::strip_any_prefix_symbol(unit_name) {
if let Some((unit, dimension)) = Dimension::find_unit_by_symbol(base) {
if dimension
.units
.first()
.map(|first_unit| first_unit.name == unit.name)
.unwrap_or(false)
{
if unit.system == whippyunits_core::System::Metric {
return Some((prefix.symbol().to_string(), base.to_string()));
}
}
}
}
if let Some((prefix, base)) = SiPrefix::strip_any_prefix_name(unit_name) {
if let Some((unit, dimension)) = Dimension::find_unit_by_name(base) {
if dimension
.units
.first()
.map(|first_unit| first_unit.name == unit.name)
.unwrap_or(false)
{
if unit.system == whippyunits_core::System::Metric {
return Some((prefix.symbol().to_string(), base.to_string()));
}
}
}
}
None
}
pub fn generate_unit_documentation_for_expr(
unit_expr: &UnitExpr,
use_storage_type_for_affine: bool,
) -> TokenStream {
let mut identifiers = Vec::new();
collect_identifiers_from_expr(unit_expr, &mut identifiers);
let doc_structs: Vec<TokenStream> = identifiers
.into_iter()
.map(|ident: Ident| {
let unit_name = ident.to_string();
let declarator_type = if use_storage_type_for_affine {
if let Some(storage_type) = get_storage_unit_type_for_unit(&unit_name) {
storage_type
} else if let Some(declarator_type) = get_declarator_type_for_unit(&unit_name) {
declarator_type
} else {
quote! { () }
}
} else {
if let Some(declarator_type) = get_declarator_type_for_unit(&unit_name) {
declarator_type
} else {
quote! { () }
}
};
let doc_comment = generate_unit_doc_comment(&unit_name);
quote! {
const _: () = {
#doc_comment
#[allow(non_camel_case_types)]
type #ident = #declarator_type;
};
}
})
.collect();
quote! {
#(#doc_structs)*
}
}