use super::common::quote_sign;
use super::float::{gen_binary_fbig_value, gen_binary_repr_const};
use core::str::FromStr;
use dashu_base::{BitTest, Sign};
use dashu_float::round::mode::Zero;
use dashu_float::{FBig, Repr};
use proc_macro2::TokenStream;
use quote::quote;
fn panic_cbig_syntax() -> ! {
panic!("Incorrect syntax, please refer to the docs for acceptable complex literal formats.")
}
fn small_coeff(f: &FBig) -> Option<(Sign, u32, isize)> {
if f.repr().is_infinite() {
return None;
}
let (signif, exp) = f.clone().into_repr().into_parts();
let (sign, mag) = signif.into_parts();
if mag.bit_len() <= 32 {
let u: u32 = mag.try_into().unwrap();
Some((sign, u, exp))
} else {
None
}
}
fn parse_coeff(s: &str) -> FBig {
let s = s.trim();
let (sign, rest) = match s.strip_prefix('-') {
Some(r) => ("-", r.trim()),
None => match s.strip_prefix('+') {
Some(r) => ("+", r.trim()),
None => ("", s),
},
};
let digits = rest.strip_prefix('_').unwrap_or(rest);
if digits.starts_with("0x") || digits.starts_with("0X") {
FBig::from_str(&format!("{sign}{digits}")).unwrap_or_else(|_| panic_cbig_syntax())
} else if let Some(body) = digits
.strip_prefix("0b")
.or_else(|| digits.strip_prefix("0B"))
{
FBig::from_str(&format!("{sign}{body}")).unwrap_or_else(|_| panic_cbig_syntax())
} else if let Some(body) = digits
.strip_prefix("0o")
.or_else(|| digits.strip_prefix("0O"))
{
FBig::<Zero, 8>::from_str(&format!("{sign}{body}"))
.unwrap_or_else(|_| panic_cbig_syntax())
.with_base::<2>()
.value()
} else {
FBig::<Zero, 10>::from_str(&format!("{sign}{digits}"))
.unwrap_or_else(|_| panic_cbig_syntax())
.with_base::<2>()
.value()
}
}
fn parse_algebraic(s: &str) -> (FBig, FBig) {
let s = s.trim();
if !s.contains('i') {
let re = parse_coeff(s);
let im = FBig::from_repr(Repr::zero(), re.context());
return (re, im);
}
if s.bytes().filter(|&c| c == b'i').count() > 1 || !s.ends_with('i') {
panic_cbig_syntax();
}
let prefix = &s[..s.len() - 1];
let split = prefix.rfind(['+', '-']).filter(|&pos| pos > 0);
let (real_str, imag_str) = match split {
Some(pos) => (&prefix[..pos], &prefix[pos..]),
None => ("", prefix),
};
let imag_str = imag_str.trim();
let im = match imag_str {
"" | "+" => FBig::ONE,
"-" => FBig::NEG_ONE,
other => parse_coeff(other),
};
let re = if real_str.is_empty() {
FBig::from_repr(Repr::zero(), im.context())
} else {
parse_coeff(real_str)
};
(re, im)
}
pub fn parse_complex(static_: bool, embedded: bool, input: TokenStream) -> TokenStream {
let value_str: String = input.into_iter().map(|tt| tt.to_string()).collect();
let value_str = value_str.trim();
let (re, im) = if let Some((re_s, im_s)) = value_str.split_once(',') {
(parse_coeff(re_s), parse_coeff(im_s))
} else {
parse_algebraic(value_str)
};
let ns = if embedded {
quote!(::dashu::complex)
} else {
quote!(::dashu_cmplx)
};
let ns_f = if embedded {
quote!(::dashu::float)
} else {
quote!(::dashu_float)
};
if static_ {
let (re_repr, prec_re) = gen_binary_repr_const(embedded, &re);
let (im_repr, prec_im) = gen_binary_repr_const(embedded, &im);
let prec = prec_re.max(prec_im);
quote! {{
static VALUE: #ns::CBig = #ns::CBig::new(
#re_repr,
#im_repr,
#ns::Context::new(#prec),
);
&VALUE
}}
} else {
match (small_coeff(&re), small_coeff(&im)) {
(Some((re_sign, re_u, re_exp)), Some((im_sign, im_u, im_exp))) => {
let prec = re.precision().max(im.precision());
let re_sign = quote_sign(embedded, re_sign);
let im_sign = quote_sign(embedded, im_sign);
quote! {
#ns::CBig::<#ns_f::round::mode::Zero, 2>::from_parts_const(
(#re_sign, #re_u as _, #re_exp),
(#im_sign, #im_u as _, #im_exp),
#prec,
)
}
}
_ => {
let re_tt = gen_binary_fbig_value(embedded, &re);
let im_tt = gen_binary_fbig_value(embedded, &im);
quote! { #ns::CBig::from_parts(#re_tt, #im_tt) }
}
}
}
}