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