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