use anyhow::Result;
use crate::CompilationIssue;
use crate::errors::KclError;
use crate::errors::KclErrorDetails;
use crate::execution::ExecState;
use crate::execution::KclValue;
use crate::execution::annotations;
use crate::execution::types::ArrayLen;
use crate::execution::types::NumericType;
use crate::execution::types::RuntimeType;
use crate::std::args::Args;
use crate::std::args::TyF64;
pub async fn rem(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let n: TyF64 = args.get_unlabeled_kw_arg("number to divide", &RuntimeType::num_any(), exec_state)?;
let d: TyF64 = args.get_kw_arg("divisor", &RuntimeType::num_any(), exec_state)?;
let valid_d = d.n != 0.0;
if !valid_d {
exec_state.warn(
CompilationIssue::err(args.source_range, "Divisor cannot be 0".to_string()),
annotations::WARN_INVALID_MATH,
);
}
let (n, d, ty) = NumericType::combine_mod(n, d);
if ty == NumericType::Unknown {
exec_state.err(CompilationIssue::err(
args.source_range,
"Calling `rem` on numbers which have unknown or incompatible units.\n\nYou may need to add information about the type of the argument, for example:\n using a numeric suffix: `42{ty}`\n or using type ascription: `foo(): number({ty})`"
));
}
let remainder = n % d;
Ok(args.make_user_val_from_f64_with_type(TyF64::new(remainder, ty)))
}
pub async fn cos(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let num: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::angle(), exec_state)?;
let num = num.to_radians(exec_state, args.source_range);
Ok(args.make_user_val_from_f64_with_type(TyF64::new(libm::cos(num), exec_state.current_default_units())))
}
pub async fn sin(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let num: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::angle(), exec_state)?;
let num = num.to_radians(exec_state, args.source_range);
Ok(args.make_user_val_from_f64_with_type(TyF64::new(libm::sin(num), exec_state.current_default_units())))
}
pub async fn tan(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let num: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::angle(), exec_state)?;
let num = num.to_radians(exec_state, args.source_range);
Ok(args.make_user_val_from_f64_with_type(TyF64::new(libm::tan(num), exec_state.current_default_units())))
}
pub async fn sqrt(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::num_any(), exec_state)?;
if input.n < 0.0 {
return Err(KclError::new_semantic(KclErrorDetails::new(
format!(
"Attempt to take square root (`sqrt`) of a number less than zero ({})",
input.n
),
vec![args.source_range],
)));
}
let result = input.n.sqrt();
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
}
pub async fn abs(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::num_any(), exec_state)?;
let result = input.n.abs();
Ok(args.make_user_val_from_f64_with_type(input.map_value(result)))
}
pub async fn round(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::num_any(), exec_state)?;
let result = input.n.round();
Ok(args.make_user_val_from_f64_with_type(input.map_value(result)))
}
pub async fn floor(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::num_any(), exec_state)?;
let result = input.n.floor();
Ok(args.make_user_val_from_f64_with_type(input.map_value(result)))
}
pub async fn ceil(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::num_any(), exec_state)?;
let result = input.n.ceil();
Ok(args.make_user_val_from_f64_with_type(input.map_value(result)))
}
pub async fn min(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let nums: Vec<TyF64> = args.get_unlabeled_kw_arg(
"input",
&RuntimeType::Array(Box::new(RuntimeType::num_any()), ArrayLen::Minimum(1)),
exec_state,
)?;
let (nums, ty) = NumericType::combine_eq_array(&nums);
if ty == NumericType::Unknown {
exec_state.warn(CompilationIssue::err(
args.source_range,
"Calling `min` on numbers which have unknown or incompatible units.\n\nYou may need to add information about the type of the argument, for example:\n using a numeric suffix: `42{ty}`\n or using type ascription: `foo(): number({ty})`",
), annotations::WARN_UNKNOWN_UNITS);
}
let mut result = f64::MAX;
for num in nums {
if num < result {
result = num;
}
}
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, ty)))
}
pub async fn max(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let nums: Vec<TyF64> = args.get_unlabeled_kw_arg(
"input",
&RuntimeType::Array(Box::new(RuntimeType::num_any()), ArrayLen::Minimum(1)),
exec_state,
)?;
let (nums, ty) = NumericType::combine_eq_array(&nums);
if ty == NumericType::Unknown {
exec_state.warn(CompilationIssue::err(
args.source_range,
"Calling `max` on numbers which have unknown or incompatible units.\n\nYou may need to add information about the type of the argument, for example:\n using a numeric suffix: `42{ty}`\n or using type ascription: `foo(): number({ty})`",
), annotations::WARN_UNKNOWN_UNITS);
}
let mut result = f64::MIN;
for num in nums {
if num > result {
result = num;
}
}
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, ty)))
}
pub async fn pow(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::num_any(), exec_state)?;
let exp: TyF64 = args.get_kw_arg("exp", &RuntimeType::count(), exec_state)?;
let exp_is_int = exp.n.fract() == 0.0;
if input.n < 0.0 && !exp_is_int {
exec_state.warn(
CompilationIssue::err(
args.source_range,
format!(
"Exponent must be an integer when input is negative, but it was {}",
exp.n
),
),
annotations::WARN_INVALID_MATH,
);
}
let valid_input = !(input.n == 0.0 && exp.n < 0.0);
if !valid_input {
exec_state.warn(
CompilationIssue::err(args.source_range, "Input cannot be 0 when exp < 0".to_string()),
annotations::WARN_INVALID_MATH,
);
}
let result = input.n.powf(exp.n);
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
}
pub async fn acos(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::count(), exec_state)?;
let in_range = (-1.0..=1.0).contains(&input.n);
if !in_range {
exec_state.warn(
CompilationIssue::err(
args.source_range,
format!("The argument must be between -1 and 1, but it was {}", input.n),
),
annotations::WARN_INVALID_MATH,
);
}
let result = libm::acos(input.n);
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
}
pub async fn asin(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::count(), exec_state)?;
let in_range = (-1.0..=1.0).contains(&input.n);
if !in_range {
exec_state.warn(
CompilationIssue::err(
args.source_range,
format!("The argument must be between -1 and 1, but it was {}", input.n),
),
annotations::WARN_INVALID_MATH,
);
}
let result = libm::asin(input.n);
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
}
pub async fn atan(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::count(), exec_state)?;
let result = libm::atan(input.n);
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
}
pub async fn atan2(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let y = args.get_kw_arg("y", &RuntimeType::length(), exec_state)?;
let x = args.get_kw_arg("x", &RuntimeType::length(), exec_state)?;
let (y, x, _) = NumericType::combine_eq_coerce(y, x, Some((exec_state, args.source_range)));
let result = libm::atan2(y, x);
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
}
pub async fn log(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::num_any(), exec_state)?;
let base: TyF64 = args.get_kw_arg("base", &RuntimeType::count(), exec_state)?;
let valid_input = input.n > 0.0;
if !valid_input {
exec_state.warn(
CompilationIssue::err(args.source_range, format!("Input must be > 0, but it was {}", input.n)),
annotations::WARN_INVALID_MATH,
);
}
let valid_base = base.n > 0.0;
if !valid_base {
exec_state.warn(
CompilationIssue::err(args.source_range, format!("Base must be > 0, but it was {}", base.n)),
annotations::WARN_INVALID_MATH,
);
}
let base_not_1 = base.n != 1.0;
if !base_not_1 {
exec_state.warn(
CompilationIssue::err(args.source_range, "Base cannot be 1".to_string()),
annotations::WARN_INVALID_MATH,
);
}
let result = input.n.log(base.n);
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
}
pub async fn log2(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::num_any(), exec_state)?;
let valid_input = input.n > 0.0;
if !valid_input {
exec_state.warn(
CompilationIssue::err(args.source_range, format!("Input must be > 0, but it was {}", input.n)),
annotations::WARN_INVALID_MATH,
);
}
let result = input.n.log2();
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
}
pub async fn log10(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::num_any(), exec_state)?;
let valid_input = input.n > 0.0;
if !valid_input {
exec_state.warn(
CompilationIssue::err(args.source_range, format!("Input must be > 0, but it was {}", input.n)),
annotations::WARN_INVALID_MATH,
);
}
let result = input.n.log10();
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
}
pub async fn ln(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::num_any(), exec_state)?;
let valid_input = input.n > 0.0;
if !valid_input {
exec_state.warn(
CompilationIssue::err(args.source_range, format!("Input must be > 0, but it was {}", input.n)),
annotations::WARN_INVALID_MATH,
);
}
let result = input.n.ln();
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
}
pub async fn leg_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let hypotenuse: TyF64 = args.get_kw_arg("hypotenuse", &RuntimeType::length(), exec_state)?;
let leg: TyF64 = args.get_kw_arg("leg", &RuntimeType::length(), exec_state)?;
let (hypotenuse, leg, ty) = NumericType::combine_eq_coerce(hypotenuse, leg, Some((exec_state, args.source_range)));
let result = (hypotenuse.powi(2) - f64::min(hypotenuse.abs(), leg.abs()).powi(2)).sqrt();
Ok(KclValue::from_number_with_type(result, ty, vec![args.into()]))
}
pub async fn leg_angle_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let hypotenuse: TyF64 = args.get_kw_arg("hypotenuse", &RuntimeType::length(), exec_state)?;
let leg: TyF64 = args.get_kw_arg("leg", &RuntimeType::length(), exec_state)?;
let (hypotenuse, leg, _ty) = NumericType::combine_eq_coerce(hypotenuse, leg, Some((exec_state, args.source_range)));
let valid_hypotenuse = hypotenuse > 0.0;
if !valid_hypotenuse {
exec_state.warn(
CompilationIssue::err(
args.source_range,
format!("Hypotenuse must be > 0, but it was {}", hypotenuse),
),
annotations::WARN_INVALID_MATH,
);
}
let ratio = leg.min(hypotenuse) / hypotenuse;
let in_range = (-1.0..=1.0).contains(&ratio);
if !in_range {
exec_state.warn(
CompilationIssue::err(
args.source_range,
format!("The argument must be between -1 and 1, but it was {}", ratio),
),
annotations::WARN_INVALID_MATH,
);
}
let result = libm::acos(ratio).to_degrees();
Ok(KclValue::from_number_with_type(
result,
NumericType::degrees(),
vec![args.into()],
))
}
pub async fn leg_angle_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let hypotenuse: TyF64 = args.get_kw_arg("hypotenuse", &RuntimeType::length(), exec_state)?;
let leg: TyF64 = args.get_kw_arg("leg", &RuntimeType::length(), exec_state)?;
let (hypotenuse, leg, _ty) = NumericType::combine_eq_coerce(hypotenuse, leg, Some((exec_state, args.source_range)));
let valid_hypotenuse = hypotenuse > 0.0;
if !valid_hypotenuse {
exec_state.warn(
CompilationIssue::err(
args.source_range,
format!("Hypotenuse must be > 0, but it was {}", hypotenuse),
),
annotations::WARN_INVALID_MATH,
);
}
let ratio = leg.min(hypotenuse) / hypotenuse;
let in_range = (-1.0..=1.0).contains(&ratio);
if !in_range {
exec_state.warn(
CompilationIssue::err(
args.source_range,
format!("The argument must be between -1 and 1, but it was {}", ratio),
),
annotations::WARN_INVALID_MATH,
);
}
let result = libm::asin(ratio).to_degrees();
Ok(KclValue::from_number_with_type(
result,
NumericType::degrees(),
vec![args.into()],
))
}