1use anyhow::Result;
4
5use crate::CompilationIssue;
6use crate::errors::KclError;
7use crate::errors::KclErrorDetails;
8use crate::execution::ExecState;
9use crate::execution::KclValue;
10use crate::execution::annotations;
11use crate::execution::types::ArrayLen;
12use crate::execution::types::NumericType;
13use crate::execution::types::RuntimeType;
14use crate::std::args::Args;
15use crate::std::args::TyF64;
16use crate::util::MathExt;
17
18pub async fn rem(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
21 let n: TyF64 = args.get_unlabeled_kw_arg("number to divide", &RuntimeType::num_any(), exec_state)?;
22 let d: TyF64 = args.get_kw_arg("divisor", &RuntimeType::num_any(), exec_state)?;
23 let valid_d = d.n != 0.0;
24 if !valid_d {
25 exec_state.warn(
26 CompilationIssue::err(args.source_range, "Divisor cannot be 0".to_string()),
27 annotations::WARN_INVALID_MATH,
28 );
29 }
30
31 let (n, d, ty) = NumericType::combine_mod(n, d);
32 if ty == NumericType::Unknown {
33 exec_state.err(CompilationIssue::err(
34 args.source_range,
35 "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})`"
36 ));
37 }
38 let remainder = n % d;
39
40 Ok(args.make_user_val_from_f64_with_type(TyF64::new(remainder, ty)))
41}
42
43pub async fn cos(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
45 let num: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::angle(), exec_state)?;
46 let num = num.to_radians(exec_state, args.source_range);
47 Ok(args.make_user_val_from_f64_with_type(TyF64::new(libm::cos(num), exec_state.current_default_units())))
48}
49
50pub async fn sin(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
52 let num: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::angle(), exec_state)?;
53 let num = num.to_radians(exec_state, args.source_range);
54 Ok(args.make_user_val_from_f64_with_type(TyF64::new(libm::sin(num), exec_state.current_default_units())))
55}
56
57pub async fn tan(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
59 let num: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::angle(), exec_state)?;
60 let num = num.to_radians(exec_state, args.source_range);
61 Ok(args.make_user_val_from_f64_with_type(TyF64::new(libm::tan(num), exec_state.current_default_units())))
62}
63
64pub async fn sqrt(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
66 let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::num_any(), exec_state)?;
67
68 if input.n < 0.0 {
69 return Err(KclError::new_semantic(KclErrorDetails::new(
70 format!(
71 "Attempt to take square root (`sqrt`) of a number less than zero ({})",
72 input.n
73 ),
74 vec![args.source_range],
75 )));
76 }
77
78 let result = input.n.sqrt();
79
80 Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
81}
82
83pub async fn abs(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
85 let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::num_any(), exec_state)?;
86 let result = input.n.abs();
87
88 Ok(args.make_user_val_from_f64_with_type(input.map_value(result)))
89}
90
91pub async fn round(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
93 let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::num_any(), exec_state)?;
94 let result = input.n.round();
95
96 Ok(args.make_user_val_from_f64_with_type(input.map_value(result)))
97}
98
99pub async fn floor(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
101 let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::num_any(), exec_state)?;
102 let result = input.n.floor();
103
104 Ok(args.make_user_val_from_f64_with_type(input.map_value(result)))
105}
106
107pub async fn ceil(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
109 let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::num_any(), exec_state)?;
110 let result = input.n.ceil();
111
112 Ok(args.make_user_val_from_f64_with_type(input.map_value(result)))
113}
114
115pub async fn min(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
117 let nums: Vec<TyF64> = args.get_unlabeled_kw_arg(
118 "input",
119 &RuntimeType::Array(Box::new(RuntimeType::num_any()), ArrayLen::Minimum(1)),
120 exec_state,
121 )?;
122 let (nums, ty) = NumericType::combine_eq_array(&nums);
123 if ty == NumericType::Unknown {
124 exec_state.warn(CompilationIssue::err(
125 args.source_range,
126 "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})`",
127 ), annotations::WARN_UNKNOWN_UNITS);
128 }
129
130 let mut result = f64::MAX;
131 for num in nums {
132 if num < result {
133 result = num;
134 }
135 }
136
137 Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, ty)))
138}
139
140pub async fn max(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
142 let nums: Vec<TyF64> = args.get_unlabeled_kw_arg(
143 "input",
144 &RuntimeType::Array(Box::new(RuntimeType::num_any()), ArrayLen::Minimum(1)),
145 exec_state,
146 )?;
147 let (nums, ty) = NumericType::combine_eq_array(&nums);
148 if ty == NumericType::Unknown {
149 exec_state.warn(CompilationIssue::err(
150 args.source_range,
151 "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})`",
152 ), annotations::WARN_UNKNOWN_UNITS);
153 }
154
155 let mut result = f64::MIN;
156 for num in nums {
157 if num > result {
158 result = num;
159 }
160 }
161
162 Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, ty)))
163}
164
165pub async fn pow(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
167 let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::num_any(), exec_state)?;
168 let exp: TyF64 = args.get_kw_arg("exp", &RuntimeType::count(), exec_state)?;
169 let exp_is_int = exp.n.fract() == 0.0;
170 if input.n < 0.0 && !exp_is_int {
171 exec_state.warn(
172 CompilationIssue::err(
173 args.source_range,
174 format!(
175 "Exponent must be an integer when input is negative, but it was {}",
176 exp.n
177 ),
178 ),
179 annotations::WARN_INVALID_MATH,
180 );
181 }
182 let valid_input = !(input.n == 0.0 && exp.n < 0.0);
183 if !valid_input {
184 exec_state.warn(
185 CompilationIssue::err(args.source_range, "Input cannot be 0 when exp < 0".to_string()),
186 annotations::WARN_INVALID_MATH,
187 );
188 }
189 let result = libm::pow(input.n, exp.n);
190
191 Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
192}
193
194pub async fn acos(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
196 let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::count(), exec_state)?;
197 let in_range = (-1.0..=1.0).contains(&input.n);
198 if !in_range {
199 exec_state.warn(
200 CompilationIssue::err(
201 args.source_range,
202 format!("The argument must be between -1 and 1, but it was {}", input.n),
203 ),
204 annotations::WARN_INVALID_MATH,
205 );
206 }
207 let result = libm::acos(input.n);
208
209 Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
210}
211
212pub async fn asin(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
214 let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::count(), exec_state)?;
215 let in_range = (-1.0..=1.0).contains(&input.n);
216 if !in_range {
217 exec_state.warn(
218 CompilationIssue::err(
219 args.source_range,
220 format!("The argument must be between -1 and 1, but it was {}", input.n),
221 ),
222 annotations::WARN_INVALID_MATH,
223 );
224 }
225 let result = libm::asin(input.n);
226
227 Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
228}
229
230pub async fn atan(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
232 let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::count(), exec_state)?;
233 let result = libm::atan(input.n);
234
235 Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
236}
237
238pub async fn atan2(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
240 let y = args.get_kw_arg("y", &RuntimeType::length(), exec_state)?;
241 let x = args.get_kw_arg("x", &RuntimeType::length(), exec_state)?;
242 let (y, x, _) = NumericType::combine_eq_coerce(y, x, Some((exec_state, args.source_range)));
243 let result = libm::atan2(y, x);
244
245 Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
246}
247
248pub async fn log(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
254 let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::num_any(), exec_state)?;
255 let base: TyF64 = args.get_kw_arg("base", &RuntimeType::count(), exec_state)?;
256 let valid_input = input.n > 0.0;
257 if !valid_input {
258 exec_state.warn(
259 CompilationIssue::err(args.source_range, format!("Input must be > 0, but it was {}", input.n)),
260 annotations::WARN_INVALID_MATH,
261 );
262 }
263 let valid_base = base.n > 0.0;
264 if !valid_base {
265 exec_state.warn(
266 CompilationIssue::err(args.source_range, format!("Base must be > 0, but it was {}", base.n)),
267 annotations::WARN_INVALID_MATH,
268 );
269 }
270 let base_not_1 = base.n != 1.0;
271 if !base_not_1 {
272 exec_state.warn(
273 CompilationIssue::err(args.source_range, "Base cannot be 1".to_string()),
274 annotations::WARN_INVALID_MATH,
275 );
276 }
277 let result = input.n.log(base.n);
278
279 Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
280}
281
282pub async fn log2(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
284 let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::num_any(), exec_state)?;
285 let valid_input = input.n > 0.0;
286 if !valid_input {
287 exec_state.warn(
288 CompilationIssue::err(args.source_range, format!("Input must be > 0, but it was {}", input.n)),
289 annotations::WARN_INVALID_MATH,
290 );
291 }
292 let result = input.n.log2();
293
294 Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
295}
296
297pub async fn log10(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
299 let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::num_any(), exec_state)?;
300 let valid_input = input.n > 0.0;
301 if !valid_input {
302 exec_state.warn(
303 CompilationIssue::err(args.source_range, format!("Input must be > 0, but it was {}", input.n)),
304 annotations::WARN_INVALID_MATH,
305 );
306 }
307 let result = input.n.log10();
308
309 Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
310}
311
312pub async fn ln(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
314 let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::num_any(), exec_state)?;
315 let valid_input = input.n > 0.0;
316 if !valid_input {
317 exec_state.warn(
318 CompilationIssue::err(args.source_range, format!("Input must be > 0, but it was {}", input.n)),
319 annotations::WARN_INVALID_MATH,
320 );
321 }
322 let result = input.n.ln();
323
324 Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
325}
326
327pub async fn leg_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
329 let hypotenuse: TyF64 = args.get_kw_arg("hypotenuse", &RuntimeType::length(), exec_state)?;
330 let leg: TyF64 = args.get_kw_arg("leg", &RuntimeType::length(), exec_state)?;
331 let (hypotenuse, leg, ty) = NumericType::combine_eq_coerce(hypotenuse, leg, Some((exec_state, args.source_range)));
332 let result = (hypotenuse.squared() - libm::fmin(hypotenuse.abs(), leg.abs()).squared()).sqrt();
333 Ok(KclValue::from_number_with_type(result, ty, vec![args.into()]))
334}
335
336pub async fn leg_angle_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
338 let hypotenuse: TyF64 = args.get_kw_arg("hypotenuse", &RuntimeType::length(), exec_state)?;
339 let leg: TyF64 = args.get_kw_arg("leg", &RuntimeType::length(), exec_state)?;
340 let (hypotenuse, leg, _ty) = NumericType::combine_eq_coerce(hypotenuse, leg, Some((exec_state, args.source_range)));
341 let valid_hypotenuse = hypotenuse > 0.0;
342 if !valid_hypotenuse {
343 exec_state.warn(
344 CompilationIssue::err(
345 args.source_range,
346 format!("Hypotenuse must be > 0, but it was {}", hypotenuse),
347 ),
348 annotations::WARN_INVALID_MATH,
349 );
350 }
351 let ratio = libm::fmin(leg, hypotenuse) / hypotenuse;
352 let in_range = (-1.0..=1.0).contains(&ratio);
353 if !in_range {
354 exec_state.warn(
355 CompilationIssue::err(
356 args.source_range,
357 format!("The argument must be between -1 and 1, but it was {}", ratio),
358 ),
359 annotations::WARN_INVALID_MATH,
360 );
361 }
362 let result = libm::acos(ratio).to_degrees();
363 Ok(KclValue::from_number_with_type(
364 result,
365 NumericType::degrees(),
366 vec![args.into()],
367 ))
368}
369
370pub async fn leg_angle_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
372 let hypotenuse: TyF64 = args.get_kw_arg("hypotenuse", &RuntimeType::length(), exec_state)?;
373 let leg: TyF64 = args.get_kw_arg("leg", &RuntimeType::length(), exec_state)?;
374 let (hypotenuse, leg, _ty) = NumericType::combine_eq_coerce(hypotenuse, leg, Some((exec_state, args.source_range)));
375 let valid_hypotenuse = hypotenuse > 0.0;
376 if !valid_hypotenuse {
377 exec_state.warn(
378 CompilationIssue::err(
379 args.source_range,
380 format!("Hypotenuse must be > 0, but it was {}", hypotenuse),
381 ),
382 annotations::WARN_INVALID_MATH,
383 );
384 }
385 let ratio = libm::fmin(leg, hypotenuse) / hypotenuse;
386 let in_range = (-1.0..=1.0).contains(&ratio);
387 if !in_range {
388 exec_state.warn(
389 CompilationIssue::err(
390 args.source_range,
391 format!("The argument must be between -1 and 1, but it was {}", ratio),
392 ),
393 annotations::WARN_INVALID_MATH,
394 );
395 }
396 let result = libm::asin(ratio).to_degrees();
397 Ok(KclValue::from_number_with_type(
398 result,
399 NumericType::degrees(),
400 vec![args.into()],
401 ))
402}