use super::{
common::{quote_sign, unwrap_with_error_msg},
int::{quote_ibig, quote_ubig},
};
use dashu_base::{BitTest, ParseError, Sign};
use dashu_int::{IBig, UBig};
use dashu_ratio::{RBig, Relaxed};
use proc_macro2::{TokenStream, TokenTree};
use quote::quote;
pub fn parse_ratio(embedded: bool, input: TokenStream) -> TokenStream {
let (num, den, relaxed) = unwrap_with_error_msg(parse_ratio_with_error(input));
let (ns, int_ns) = if embedded {
(quote!(::dashu::rational), quote!(::dashu::integer))
} else {
(quote!(::dashu_ratio), quote!(::dashu_int))
};
let type_tt = if relaxed {
quote!( #ns::Relaxed )
} else {
quote!( #ns::RBig )
};
if num.bit_len() <= 32 && den.bit_len() <= 32 {
let (sign, num) = num.into_parts();
let sign = quote_sign(embedded, sign);
let num: u32 = num.try_into().unwrap();
let den: u32 = den.try_into().unwrap();
return quote! { #type_tt::from_parts_const(#sign, #num as _, #den as _) };
}
let num_tt = if num.bit_len() <= 32 {
let (sign, num) = num.into_parts();
let sign = quote_sign(embedded, sign);
let u: u32 = num.try_into().unwrap();
quote!( #int_ns::IBig::from_parts_const(#sign, #u as _) )
} else {
quote_ibig(embedded, num)
};
let den_tt = if den.bit_len() <= 32 {
let u: u32 = den.try_into().unwrap();
quote!( #int_ns::UBig::from_dword(#u as _) )
} else {
quote_ubig(embedded, den)
};
quote! { #type_tt::from_parts(#num_tt, #den_tt) }
}
#[rustversion::since(1.64)]
pub fn parse_static_ratio(embedded: bool, input: TokenStream) -> TokenStream {
let (num, den, relaxed) = unwrap_with_error_msg(parse_ratio_with_error(input));
let ns = if embedded {
quote!(::dashu::rational)
} else {
quote!(::dashu_ratio)
};
let type_tt = if relaxed {
quote!( #ns::Relaxed )
} else {
quote!( #ns::RBig )
};
use super::common::quote_words;
let (sign, num) = num.into_parts();
let num_data_defs = quote_words(&num.to_le_bytes(), embedded);
let den_data_defs = quote_words(&den.to_le_bytes(), embedded);
let sign = quote_sign(embedded, sign);
if relaxed {
quote! {{
static NUM_DATA: &'static [#ns::Word] = #num_data_defs;
static DEN_DATA: &'static [#ns::Word] = #den_data_defs;
static VALUE: #type_tt = unsafe {
#ns::Relaxed::from_static_words(#sign, NUM_DATA, DEN_DATA)
};
&VALUE
}}
} else {
quote! {{
static NUM_DATA: &'static [#ns::Word] = #num_data_defs;
static DEN_DATA: &'static [#ns::Word] = #den_data_defs;
static VALUE: #type_tt = unsafe { core::mem::transmute(
#ns::Relaxed::from_static_words(#sign, NUM_DATA, DEN_DATA)
)};
&VALUE
}}
}
}
fn parse_ratio_with_error(input: TokenStream) -> Result<(IBig, UBig, bool), ParseError> {
let mut num_val: Option<_> = None;
let mut num_neg = false;
let mut den_val: Option<_> = None;
let mut den_neg = false;
let mut den_marked = false;
let mut relaxed = false;
let mut base_marked = false;
let mut base: Option<_> = None;
for token in input {
match token {
TokenTree::Literal(lit) => {
if num_val.is_none() {
num_val = Some(lit.to_string());
} else if den_val.is_none() {
den_val = Some(lit.to_string());
} else if base.is_none() && base_marked {
base = Some(lit.to_string());
} else {
return Err(ParseError::InvalidDigit);
}
}
TokenTree::Ident(ident) => {
if num_val.is_none() {
num_val = Some(ident.to_string())
} else if den_val.is_none() {
den_val = Some(ident.to_string());
} else if base.is_none() && ident == "base" {
base_marked = true
} else {
return Err(ParseError::InvalidDigit);
}
}
TokenTree::Punct(punct) => {
if punct.as_char() == '/' {
if !den_marked && !base_marked {
den_marked = true;
} else {
return Err(ParseError::InvalidDigit);
}
} else if punct.as_char() == '~' {
if num_val.is_none() && den_val.is_none() {
relaxed = true;
} else {
return Err(ParseError::InvalidDigit);
}
} else if num_val.is_none() {
if punct.as_char() == '-' {
num_neg = true;
} else if punct.as_char() != '+' {
return Err(ParseError::InvalidDigit);
}
} else if den_val.is_none() {
if punct.as_char() == '-' {
den_neg = true;
} else if punct.as_char() != '+' {
return Err(ParseError::InvalidDigit);
}
} else {
return Err(ParseError::InvalidDigit);
}
}
_ => return Err(ParseError::InvalidDigit),
}
}
let num_val = num_val.ok_or(ParseError::NoDigits)?;
let (num, den) = match base {
Some(b) => {
let b = b.parse::<u32>().or(Err(ParseError::UnsupportedRadix))?;
(
UBig::from_str_radix(&num_val, b)?,
den_val.map_or(Ok(UBig::ONE), |val| UBig::from_str_radix(&val, b))?,
)
}
None => {
if base_marked {
return Err(ParseError::UnsupportedRadix);
} else {
let (num_val, num_base) = UBig::from_str_with_radix_prefix(&num_val)?;
if let Some(val) = den_val {
let (den_val, den_base) = UBig::from_str_with_radix_default(&val, num_base)?;
if num_base != den_base {
return Err(ParseError::InconsistentRadix);
}
(num_val, den_val)
} else {
(num_val, UBig::ONE)
}
}
}
};
let (num, den) = if relaxed {
Relaxed::from_parts_signed(Sign::from(num_neg) * num, Sign::from(den_neg) * den)
.into_parts()
} else {
RBig::from_parts_signed(Sign::from(num_neg) * num, Sign::from(den_neg) * den).into_parts()
};
Ok((num, den, relaxed))
}