//! Helper CLI utility for common tasks. #![cfg_attr(f16_enabled, feature(f16))] #![cfg_attr(f128_enabled, feature(f128))] use std::any::type_name; use std::env; use std::num::ParseIntError; use std::str::FromStr; use libm::support::{Hexf, hf32, hf64}; #[cfg(feature = "build-mpfr")] use libm_test::mpfloat::MpOp; use libm_test::{MathOp, TupleCall}; #[cfg(feature = "build-mpfr")] use rug::az::{self, Az}; const USAGE: &str = "\ usage: cargo run -p util -- SUBCOMMAND: eval inputs... Evaulate the expression with a given basis. This can be useful for running routines with a debugger, or quickly checking input. Examples: * eval musl sinf 1.234 # print the results of musl sinf(1.234f32) * eval mpfr pow 1.234 2.432 # print the results of mpfr pow(1.234, 2.432) "; fn main() { let args = env::args().collect::>(); let str_args = args.iter().map(|s| s.as_str()).collect::>(); match &str_args.as_slice()[1..] { ["eval", basis, op, inputs @ ..] => do_eval(basis, op, inputs), _ => { println!("{USAGE}\nunrecognized input `{str_args:?}`"); std::process::exit(1); } } } macro_rules! handle_call { ( fn_name: $fn_name:ident, CFn: $CFn:ty, RustFn: $RustFn:ty, RustArgs: $RustArgs:ty, attrs: [$($attr:meta),*], extra: ($basis:ident, $op:ident, $inputs:ident), fn_extra: $musl_fn:expr, ) => { $(#[$attr])* if $op == stringify!($fn_name) { type Op = libm_test::op::$fn_name::Routine; let input = <$RustArgs>::parse($inputs); let libm_fn: ::RustFn = libm::$fn_name; let output = match $basis { "libm" => input.call_intercept_panics(libm_fn), #[cfg(feature = "build-musl")] "musl" => { let musl_fn: ::CFn = $musl_fn.unwrap_or_else(|| panic!("no musl function for {}", $op)); input.call(musl_fn) } #[cfg(feature = "build-mpfr")] "mpfr" => { let mut mp = ::new_mp(); Op::run(&mut mp, input) } _ => panic!("unrecognized or disabled basis '{}'", $basis), }; println!("{output:?} {:x}", Hexf(output)); return; } }; } /// Evaluate the specified operation with a given basis. fn do_eval(basis: &str, op: &str, inputs: &[&str]) { libm_macros::for_each_function! { callback: handle_call, emit_types: [CFn, RustFn, RustArgs], extra: (basis, op, inputs), fn_extra: match MACRO_FN_NAME { // Not provided by musl fmaximum | fmaximum_num | fmaximum_numf | fmaximumf | fminimum | fminimum_num | fminimum_numf | fminimumf | roundeven | roundevenf | ALL_F16 | ALL_F128 => None, _ => Some(musl_math_sys::MACRO_FN_NAME) } } panic!("no operation matching {op}"); } /// Parse a tuple from a space-delimited string. trait ParseTuple { fn parse(input: &[&str]) -> Self; } macro_rules! impl_parse_tuple { ($ty:ty) => { impl ParseTuple for ($ty,) { fn parse(input: &[&str]) -> Self { assert_eq!(input.len(), 1, "expected a single argument, got {input:?}"); (parse(input, 0),) } } impl ParseTuple for ($ty, $ty) { fn parse(input: &[&str]) -> Self { assert_eq!(input.len(), 2, "expected two arguments, got {input:?}"); (parse(input, 0), parse(input, 1)) } } impl ParseTuple for ($ty, i32) { fn parse(input: &[&str]) -> Self { assert_eq!(input.len(), 2, "expected two arguments, got {input:?}"); (parse(input, 0), parse(input, 1)) } } impl ParseTuple for (i32, $ty) { fn parse(input: &[&str]) -> Self { assert_eq!(input.len(), 2, "expected two arguments, got {input:?}"); (parse(input, 0), parse(input, 1)) } } impl ParseTuple for ($ty, $ty, $ty) { fn parse(input: &[&str]) -> Self { assert_eq!(input.len(), 3, "expected three arguments, got {input:?}"); (parse(input, 0), parse(input, 1), parse(input, 2)) } } }; } #[allow(unused_macros)] #[cfg(feature = "build-mpfr")] macro_rules! impl_parse_tuple_via_rug { ($ty:ty) => { impl ParseTuple for ($ty,) { fn parse(input: &[&str]) -> Self { assert_eq!(input.len(), 1, "expected a single argument, got {input:?}"); (parse_rug(input, 0),) } } impl ParseTuple for ($ty, $ty) { fn parse(input: &[&str]) -> Self { assert_eq!(input.len(), 2, "expected two arguments, got {input:?}"); (parse_rug(input, 0), parse_rug(input, 1)) } } impl ParseTuple for ($ty, i32) { fn parse(input: &[&str]) -> Self { assert_eq!(input.len(), 2, "expected two arguments, got {input:?}"); (parse_rug(input, 0), parse(input, 1)) } } impl ParseTuple for (i32, $ty) { fn parse(input: &[&str]) -> Self { assert_eq!(input.len(), 2, "expected two arguments, got {input:?}"); (parse(input, 0), parse_rug(input, 1)) } } impl ParseTuple for ($ty, $ty, $ty) { fn parse(input: &[&str]) -> Self { assert_eq!(input.len(), 3, "expected three arguments, got {input:?}"); ( parse_rug(input, 0), parse_rug(input, 1), parse_rug(input, 2), ) } } }; } // Fallback for when Rug is not built. #[allow(unused_macros)] #[cfg(not(feature = "build-mpfr"))] macro_rules! impl_parse_tuple_via_rug { ($ty:ty) => { impl ParseTuple for ($ty,) { fn parse(_input: &[&str]) -> Self { panic!("parsing this type requires the `build-mpfr` feature") } } impl ParseTuple for ($ty, $ty) { fn parse(_input: &[&str]) -> Self { panic!("parsing this type requires the `build-mpfr` feature") } } impl ParseTuple for ($ty, i32) { fn parse(_input: &[&str]) -> Self { panic!("parsing this type requires the `build-mpfr` feature") } } impl ParseTuple for (i32, $ty) { fn parse(_input: &[&str]) -> Self { panic!("parsing this type requires the `build-mpfr` feature") } } impl ParseTuple for ($ty, $ty, $ty) { fn parse(_input: &[&str]) -> Self { panic!("parsing this type requires the `build-mpfr` feature") } } }; } impl_parse_tuple!(f32); impl_parse_tuple!(f64); #[cfg(f16_enabled)] impl_parse_tuple_via_rug!(f16); #[cfg(f128_enabled)] impl_parse_tuple_via_rug!(f128); /// Try to parse the number, printing a nice message on failure. fn parse(input: &[&str], idx: usize) -> T { let s = input[idx]; let msg = || format!("invalid {} input '{s}'", type_name::()); if s.starts_with("0x") || s.starts_with("-0x") { return T::from_str_radix(s, 16).unwrap_or_else(|_| panic!("{}", msg())); } if s.starts_with("0b") { return T::from_str_radix(s, 2).unwrap_or_else(|_| panic!("{}", msg())); } s.parse().unwrap_or_else(|_| panic!("{}", msg())) } /// Try to parse the float type going via `rug`, for `f16` and `f128` which don't yet implement /// `FromStr`. #[cfg(feature = "build-mpfr")] fn parse_rug(input: &[&str], idx: usize) -> F where F: libm_test::Float + FromStrRadix, rug::Float: az::Cast, { let s = input[idx]; let msg = || format!("invalid {} input '{s}'", type_name::()); if s.starts_with("0x") { return F::from_str_radix(s, 16).unwrap_or_else(|_| panic!("{}", msg())); } if s.starts_with("0b") { return F::from_str_radix(s, 2).unwrap_or_else(|_| panic!("{}", msg())); } let x = rug::Float::parse(s).unwrap_or_else(|_| panic!("{}", msg())); let x = rug::Float::with_val(F::BITS, x); x.az() } trait FromStrRadix: Sized { fn from_str_radix(s: &str, radix: u32) -> Result; } impl FromStrRadix for i32 { fn from_str_radix(s: &str, radix: u32) -> Result { let s = strip_radix_prefix(s, radix); i32::from_str_radix(s, radix) } } #[cfg(f16_enabled)] impl FromStrRadix for f16 { fn from_str_radix(s: &str, radix: u32) -> Result { if radix == 16 && s.contains("p") { return Ok(libm::support::hf16(s)); } let s = strip_radix_prefix(s, radix); u16::from_str_radix(s, radix).map(Self::from_bits) } } impl FromStrRadix for f32 { fn from_str_radix(s: &str, radix: u32) -> Result { if radix == 16 && s.contains("p") { // Parse as hex float return Ok(hf32(s)); } let s = strip_radix_prefix(s, radix); u32::from_str_radix(s, radix).map(Self::from_bits) } } impl FromStrRadix for f64 { fn from_str_radix(s: &str, radix: u32) -> Result { if s.contains("p") { return Ok(hf64(s)); } let s = strip_radix_prefix(s, radix); u64::from_str_radix(s, radix).map(Self::from_bits) } } #[cfg(f128_enabled)] impl FromStrRadix for f128 { fn from_str_radix(s: &str, radix: u32) -> Result { if radix == 16 && s.contains("p") { return Ok(libm::support::hf128(s)); } let s = strip_radix_prefix(s, radix); u128::from_str_radix(s, radix).map(Self::from_bits) } } fn strip_radix_prefix(s: &str, radix: u32) -> &str { if radix == 16 { s.strip_prefix("0x").unwrap() } else if radix == 2 { s.strip_prefix("0b").unwrap() } else { s } }