blots_core/
functions.rs

1use crate::{
2    expressions::evaluate_ast,
3    heap::{Heap, HeapPointer, IterablePointer},
4    units,
5    values::{FunctionArity, LambdaArg, LambdaDef, ReifiedValue, Value},
6};
7use anyhow::{Result, anyhow};
8use dyn_fmt::AsStrFormatExt;
9use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::LazyLock};
10
11#[cfg(not(target_arch = "wasm32"))]
12use std::sync::Mutex;
13
14#[cfg(not(target_arch = "wasm32"))]
15use crate::stats::FunctionCallStats;
16
17#[cfg(not(target_arch = "wasm32"))]
18pub static FUNCTION_CALLS: LazyLock<Mutex<Vec<FunctionCallStats>>> =
19    LazyLock::new(|| Mutex::new(Vec::new()));
20
21// Cache built-in function names since they're static
22pub static BUILTIN_FUNCTION_NAMES: LazyLock<Vec<&'static str>> =
23    LazyLock::new(BuiltInFunction::all_names);
24
25#[derive(
26    Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
27)]
28pub enum BuiltInFunction {
29    // Math functions
30    Sqrt,
31    Sin,
32    Cos,
33    Tan,
34    Asin,
35    Acos,
36    Atan,
37    Log,
38    Log10,
39    Exp,
40    Abs,
41    Floor,
42    Ceil,
43    Round,
44    Trunc,
45    Random,
46
47    // Aggregate functions
48    Min,
49    Max,
50    Avg,
51    Sum,
52    Prod,
53    Median,
54    Percentile,
55
56    // List functions
57    Range,
58    Len,
59    Head,
60    Tail,
61    Slice,
62    Concat,
63    Dot,
64    Unique,
65    Sort,
66    SortBy,
67    Reverse,
68    Any,
69    All,
70
71    // Higher-order functions
72    Map,
73    Reduce,
74    Filter,
75    Every,
76    Some,
77
78    // String functions
79    Split,
80    Join,
81    Replace,
82    Trim,
83    Uppercase,
84    Lowercase,
85    Includes,
86    Format,
87
88    // Type functions
89    Typeof,
90    Arity,
91
92    // Record functions
93    Keys,
94    Values,
95    Entries,
96
97    // Conversion functions
98    ToString,
99    ToNumber,
100    ToBool,
101    Convert,
102
103    // Platform-specific functions
104    #[cfg(not(target_arch = "wasm32"))]
105    Print,
106    #[cfg(not(target_arch = "wasm32"))]
107    TimeNow,
108}
109
110#[derive(Debug, Clone)]
111pub enum FunctionDef {
112    BuiltIn(BuiltInFunction),
113    Lambda(LambdaDef),
114}
115
116impl BuiltInFunction {
117    pub fn from_ident(ident: &str) -> Option<Self> {
118        match ident {
119            "sqrt" => Some(Self::Sqrt),
120            "sin" => Some(Self::Sin),
121            "cos" => Some(Self::Cos),
122            "tan" => Some(Self::Tan),
123            "asin" => Some(Self::Asin),
124            "acos" => Some(Self::Acos),
125            "atan" => Some(Self::Atan),
126            "log" => Some(Self::Log),
127            "log10" => Some(Self::Log10),
128            "exp" => Some(Self::Exp),
129            "abs" => Some(Self::Abs),
130            "floor" => Some(Self::Floor),
131            "ceil" => Some(Self::Ceil),
132            "round" => Some(Self::Round),
133            "trunc" => Some(Self::Trunc),
134            "random" => Some(Self::Random),
135            "min" => Some(Self::Min),
136            "max" => Some(Self::Max),
137            "avg" => Some(Self::Avg),
138            "sum" => Some(Self::Sum),
139            "prod" => Some(Self::Prod),
140            "median" => Some(Self::Median),
141            "percentile" => Some(Self::Percentile),
142            "range" => Some(Self::Range),
143            "any" => Some(Self::Any),
144            "all" => Some(Self::All),
145            "len" => Some(Self::Len),
146            "head" => Some(Self::Head),
147            "tail" => Some(Self::Tail),
148            "slice" => Some(Self::Slice),
149            "concat" => Some(Self::Concat),
150            "dot" => Some(Self::Dot),
151            "unique" => Some(Self::Unique),
152            "sort" => Some(Self::Sort),
153            "sort_by" => Some(Self::SortBy),
154            "reverse" => Some(Self::Reverse),
155            "map" => Some(Self::Map),
156            "reduce" => Some(Self::Reduce),
157            "filter" => Some(Self::Filter),
158            "every" => Some(Self::Every),
159            "some" => Some(Self::Some),
160            "split" => Some(Self::Split),
161            "join" => Some(Self::Join),
162            "replace" => Some(Self::Replace),
163            "trim" => Some(Self::Trim),
164            "uppercase" => Some(Self::Uppercase),
165            "lowercase" => Some(Self::Lowercase),
166            "to_string" => Some(Self::ToString),
167            "to_number" => Some(Self::ToNumber),
168            "to_bool" => Some(Self::ToBool),
169            "convert" => Some(Self::Convert),
170            "includes" => Some(Self::Includes),
171            "format" => Some(Self::Format),
172            "typeof" => Some(Self::Typeof),
173            "arity" => Some(Self::Arity),
174            "keys" => Some(Self::Keys),
175            "values" => Some(Self::Values),
176            "entries" => Some(Self::Entries),
177            #[cfg(not(target_arch = "wasm32"))]
178            "print" => Some(Self::Print),
179            #[cfg(not(target_arch = "wasm32"))]
180            "time_now" => Some(Self::TimeNow),
181            _ => None,
182        }
183    }
184
185    pub fn name(&self) -> &'static str {
186        match self {
187            Self::Sqrt => "sqrt",
188            Self::Sin => "sin",
189            Self::Cos => "cos",
190            Self::Tan => "tan",
191            Self::Asin => "asin",
192            Self::Acos => "acos",
193            Self::Atan => "atan",
194            Self::Log => "log",
195            Self::Log10 => "log10",
196            Self::Exp => "exp",
197            Self::Abs => "abs",
198            Self::Floor => "floor",
199            Self::Ceil => "ceil",
200            Self::Round => "round",
201            Self::Trunc => "trunc",
202            Self::Random => "random",
203            Self::Min => "min",
204            Self::Max => "max",
205            Self::Avg => "avg",
206            Self::Sum => "sum",
207            Self::Prod => "prod",
208            Self::Median => "median",
209            Self::Percentile => "percentile",
210            Self::Range => "range",
211            Self::Any => "any",
212            Self::All => "all",
213            Self::Len => "len",
214            Self::Head => "head",
215            Self::Tail => "tail",
216            Self::Slice => "slice",
217            Self::Concat => "concat",
218            Self::Dot => "dot",
219            Self::Unique => "unique",
220            Self::Sort => "sort",
221            Self::SortBy => "sort_by",
222            Self::Reverse => "reverse",
223            Self::Map => "map",
224            Self::Reduce => "reduce",
225            Self::Filter => "filter",
226            Self::Every => "every",
227            Self::Some => "some",
228            Self::Split => "split",
229            Self::Join => "join",
230            Self::Replace => "replace",
231            Self::Trim => "trim",
232            Self::Uppercase => "uppercase",
233            Self::Lowercase => "lowercase",
234            Self::Includes => "includes",
235            Self::Format => "format",
236            Self::Typeof => "typeof",
237            Self::Arity => "arity",
238            Self::Keys => "keys",
239            Self::Values => "values",
240            Self::Entries => "entries",
241            Self::ToString => "to_string",
242            Self::ToNumber => "to_number",
243            Self::ToBool => "to_bool",
244            Self::Convert => "convert",
245            #[cfg(not(target_arch = "wasm32"))]
246            Self::Print => "print",
247            #[cfg(not(target_arch = "wasm32"))]
248            Self::TimeNow => "time_now",
249        }
250    }
251
252    pub fn arity(&self) -> FunctionArity {
253        match self {
254            // Math functions - single argument
255            Self::Sqrt
256            | Self::Sin
257            | Self::Cos
258            | Self::Tan
259            | Self::Asin
260            | Self::Acos
261            | Self::Atan
262            | Self::Log
263            | Self::Log10
264            | Self::Exp
265            | Self::Abs
266            | Self::Floor
267            | Self::Ceil
268            | Self::Trunc
269            | Self::Random => FunctionArity::Exact(1),
270
271            // Round can take 1 or 2 arguments
272            Self::Round => FunctionArity::Between(1, 2),
273
274            // Aggregate functions
275            Self::Min | Self::Max | Self::Avg | Self::Sum | Self::Prod | Self::Median => {
276                FunctionArity::AtLeast(1)
277            }
278
279            // Range can take 1 or 2 arguments
280            Self::Range => FunctionArity::Between(1, 2),
281
282            // List functions
283            Self::Len
284            | Self::Head
285            | Self::Tail
286            | Self::Unique
287            | Self::Sort
288            | Self::Reverse
289            | Self::Any
290            | Self::All => FunctionArity::Exact(1),
291            Self::Slice => FunctionArity::Exact(3),
292            Self::Concat => FunctionArity::AtLeast(2),
293            Self::Dot | Self::Percentile => FunctionArity::Exact(2),
294
295            // Higher-order functions
296            Self::Map | Self::Filter | Self::Every | Self::Some | Self::SortBy => {
297                FunctionArity::Exact(2)
298            }
299            Self::Reduce => FunctionArity::Exact(3),
300
301            // String functions
302            Self::Split | Self::Join | Self::Includes => FunctionArity::Exact(2),
303            Self::Replace => FunctionArity::Exact(3),
304            Self::Trim
305            | Self::Uppercase
306            | Self::Lowercase
307            | Self::ToString
308            | Self::ToNumber
309            | Self::ToBool => FunctionArity::Exact(1),
310            Self::Convert => FunctionArity::Exact(3),
311            Self::Format => FunctionArity::AtLeast(1),
312
313            // Type functions
314            Self::Typeof | Self::Arity => FunctionArity::Exact(1),
315
316            // Record functions
317            Self::Keys | Self::Values | Self::Entries => FunctionArity::Exact(1),
318
319            // Platform-specific functions
320            #[cfg(not(target_arch = "wasm32"))]
321            Self::Print => FunctionArity::AtLeast(1),
322            #[cfg(not(target_arch = "wasm32"))]
323            Self::TimeNow => FunctionArity::Exact(0),
324        }
325    }
326
327    pub fn call(
328        &self,
329        args: Vec<Value>,
330        heap: Rc<RefCell<Heap>>,
331        bindings: Rc<RefCell<HashMap<String, Value>>>,
332        call_depth: usize,
333        source: &str,
334    ) -> Result<Value> {
335        match self {
336            // Math functions
337            Self::Sqrt => Ok(Value::Number(args[0].as_number()?.sqrt())),
338            Self::Sin => Ok(Value::Number(args[0].as_number()?.sin())),
339            Self::Cos => Ok(Value::Number(args[0].as_number()?.cos())),
340            Self::Tan => Ok(Value::Number(args[0].as_number()?.tan())),
341            Self::Asin => Ok(Value::Number(args[0].as_number()?.asin())),
342            Self::Acos => Ok(Value::Number(args[0].as_number()?.acos())),
343            Self::Atan => Ok(Value::Number(args[0].as_number()?.atan())),
344            Self::Log => Ok(Value::Number(args[0].as_number()?.ln())),
345            Self::Log10 => Ok(Value::Number(args[0].as_number()?.log10())),
346            Self::Exp => Ok(Value::Number(args[0].as_number()?.exp())),
347            Self::Abs => Ok(Value::Number(args[0].as_number()?.abs())),
348            Self::Floor => Ok(Value::Number(args[0].as_number()?.floor())),
349            Self::Ceil => Ok(Value::Number(args[0].as_number()?.ceil())),
350            Self::Trunc => Ok(Value::Number(args[0].as_number()?.trunc())),
351
352            Self::Random => {
353                let seed = args[0].as_number()? as u64;
354                let mut rng = fastrand::Rng::with_seed(seed);
355                Ok(Value::Number(rng.f64()))
356            }
357
358            Self::Round => {
359                let num = args[0].as_number()?;
360                if args.len() == 1 {
361                    Ok(Value::Number(num.round()))
362                } else {
363                    let decimal_places = args[1].as_number()? as i32;
364                    let multiplier = 10_f64.powi(decimal_places);
365                    Ok(Value::Number((num * multiplier).round() / multiplier))
366                }
367            }
368
369            // Aggregate functions
370            Self::Min => {
371                let nums = if args.len() == 1 {
372                    // Check if single argument is a list
373                    match &args[0] {
374                        Value::List(_) => {
375                            let borrowed_heap = heap.borrow();
376                            let list = args[0].as_list(&borrowed_heap)?;
377                            list.iter()
378                                .map(|a| a.as_number())
379                                .collect::<Result<Vec<f64>>>()?
380                        }
381                        _ => vec![args[0].as_number()?],
382                    }
383                } else {
384                    args.iter()
385                        .map(|a| a.as_number())
386                        .collect::<Result<Vec<f64>>>()?
387                };
388
389                if nums.is_empty() {
390                    return Err(anyhow!("min requires at least one number"));
391                }
392
393                Ok(Value::Number(
394                    nums.iter().copied().fold(f64::INFINITY, f64::min),
395                ))
396            }
397
398            Self::Max => {
399                let nums = if args.len() == 1 {
400                    // Check if single argument is a list
401                    match &args[0] {
402                        Value::List(_) => {
403                            let borrowed_heap = heap.borrow();
404                            let list = args[0].as_list(&borrowed_heap)?;
405                            list.iter()
406                                .map(|a| a.as_number())
407                                .collect::<Result<Vec<f64>>>()?
408                        }
409                        _ => vec![args[0].as_number()?],
410                    }
411                } else {
412                    args.iter()
413                        .map(|a| a.as_number())
414                        .collect::<Result<Vec<f64>>>()?
415                };
416
417                if nums.is_empty() {
418                    return Err(anyhow!("max requires at least one number"));
419                }
420
421                Ok(Value::Number(
422                    nums.iter().copied().fold(f64::NEG_INFINITY, f64::max),
423                ))
424            }
425
426            Self::Avg => {
427                let nums = if args.len() == 1 {
428                    match &args[0] {
429                        Value::List(_) => {
430                            let borrowed_heap = heap.borrow();
431                            let list = args[0].as_list(&borrowed_heap)?;
432                            list.iter()
433                                .map(|a| a.as_number())
434                                .collect::<Result<Vec<f64>>>()?
435                        }
436                        _ => vec![args[0].as_number()?],
437                    }
438                } else {
439                    args.iter()
440                        .map(|a| a.as_number())
441                        .collect::<Result<Vec<f64>>>()?
442                };
443                if nums.is_empty() {
444                    return Err(anyhow!("avg requires at least one number"));
445                }
446                Ok(Value::Number(nums.iter().sum::<f64>() / nums.len() as f64))
447            }
448
449            Self::Prod => {
450                let nums = if args.len() == 1 {
451                    match &args[0] {
452                        Value::List(_) => {
453                            let borrowed_heap = heap.borrow();
454                            let list = args[0].as_list(&borrowed_heap)?;
455                            list.iter()
456                                .map(|a| a.as_number())
457                                .collect::<Result<Vec<f64>>>()?
458                        }
459                        _ => vec![args[0].as_number()?],
460                    }
461                } else {
462                    args.iter()
463                        .map(|a| a.as_number())
464                        .collect::<Result<Vec<f64>>>()?
465                };
466                if nums.is_empty() {
467                    return Err(anyhow!("prod requires at least one number"));
468                }
469                Ok(Value::Number(nums.iter().product()))
470            }
471
472            Self::Range => {
473                let (start, end) = match args[..] {
474                    [Value::Number(start)] => (0.0, start),
475                    [Value::Number(start), Value::Number(end)] => (start, end),
476                    _ => return Err(anyhow!("range requires 1 or 2 numbers")),
477                };
478
479                if start > end {
480                    return Err(anyhow!(
481                        "range requires start to be less than or equal to end"
482                    ));
483                }
484
485                if !f64::is_finite(start) || !f64::is_finite(end) {
486                    return Err(anyhow!("range requires finite numbers"));
487                }
488
489                let start_i64 = start as i64;
490                let end_i64 = end as i64;
491                let length = end_i64 - start_i64;
492
493                if length > u32::MAX as i64 {
494                    return Err(anyhow!(
495                        "list would be longer than the maximum length of {}",
496                        u32::MAX
497                    ));
498                }
499
500                let values = (start_i64..end_i64)
501                    .map(|e| Value::Number(e as f64))
502                    .collect();
503                let list = heap.borrow_mut().insert_list(values);
504
505                Ok(list)
506            }
507
508            Self::Sum => {
509                let nums = if args.len() == 1 {
510                    match &args[0] {
511                        Value::List(_) => {
512                            let borrowed_heap = heap.borrow();
513                            let list = args[0].as_list(&borrowed_heap)?;
514                            list.iter()
515                                .map(|a| a.as_number())
516                                .collect::<Result<Vec<f64>>>()?
517                        }
518                        _ => vec![args[0].as_number()?],
519                    }
520                } else {
521                    args.iter()
522                        .map(|a| a.as_number())
523                        .collect::<Result<Vec<f64>>>()?
524                };
525                if nums.is_empty() {
526                    return Err(anyhow!("sum requires at least one number"));
527                }
528                Ok(Value::Number(nums.iter().sum()))
529            }
530
531            // Remaining aggregate functions
532            Self::Median => {
533                let mut nums = if args.len() == 1 {
534                    match &args[0] {
535                        Value::List(_) => {
536                            let borrowed_heap = heap.borrow();
537                            let list = args[0].as_list(&borrowed_heap)?;
538                            list.iter()
539                                .map(|a| a.as_number())
540                                .collect::<Result<Vec<f64>>>()?
541                        }
542                        _ => vec![args[0].as_number()?],
543                    }
544                } else {
545                    args.into_iter()
546                        .map(|a| a.as_number())
547                        .collect::<Result<Vec<f64>>>()?
548                };
549
550                if nums.is_empty() {
551                    return Err(anyhow!("median requires at least one number"));
552                }
553
554                nums.sort_by(|a, b| a.partial_cmp(b).unwrap());
555                let len = nums.len();
556                if len % 2 == 0 {
557                    Ok(Value::Number((nums[len / 2 - 1] + nums[len / 2]) / 2.0))
558                } else {
559                    Ok(Value::Number(nums[len / 2]))
560                }
561            }
562
563            Self::Percentile => {
564                let p = args[1].as_number()?;
565                let heap = &heap.borrow();
566                let list = args[0].as_list(heap)?;
567
568                if !(0.0..=100.0).contains(&p) {
569                    return Err(anyhow!("percentile must be between 0 and 100"));
570                }
571
572                let mut nums = list
573                    .iter()
574                    .map(|a| a.as_number())
575                    .collect::<Result<Vec<f64>>>()?;
576
577                nums.sort_by(|a, b| a.partial_cmp(b).unwrap());
578                let index = (p / 100.0 * (nums.len() - 1) as f64).round() as usize;
579
580                Ok(Value::Number(nums[index]))
581            }
582
583            // List functions
584            Self::Len => match &args[0] {
585                Value::List(l) => Ok(Value::Number(
586                    l.reify(&heap.borrow()).as_list()?.len() as f64
587                )),
588                Value::String(s) => Ok(Value::Number(
589                    s.reify(&heap.borrow()).as_string()?.len() as f64
590                )),
591                _ => Err(anyhow!("argument must be a list or string")),
592            },
593
594            Self::Head => match &args[0] {
595                Value::List(p) => Ok(p
596                    .reify(&heap.borrow())
597                    .as_list()?
598                    .first()
599                    .copied()
600                    .unwrap_or(Value::Null)),
601                Value::String(p) => {
602                    let val = {
603                        p.reify(&heap.borrow())
604                            .as_string()?
605                            .get(0..1)
606                            .unwrap_or("")
607                            .to_string()
608                    };
609
610                    Ok(heap.borrow_mut().insert_string(val))
611                }
612                _ => Err(anyhow!("argument must be a list or string")),
613            },
614
615            Self::Tail => match &args[0] {
616                Value::List(p) => {
617                    let val = {
618                        p.reify(&heap.borrow())
619                            .as_list()?
620                            .get(1..)
621                            .unwrap_or([].as_slice())
622                            .to_vec()
623                    };
624
625                    Ok(heap.borrow_mut().insert_list(val))
626                }
627                Value::String(s) => {
628                    let val = {
629                        s.reify(&heap.borrow())
630                            .as_string()?
631                            .get(1..)
632                            .unwrap_or("")
633                            .to_string()
634                    };
635
636                    Ok(heap.borrow_mut().insert_string(val))
637                }
638                _ => Err(anyhow!("argument must be a list or string")),
639            },
640
641            Self::Slice => {
642                let start = args[1].as_number()? as usize;
643                let end = args[2].as_number()? as usize;
644
645                match args[0] {
646                    Value::List(_) => {
647                        let l = {
648                            let borrowed_heap = &heap.borrow();
649                            args[0].as_list(borrowed_heap)?.clone()
650                        };
651
652                        l.get(start..end)
653                            .map_or(Err(anyhow!("index out of bounds")), |l| {
654                                Ok(heap.borrow_mut().insert_list(l.to_vec()))
655                            })
656                    }
657                    Value::String(_) => {
658                        let s = {
659                            let borrowed_heap = &heap.borrow();
660                            args[0].as_string(borrowed_heap)?.to_string()
661                        };
662
663                        s.get(start..end)
664                            .map_or(Err(anyhow!("index out of bounds")), |s| {
665                                Ok(heap.borrow_mut().insert_string(s.to_string()))
666                            })
667                    }
668                    _ => Err(anyhow!("argument must be a list or string")),
669                }
670            }
671
672            Self::Concat => {
673                let mut list = vec![];
674
675                for arg in args {
676                    match arg {
677                        Value::List(p) => list.extend(p.reify(&heap.borrow()).as_list()?.clone()),
678                        Value::Spread(IterablePointer::List(p)) => {
679                            list.extend(p.reify(&heap.borrow()).as_list()?.clone())
680                        }
681                        Value::Spread(IterablePointer::String(p)) => {
682                            let string = {
683                                let borrowed_heap = &heap.borrow();
684                                p.reify(borrowed_heap).as_string()?.to_string()
685                            };
686
687                            list.extend(
688                                string
689                                    .chars()
690                                    .map(|c| heap.borrow_mut().insert_string(c.to_string())),
691                            );
692                        }
693                        _ => list.push(arg),
694                    }
695                }
696
697                Ok(heap.borrow_mut().insert_list(list))
698            }
699
700            Self::Dot => {
701                let (a, b) = {
702                    let borrowed_heap = &heap.borrow();
703                    (
704                        args[0].as_list(borrowed_heap)?.clone(),
705                        args[1].as_list(borrowed_heap)?.clone(),
706                    )
707                };
708
709                if a.len() != b.len() {
710                    return Err(anyhow!(
711                        "cannot calculate dot product of lists with different lengths"
712                    ));
713                }
714
715                Ok(Value::Number(
716                    a.iter()
717                        .zip(b.iter())
718                        .map(|(a, b)| {
719                            let a_num = a.as_number()?;
720                            let b_num = b.as_number()?;
721                            Ok(a_num * b_num)
722                        })
723                        .collect::<Result<Vec<f64>>>()?
724                        .iter()
725                        .sum(),
726                ))
727            }
728
729            Self::Unique => {
730                let list = {
731                    let borrowed_heap = &heap.borrow();
732                    args[0].as_list(borrowed_heap)?.clone()
733                };
734                let mut unique_list = vec![];
735                let borrowed_heap = heap.borrow();
736
737                for item in list {
738                    let mut is_duplicate = false;
739                    for existing in &unique_list {
740                        if item.equals(existing, &borrowed_heap)? {
741                            is_duplicate = true;
742                            break;
743                        }
744                    }
745                    if !is_duplicate {
746                        unique_list.push(item);
747                    }
748                }
749
750                drop(borrowed_heap);
751                Ok(heap.borrow_mut().insert_list(unique_list))
752            }
753
754            Self::Sort => {
755                let mut list = {
756                    let borrowed_heap = &heap.borrow();
757                    args[0].as_list(borrowed_heap)?.clone()
758                };
759                let borrowed_heap = heap.borrow();
760                list.sort_by(|a, b| {
761                    a.compare(b, &borrowed_heap)
762                        .unwrap_or(None)
763                        .unwrap_or(std::cmp::Ordering::Equal)
764                });
765                drop(borrowed_heap);
766                Ok(heap.borrow_mut().insert_list(list))
767            }
768
769            Self::Reverse => {
770                let mut list = { args[0].as_list(&heap.borrow())?.clone() };
771                list.reverse();
772                Ok(heap.borrow_mut().insert_list(list))
773            }
774
775            Self::Any => {
776                let list = args[0].as_list(&heap.borrow())?.clone();
777                Ok(Value::Bool(
778                    list.iter().any(|v| v.as_bool().unwrap_or(false)),
779                ))
780            }
781
782            Self::All => {
783                let list = args[0].as_list(&heap.borrow())?.clone();
784                Ok(Value::Bool(
785                    list.iter().all(|v| v.as_bool().unwrap_or(false)),
786                ))
787            }
788
789            // String functions
790            Self::Split => {
791                let (s, delimeter) = {
792                    let borrowed_heap = &heap.borrow();
793                    (
794                        args[0].as_string(borrowed_heap)?.to_string(),
795                        args[1].as_string(borrowed_heap)?.to_string(),
796                    )
797                };
798
799                let list = s
800                    .split(&delimeter)
801                    .map(|s| heap.borrow_mut().insert_string(s.to_string()))
802                    .collect();
803
804                Ok(heap.borrow_mut().insert_list(list))
805            }
806
807            Self::Join => {
808                let joined_string = {
809                    let borrowed_heap = &heap.borrow();
810                    let delimeter = args[1].as_string(borrowed_heap)?;
811                    let list = args[0].as_list(borrowed_heap)?;
812                    list.iter()
813                        .map(|v| v.stringify_internal(borrowed_heap))
814                        .collect::<Vec<String>>()
815                        .join(delimeter)
816                };
817
818                Ok(heap.borrow_mut().insert_string(joined_string))
819            }
820
821            Self::Replace => {
822                let string = {
823                    let borrowed_heap = heap.borrow();
824                    let old = args[1].as_string(&borrowed_heap)?.to_string();
825                    let new = args[2].as_string(&borrowed_heap)?.to_string();
826                    let s = args[0].as_string(&borrowed_heap)?.to_string();
827                    s.replace(&old, &new)
828                };
829
830                Ok(heap.borrow_mut().insert_string(string))
831            }
832
833            Self::Trim => {
834                let string = {
835                    let borrowed_heap = heap.borrow();
836                    args[0].as_string(&borrowed_heap)?.trim().to_string()
837                };
838
839                Ok(heap.borrow_mut().insert_string(string))
840            }
841
842            Self::Uppercase => {
843                let string = args[0].as_string(&heap.borrow())?.to_uppercase();
844                Ok(heap.borrow_mut().insert_string(string))
845            }
846
847            Self::Lowercase => {
848                let string = args[0].as_string(&heap.borrow())?.to_lowercase();
849                Ok(heap.borrow_mut().insert_string(string))
850            }
851
852            Self::Includes => {
853                match &args[0].reify(&heap.borrow())? {
854                    // If haystack is a list, check for structural equality with any-type needle
855                    ReifiedValue::List(l, _) => {
856                        let borrowed_heap = heap.borrow();
857                        for item in (*l).iter() {
858                            if item.equals(&args[1], &borrowed_heap)? {
859                                return Ok(Value::Bool(true));
860                            }
861                        }
862                        Ok(Value::Bool(false))
863                    }
864                    // If haystack is a string, require needle to be a string and do substring search
865                    ReifiedValue::String(s, _) => {
866                        let needle = {
867                            let borrowed_heap = &heap.borrow();
868                            args[1]
869                                .as_string(borrowed_heap)
870                                .map_err(|_| anyhow!("second argument must be a string"))?
871                                .to_string()
872                        };
873                        Ok(Value::Bool(s.contains(&needle)))
874                    }
875                    _ => Err(anyhow!("first argument must be a list or string")),
876                }
877            }
878
879            Self::Format => {
880                let format_str = {
881                    let borrowed_heap = &heap.borrow();
882                    args[0]
883                        .as_string(borrowed_heap)
884                        .map_err(|_| anyhow!("first argument must be a string"))?
885                        .to_string()
886                };
887
888                let format_args = {
889                    let borrowed_heap = &heap.borrow();
890                    &args[1..]
891                        .iter()
892                        .map(|v| v.stringify_internal(borrowed_heap))
893                        .collect::<Vec<String>>()
894                };
895
896                Ok(heap
897                    .borrow_mut()
898                    .insert_string(format_str.format(format_args)))
899            }
900
901            // Type functions
902            Self::Typeof => Ok(heap
903                .borrow_mut()
904                .insert_string(args[0].get_type().to_string())),
905
906            Self::Arity => {
907                let arity = match args[0] {
908                    Value::Lambda(p) => p.reify(&heap.borrow()).as_lambda()?.get_arity(),
909                    Value::BuiltIn(built_in) => built_in.arity(),
910                    _ => return Err(anyhow!("argument must be a function or built-in function")),
911                };
912
913                Ok(Value::Number(match arity {
914                    FunctionArity::Exact(n) => n as f64,
915                    FunctionArity::AtLeast(n) => n as f64,
916                    FunctionArity::Between(min, _max) => min as f64,
917                }))
918            }
919
920            // Record functions
921            Self::Keys => {
922                let record = args[0].as_record(&heap.borrow())?.clone();
923                let keys = {
924                    let key_strings = record.keys().cloned().collect::<Vec<String>>();
925                    key_strings
926                        .iter()
927                        .map(|k| heap.borrow_mut().insert_string(k.to_string()))
928                        .collect()
929                };
930
931                Ok(heap.borrow_mut().insert_list(keys))
932            }
933
934            Self::Values => {
935                let record = args[0].as_record(&heap.borrow())?.clone();
936                let values = record.values().cloned().collect();
937
938                Ok(heap.borrow_mut().insert_list(values))
939            }
940
941            Self::Entries => {
942                let record = args[0].as_record(&heap.borrow())?.clone();
943                let entries = record
944                    .iter()
945                    .map(|(k, v)| {
946                        let entry = {
947                            let mut borrowed_heap = heap.borrow_mut();
948                            vec![borrowed_heap.insert_string(k.to_string()), *v]
949                        };
950                        heap.borrow_mut().insert_list(entry)
951                    })
952                    .collect();
953
954                Ok(heap.borrow_mut().insert_list(entries))
955            }
956
957            // Conversion functions
958            Self::ToString => match args[0] {
959                Value::String(_) => Ok(args[0]), // If it's already a string, just return it
960                _ => {
961                    let string = args[0].stringify_internal(&heap.borrow());
962                    Ok(heap.borrow_mut().insert_string(string))
963                }
964            },
965
966            Self::ToNumber => match args[0] {
967                Value::Number(_) => Ok(args[0]), // If it's already a number, just return it
968                Value::Bool(b) => Ok(Value::Number(if b { 1.0 } else { 0.0 })),
969                _ => Ok(Value::Number(args[0].as_string(&heap.borrow())?.parse()?)),
970            },
971
972            Self::ToBool => match args[0] {
973                Value::Bool(_) => Ok(args[0]), // If it's already a boolean, just return it
974                Value::Number(_) => Ok(Value::Bool(args[0].as_number()? != 0.0)),
975                _ => Err(anyhow!(
976                    "expected a boolean or number, but got a {}",
977                    args[0].get_type()
978                )),
979            },
980
981            Self::Convert => {
982                let value = args[0].as_number()?;
983                let from_unit = {
984                    let borrowed_heap = &heap.borrow();
985                    args[1].as_string(borrowed_heap)?.to_string()
986                };
987                let to_unit = {
988                    let borrowed_heap = &heap.borrow();
989                    args[2].as_string(borrowed_heap)?.to_string()
990                };
991
992                let result = units::convert(value, &from_unit, &to_unit)?;
993                Ok(Value::Number(result))
994            }
995
996            // Platform-specific functions
997            #[cfg(not(target_arch = "wasm32"))]
998            Self::Print => {
999                let borrowed_heap = &heap.borrow();
1000
1001                let output = if args.len() == 1 {
1002                    args[0].stringify_internal(borrowed_heap)
1003                } else {
1004                    let format_str = args[0].as_string(borrowed_heap).map_err(|_| {
1005                        anyhow!("first argument must be a formatting string if multiple arguments are given")
1006                    })?;
1007                    let format_args = &args[1..]
1008                        .iter()
1009                        .map(|v| v.stringify_internal(borrowed_heap))
1010                        .collect::<Vec<String>>();
1011                    format_str.format(format_args)
1012                };
1013
1014                // Print to stderr, to not pollute stdout
1015                eprintln!("{}", output);
1016
1017                Ok(Value::Null)
1018            }
1019
1020            #[cfg(not(target_arch = "wasm32"))]
1021            Self::TimeNow => Ok(Value::Number(
1022                std::time::SystemTime::now()
1023                    .duration_since(std::time::UNIX_EPOCH)
1024                    .unwrap()
1025                    .as_secs_f64(),
1026            )),
1027
1028            // Higher-order functions
1029            Self::Map => {
1030                let func = &args[1];
1031                let (list, func_accepts_two_args) = {
1032                    let borrowed_heap = &heap.borrow();
1033                    let list = args[0].as_list(borrowed_heap)?.clone();
1034                    let func_def = get_function_def(func, borrowed_heap)
1035                        .ok_or_else(|| anyhow!("second argument must be a function"))?;
1036
1037                    // Check if the function can accept 2 arguments (element, index)
1038                    let accepts_two = func_def.arity().can_accept(2);
1039
1040                    (list, accepts_two)
1041                };
1042
1043                let mut mapped_list = vec![];
1044                for (idx, item) in list.iter().enumerate() {
1045                    let func_def = get_function_def(func, &heap.borrow())
1046                        .ok_or_else(|| anyhow!("second argument must be a function"))?;
1047
1048                    let args = if func_accepts_two_args {
1049                        vec![*item, Value::Number(idx as f64)]
1050                    } else {
1051                        vec![*item]
1052                    };
1053
1054                    let result = func_def.call(
1055                        Value::Null,
1056                        args,
1057                        Rc::clone(&heap),
1058                        Rc::clone(&bindings),
1059                        call_depth + 1,
1060                        source,
1061                    )?;
1062                    mapped_list.push(result);
1063                }
1064
1065                Ok(heap.borrow_mut().insert_list(mapped_list))
1066            }
1067
1068            Self::Filter => {
1069                let func = &args[1];
1070                let (list, func_accepts_two_args) = {
1071                    let borrowed_heap = &heap.borrow();
1072                    let list = args[0].as_list(borrowed_heap)?.clone();
1073                    let func_def = get_function_def(func, borrowed_heap)
1074                        .ok_or_else(|| anyhow!("second argument must be a function"))?;
1075
1076                    // Check if the function can accept 2 arguments (element, index)
1077                    let accepts_two = func_def.arity().can_accept(2);
1078
1079                    (list, accepts_two)
1080                };
1081
1082                let mut filtered_list = vec![];
1083                for (idx, item) in list.iter().enumerate() {
1084                    let func_def = get_function_def(func, &heap.borrow())
1085                        .ok_or_else(|| anyhow!("second argument must be a function"))?;
1086
1087                    let args = if func_accepts_two_args {
1088                        vec![*item, Value::Number(idx as f64)]
1089                    } else {
1090                        vec![*item]
1091                    };
1092
1093                    let result = func_def.call(
1094                        Value::Null,
1095                        args,
1096                        Rc::clone(&heap),
1097                        Rc::clone(&bindings),
1098                        call_depth + 1,
1099                        source,
1100                    )?;
1101                    if result.as_bool()? {
1102                        filtered_list.push(*item);
1103                    }
1104                }
1105
1106                Ok(heap.borrow_mut().insert_list(filtered_list))
1107            }
1108
1109            Self::Reduce => {
1110                let func = &args[1];
1111                let initial = args[2];
1112                let (list, func_accepts_three_args) = {
1113                    let borrowed_heap = &heap.borrow();
1114                    let list = args[0].as_list(borrowed_heap)?.clone();
1115                    let func_def = get_function_def(func, borrowed_heap)
1116                        .ok_or_else(|| anyhow!("second argument must be a function"))?;
1117
1118                    // Check if the function can accept 3 arguments (accumulator, element, index)
1119                    let accepts_three = func_def.arity().can_accept(3);
1120
1121                    (list, accepts_three)
1122                };
1123
1124                let mut accumulator = initial;
1125                for (idx, item) in list.iter().enumerate() {
1126                    let func_def = get_function_def(func, &heap.borrow())
1127                        .ok_or_else(|| anyhow!("second argument must be a function"))?;
1128
1129                    let args = if func_accepts_three_args {
1130                        vec![accumulator, *item, Value::Number(idx as f64)]
1131                    } else {
1132                        vec![accumulator, *item]
1133                    };
1134
1135                    accumulator = func_def.call(
1136                        Value::Null,
1137                        args,
1138                        Rc::clone(&heap),
1139                        Rc::clone(&bindings),
1140                        call_depth + 1,
1141                        source,
1142                    )?;
1143                }
1144
1145                Ok(accumulator)
1146            }
1147
1148            Self::Every => {
1149                let func = &args[1];
1150                let (list, func_accepts_two_args) = {
1151                    let borrowed_heap = &heap.borrow();
1152                    let list = args[0].as_list(borrowed_heap)?.clone();
1153                    let func_def = get_function_def(func, borrowed_heap)
1154                        .ok_or_else(|| anyhow!("second argument must be a function"))?;
1155
1156                    // Check if the function can accept 2 arguments (element, index)
1157                    let accepts_two = func_def.arity().can_accept(2);
1158
1159                    (list, accepts_two)
1160                };
1161
1162                for (idx, item) in list.iter().enumerate() {
1163                    let func_def = get_function_def(func, &heap.borrow())
1164                        .ok_or_else(|| anyhow!("second argument must be a function"))?;
1165
1166                    let args = if func_accepts_two_args {
1167                        vec![*item, Value::Number(idx as f64)]
1168                    } else {
1169                        vec![*item]
1170                    };
1171
1172                    let result = func_def.call(
1173                        Value::Null,
1174                        args,
1175                        Rc::clone(&heap),
1176                        Rc::clone(&bindings),
1177                        call_depth + 1,
1178                        source,
1179                    )?;
1180                    if !result.as_bool()? {
1181                        return Ok(Value::Bool(false));
1182                    }
1183                }
1184
1185                Ok(Value::Bool(true))
1186            }
1187
1188            Self::Some => {
1189                let func = &args[1];
1190                let (list, func_accepts_two_args) = {
1191                    let borrowed_heap = &heap.borrow();
1192                    let list = args[0].as_list(borrowed_heap)?.clone();
1193                    let func_def = get_function_def(func, borrowed_heap)
1194                        .ok_or_else(|| anyhow!("second argument must be a function"))?;
1195
1196                    // Check if the function can accept 2 arguments (element, index)
1197                    let accepts_two = func_def.arity().can_accept(2);
1198
1199                    (list, accepts_two)
1200                };
1201
1202                for (idx, item) in list.iter().enumerate() {
1203                    let func_def = get_function_def(func, &heap.borrow())
1204                        .ok_or_else(|| anyhow!("second argument must be a function"))?;
1205
1206                    let args = if func_accepts_two_args {
1207                        vec![*item, Value::Number(idx as f64)]
1208                    } else {
1209                        vec![*item]
1210                    };
1211
1212                    let result = func_def.call(
1213                        Value::Null,
1214                        args,
1215                        Rc::clone(&heap),
1216                        Rc::clone(&bindings),
1217                        call_depth + 1,
1218                        source,
1219                    )?;
1220                    if result.as_bool()? {
1221                        return Ok(Value::Bool(true));
1222                    }
1223                }
1224
1225                Ok(Value::Bool(false))
1226            }
1227
1228            Self::SortBy => {
1229                let func = &args[1];
1230                let mut list = {
1231                    let borrowed_heap = &heap.borrow();
1232                    args[0].as_list(borrowed_heap)?.clone()
1233                };
1234
1235                list.sort_by(|a, b| {
1236                    let func_def_a = get_function_def(func, &heap.borrow());
1237                    let func_def_b = get_function_def(func, &heap.borrow());
1238
1239                    match (func_def_a, func_def_b) {
1240                        (Some(fd_a), Some(fd_b)) => {
1241                            let result_a = fd_a.call(
1242                                Value::Null,
1243                                vec![*a],
1244                                Rc::clone(&heap),
1245                                Rc::clone(&bindings),
1246                                call_depth + 1,
1247                                source,
1248                            );
1249                            let result_b = fd_b.call(
1250                                Value::Null,
1251                                vec![*b],
1252                                Rc::clone(&heap),
1253                                Rc::clone(&bindings),
1254                                call_depth + 1,
1255                                source,
1256                            );
1257
1258                            match (result_a, result_b) {
1259                                (Ok(val_a), Ok(val_b)) => val_a
1260                                    .compare(&val_b, &heap.borrow())
1261                                    .unwrap_or(None)
1262                                    .unwrap_or(std::cmp::Ordering::Equal),
1263                                _ => std::cmp::Ordering::Equal,
1264                            }
1265                        }
1266                        _ => std::cmp::Ordering::Equal,
1267                    }
1268                });
1269
1270                Ok(heap.borrow_mut().insert_list(list))
1271            }
1272        }
1273    }
1274
1275    pub fn all() -> Vec<Self> {
1276        vec![
1277            Self::Sqrt,
1278            Self::Sin,
1279            Self::Cos,
1280            Self::Tan,
1281            Self::Asin,
1282            Self::Acos,
1283            Self::Atan,
1284            Self::Log,
1285            Self::Log10,
1286            Self::Exp,
1287            Self::Abs,
1288            Self::Floor,
1289            Self::Ceil,
1290            Self::Round,
1291            Self::Trunc,
1292            Self::Random,
1293            Self::Min,
1294            Self::Max,
1295            Self::Avg,
1296            Self::Sum,
1297            Self::Prod,
1298            Self::Median,
1299            Self::Percentile,
1300            Self::Range,
1301            Self::Len,
1302            Self::Head,
1303            Self::Tail,
1304            Self::Slice,
1305            Self::Concat,
1306            Self::Dot,
1307            Self::Unique,
1308            Self::Sort,
1309            Self::SortBy,
1310            Self::Reverse,
1311            Self::Any,
1312            Self::All,
1313            Self::Map,
1314            Self::Reduce,
1315            Self::Filter,
1316            Self::Every,
1317            Self::Some,
1318            Self::Split,
1319            Self::Join,
1320            Self::Replace,
1321            Self::Trim,
1322            Self::Uppercase,
1323            Self::Lowercase,
1324            Self::ToString,
1325            Self::ToNumber,
1326            Self::ToBool,
1327            Self::Convert,
1328            Self::Includes,
1329            Self::Format,
1330            Self::Typeof,
1331            Self::Arity,
1332            Self::Keys,
1333            Self::Values,
1334            Self::Entries,
1335            #[cfg(not(target_arch = "wasm32"))]
1336            Self::Print,
1337            #[cfg(not(target_arch = "wasm32"))]
1338            Self::TimeNow,
1339        ]
1340    }
1341
1342    pub fn all_names() -> Vec<&'static str> {
1343        Self::all().iter().map(|f| f.name()).collect()
1344    }
1345}
1346
1347impl FunctionDef {
1348    pub fn get_name(&self) -> String {
1349        match self {
1350            FunctionDef::BuiltIn(built_in) => {
1351                format!("built-in function \"{}\"", built_in.name())
1352            }
1353            FunctionDef::Lambda(LambdaDef { name, .. }) => name
1354                .clone()
1355                .map_or(String::from("anonymous function"), |n| {
1356                    format!("function \"{}\"", n)
1357                }),
1358        }
1359    }
1360
1361    pub fn arity(&self) -> FunctionArity {
1362        match self {
1363            FunctionDef::BuiltIn(built_in) => built_in.arity(),
1364            FunctionDef::Lambda(lambda_def) => lambda_def.get_arity(),
1365        }
1366    }
1367
1368    pub fn check_arity(&self, arg_count: usize) -> Result<()> {
1369        match self {
1370            FunctionDef::BuiltIn(built_in) => match built_in.arity() {
1371                FunctionArity::Exact(expected) => {
1372                    if arg_count == expected {
1373                        Ok(())
1374                    } else {
1375                        Err(anyhow!(
1376                            "{} takes exactly {} arguments, but {} were given",
1377                            self.get_name(),
1378                            expected,
1379                            arg_count
1380                        ))
1381                    }
1382                }
1383                FunctionArity::AtLeast(expected) => {
1384                    if arg_count >= expected {
1385                        Ok(())
1386                    } else {
1387                        Err(anyhow!(
1388                            "{} takes at least {} arguments, but {} were given",
1389                            self.get_name(),
1390                            expected,
1391                            arg_count
1392                        ))
1393                    }
1394                }
1395                FunctionArity::Between(min, max) => {
1396                    if arg_count >= min && arg_count <= max {
1397                        Ok(())
1398                    } else {
1399                        Err(anyhow!(
1400                            "{} takes between {} and {} arguments, but {} were given",
1401                            self.get_name(),
1402                            min,
1403                            max,
1404                            arg_count
1405                        ))
1406                    }
1407                }
1408            },
1409            FunctionDef::Lambda(def) => {
1410                let arity = def.get_arity();
1411
1412                match arity {
1413                    FunctionArity::Exact(expected) => {
1414                        if arg_count == expected {
1415                            Ok(())
1416                        } else {
1417                            Err(anyhow!(
1418                                "{} takes exactly {} arguments, but {} were given",
1419                                self.get_name(),
1420                                expected,
1421                                arg_count
1422                            ))
1423                        }
1424                    }
1425                    FunctionArity::AtLeast(expected) => {
1426                        if arg_count >= expected {
1427                            Ok(())
1428                        } else {
1429                            Err(anyhow!(
1430                                "{} takes at least {} arguments, but {} were given",
1431                                self.get_name(),
1432                                expected,
1433                                arg_count
1434                            ))
1435                        }
1436                    }
1437                    FunctionArity::Between(min, max) => {
1438                        if arg_count >= min && arg_count <= max {
1439                            Ok(())
1440                        } else {
1441                            Err(anyhow!(
1442                                "{} takes between {} and {} arguments, but {} were given",
1443                                self.get_name(),
1444                                min,
1445                                max,
1446                                arg_count
1447                            ))
1448                        }
1449                    }
1450                }
1451            }
1452        }
1453    }
1454
1455    pub fn call(
1456        &self,
1457        this_value: Value,
1458        args: Vec<Value>,
1459        heap: Rc<RefCell<Heap>>,
1460        bindings: Rc<RefCell<HashMap<String, Value>>>,
1461        call_depth: usize,
1462        source: &str,
1463    ) -> Result<Value> {
1464        #[cfg(not(target_arch = "wasm32"))]
1465        let start = std::time::Instant::now();
1466
1467        self.check_arity(args.len())?;
1468
1469        if call_depth > 1000 {
1470            return Err(anyhow!(
1471                "in {}: maximum call depth of 1000 exceeded",
1472                self.get_name()
1473            ));
1474        }
1475
1476        match self {
1477            FunctionDef::Lambda(LambdaDef {
1478                name,
1479                args: expected_args,
1480                body,
1481                scope,
1482                source: lambda_source,
1483            }) => {
1484                #[cfg(not(target_arch = "wasm32"))]
1485                let start_var_env = std::time::Instant::now();
1486
1487                // Start with current environment as fallback for late binding
1488                let mut new_bindings = bindings.borrow().clone();
1489
1490                // Override with captured scope (captured variables take precedence)
1491                for (key, value) in scope {
1492                    new_bindings.insert(key.clone(), *value);
1493                }
1494
1495                // Add self-reference if named
1496                if let Some(fn_name) = name {
1497                    new_bindings.insert(fn_name.clone(), this_value);
1498                }
1499
1500                // Preserve inputs if present
1501                if let Some(inputs) = new_bindings.get("inputs").copied() {
1502                    new_bindings.insert(String::from("inputs"), inputs);
1503                }
1504
1505                // Add function arguments (highest precedence)
1506                for (idx, expected_arg) in expected_args.iter().enumerate() {
1507                    match expected_arg {
1508                        LambdaArg::Required(arg_name) => {
1509                            new_bindings.insert(arg_name.clone(), args[idx]);
1510                        }
1511                        LambdaArg::Optional(arg_name) => {
1512                            new_bindings.insert(
1513                                arg_name.clone(),
1514                                args.get(idx).copied().unwrap_or(Value::Null),
1515                            );
1516                        }
1517                        LambdaArg::Rest(arg_name) => {
1518                            new_bindings.insert(
1519                                arg_name.clone(),
1520                                heap.borrow_mut()
1521                                    .insert_list(args.iter().skip(idx).copied().collect()),
1522                            );
1523                        }
1524                    }
1525                }
1526
1527                #[cfg(not(target_arch = "wasm32"))]
1528                let end_var_env = std::time::Instant::now();
1529
1530                let return_value = evaluate_ast(
1531                    body,
1532                    Rc::clone(&heap),
1533                    Rc::new(RefCell::new(new_bindings)),
1534                    call_depth + 1,
1535                    lambda_source.clone(),
1536                )
1537                .map_err(|error| anyhow!("in {}: {}", self.get_name(), error));
1538
1539                #[cfg(not(target_arch = "wasm32"))]
1540                FUNCTION_CALLS.lock().unwrap().push(FunctionCallStats {
1541                    name: self.get_name(),
1542                    start,
1543                    end: std::time::Instant::now(),
1544                    start_var_env: Some(start_var_env),
1545                    end_var_env: Some(end_var_env),
1546                });
1547
1548                return_value
1549            }
1550            FunctionDef::BuiltIn(built_in) => {
1551                let return_value = built_in
1552                    .call(args, heap, bindings, call_depth + 1, source)
1553                    .map_err(|error| anyhow!("in {}: {}", self.get_name(), error));
1554
1555                #[cfg(not(target_arch = "wasm32"))]
1556                FUNCTION_CALLS.lock().unwrap().push(FunctionCallStats {
1557                    name: self.get_name(),
1558                    start,
1559                    end: std::time::Instant::now(),
1560                    start_var_env: None,
1561                    end_var_env: None,
1562                });
1563
1564                return_value
1565            }
1566        }
1567    }
1568}
1569
1570pub fn is_built_in_function(ident: &str) -> bool {
1571    BuiltInFunction::from_ident(ident).is_some()
1572}
1573
1574pub fn get_built_in_function_def_by_ident(ident: &str) -> Option<FunctionDef> {
1575    BuiltInFunction::from_ident(ident).map(FunctionDef::BuiltIn)
1576}
1577
1578pub fn get_built_in_function_idents() -> Vec<&'static str> {
1579    BuiltInFunction::all_names()
1580}
1581
1582pub fn get_function_def(value: &Value, heap: &Heap) -> Option<FunctionDef> {
1583    match value {
1584        Value::Lambda(pointer) => Some(FunctionDef::Lambda(
1585            pointer.reify(heap).as_lambda().ok()?.clone(),
1586        )),
1587        Value::BuiltIn(built_in) => Some(FunctionDef::BuiltIn(*built_in)),
1588        _ => None,
1589    }
1590}
1591
1592#[cfg(test)]
1593mod tests {
1594    use super::*;
1595    use std::cell::RefCell;
1596    use std::collections::HashMap;
1597    use std::rc::Rc;
1598
1599    #[test]
1600    fn test_range_function() {
1601        // Test single argument
1602        let heap = Rc::new(RefCell::new(Heap::new()));
1603        let bindings = Rc::new(RefCell::new(HashMap::new()));
1604        let range_fn = BuiltInFunction::Range;
1605
1606        // Test range(4) - exclusive, so [0, 1, 2, 3]
1607        let args = vec![Value::Number(4.0)];
1608        let result = range_fn
1609            .call(args, heap.clone(), bindings.clone(), 0, "")
1610            .unwrap();
1611
1612        let heap_borrow = heap.borrow();
1613        let list = result.as_list(&heap_borrow).unwrap();
1614        assert_eq!(list.len(), 4);
1615        assert_eq!(list[0], Value::Number(0.0));
1616        assert_eq!(list[3], Value::Number(3.0));
1617    }
1618
1619    #[test]
1620    fn test_range_function_two_args() {
1621        // Test two arguments
1622        let heap = Rc::new(RefCell::new(Heap::new()));
1623        let bindings = Rc::new(RefCell::new(HashMap::new()));
1624        let range_fn = BuiltInFunction::Range;
1625
1626        // Test range(4, 10) - exclusive, so [4, 5, 6, 7, 8, 9]
1627        let args = vec![Value::Number(4.0), Value::Number(10.0)];
1628        let result = range_fn
1629            .call(args, heap.clone(), bindings.clone(), 0, "")
1630            .unwrap();
1631
1632        let heap_borrow = heap.borrow();
1633        let list = result.as_list(&heap_borrow).unwrap();
1634        assert_eq!(list.len(), 6);
1635        assert_eq!(list[0], Value::Number(4.0));
1636        assert_eq!(list[5], Value::Number(9.0));
1637    }
1638
1639    #[test]
1640    fn test_round_function_single_arg() {
1641        let heap = Rc::new(RefCell::new(Heap::new()));
1642        let bindings = Rc::new(RefCell::new(HashMap::new()));
1643        let round_fn = BuiltInFunction::Round;
1644
1645        // Test basic rounding
1646        let test_cases = vec![
1647            (42.4, 42.0),
1648            (42.5, 43.0),
1649            (42.6, 43.0),
1650            (-42.4, -42.0),
1651            (-42.5, -43.0),
1652            (-42.6, -43.0),
1653            (0.0, 0.0),
1654            (1.999, 2.0),
1655            (-1.999, -2.0),
1656        ];
1657
1658        for (input, expected) in test_cases {
1659            let args = vec![Value::Number(input)];
1660            let result = round_fn
1661                .call(args, heap.clone(), bindings.clone(), 0, "")
1662                .unwrap();
1663            assert_eq!(
1664                result,
1665                Value::Number(expected),
1666                "round({}) should be {}",
1667                input,
1668                expected
1669            );
1670        }
1671    }
1672
1673    #[test]
1674    fn test_round_function_with_decimal_places() {
1675        let heap = Rc::new(RefCell::new(Heap::new()));
1676        let bindings = Rc::new(RefCell::new(HashMap::new()));
1677        let round_fn = BuiltInFunction::Round;
1678
1679        // Test rounding to decimal places
1680        let test_cases = vec![
1681            (42.4543, 0.0, 42.0),
1682            (42.4543, 1.0, 42.5),
1683            (42.4543, 2.0, 42.45),
1684            (42.4543, 3.0, 42.454),
1685            (42.4543, 4.0, 42.4543),
1686            (4.14159, 4.0, 4.1416),
1687            (0.005, 2.0, 0.01),
1688            (0.995, 2.0, 1.0),
1689            // Note: 9.995 has floating point representation issues
1690            // In binary, it's actually slightly less than 9.995
1691            (9.995, 2.0, 9.99),
1692            (-9.995, 2.0, -9.99),
1693        ];
1694
1695        for (input, places, expected) in test_cases {
1696            let args = vec![Value::Number(input), Value::Number(places)];
1697            let result = round_fn
1698                .call(args, heap.clone(), bindings.clone(), 0, "")
1699                .unwrap();
1700
1701            // Use approximate comparison for floating point
1702            if let Value::Number(result_num) = result {
1703                assert!(
1704                    (result_num - expected).abs() < 1e-10,
1705                    "round({}, {}) = {} should be close to {}",
1706                    input,
1707                    places,
1708                    result_num,
1709                    expected
1710                );
1711            } else {
1712                panic!("Expected number result");
1713            }
1714        }
1715    }
1716
1717    #[test]
1718    fn test_round_function_negative_decimal_places() {
1719        let heap = Rc::new(RefCell::new(Heap::new()));
1720        let bindings = Rc::new(RefCell::new(HashMap::new()));
1721        let round_fn = BuiltInFunction::Round;
1722
1723        // Test rounding to tens, hundreds, etc.
1724        let test_cases = vec![
1725            (1234.567, -1.0, 1230.0),
1726            (1234.567, -2.0, 1200.0),
1727            (1234.567, -3.0, 1000.0),
1728            (1234.567, -4.0, 0.0),
1729            (5678.9, -1.0, 5680.0),
1730            (5678.9, -2.0, 5700.0),
1731            (5678.9, -3.0, 6000.0),
1732            (-1234.567, -1.0, -1230.0),
1733            (-1234.567, -2.0, -1200.0),
1734            (1500.0, -3.0, 2000.0),
1735            (-1500.0, -3.0, -2000.0),
1736        ];
1737
1738        for (input, places, expected) in test_cases {
1739            let args = vec![Value::Number(input), Value::Number(places)];
1740            let result = round_fn
1741                .call(args, heap.clone(), bindings.clone(), 0, "")
1742                .unwrap();
1743            assert_eq!(
1744                result,
1745                Value::Number(expected),
1746                "round({}, {}) should be {}",
1747                input,
1748                places,
1749                expected
1750            );
1751        }
1752    }
1753
1754    #[test]
1755    fn test_round_function_edge_cases() {
1756        let heap = Rc::new(RefCell::new(Heap::new()));
1757        let bindings = Rc::new(RefCell::new(HashMap::new()));
1758        let round_fn = BuiltInFunction::Round;
1759
1760        // Test edge cases
1761        let test_cases = vec![
1762            (f64::INFINITY, 0.0, f64::INFINITY),
1763            (f64::NEG_INFINITY, 0.0, f64::NEG_INFINITY),
1764            (0.0, 5.0, 0.0),
1765            (-0.0, 5.0, -0.0),
1766            (1e-10, 5.0, 0.0),
1767            (1e-10, 15.0, 1e-10),
1768        ];
1769
1770        for (input, places, expected) in test_cases {
1771            let args = vec![Value::Number(input), Value::Number(places)];
1772            let result = round_fn
1773                .call(args, heap.clone(), bindings.clone(), 0, "")
1774                .unwrap();
1775
1776            if let Value::Number(result_num) = result {
1777                if expected.is_infinite() {
1778                    assert!(
1779                        result_num.is_infinite()
1780                            && result_num.is_sign_positive() == expected.is_sign_positive(),
1781                        "round({}, {}) should be {}",
1782                        input,
1783                        places,
1784                        expected
1785                    );
1786                } else if expected == 0.0 || expected == -0.0 {
1787                    assert!(
1788                        result_num.abs() < 1e-10,
1789                        "round({}, {}) = {} should be close to 0",
1790                        input,
1791                        places,
1792                        result_num
1793                    );
1794                } else {
1795                    assert!(
1796                        (result_num - expected).abs() < 1e-15,
1797                        "round({}, {}) = {} should be close to {}",
1798                        input,
1799                        places,
1800                        result_num,
1801                        expected
1802                    );
1803                }
1804            } else {
1805                panic!("Expected number result");
1806            }
1807        }
1808    }
1809
1810    #[test]
1811    fn test_random_function_deterministic() {
1812        let heap = Rc::new(RefCell::new(Heap::new()));
1813        let bindings = Rc::new(RefCell::new(HashMap::new()));
1814        let random_fn = BuiltInFunction::Random;
1815
1816        // Test that same seed produces same result
1817        let seed = 42.0;
1818        let args1 = vec![Value::Number(seed)];
1819        let result1 = random_fn
1820            .call(args1, heap.clone(), bindings.clone(), 0, "")
1821            .unwrap();
1822
1823        let args2 = vec![Value::Number(seed)];
1824        let result2 = random_fn
1825            .call(args2, heap.clone(), bindings.clone(), 0, "")
1826            .unwrap();
1827
1828        assert_eq!(result1, result2, "Same seed should produce same result");
1829    }
1830
1831    #[test]
1832    fn test_random_function_different_seeds() {
1833        let heap = Rc::new(RefCell::new(Heap::new()));
1834        let bindings = Rc::new(RefCell::new(HashMap::new()));
1835        let random_fn = BuiltInFunction::Random;
1836
1837        // Test that different seeds produce different results
1838        let args1 = vec![Value::Number(42.0)];
1839        let result1 = random_fn
1840            .call(args1, heap.clone(), bindings.clone(), 0, "")
1841            .unwrap();
1842
1843        let args2 = vec![Value::Number(100.0)];
1844        let result2 = random_fn
1845            .call(args2, heap.clone(), bindings.clone(), 0, "")
1846            .unwrap();
1847
1848        assert_ne!(result1, result2, "Different seeds should produce different results");
1849    }
1850
1851    #[test]
1852    fn test_random_function_range() {
1853        let heap = Rc::new(RefCell::new(Heap::new()));
1854        let bindings = Rc::new(RefCell::new(HashMap::new()));
1855        let random_fn = BuiltInFunction::Random;
1856
1857        // Test that output is in range [0, 1)
1858        for seed in [0.0, 1.0, 42.0, 100.0, 999.0, 12345.0] {
1859            let args = vec![Value::Number(seed)];
1860            let result = random_fn
1861                .call(args, heap.clone(), bindings.clone(), 0, "")
1862                .unwrap();
1863
1864            if let Value::Number(num) = result {
1865                assert!(num >= 0.0 && num < 1.0, "Random value {} should be in range [0, 1)", num);
1866            } else {
1867                panic!("Expected number result");
1868            }
1869        }
1870    }
1871
1872    #[test]
1873    fn test_random_function_negative_seed() {
1874        let heap = Rc::new(RefCell::new(Heap::new()));
1875        let bindings = Rc::new(RefCell::new(HashMap::new()));
1876        let random_fn = BuiltInFunction::Random;
1877
1878        // Test with negative seeds (they get cast to u64)
1879        let args = vec![Value::Number(-42.0)];
1880        let result = random_fn
1881            .call(args, heap.clone(), bindings.clone(), 0, "")
1882            .unwrap();
1883
1884        if let Value::Number(num) = result {
1885            assert!(num >= 0.0 && num < 1.0, "Random value {} should be in range [0, 1)", num);
1886        } else {
1887            panic!("Expected number result");
1888        }
1889    }
1890
1891    #[test]
1892    fn test_map_with_index() {
1893        use crate::values::LambdaDef;
1894
1895        let heap = Rc::new(RefCell::new(Heap::new()));
1896        let bindings = Rc::new(RefCell::new(HashMap::new()));
1897
1898        // Create a list [10, 20, 30]
1899        let list = heap.borrow_mut().insert_list(vec![
1900            Value::Number(10.0),
1901            Value::Number(20.0),
1902            Value::Number(30.0),
1903        ]);
1904
1905        // Create a lambda (x, i) => x + i
1906        let lambda = LambdaDef {
1907            name: None,
1908            args: vec![
1909                crate::values::LambdaArg::Required("x".to_string()),
1910                crate::values::LambdaArg::Required("i".to_string()),
1911            ],
1912            body: crate::ast::Spanned::dummy(crate::ast::Expr::BinaryOp {
1913                op: crate::ast::BinaryOp::Add,
1914                left: Box::new(crate::ast::Spanned::dummy(crate::ast::Expr::Identifier(
1915                    "x".to_string(),
1916                ))),
1917                right: Box::new(crate::ast::Spanned::dummy(crate::ast::Expr::Identifier(
1918                    "i".to_string(),
1919                ))),
1920            }),
1921            scope: HashMap::new(),
1922            source: Rc::from(""),
1923        };
1924        let lambda_value = heap.borrow_mut().insert_lambda(lambda);
1925
1926        // Call map
1927        let result = BuiltInFunction::Map
1928            .call(
1929                vec![list, lambda_value],
1930                heap.clone(),
1931                bindings.clone(),
1932                0,
1933                "",
1934            )
1935            .unwrap();
1936
1937        // Verify result is [10, 21, 32]
1938        let heap_borrow = heap.borrow();
1939        let result_list = result.as_list(&heap_borrow).unwrap();
1940        assert_eq!(result_list.len(), 3);
1941        assert_eq!(result_list[0], Value::Number(10.0)); // 10 + 0
1942        assert_eq!(result_list[1], Value::Number(21.0)); // 20 + 1
1943        assert_eq!(result_list[2], Value::Number(32.0)); // 30 + 2
1944    }
1945
1946    #[test]
1947    fn test_filter_with_index() {
1948        use crate::values::LambdaDef;
1949
1950        let heap = Rc::new(RefCell::new(Heap::new()));
1951        let bindings = Rc::new(RefCell::new(HashMap::new()));
1952
1953        // Create a list [10, 20, 30, 40]
1954        let list = heap.borrow_mut().insert_list(vec![
1955            Value::Number(10.0),
1956            Value::Number(20.0),
1957            Value::Number(30.0),
1958            Value::Number(40.0),
1959        ]);
1960
1961        // Create a lambda (x, i) => i > 1
1962        let lambda = LambdaDef {
1963            name: None,
1964            args: vec![
1965                crate::values::LambdaArg::Required("x".to_string()),
1966                crate::values::LambdaArg::Required("i".to_string()),
1967            ],
1968            body: crate::ast::Spanned::dummy(crate::ast::Expr::BinaryOp {
1969                op: crate::ast::BinaryOp::Greater,
1970                left: Box::new(crate::ast::Spanned::dummy(crate::ast::Expr::Identifier(
1971                    "i".to_string(),
1972                ))),
1973                right: Box::new(crate::ast::Spanned::dummy(crate::ast::Expr::Number(1.0))),
1974            }),
1975            scope: HashMap::new(),
1976            source: Rc::from(""),
1977        };
1978        let lambda_value = heap.borrow_mut().insert_lambda(lambda);
1979
1980        // Call filter
1981        let result = BuiltInFunction::Filter
1982            .call(
1983                vec![list, lambda_value],
1984                heap.clone(),
1985                bindings.clone(),
1986                0,
1987                "",
1988            )
1989            .unwrap();
1990
1991        // Verify result is [30, 40] (indices 2 and 3)
1992        let heap_borrow = heap.borrow();
1993        let result_list = result.as_list(&heap_borrow).unwrap();
1994        assert_eq!(result_list.len(), 2);
1995        assert_eq!(result_list[0], Value::Number(30.0));
1996        assert_eq!(result_list[1], Value::Number(40.0));
1997    }
1998
1999    #[test]
2000    fn test_reduce_with_index() {
2001        use crate::values::LambdaDef;
2002
2003        let heap = Rc::new(RefCell::new(Heap::new()));
2004        let bindings = Rc::new(RefCell::new(HashMap::new()));
2005
2006        // Create a list [10, 20, 30]
2007        let list = heap.borrow_mut().insert_list(vec![
2008            Value::Number(10.0),
2009            Value::Number(20.0),
2010            Value::Number(30.0),
2011        ]);
2012
2013        // Create a lambda (acc, x, i) => acc + x + i
2014        let lambda = LambdaDef {
2015            name: None,
2016            args: vec![
2017                crate::values::LambdaArg::Required("acc".to_string()),
2018                crate::values::LambdaArg::Required("x".to_string()),
2019                crate::values::LambdaArg::Required("i".to_string()),
2020            ],
2021            body: crate::ast::Spanned::dummy(crate::ast::Expr::BinaryOp {
2022                op: crate::ast::BinaryOp::Add,
2023                left: Box::new(crate::ast::Spanned::dummy(crate::ast::Expr::BinaryOp {
2024                    op: crate::ast::BinaryOp::Add,
2025                    left: Box::new(crate::ast::Spanned::dummy(crate::ast::Expr::Identifier(
2026                        "acc".to_string(),
2027                    ))),
2028                    right: Box::new(crate::ast::Spanned::dummy(crate::ast::Expr::Identifier(
2029                        "x".to_string(),
2030                    ))),
2031                })),
2032                right: Box::new(crate::ast::Spanned::dummy(crate::ast::Expr::Identifier(
2033                    "i".to_string(),
2034                ))),
2035            }),
2036            scope: HashMap::new(),
2037            source: Rc::from(""),
2038        };
2039        let lambda_value = heap.borrow_mut().insert_lambda(lambda);
2040
2041        // Call reduce with initial value 0
2042        let result = BuiltInFunction::Reduce
2043            .call(
2044                vec![list, lambda_value, Value::Number(0.0)],
2045                heap.clone(),
2046                bindings.clone(),
2047                0,
2048                "",
2049            )
2050            .unwrap();
2051
2052        // Verify result is 63 (0 + 10+0 + 20+1 + 30+2)
2053        assert_eq!(result, Value::Number(63.0));
2054    }
2055
2056    #[test]
2057    fn test_map_backward_compatible() {
2058        use crate::values::LambdaDef;
2059
2060        let heap = Rc::new(RefCell::new(Heap::new()));
2061        let bindings = Rc::new(RefCell::new(HashMap::new()));
2062
2063        // Create a list [10, 20, 30]
2064        let list = heap.borrow_mut().insert_list(vec![
2065            Value::Number(10.0),
2066            Value::Number(20.0),
2067            Value::Number(30.0),
2068        ]);
2069
2070        // Create a lambda x => x * 2 (only one argument)
2071        let lambda = LambdaDef {
2072            name: None,
2073            args: vec![crate::values::LambdaArg::Required("x".to_string())],
2074            body: crate::ast::Spanned::dummy(crate::ast::Expr::BinaryOp {
2075                op: crate::ast::BinaryOp::Multiply,
2076                left: Box::new(crate::ast::Spanned::dummy(crate::ast::Expr::Identifier(
2077                    "x".to_string(),
2078                ))),
2079                right: Box::new(crate::ast::Spanned::dummy(crate::ast::Expr::Number(2.0))),
2080            }),
2081            scope: HashMap::new(),
2082            source: Rc::from(""),
2083        };
2084        let lambda_value = heap.borrow_mut().insert_lambda(lambda);
2085
2086        // Call map
2087        let result = BuiltInFunction::Map
2088            .call(
2089                vec![list, lambda_value],
2090                heap.clone(),
2091                bindings.clone(),
2092                0,
2093                "",
2094            )
2095            .unwrap();
2096
2097        // Verify result is [20, 40, 60] - backward compatible, no index passed
2098        let heap_borrow = heap.borrow();
2099        let result_list = result.as_list(&heap_borrow).unwrap();
2100        assert_eq!(result_list.len(), 3);
2101        assert_eq!(result_list[0], Value::Number(20.0));
2102        assert_eq!(result_list[1], Value::Number(40.0));
2103        assert_eq!(result_list[2], Value::Number(60.0));
2104    }
2105
2106    #[test]
2107    fn test_convert_length() {
2108        let heap = Rc::new(RefCell::new(Heap::new()));
2109        let bindings = Rc::new(RefCell::new(HashMap::new()));
2110        let convert_fn = BuiltInFunction::Convert;
2111
2112        // Test km to m
2113        let from_unit = heap.borrow_mut().insert_string("km".to_string());
2114        let to_unit = heap.borrow_mut().insert_string("m".to_string());
2115        let args = vec![Value::Number(1.0), from_unit, to_unit];
2116        let result = convert_fn
2117            .call(args, heap.clone(), bindings.clone(), 0, "")
2118            .unwrap();
2119        assert_eq!(result, Value::Number(1000.0));
2120
2121        // Test miles to km
2122        let from_unit = heap.borrow_mut().insert_string("miles".to_string());
2123        let to_unit = heap.borrow_mut().insert_string("km".to_string());
2124        let args = vec![Value::Number(1.0), from_unit, to_unit];
2125        let result = convert_fn
2126            .call(args, heap.clone(), bindings.clone(), 0, "")
2127            .unwrap();
2128        if let Value::Number(n) = result {
2129            assert!((n - 1.609344).abs() < 1e-6);
2130        } else {
2131            panic!("Expected number");
2132        }
2133    }
2134
2135    #[test]
2136    fn test_convert_temperature() {
2137        let heap = Rc::new(RefCell::new(Heap::new()));
2138        let bindings = Rc::new(RefCell::new(HashMap::new()));
2139        let convert_fn = BuiltInFunction::Convert;
2140
2141        // Test 0°C to °F (should be 32°F)
2142        let from_unit = heap.borrow_mut().insert_string("celsius".to_string());
2143        let to_unit = heap.borrow_mut().insert_string("fahrenheit".to_string());
2144        let args = vec![Value::Number(0.0), from_unit, to_unit];
2145        let result = convert_fn
2146            .call(args, heap.clone(), bindings.clone(), 0, "")
2147            .unwrap();
2148        if let Value::Number(n) = result {
2149            assert!((n - 32.0).abs() < 1e-10);
2150        }
2151
2152        // Test 100°C to °F (should be 212°F)
2153        let from_unit = heap.borrow_mut().insert_string("celsius".to_string());
2154        let to_unit = heap.borrow_mut().insert_string("fahrenheit".to_string());
2155        let args = vec![Value::Number(100.0), from_unit, to_unit];
2156        let result = convert_fn
2157            .call(args, heap.clone(), bindings.clone(), 0, "")
2158            .unwrap();
2159        if let Value::Number(n) = result {
2160            assert!((n - 212.0).abs() < 1e-10);
2161        }
2162    }
2163
2164    #[test]
2165    fn test_convert_mass() {
2166        let heap = Rc::new(RefCell::new(Heap::new()));
2167        let bindings = Rc::new(RefCell::new(HashMap::new()));
2168        let convert_fn = BuiltInFunction::Convert;
2169
2170        // Test 1 kg to lbs
2171        let from_unit = heap.borrow_mut().insert_string("kg".to_string());
2172        let to_unit = heap.borrow_mut().insert_string("lbs".to_string());
2173        let args = vec![Value::Number(1.0), from_unit, to_unit];
2174        let result = convert_fn
2175            .call(args, heap.clone(), bindings.clone(), 0, "")
2176            .unwrap();
2177        if let Value::Number(n) = result {
2178            assert!((n - 2.20462).abs() < 0.001);
2179        }
2180    }
2181
2182    #[test]
2183    fn test_convert_information_storage() {
2184        let heap = Rc::new(RefCell::new(Heap::new()));
2185        let bindings = Rc::new(RefCell::new(HashMap::new()));
2186        let convert_fn = BuiltInFunction::Convert;
2187
2188        // Test 1 kibibyte to bytes
2189        let from_unit = heap.borrow_mut().insert_string("kibibytes".to_string());
2190        let to_unit = heap.borrow_mut().insert_string("bytes".to_string());
2191        let args = vec![Value::Number(1.0), from_unit, to_unit];
2192        let result = convert_fn
2193            .call(args, heap.clone(), bindings.clone(), 0, "")
2194            .unwrap();
2195        assert_eq!(result, Value::Number(1024.0));
2196    }
2197
2198    #[test]
2199    fn test_convert_same_unit() {
2200        let heap = Rc::new(RefCell::new(Heap::new()));
2201        let bindings = Rc::new(RefCell::new(HashMap::new()));
2202        let convert_fn = BuiltInFunction::Convert;
2203
2204        // Test converting to same unit
2205        let from_unit = heap.borrow_mut().insert_string("meters".to_string());
2206        let to_unit = heap.borrow_mut().insert_string("m".to_string());
2207        let args = vec![Value::Number(42.0), from_unit, to_unit];
2208        let result = convert_fn
2209            .call(args, heap.clone(), bindings.clone(), 0, "")
2210            .unwrap();
2211        assert_eq!(result, Value::Number(42.0));
2212    }
2213
2214    #[test]
2215    fn test_convert_incompatible_units() {
2216        let heap = Rc::new(RefCell::new(Heap::new()));
2217        let bindings = Rc::new(RefCell::new(HashMap::new()));
2218        let convert_fn = BuiltInFunction::Convert;
2219
2220        // Test incompatible units
2221        let from_unit = heap.borrow_mut().insert_string("kg".to_string());
2222        let to_unit = heap.borrow_mut().insert_string("meters".to_string());
2223        let args = vec![Value::Number(1.0), from_unit, to_unit];
2224        let result = convert_fn.call(args, heap.clone(), bindings.clone(), 0, "");
2225        assert!(result.is_err());
2226        assert!(result.unwrap_err().to_string().contains("Cannot convert"));
2227    }
2228
2229    #[test]
2230    fn test_convert_unknown_unit() {
2231        let heap = Rc::new(RefCell::new(Heap::new()));
2232        let bindings = Rc::new(RefCell::new(HashMap::new()));
2233        let convert_fn = BuiltInFunction::Convert;
2234
2235        // Test unknown unit
2236        let from_unit = heap.borrow_mut().insert_string("foobar".to_string());
2237        let to_unit = heap.borrow_mut().insert_string("meters".to_string());
2238        let args = vec![Value::Number(1.0), from_unit, to_unit];
2239        let result = convert_fn.call(args, heap.clone(), bindings.clone(), 0, "");
2240        assert!(result.is_err());
2241        assert!(result.unwrap_err().to_string().contains("Unknown unit"));
2242    }
2243
2244    #[test]
2245    fn test_convert_case_insensitive() {
2246        let heap = Rc::new(RefCell::new(Heap::new()));
2247        let bindings = Rc::new(RefCell::new(HashMap::new()));
2248        let convert_fn = BuiltInFunction::Convert;
2249
2250        // Test case insensitivity
2251        let from_unit = heap.borrow_mut().insert_string("KM".to_string());
2252        let to_unit = heap.borrow_mut().insert_string("M".to_string());
2253        let args = vec![Value::Number(1.0), from_unit, to_unit];
2254        let result = convert_fn
2255            .call(args, heap.clone(), bindings.clone(), 0, "")
2256            .unwrap();
2257        assert_eq!(result, Value::Number(1000.0));
2258    }
2259}