blots_core/
functions.rs

1use crate::{
2    expressions::evaluate_ast,
3    heap::{Heap, HeapPointer, IterablePointer},
4    values::{FunctionArity, LambdaArg, LambdaDef, ReifiedValue, Value},
5};
6use anyhow::{Result, anyhow};
7use dyn_fmt::AsStrFormatExt;
8use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::LazyLock};
9
10#[cfg(not(target_arch = "wasm32"))]
11use std::sync::Mutex;
12
13#[cfg(not(target_arch = "wasm32"))]
14use crate::stats::FunctionCallStats;
15
16#[cfg(not(target_arch = "wasm32"))]
17pub static FUNCTION_CALLS: LazyLock<Mutex<Vec<FunctionCallStats>>> =
18    LazyLock::new(|| Mutex::new(Vec::new()));
19
20// Cache built-in function names since they're static
21pub static BUILTIN_FUNCTION_NAMES: LazyLock<Vec<&'static str>> =
22    LazyLock::new(BuiltInFunction::all_names);
23
24#[derive(
25    Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
26)]
27pub enum BuiltInFunction {
28    // Math functions
29    Sqrt,
30    Sin,
31    Cos,
32    Tan,
33    Asin,
34    Acos,
35    Atan,
36    Log,
37    Log10,
38    Exp,
39    Abs,
40    Floor,
41    Ceil,
42    Round,
43    Trunc,
44
45    // Aggregate functions
46    Min,
47    Max,
48    Avg,
49    Sum,
50    Prod,
51    Median,
52    Percentile,
53
54    // List functions
55    Range,
56    Len,
57    Head,
58    Tail,
59    Slice,
60    Concat,
61    Dot,
62    Unique,
63    Sort,
64    SortBy,
65    Reverse,
66    Any,
67    All,
68
69    // Higher-order functions
70    Map,
71    Reduce,
72    Filter,
73    Every,
74    Some,
75
76    // String functions
77    Split,
78    Join,
79    Replace,
80    Trim,
81    Uppercase,
82    Lowercase,
83    Includes,
84    Format,
85
86    // Type functions
87    Typeof,
88    Arity,
89
90    // Record functions
91    Keys,
92    Values,
93    Entries,
94
95    // Conversion functions
96    ToString,
97    ToNumber,
98    ToBool,
99
100    // Platform-specific functions
101    #[cfg(not(target_arch = "wasm32"))]
102    Print,
103    #[cfg(not(target_arch = "wasm32"))]
104    TimeNow,
105}
106
107#[derive(Debug, Clone)]
108pub enum FunctionDef {
109    BuiltIn(BuiltInFunction),
110    Lambda(LambdaDef),
111}
112
113impl BuiltInFunction {
114    pub fn from_ident(ident: &str) -> Option<Self> {
115        match ident {
116            "sqrt" => Some(Self::Sqrt),
117            "sin" => Some(Self::Sin),
118            "cos" => Some(Self::Cos),
119            "tan" => Some(Self::Tan),
120            "asin" => Some(Self::Asin),
121            "acos" => Some(Self::Acos),
122            "atan" => Some(Self::Atan),
123            "log" => Some(Self::Log),
124            "log10" => Some(Self::Log10),
125            "exp" => Some(Self::Exp),
126            "abs" => Some(Self::Abs),
127            "floor" => Some(Self::Floor),
128            "ceil" => Some(Self::Ceil),
129            "round" => Some(Self::Round),
130            "trunc" => Some(Self::Trunc),
131            "min" => Some(Self::Min),
132            "max" => Some(Self::Max),
133            "avg" => Some(Self::Avg),
134            "sum" => Some(Self::Sum),
135            "prod" => Some(Self::Prod),
136            "median" => Some(Self::Median),
137            "percentile" => Some(Self::Percentile),
138            "range" => Some(Self::Range),
139            "any" => Some(Self::Any),
140            "all" => Some(Self::All),
141            "len" => Some(Self::Len),
142            "head" => Some(Self::Head),
143            "tail" => Some(Self::Tail),
144            "slice" => Some(Self::Slice),
145            "concat" => Some(Self::Concat),
146            "dot" => Some(Self::Dot),
147            "unique" => Some(Self::Unique),
148            "sort" => Some(Self::Sort),
149            "sort_by" => Some(Self::SortBy),
150            "reverse" => Some(Self::Reverse),
151            "map" => Some(Self::Map),
152            "reduce" => Some(Self::Reduce),
153            "filter" => Some(Self::Filter),
154            "every" => Some(Self::Every),
155            "some" => Some(Self::Some),
156            "split" => Some(Self::Split),
157            "join" => Some(Self::Join),
158            "replace" => Some(Self::Replace),
159            "trim" => Some(Self::Trim),
160            "uppercase" => Some(Self::Uppercase),
161            "lowercase" => Some(Self::Lowercase),
162            "to_string" => Some(Self::ToString),
163            "to_number" => Some(Self::ToNumber),
164            "to_bool" => Some(Self::ToBool),
165            "includes" => Some(Self::Includes),
166            "format" => Some(Self::Format),
167            "typeof" => Some(Self::Typeof),
168            "arity" => Some(Self::Arity),
169            "keys" => Some(Self::Keys),
170            "values" => Some(Self::Values),
171            "entries" => Some(Self::Entries),
172            #[cfg(not(target_arch = "wasm32"))]
173            "print" => Some(Self::Print),
174            #[cfg(not(target_arch = "wasm32"))]
175            "time_now" => Some(Self::TimeNow),
176            _ => None,
177        }
178    }
179
180    pub fn name(&self) -> &'static str {
181        match self {
182            Self::Sqrt => "sqrt",
183            Self::Sin => "sin",
184            Self::Cos => "cos",
185            Self::Tan => "tan",
186            Self::Asin => "asin",
187            Self::Acos => "acos",
188            Self::Atan => "atan",
189            Self::Log => "log",
190            Self::Log10 => "log10",
191            Self::Exp => "exp",
192            Self::Abs => "abs",
193            Self::Floor => "floor",
194            Self::Ceil => "ceil",
195            Self::Round => "round",
196            Self::Trunc => "trunc",
197            Self::Min => "min",
198            Self::Max => "max",
199            Self::Avg => "avg",
200            Self::Sum => "sum",
201            Self::Prod => "prod",
202            Self::Median => "median",
203            Self::Percentile => "percentile",
204            Self::Range => "range",
205            Self::Any => "any",
206            Self::All => "all",
207            Self::Len => "len",
208            Self::Head => "head",
209            Self::Tail => "tail",
210            Self::Slice => "slice",
211            Self::Concat => "concat",
212            Self::Dot => "dot",
213            Self::Unique => "unique",
214            Self::Sort => "sort",
215            Self::SortBy => "sort_by",
216            Self::Reverse => "reverse",
217            Self::Map => "map",
218            Self::Reduce => "reduce",
219            Self::Filter => "filter",
220            Self::Every => "every",
221            Self::Some => "some",
222            Self::Split => "split",
223            Self::Join => "join",
224            Self::Replace => "replace",
225            Self::Trim => "trim",
226            Self::Uppercase => "uppercase",
227            Self::Lowercase => "lowercase",
228            Self::Includes => "includes",
229            Self::Format => "format",
230            Self::Typeof => "typeof",
231            Self::Arity => "arity",
232            Self::Keys => "keys",
233            Self::Values => "values",
234            Self::Entries => "entries",
235            Self::ToString => "to_string",
236            Self::ToNumber => "to_number",
237            Self::ToBool => "to_bool",
238            #[cfg(not(target_arch = "wasm32"))]
239            Self::Print => "print",
240            #[cfg(not(target_arch = "wasm32"))]
241            Self::TimeNow => "time_now",
242        }
243    }
244
245    pub fn arity(&self) -> FunctionArity {
246        match self {
247            // Math functions - single argument
248            Self::Sqrt
249            | Self::Sin
250            | Self::Cos
251            | Self::Tan
252            | Self::Asin
253            | Self::Acos
254            | Self::Atan
255            | Self::Log
256            | Self::Log10
257            | Self::Exp
258            | Self::Abs
259            | Self::Floor
260            | Self::Ceil
261            | Self::Trunc => FunctionArity::Exact(1),
262
263            // Round can take 1 or 2 arguments
264            Self::Round => FunctionArity::Between(1, 2),
265
266            // Aggregate functions
267            Self::Min | Self::Max | Self::Avg | Self::Sum | Self::Prod | Self::Median => {
268                FunctionArity::AtLeast(1)
269            }
270
271            // Range can take 1 or 2 arguments
272            Self::Range => FunctionArity::Between(1, 2),
273
274            // List functions
275            Self::Len
276            | Self::Head
277            | Self::Tail
278            | Self::Unique
279            | Self::Sort
280            | Self::Reverse
281            | Self::Any
282            | Self::All => FunctionArity::Exact(1),
283            Self::Slice => FunctionArity::Exact(3),
284            Self::Concat => FunctionArity::AtLeast(2),
285            Self::Dot | Self::Percentile => FunctionArity::Exact(2),
286
287            // Higher-order functions
288            Self::Map | Self::Filter | Self::Every | Self::Some | Self::SortBy => {
289                FunctionArity::Exact(2)
290            }
291            Self::Reduce => FunctionArity::Exact(3),
292
293            // String functions
294            Self::Split | Self::Join | Self::Includes => FunctionArity::Exact(2),
295            Self::Replace => FunctionArity::Exact(3),
296            Self::Trim
297            | Self::Uppercase
298            | Self::Lowercase
299            | Self::ToString
300            | Self::ToNumber
301            | Self::ToBool => FunctionArity::Exact(1),
302            Self::Format => FunctionArity::AtLeast(1),
303
304            // Type functions
305            Self::Typeof | Self::Arity => FunctionArity::Exact(1),
306
307            // Record functions
308            Self::Keys | Self::Values | Self::Entries => FunctionArity::Exact(1),
309
310            // Platform-specific functions
311            #[cfg(not(target_arch = "wasm32"))]
312            Self::Print => FunctionArity::AtLeast(1),
313            #[cfg(not(target_arch = "wasm32"))]
314            Self::TimeNow => FunctionArity::Exact(0),
315        }
316    }
317
318    pub fn call(
319        &self,
320        args: Vec<Value>,
321        heap: Rc<RefCell<Heap>>,
322        bindings: Rc<RefCell<HashMap<String, Value>>>,
323        call_depth: usize,
324    ) -> Result<Value> {
325        match self {
326            // Math functions
327            Self::Sqrt => Ok(Value::Number(args[0].as_number()?.sqrt())),
328            Self::Sin => Ok(Value::Number(args[0].as_number()?.sin())),
329            Self::Cos => Ok(Value::Number(args[0].as_number()?.cos())),
330            Self::Tan => Ok(Value::Number(args[0].as_number()?.tan())),
331            Self::Asin => Ok(Value::Number(args[0].as_number()?.asin())),
332            Self::Acos => Ok(Value::Number(args[0].as_number()?.acos())),
333            Self::Atan => Ok(Value::Number(args[0].as_number()?.atan())),
334            Self::Log => Ok(Value::Number(args[0].as_number()?.ln())),
335            Self::Log10 => Ok(Value::Number(args[0].as_number()?.log10())),
336            Self::Exp => Ok(Value::Number(args[0].as_number()?.exp())),
337            Self::Abs => Ok(Value::Number(args[0].as_number()?.abs())),
338            Self::Floor => Ok(Value::Number(args[0].as_number()?.floor())),
339            Self::Ceil => Ok(Value::Number(args[0].as_number()?.ceil())),
340            Self::Trunc => Ok(Value::Number(args[0].as_number()?.trunc())),
341
342            Self::Round => {
343                let num = args[0].as_number()?;
344                if args.len() == 1 {
345                    Ok(Value::Number(num.round()))
346                } else {
347                    let decimal_places = args[1].as_number()? as i32;
348                    let multiplier = 10_f64.powi(decimal_places);
349                    Ok(Value::Number((num * multiplier).round() / multiplier))
350                }
351            }
352
353            // Aggregate functions
354            Self::Min => {
355                let nums = if args.len() == 1 {
356                    // Check if single argument is a list
357                    match &args[0] {
358                        Value::List(_) => {
359                            let borrowed_heap = heap.borrow();
360                            let list = args[0].as_list(&borrowed_heap)?;
361                            list.iter()
362                                .map(|a| a.as_number())
363                                .collect::<Result<Vec<f64>>>()?
364                        }
365                        _ => vec![args[0].as_number()?],
366                    }
367                } else {
368                    args.iter()
369                        .map(|a| a.as_number())
370                        .collect::<Result<Vec<f64>>>()?
371                };
372
373                if nums.is_empty() {
374                    return Err(anyhow!("min requires at least one number"));
375                }
376
377                Ok(Value::Number(
378                    nums.iter().copied().fold(f64::INFINITY, f64::min),
379                ))
380            }
381
382            Self::Max => {
383                let nums = if args.len() == 1 {
384                    // Check if single argument is a list
385                    match &args[0] {
386                        Value::List(_) => {
387                            let borrowed_heap = heap.borrow();
388                            let list = args[0].as_list(&borrowed_heap)?;
389                            list.iter()
390                                .map(|a| a.as_number())
391                                .collect::<Result<Vec<f64>>>()?
392                        }
393                        _ => vec![args[0].as_number()?],
394                    }
395                } else {
396                    args.iter()
397                        .map(|a| a.as_number())
398                        .collect::<Result<Vec<f64>>>()?
399                };
400
401                if nums.is_empty() {
402                    return Err(anyhow!("max requires at least one number"));
403                }
404
405                Ok(Value::Number(
406                    nums.iter().copied().fold(f64::NEG_INFINITY, f64::max),
407                ))
408            }
409
410            Self::Avg => {
411                let nums = if args.len() == 1 {
412                    match &args[0] {
413                        Value::List(_) => {
414                            let borrowed_heap = heap.borrow();
415                            let list = args[0].as_list(&borrowed_heap)?;
416                            list.iter()
417                                .map(|a| a.as_number())
418                                .collect::<Result<Vec<f64>>>()?
419                        }
420                        _ => vec![args[0].as_number()?],
421                    }
422                } else {
423                    args.iter()
424                        .map(|a| a.as_number())
425                        .collect::<Result<Vec<f64>>>()?
426                };
427                if nums.is_empty() {
428                    return Err(anyhow!("avg requires at least one number"));
429                }
430                Ok(Value::Number(nums.iter().sum::<f64>() / nums.len() as f64))
431            }
432
433            Self::Prod => {
434                let nums = if args.len() == 1 {
435                    match &args[0] {
436                        Value::List(_) => {
437                            let borrowed_heap = heap.borrow();
438                            let list = args[0].as_list(&borrowed_heap)?;
439                            list.iter()
440                                .map(|a| a.as_number())
441                                .collect::<Result<Vec<f64>>>()?
442                        }
443                        _ => vec![args[0].as_number()?],
444                    }
445                } else {
446                    args.iter()
447                        .map(|a| a.as_number())
448                        .collect::<Result<Vec<f64>>>()?
449                };
450                if nums.is_empty() {
451                    return Err(anyhow!("prod requires at least one number"));
452                }
453                Ok(Value::Number(nums.iter().product()))
454            }
455
456            Self::Range => {
457                let (start, end) = match args[..] {
458                    [Value::Number(start)] => (0.0, start),
459                    [Value::Number(start), Value::Number(end)] => (start, end),
460                    _ => return Err(anyhow!("range requires 1 or 2 numbers")),
461                };
462
463                if start > end {
464                    return Err(anyhow!(
465                        "range requires start to be less than or equal to end"
466                    ));
467                }
468
469                if !f64::is_finite(start) || !f64::is_finite(end) {
470                    return Err(anyhow!("range requires finite numbers"));
471                }
472
473                let start_i64 = start as i64;
474                let end_i64 = end as i64;
475                let length = end_i64 - start_i64;
476
477                if length > u32::MAX as i64 {
478                    return Err(anyhow!(
479                        "list would be longer than the maximum length of {}",
480                        u32::MAX
481                    ));
482                }
483
484                let values = (start_i64..end_i64)
485                    .map(|e| Value::Number(e as f64))
486                    .collect();
487                let list = heap.borrow_mut().insert_list(values);
488
489                Ok(list)
490            }
491
492            Self::Sum => {
493                let nums = if args.len() == 1 {
494                    match &args[0] {
495                        Value::List(_) => {
496                            let borrowed_heap = heap.borrow();
497                            let list = args[0].as_list(&borrowed_heap)?;
498                            list.iter()
499                                .map(|a| a.as_number())
500                                .collect::<Result<Vec<f64>>>()?
501                        }
502                        _ => vec![args[0].as_number()?],
503                    }
504                } else {
505                    args.iter()
506                        .map(|a| a.as_number())
507                        .collect::<Result<Vec<f64>>>()?
508                };
509                if nums.is_empty() {
510                    return Err(anyhow!("sum requires at least one number"));
511                }
512                Ok(Value::Number(nums.iter().sum()))
513            }
514
515            // Remaining aggregate functions
516            Self::Median => {
517                let mut nums = if args.len() == 1 {
518                    match &args[0] {
519                        Value::List(_) => {
520                            let borrowed_heap = heap.borrow();
521                            let list = args[0].as_list(&borrowed_heap)?;
522                            list.iter()
523                                .map(|a| a.as_number())
524                                .collect::<Result<Vec<f64>>>()?
525                        }
526                        _ => vec![args[0].as_number()?],
527                    }
528                } else {
529                    args.into_iter()
530                        .map(|a| a.as_number())
531                        .collect::<Result<Vec<f64>>>()?
532                };
533
534                if nums.is_empty() {
535                    return Err(anyhow!("median requires at least one number"));
536                }
537
538                nums.sort_by(|a, b| a.partial_cmp(b).unwrap());
539                let len = nums.len();
540                if len % 2 == 0 {
541                    Ok(Value::Number((nums[len / 2 - 1] + nums[len / 2]) / 2.0))
542                } else {
543                    Ok(Value::Number(nums[len / 2]))
544                }
545            }
546
547            Self::Percentile => {
548                let p = args[1].as_number()?;
549                let heap = &heap.borrow();
550                let list = args[0].as_list(heap)?;
551
552                if !(0.0..=100.0).contains(&p) {
553                    return Err(anyhow!("percentile must be between 0 and 100"));
554                }
555
556                let mut nums = list
557                    .iter()
558                    .map(|a| a.as_number())
559                    .collect::<Result<Vec<f64>>>()?;
560
561                nums.sort_by(|a, b| a.partial_cmp(b).unwrap());
562                let index = (p / 100.0 * (nums.len() - 1) as f64).round() as usize;
563
564                Ok(Value::Number(nums[index]))
565            }
566
567            // List functions
568            Self::Len => match &args[0] {
569                Value::List(l) => Ok(Value::Number(
570                    l.reify(&heap.borrow()).as_list()?.len() as f64
571                )),
572                Value::String(s) => Ok(Value::Number(
573                    s.reify(&heap.borrow()).as_string()?.len() as f64
574                )),
575                _ => Err(anyhow!("argument must be a list or string")),
576            },
577
578            Self::Head => match &args[0] {
579                Value::List(p) => Ok(p
580                    .reify(&heap.borrow())
581                    .as_list()?
582                    .first()
583                    .copied()
584                    .unwrap_or(Value::Null)),
585                Value::String(p) => {
586                    let val = {
587                        p.reify(&heap.borrow())
588                            .as_string()?
589                            .get(0..1)
590                            .unwrap_or("")
591                            .to_string()
592                    };
593
594                    Ok(heap.borrow_mut().insert_string(val))
595                }
596                _ => Err(anyhow!("argument must be a list or string")),
597            },
598
599            Self::Tail => match &args[0] {
600                Value::List(p) => {
601                    let val = {
602                        p.reify(&heap.borrow())
603                            .as_list()?
604                            .get(1..)
605                            .unwrap_or([].as_slice())
606                            .to_vec()
607                    };
608
609                    Ok(heap.borrow_mut().insert_list(val))
610                }
611                Value::String(s) => {
612                    let val = {
613                        s.reify(&heap.borrow())
614                            .as_string()?
615                            .get(1..)
616                            .unwrap_or("")
617                            .to_string()
618                    };
619
620                    Ok(heap.borrow_mut().insert_string(val))
621                }
622                _ => Err(anyhow!("argument must be a list or string")),
623            },
624
625            Self::Slice => {
626                let start = args[1].as_number()? as usize;
627                let end = args[2].as_number()? as usize;
628
629                match args[0] {
630                    Value::List(_) => {
631                        let l = {
632                            let borrowed_heap = &heap.borrow();
633                            args[0].as_list(borrowed_heap)?.clone()
634                        };
635
636                        l.get(start..end)
637                            .map_or(Err(anyhow!("index out of bounds")), |l| {
638                                Ok(heap.borrow_mut().insert_list(l.to_vec()))
639                            })
640                    }
641                    Value::String(_) => {
642                        let s = {
643                            let borrowed_heap = &heap.borrow();
644                            args[0].as_string(borrowed_heap)?.to_string()
645                        };
646
647                        s.get(start..end)
648                            .map_or(Err(anyhow!("index out of bounds")), |s| {
649                                Ok(heap.borrow_mut().insert_string(s.to_string()))
650                            })
651                    }
652                    _ => Err(anyhow!("argument must be a list or string")),
653                }
654            }
655
656            Self::Concat => {
657                let mut list = vec![];
658
659                for arg in args {
660                    match arg {
661                        Value::List(p) => list.extend(p.reify(&heap.borrow()).as_list()?.clone()),
662                        Value::Spread(IterablePointer::List(p)) => {
663                            list.extend(p.reify(&heap.borrow()).as_list()?.clone())
664                        }
665                        Value::Spread(IterablePointer::String(p)) => {
666                            let string = {
667                                let borrowed_heap = &heap.borrow();
668                                p.reify(borrowed_heap).as_string()?.to_string()
669                            };
670
671                            list.extend(
672                                string
673                                    .chars()
674                                    .map(|c| heap.borrow_mut().insert_string(c.to_string())),
675                            );
676                        }
677                        _ => list.push(arg),
678                    }
679                }
680
681                Ok(heap.borrow_mut().insert_list(list))
682            }
683
684            Self::Dot => {
685                let (a, b) = {
686                    let borrowed_heap = &heap.borrow();
687                    (
688                        args[0].as_list(borrowed_heap)?.clone(),
689                        args[1].as_list(borrowed_heap)?.clone(),
690                    )
691                };
692
693                if a.len() != b.len() {
694                    return Err(anyhow!(
695                        "cannot calculate dot product of lists with different lengths"
696                    ));
697                }
698
699                Ok(Value::Number(
700                    a.iter()
701                        .zip(b.iter())
702                        .map(|(a, b)| {
703                            let a_num = a.as_number()?;
704                            let b_num = b.as_number()?;
705                            Ok(a_num * b_num)
706                        })
707                        .collect::<Result<Vec<f64>>>()?
708                        .iter()
709                        .sum(),
710                ))
711            }
712
713            Self::Unique => {
714                let list = {
715                    let borrowed_heap = &heap.borrow();
716                    args[0].as_list(borrowed_heap)?.clone()
717                };
718                let mut unique_list = vec![];
719                let borrowed_heap = heap.borrow();
720
721                for item in list {
722                    let mut is_duplicate = false;
723                    for existing in &unique_list {
724                        if item.equals(existing, &borrowed_heap)? {
725                            is_duplicate = true;
726                            break;
727                        }
728                    }
729                    if !is_duplicate {
730                        unique_list.push(item);
731                    }
732                }
733
734                drop(borrowed_heap);
735                Ok(heap.borrow_mut().insert_list(unique_list))
736            }
737
738            Self::Sort => {
739                let mut list = {
740                    let borrowed_heap = &heap.borrow();
741                    args[0].as_list(borrowed_heap)?.clone()
742                };
743                let borrowed_heap = heap.borrow();
744                list.sort_by(|a, b| {
745                    a.compare(b, &borrowed_heap)
746                        .unwrap_or(None)
747                        .unwrap_or(std::cmp::Ordering::Equal)
748                });
749                drop(borrowed_heap);
750                Ok(heap.borrow_mut().insert_list(list))
751            }
752
753            Self::Reverse => {
754                let mut list = { args[0].as_list(&heap.borrow())?.clone() };
755                list.reverse();
756                Ok(heap.borrow_mut().insert_list(list))
757            }
758
759            Self::Any => {
760                let list = args[0].as_list(&heap.borrow())?.clone();
761                Ok(Value::Bool(
762                    list.iter().any(|v| v.as_bool().unwrap_or(false)),
763                ))
764            }
765
766            Self::All => {
767                let list = args[0].as_list(&heap.borrow())?.clone();
768                Ok(Value::Bool(
769                    list.iter().all(|v| v.as_bool().unwrap_or(false)),
770                ))
771            }
772
773            // String functions
774            Self::Split => {
775                let (s, delimeter) = {
776                    let borrowed_heap = &heap.borrow();
777                    (
778                        args[0].as_string(borrowed_heap)?.to_string(),
779                        args[1].as_string(borrowed_heap)?.to_string(),
780                    )
781                };
782
783                let list = s
784                    .split(&delimeter)
785                    .map(|s| heap.borrow_mut().insert_string(s.to_string()))
786                    .collect();
787
788                Ok(heap.borrow_mut().insert_list(list))
789            }
790
791            Self::Join => {
792                let joined_string = {
793                    let borrowed_heap = &heap.borrow();
794                    let delimeter = args[1].as_string(borrowed_heap)?;
795                    let list = args[0].as_list(borrowed_heap)?;
796                    list.iter()
797                        .map(|v| v.stringify_internal(borrowed_heap))
798                        .collect::<Vec<String>>()
799                        .join(delimeter)
800                };
801
802                Ok(heap.borrow_mut().insert_string(joined_string))
803            }
804
805            Self::Replace => {
806                let string = {
807                    let borrowed_heap = heap.borrow();
808                    let old = args[1].as_string(&borrowed_heap)?.to_string();
809                    let new = args[2].as_string(&borrowed_heap)?.to_string();
810                    let s = args[0].as_string(&borrowed_heap)?.to_string();
811                    s.replace(&old, &new)
812                };
813
814                Ok(heap.borrow_mut().insert_string(string))
815            }
816
817            Self::Trim => {
818                let string = {
819                    let borrowed_heap = heap.borrow();
820                    args[0].as_string(&borrowed_heap)?.trim().to_string()
821                };
822
823                Ok(heap.borrow_mut().insert_string(string))
824            }
825
826            Self::Uppercase => {
827                let string = args[0].as_string(&heap.borrow())?.to_uppercase();
828                Ok(heap.borrow_mut().insert_string(string))
829            }
830
831            Self::Lowercase => {
832                let string = args[0].as_string(&heap.borrow())?.to_lowercase();
833                Ok(heap.borrow_mut().insert_string(string))
834            }
835
836            Self::Includes => {
837                match &args[0].reify(&heap.borrow())? {
838                    // If haystack is a list, check for structural equality with any-type needle
839                    ReifiedValue::List(l, _) => {
840                        let borrowed_heap = heap.borrow();
841                        for item in (*l).iter() {
842                            if item.equals(&args[1], &borrowed_heap)? {
843                                return Ok(Value::Bool(true));
844                            }
845                        }
846                        Ok(Value::Bool(false))
847                    }
848                    // If haystack is a string, require needle to be a string and do substring search
849                    ReifiedValue::String(s, _) => {
850                        let needle = {
851                            let borrowed_heap = &heap.borrow();
852                            args[1]
853                                .as_string(borrowed_heap)
854                                .map_err(|_| anyhow!("second argument must be a string"))?
855                                .to_string()
856                        };
857                        Ok(Value::Bool(s.contains(&needle)))
858                    }
859                    _ => Err(anyhow!("first argument must be a list or string")),
860                }
861            }
862
863            Self::Format => {
864                let format_str = {
865                    let borrowed_heap = &heap.borrow();
866                    args[0]
867                        .as_string(borrowed_heap)
868                        .map_err(|_| anyhow!("first argument must be a string"))?
869                        .to_string()
870                };
871
872                let format_args = {
873                    let borrowed_heap = &heap.borrow();
874                    &args[1..]
875                        .iter()
876                        .map(|v| v.stringify_internal(borrowed_heap))
877                        .collect::<Vec<String>>()
878                };
879
880                Ok(heap
881                    .borrow_mut()
882                    .insert_string(format_str.format(format_args)))
883            }
884
885            // Type functions
886            Self::Typeof => Ok(heap
887                .borrow_mut()
888                .insert_string(args[0].get_type().to_string())),
889
890            Self::Arity => {
891                let arity = match args[0] {
892                    Value::Lambda(p) => p.reify(&heap.borrow()).as_lambda()?.get_arity(),
893                    Value::BuiltIn(built_in) => built_in.arity(),
894                    _ => return Err(anyhow!("argument must be a function or built-in function")),
895                };
896
897                Ok(Value::Number(match arity {
898                    FunctionArity::Exact(n) => n as f64,
899                    FunctionArity::AtLeast(n) => n as f64,
900                    FunctionArity::Between(min, _max) => min as f64,
901                }))
902            }
903
904            // Record functions
905            Self::Keys => {
906                let record = args[0].as_record(&heap.borrow())?.clone();
907                let keys = {
908                    let key_strings = record.keys().cloned().collect::<Vec<String>>();
909                    key_strings
910                        .iter()
911                        .map(|k| heap.borrow_mut().insert_string(k.to_string()))
912                        .collect()
913                };
914
915                Ok(heap.borrow_mut().insert_list(keys))
916            }
917
918            Self::Values => {
919                let record = args[0].as_record(&heap.borrow())?.clone();
920                let values = record.values().cloned().collect();
921
922                Ok(heap.borrow_mut().insert_list(values))
923            }
924
925            Self::Entries => {
926                let record = args[0].as_record(&heap.borrow())?.clone();
927                let entries = record
928                    .iter()
929                    .map(|(k, v)| {
930                        let entry = {
931                            let mut borrowed_heap = heap.borrow_mut();
932                            vec![borrowed_heap.insert_string(k.to_string()), *v]
933                        };
934                        heap.borrow_mut().insert_list(entry)
935                    })
936                    .collect();
937
938                Ok(heap.borrow_mut().insert_list(entries))
939            }
940
941            // Conversion functions
942            Self::ToString => match args[0] {
943                Value::String(_) => Ok(args[0]), // If it's already a string, just return it
944                _ => {
945                    let string = args[0].stringify_internal(&heap.borrow());
946                    Ok(heap.borrow_mut().insert_string(string))
947                }
948            },
949
950            Self::ToNumber => match args[0] {
951                Value::Number(_) => Ok(args[0]), // If it's already a number, just return it
952                Value::Bool(b) => Ok(Value::Number(if b { 1.0 } else { 0.0 })),
953                _ => Ok(Value::Number(args[0].as_string(&heap.borrow())?.parse()?)),
954            },
955
956            Self::ToBool => match args[0] {
957                Value::Bool(_) => Ok(args[0]), // If it's already a boolean, just return it
958                Value::Number(_) => Ok(Value::Bool(args[0].as_number()? != 0.0)),
959                _ => Err(anyhow!(
960                    "expected a boolean or number, but got a {}",
961                    args[0].get_type()
962                )),
963            },
964
965            // Platform-specific functions
966            #[cfg(not(target_arch = "wasm32"))]
967            Self::Print => {
968                let borrowed_heap = &heap.borrow();
969
970                let output = if args.len() == 1 {
971                    args[0].stringify_internal(borrowed_heap)
972                } else {
973                    let format_str = args[0].as_string(borrowed_heap).map_err(|_| {
974                        anyhow!("first argument must be a formatting string if multiple arguments are given")
975                    })?;
976                    let format_args = &args[1..]
977                        .iter()
978                        .map(|v| v.stringify_internal(borrowed_heap))
979                        .collect::<Vec<String>>();
980                    format_str.format(format_args)
981                };
982
983                // Print to stderr, to not pollute stdout
984                eprintln!("{}", output);
985
986                Ok(Value::Null)
987            }
988
989            #[cfg(not(target_arch = "wasm32"))]
990            Self::TimeNow => Ok(Value::Number(
991                std::time::SystemTime::now()
992                    .duration_since(std::time::UNIX_EPOCH)
993                    .unwrap()
994                    .as_secs_f64(),
995            )),
996
997            // Higher-order functions
998            Self::Map => {
999                let func = &args[1];
1000                let list = {
1001                    let borrowed_heap = &heap.borrow();
1002                    let list = args[0].as_list(borrowed_heap)?.clone();
1003                    let func_def = get_function_def(func, borrowed_heap)
1004                        .ok_or_else(|| anyhow!("second argument must be a function"))?;
1005                    drop(func_def);
1006                    list
1007                };
1008
1009                let mut mapped_list = vec![];
1010                for item in list {
1011                    let func_def = get_function_def(func, &heap.borrow())
1012                        .ok_or_else(|| anyhow!("second argument must be a function"))?;
1013                    let result = func_def.call(
1014                        Value::Null,
1015                        vec![item],
1016                        Rc::clone(&heap),
1017                        Rc::clone(&bindings),
1018                        call_depth + 1,
1019                    )?;
1020                    mapped_list.push(result);
1021                }
1022
1023                Ok(heap.borrow_mut().insert_list(mapped_list))
1024            }
1025
1026            Self::Filter => {
1027                let func = &args[1];
1028                let list = {
1029                    let borrowed_heap = &heap.borrow();
1030                    args[0].as_list(borrowed_heap)?.clone()
1031                };
1032
1033                let mut filtered_list = vec![];
1034                for item in list {
1035                    let func_def = get_function_def(func, &heap.borrow())
1036                        .ok_or_else(|| anyhow!("second argument must be a function"))?;
1037                    let result = func_def.call(
1038                        Value::Null,
1039                        vec![item],
1040                        Rc::clone(&heap),
1041                        Rc::clone(&bindings),
1042                        call_depth + 1,
1043                    )?;
1044                    if result.as_bool()? {
1045                        filtered_list.push(item);
1046                    }
1047                }
1048
1049                Ok(heap.borrow_mut().insert_list(filtered_list))
1050            }
1051
1052            Self::Reduce => {
1053                let func = &args[1];
1054                let initial = args[2];
1055                let list = {
1056                    let borrowed_heap = &heap.borrow();
1057                    args[0].as_list(borrowed_heap)?.clone()
1058                };
1059
1060                let mut accumulator = initial;
1061                for item in list {
1062                    let func_def = get_function_def(func, &heap.borrow())
1063                        .ok_or_else(|| anyhow!("second argument must be a function"))?;
1064                    accumulator = func_def.call(
1065                        Value::Null,
1066                        vec![accumulator, item],
1067                        Rc::clone(&heap),
1068                        Rc::clone(&bindings),
1069                        call_depth + 1,
1070                    )?;
1071                }
1072
1073                Ok(accumulator)
1074            }
1075
1076            Self::Every => {
1077                let func = &args[1];
1078                let list = {
1079                    let borrowed_heap = &heap.borrow();
1080                    args[0].as_list(borrowed_heap)?.clone()
1081                };
1082
1083                for item in list {
1084                    let func_def = get_function_def(func, &heap.borrow())
1085                        .ok_or_else(|| anyhow!("second argument must be a function"))?;
1086                    let result = func_def.call(
1087                        Value::Null,
1088                        vec![item],
1089                        Rc::clone(&heap),
1090                        Rc::clone(&bindings),
1091                        call_depth + 1,
1092                    )?;
1093                    if !result.as_bool()? {
1094                        return Ok(Value::Bool(false));
1095                    }
1096                }
1097
1098                Ok(Value::Bool(true))
1099            }
1100
1101            Self::Some => {
1102                let func = &args[1];
1103                let list = {
1104                    let borrowed_heap = &heap.borrow();
1105                    args[0].as_list(borrowed_heap)?.clone()
1106                };
1107
1108                for item in list {
1109                    let func_def = get_function_def(func, &heap.borrow())
1110                        .ok_or_else(|| anyhow!("second argument must be a function"))?;
1111                    let result = func_def.call(
1112                        Value::Null,
1113                        vec![item],
1114                        Rc::clone(&heap),
1115                        Rc::clone(&bindings),
1116                        call_depth + 1,
1117                    )?;
1118                    if result.as_bool()? {
1119                        return Ok(Value::Bool(true));
1120                    }
1121                }
1122
1123                Ok(Value::Bool(false))
1124            }
1125
1126            Self::SortBy => {
1127                let func = &args[1];
1128                let mut list = {
1129                    let borrowed_heap = &heap.borrow();
1130                    args[0].as_list(borrowed_heap)?.clone()
1131                };
1132
1133                list.sort_by(|a, b| {
1134                    let func_def_a = get_function_def(func, &heap.borrow());
1135                    let func_def_b = get_function_def(func, &heap.borrow());
1136
1137                    match (func_def_a, func_def_b) {
1138                        (Some(fd_a), Some(fd_b)) => {
1139                            let result_a = fd_a.call(
1140                                Value::Null,
1141                                vec![*a],
1142                                Rc::clone(&heap),
1143                                Rc::clone(&bindings),
1144                                call_depth + 1,
1145                            );
1146                            let result_b = fd_b.call(
1147                                Value::Null,
1148                                vec![*b],
1149                                Rc::clone(&heap),
1150                                Rc::clone(&bindings),
1151                                call_depth + 1,
1152                            );
1153
1154                            match (result_a, result_b) {
1155                                (Ok(val_a), Ok(val_b)) => val_a
1156                                    .compare(&val_b, &heap.borrow())
1157                                    .unwrap_or(None)
1158                                    .unwrap_or(std::cmp::Ordering::Equal),
1159                                _ => std::cmp::Ordering::Equal,
1160                            }
1161                        }
1162                        _ => std::cmp::Ordering::Equal,
1163                    }
1164                });
1165
1166                Ok(heap.borrow_mut().insert_list(list))
1167            }
1168        }
1169    }
1170
1171    pub fn all() -> Vec<Self> {
1172        vec![
1173            Self::Sqrt,
1174            Self::Sin,
1175            Self::Cos,
1176            Self::Tan,
1177            Self::Asin,
1178            Self::Acos,
1179            Self::Atan,
1180            Self::Log,
1181            Self::Log10,
1182            Self::Exp,
1183            Self::Abs,
1184            Self::Floor,
1185            Self::Ceil,
1186            Self::Round,
1187            Self::Trunc,
1188            Self::Min,
1189            Self::Max,
1190            Self::Avg,
1191            Self::Sum,
1192            Self::Prod,
1193            Self::Median,
1194            Self::Percentile,
1195            Self::Range,
1196            Self::Len,
1197            Self::Head,
1198            Self::Tail,
1199            Self::Slice,
1200            Self::Concat,
1201            Self::Dot,
1202            Self::Unique,
1203            Self::Sort,
1204            Self::SortBy,
1205            Self::Reverse,
1206            Self::Any,
1207            Self::All,
1208            Self::Map,
1209            Self::Reduce,
1210            Self::Filter,
1211            Self::Every,
1212            Self::Some,
1213            Self::Split,
1214            Self::Join,
1215            Self::Replace,
1216            Self::Trim,
1217            Self::Uppercase,
1218            Self::Lowercase,
1219            Self::ToString,
1220            Self::ToNumber,
1221            Self::ToBool,
1222            Self::Includes,
1223            Self::Format,
1224            Self::Typeof,
1225            Self::Arity,
1226            Self::Keys,
1227            Self::Values,
1228            Self::Entries,
1229            #[cfg(not(target_arch = "wasm32"))]
1230            Self::Print,
1231            #[cfg(not(target_arch = "wasm32"))]
1232            Self::TimeNow,
1233        ]
1234    }
1235
1236    pub fn all_names() -> Vec<&'static str> {
1237        Self::all().iter().map(|f| f.name()).collect()
1238    }
1239}
1240
1241impl FunctionDef {
1242    pub fn get_name(&self) -> String {
1243        match self {
1244            FunctionDef::BuiltIn(built_in) => {
1245                format!("built-in function \"{}\"", built_in.name())
1246            }
1247            FunctionDef::Lambda(LambdaDef { name, .. }) => name
1248                .clone()
1249                .map_or(String::from("anonymous function"), |n| {
1250                    format!("function \"{}\"", n)
1251                }),
1252        }
1253    }
1254
1255    pub fn check_arity(&self, arg_count: usize) -> Result<()> {
1256        match self {
1257            FunctionDef::BuiltIn(built_in) => match built_in.arity() {
1258                FunctionArity::Exact(expected) => {
1259                    if arg_count == expected {
1260                        Ok(())
1261                    } else {
1262                        Err(anyhow!(
1263                            "{} takes exactly {} arguments, but {} were given",
1264                            self.get_name(),
1265                            expected,
1266                            arg_count
1267                        ))
1268                    }
1269                }
1270                FunctionArity::AtLeast(expected) => {
1271                    if arg_count >= expected {
1272                        Ok(())
1273                    } else {
1274                        Err(anyhow!(
1275                            "{} takes at least {} arguments, but {} were given",
1276                            self.get_name(),
1277                            expected,
1278                            arg_count
1279                        ))
1280                    }
1281                }
1282                FunctionArity::Between(min, max) => {
1283                    if arg_count >= min && arg_count <= max {
1284                        Ok(())
1285                    } else {
1286                        Err(anyhow!(
1287                            "{} takes between {} and {} arguments, but {} were given",
1288                            self.get_name(),
1289                            min,
1290                            max,
1291                            arg_count
1292                        ))
1293                    }
1294                }
1295            },
1296            FunctionDef::Lambda(def) => {
1297                let arity = def.get_arity();
1298
1299                match arity {
1300                    FunctionArity::Exact(expected) => {
1301                        if arg_count == expected {
1302                            Ok(())
1303                        } else {
1304                            Err(anyhow!(
1305                                "{} takes exactly {} arguments, but {} were given",
1306                                self.get_name(),
1307                                expected,
1308                                arg_count
1309                            ))
1310                        }
1311                    }
1312                    FunctionArity::AtLeast(expected) => {
1313                        if arg_count >= expected {
1314                            Ok(())
1315                        } else {
1316                            Err(anyhow!(
1317                                "{} takes at least {} arguments, but {} were given",
1318                                self.get_name(),
1319                                expected,
1320                                arg_count
1321                            ))
1322                        }
1323                    }
1324                    FunctionArity::Between(min, max) => {
1325                        if arg_count >= min && arg_count <= max {
1326                            Ok(())
1327                        } else {
1328                            Err(anyhow!(
1329                                "{} takes between {} and {} arguments, but {} were given",
1330                                self.get_name(),
1331                                min,
1332                                max,
1333                                arg_count
1334                            ))
1335                        }
1336                    }
1337                }
1338            }
1339        }
1340    }
1341
1342    pub fn call(
1343        &self,
1344        this_value: Value,
1345        args: Vec<Value>,
1346        heap: Rc<RefCell<Heap>>,
1347        bindings: Rc<RefCell<HashMap<String, Value>>>,
1348        call_depth: usize,
1349    ) -> Result<Value> {
1350        #[cfg(not(target_arch = "wasm32"))]
1351        let start = std::time::Instant::now();
1352
1353        self.check_arity(args.len())?;
1354
1355        if call_depth > 1000 {
1356            return Err(anyhow!(
1357                "in {}: maximum call depth of 1000 exceeded",
1358                self.get_name()
1359            ));
1360        }
1361
1362        match self {
1363            FunctionDef::Lambda(LambdaDef {
1364                name,
1365                args: expected_args,
1366                body,
1367                scope,
1368            }) => {
1369                #[cfg(not(target_arch = "wasm32"))]
1370                let start_var_env = std::time::Instant::now();
1371
1372                // Start with current environment as fallback for late binding
1373                let mut new_bindings = bindings.borrow().clone();
1374
1375                // Override with captured scope (captured variables take precedence)
1376                for (key, value) in scope {
1377                    new_bindings.insert(key.clone(), *value);
1378                }
1379
1380                // Add self-reference if named
1381                if let Some(fn_name) = name {
1382                    new_bindings.insert(fn_name.clone(), this_value);
1383                }
1384
1385                // Preserve inputs if present
1386                if let Some(inputs) = new_bindings.get("inputs").copied() {
1387                    new_bindings.insert(String::from("inputs"), inputs);
1388                }
1389
1390                // Add function arguments (highest precedence)
1391                for (idx, expected_arg) in expected_args.iter().enumerate() {
1392                    match expected_arg {
1393                        LambdaArg::Required(arg_name) => {
1394                            new_bindings.insert(arg_name.clone(), args[idx]);
1395                        }
1396                        LambdaArg::Optional(arg_name) => {
1397                            new_bindings.insert(
1398                                arg_name.clone(),
1399                                args.get(idx).copied().unwrap_or(Value::Null),
1400                            );
1401                        }
1402                        LambdaArg::Rest(arg_name) => {
1403                            new_bindings.insert(
1404                                arg_name.clone(),
1405                                heap.borrow_mut()
1406                                    .insert_list(args.iter().skip(idx).copied().collect()),
1407                            );
1408                        }
1409                    }
1410                }
1411
1412                #[cfg(not(target_arch = "wasm32"))]
1413                let end_var_env = std::time::Instant::now();
1414
1415                let return_value = evaluate_ast(
1416                    body,
1417                    Rc::clone(&heap),
1418                    Rc::new(RefCell::new(new_bindings)),
1419                    call_depth + 1,
1420                )
1421                .map_err(|error| anyhow!("in {}: {}", self.get_name(), error));
1422
1423                #[cfg(not(target_arch = "wasm32"))]
1424                FUNCTION_CALLS.lock().unwrap().push(FunctionCallStats {
1425                    name: self.get_name(),
1426                    start,
1427                    end: std::time::Instant::now(),
1428                    start_var_env: Some(start_var_env),
1429                    end_var_env: Some(end_var_env),
1430                });
1431
1432                return_value
1433            }
1434            FunctionDef::BuiltIn(built_in) => {
1435                let return_value = built_in
1436                    .call(args, heap, bindings, call_depth + 1)
1437                    .map_err(|error| anyhow!("in {}: {}", self.get_name(), error));
1438
1439                #[cfg(not(target_arch = "wasm32"))]
1440                FUNCTION_CALLS.lock().unwrap().push(FunctionCallStats {
1441                    name: self.get_name(),
1442                    start,
1443                    end: std::time::Instant::now(),
1444                    start_var_env: None,
1445                    end_var_env: None,
1446                });
1447
1448                return_value
1449            }
1450        }
1451    }
1452}
1453
1454pub fn is_built_in_function(ident: &str) -> bool {
1455    BuiltInFunction::from_ident(ident).is_some()
1456}
1457
1458pub fn get_built_in_function_def_by_ident(ident: &str) -> Option<FunctionDef> {
1459    BuiltInFunction::from_ident(ident).map(FunctionDef::BuiltIn)
1460}
1461
1462pub fn get_built_in_function_idents() -> Vec<&'static str> {
1463    BuiltInFunction::all_names()
1464}
1465
1466pub fn get_function_def(value: &Value, heap: &Heap) -> Option<FunctionDef> {
1467    match value {
1468        Value::Lambda(pointer) => Some(FunctionDef::Lambda(
1469            pointer.reify(heap).as_lambda().ok()?.clone(),
1470        )),
1471        Value::BuiltIn(built_in) => Some(FunctionDef::BuiltIn(*built_in)),
1472        _ => None,
1473    }
1474}
1475
1476#[cfg(test)]
1477mod tests {
1478    use super::*;
1479    use std::cell::RefCell;
1480    use std::collections::HashMap;
1481    use std::rc::Rc;
1482
1483    #[test]
1484    fn test_range_function() {
1485        // Test single argument
1486        let heap = Rc::new(RefCell::new(Heap::new()));
1487        let bindings = Rc::new(RefCell::new(HashMap::new()));
1488        let range_fn = BuiltInFunction::Range;
1489
1490        // Test range(4) - exclusive, so [0, 1, 2, 3]
1491        let args = vec![Value::Number(4.0)];
1492        let result = range_fn
1493            .call(args, heap.clone(), bindings.clone(), 0)
1494            .unwrap();
1495
1496        let heap_borrow = heap.borrow();
1497        let list = result.as_list(&heap_borrow).unwrap();
1498        assert_eq!(list.len(), 4);
1499        assert_eq!(list[0], Value::Number(0.0));
1500        assert_eq!(list[3], Value::Number(3.0));
1501    }
1502
1503    #[test]
1504    fn test_range_function_two_args() {
1505        // Test two arguments
1506        let heap = Rc::new(RefCell::new(Heap::new()));
1507        let bindings = Rc::new(RefCell::new(HashMap::new()));
1508        let range_fn = BuiltInFunction::Range;
1509
1510        // Test range(4, 10) - exclusive, so [4, 5, 6, 7, 8, 9]
1511        let args = vec![Value::Number(4.0), Value::Number(10.0)];
1512        let result = range_fn
1513            .call(args, heap.clone(), bindings.clone(), 0)
1514            .unwrap();
1515
1516        let heap_borrow = heap.borrow();
1517        let list = result.as_list(&heap_borrow).unwrap();
1518        assert_eq!(list.len(), 6);
1519        assert_eq!(list[0], Value::Number(4.0));
1520        assert_eq!(list[5], Value::Number(9.0));
1521    }
1522
1523    #[test]
1524    fn test_round_function_single_arg() {
1525        let heap = Rc::new(RefCell::new(Heap::new()));
1526        let bindings = Rc::new(RefCell::new(HashMap::new()));
1527        let round_fn = BuiltInFunction::Round;
1528
1529        // Test basic rounding
1530        let test_cases = vec![
1531            (42.4, 42.0),
1532            (42.5, 43.0),
1533            (42.6, 43.0),
1534            (-42.4, -42.0),
1535            (-42.5, -43.0),
1536            (-42.6, -43.0),
1537            (0.0, 0.0),
1538            (1.999, 2.0),
1539            (-1.999, -2.0),
1540        ];
1541
1542        for (input, expected) in test_cases {
1543            let args = vec![Value::Number(input)];
1544            let result = round_fn
1545                .call(args, heap.clone(), bindings.clone(), 0)
1546                .unwrap();
1547            assert_eq!(
1548                result,
1549                Value::Number(expected),
1550                "round({}) should be {}",
1551                input,
1552                expected
1553            );
1554        }
1555    }
1556
1557    #[test]
1558    fn test_round_function_with_decimal_places() {
1559        let heap = Rc::new(RefCell::new(Heap::new()));
1560        let bindings = Rc::new(RefCell::new(HashMap::new()));
1561        let round_fn = BuiltInFunction::Round;
1562
1563        // Test rounding to decimal places
1564        let test_cases = vec![
1565            (42.4543, 0.0, 42.0),
1566            (42.4543, 1.0, 42.5),
1567            (42.4543, 2.0, 42.45),
1568            (42.4543, 3.0, 42.454),
1569            (42.4543, 4.0, 42.4543),
1570            (4.14159, 4.0, 4.1416),
1571            (0.005, 2.0, 0.01),
1572            (0.995, 2.0, 1.0),
1573            // Note: 9.995 has floating point representation issues
1574            // In binary, it's actually slightly less than 9.995
1575            (9.995, 2.0, 9.99),
1576            (-9.995, 2.0, -9.99),
1577        ];
1578
1579        for (input, places, expected) in test_cases {
1580            let args = vec![Value::Number(input), Value::Number(places)];
1581            let result = round_fn
1582                .call(args, heap.clone(), bindings.clone(), 0)
1583                .unwrap();
1584
1585            // Use approximate comparison for floating point
1586            if let Value::Number(result_num) = result {
1587                assert!(
1588                    (result_num - expected).abs() < 1e-10,
1589                    "round({}, {}) = {} should be close to {}",
1590                    input,
1591                    places,
1592                    result_num,
1593                    expected
1594                );
1595            } else {
1596                panic!("Expected number result");
1597            }
1598        }
1599    }
1600
1601    #[test]
1602    fn test_round_function_negative_decimal_places() {
1603        let heap = Rc::new(RefCell::new(Heap::new()));
1604        let bindings = Rc::new(RefCell::new(HashMap::new()));
1605        let round_fn = BuiltInFunction::Round;
1606
1607        // Test rounding to tens, hundreds, etc.
1608        let test_cases = vec![
1609            (1234.567, -1.0, 1230.0),
1610            (1234.567, -2.0, 1200.0),
1611            (1234.567, -3.0, 1000.0),
1612            (1234.567, -4.0, 0.0),
1613            (5678.9, -1.0, 5680.0),
1614            (5678.9, -2.0, 5700.0),
1615            (5678.9, -3.0, 6000.0),
1616            (-1234.567, -1.0, -1230.0),
1617            (-1234.567, -2.0, -1200.0),
1618            (1500.0, -3.0, 2000.0),
1619            (-1500.0, -3.0, -2000.0),
1620        ];
1621
1622        for (input, places, expected) in test_cases {
1623            let args = vec![Value::Number(input), Value::Number(places)];
1624            let result = round_fn
1625                .call(args, heap.clone(), bindings.clone(), 0)
1626                .unwrap();
1627            assert_eq!(
1628                result,
1629                Value::Number(expected),
1630                "round({}, {}) should be {}",
1631                input,
1632                places,
1633                expected
1634            );
1635        }
1636    }
1637
1638    #[test]
1639    fn test_round_function_edge_cases() {
1640        let heap = Rc::new(RefCell::new(Heap::new()));
1641        let bindings = Rc::new(RefCell::new(HashMap::new()));
1642        let round_fn = BuiltInFunction::Round;
1643
1644        // Test edge cases
1645        let test_cases = vec![
1646            (f64::INFINITY, 0.0, f64::INFINITY),
1647            (f64::NEG_INFINITY, 0.0, f64::NEG_INFINITY),
1648            (0.0, 5.0, 0.0),
1649            (-0.0, 5.0, -0.0),
1650            (1e-10, 5.0, 0.0),
1651            (1e-10, 15.0, 1e-10),
1652        ];
1653
1654        for (input, places, expected) in test_cases {
1655            let args = vec![Value::Number(input), Value::Number(places)];
1656            let result = round_fn
1657                .call(args, heap.clone(), bindings.clone(), 0)
1658                .unwrap();
1659
1660            if let Value::Number(result_num) = result {
1661                if expected.is_infinite() {
1662                    assert!(
1663                        result_num.is_infinite()
1664                            && result_num.is_sign_positive() == expected.is_sign_positive(),
1665                        "round({}, {}) should be {}",
1666                        input,
1667                        places,
1668                        expected
1669                    );
1670                } else if expected == 0.0 || expected == -0.0 {
1671                    assert!(
1672                        result_num.abs() < 1e-10,
1673                        "round({}, {}) = {} should be close to 0",
1674                        input,
1675                        places,
1676                        result_num
1677                    );
1678                } else {
1679                    assert!(
1680                        (result_num - expected).abs() < 1e-15,
1681                        "round({}, {}) = {} should be close to {}",
1682                        input,
1683                        places,
1684                        result_num,
1685                        expected
1686                    );
1687                }
1688            } else {
1689                panic!("Expected number result");
1690            }
1691        }
1692    }
1693}