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_def, 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, func_def, accepts_two)
1041                };
1042
1043                let mut mapped_list = vec![];
1044                for (idx, item) in list.iter().enumerate() {
1045                    let args = if func_accepts_two_args {
1046                        vec![*item, Value::Number(idx as f64)]
1047                    } else {
1048                        vec![*item]
1049                    };
1050
1051                    let result = func_def.call(
1052                        Value::Null,
1053                        args,
1054                        Rc::clone(&heap),
1055                        Rc::clone(&bindings),
1056                        call_depth + 1,
1057                        source,
1058                    )?;
1059                    mapped_list.push(result);
1060                }
1061
1062                Ok(heap.borrow_mut().insert_list(mapped_list))
1063            }
1064
1065            Self::Filter => {
1066                let func = &args[1];
1067                let (list, func_def, func_accepts_two_args) = {
1068                    let borrowed_heap = &heap.borrow();
1069                    let list = args[0].as_list(borrowed_heap)?.clone();
1070                    let func_def = get_function_def(func, borrowed_heap)
1071                        .ok_or_else(|| anyhow!("second argument must be a function"))?;
1072
1073                    // Check if the function can accept 2 arguments (element, index)
1074                    let accepts_two = func_def.arity().can_accept(2);
1075
1076                    (list, func_def, accepts_two)
1077                };
1078
1079                let mut filtered_list = vec![];
1080                for (idx, item) in list.iter().enumerate() {
1081                    let args = if func_accepts_two_args {
1082                        vec![*item, Value::Number(idx as f64)]
1083                    } else {
1084                        vec![*item]
1085                    };
1086
1087                    let result = func_def.call(
1088                        Value::Null,
1089                        args,
1090                        Rc::clone(&heap),
1091                        Rc::clone(&bindings),
1092                        call_depth + 1,
1093                        source,
1094                    )?;
1095                    if result.as_bool()? {
1096                        filtered_list.push(*item);
1097                    }
1098                }
1099
1100                Ok(heap.borrow_mut().insert_list(filtered_list))
1101            }
1102
1103            Self::Reduce => {
1104                let func = &args[1];
1105                let initial = args[2];
1106                let (list, func_def, func_accepts_three_args) = {
1107                    let borrowed_heap = &heap.borrow();
1108                    let list = args[0].as_list(borrowed_heap)?.clone();
1109                    let func_def = get_function_def(func, borrowed_heap)
1110                        .ok_or_else(|| anyhow!("second argument must be a function"))?;
1111
1112                    // Check if the function can accept 3 arguments (accumulator, element, index)
1113                    let accepts_three = func_def.arity().can_accept(3);
1114
1115                    (list, func_def, accepts_three)
1116                };
1117
1118                let mut accumulator = initial;
1119                for (idx, item) in list.iter().enumerate() {
1120                    let args = if func_accepts_three_args {
1121                        vec![accumulator, *item, Value::Number(idx as f64)]
1122                    } else {
1123                        vec![accumulator, *item]
1124                    };
1125
1126                    accumulator = func_def.call(
1127                        Value::Null,
1128                        args,
1129                        Rc::clone(&heap),
1130                        Rc::clone(&bindings),
1131                        call_depth + 1,
1132                        source,
1133                    )?;
1134                }
1135
1136                Ok(accumulator)
1137            }
1138
1139            Self::Every => {
1140                let func = &args[1];
1141                let (list, func_def, func_accepts_two_args) = {
1142                    let borrowed_heap = &heap.borrow();
1143                    let list = args[0].as_list(borrowed_heap)?.clone();
1144                    let func_def = get_function_def(func, borrowed_heap)
1145                        .ok_or_else(|| anyhow!("second argument must be a function"))?;
1146
1147                    // Check if the function can accept 2 arguments (element, index)
1148                    let accepts_two = func_def.arity().can_accept(2);
1149
1150                    (list, func_def, accepts_two)
1151                };
1152
1153                for (idx, item) in list.iter().enumerate() {
1154                    let args = if func_accepts_two_args {
1155                        vec![*item, Value::Number(idx as f64)]
1156                    } else {
1157                        vec![*item]
1158                    };
1159
1160                    let result = func_def.call(
1161                        Value::Null,
1162                        args,
1163                        Rc::clone(&heap),
1164                        Rc::clone(&bindings),
1165                        call_depth + 1,
1166                        source,
1167                    )?;
1168                    if !result.as_bool()? {
1169                        return Ok(Value::Bool(false));
1170                    }
1171                }
1172
1173                Ok(Value::Bool(true))
1174            }
1175
1176            Self::Some => {
1177                let func = &args[1];
1178                let (list, func_def, func_accepts_two_args) = {
1179                    let borrowed_heap = &heap.borrow();
1180                    let list = args[0].as_list(borrowed_heap)?.clone();
1181                    let func_def = get_function_def(func, borrowed_heap)
1182                        .ok_or_else(|| anyhow!("second argument must be a function"))?;
1183
1184                    // Check if the function can accept 2 arguments (element, index)
1185                    let accepts_two = func_def.arity().can_accept(2);
1186
1187                    (list, func_def, accepts_two)
1188                };
1189
1190                for (idx, item) in list.iter().enumerate() {
1191                    let args = if func_accepts_two_args {
1192                        vec![*item, Value::Number(idx as f64)]
1193                    } else {
1194                        vec![*item]
1195                    };
1196
1197                    let result = func_def.call(
1198                        Value::Null,
1199                        args,
1200                        Rc::clone(&heap),
1201                        Rc::clone(&bindings),
1202                        call_depth + 1,
1203                        source,
1204                    )?;
1205                    if result.as_bool()? {
1206                        return Ok(Value::Bool(true));
1207                    }
1208                }
1209
1210                Ok(Value::Bool(false))
1211            }
1212
1213            Self::SortBy => {
1214                let func = &args[1];
1215                let mut list = {
1216                    let borrowed_heap = &heap.borrow();
1217                    args[0].as_list(borrowed_heap)?.clone()
1218                };
1219
1220                list.sort_by(|a, b| {
1221                    // Only look up the function once, not twice
1222                    let func_def = get_function_def(func, &heap.borrow());
1223
1224                    match func_def {
1225                        Some(fd) => {
1226                            let result_a = fd.call(
1227                                Value::Null,
1228                                vec![*a],
1229                                Rc::clone(&heap),
1230                                Rc::clone(&bindings),
1231                                call_depth + 1,
1232                                source,
1233                            );
1234                            let result_b = fd.call(
1235                                Value::Null,
1236                                vec![*b],
1237                                Rc::clone(&heap),
1238                                Rc::clone(&bindings),
1239                                call_depth + 1,
1240                                source,
1241                            );
1242
1243                            match (result_a, result_b) {
1244                                (Ok(val_a), Ok(val_b)) => val_a
1245                                    .compare(&val_b, &heap.borrow())
1246                                    .unwrap_or(None)
1247                                    .unwrap_or(std::cmp::Ordering::Equal),
1248                                _ => std::cmp::Ordering::Equal,
1249                            }
1250                        }
1251                        _ => std::cmp::Ordering::Equal,
1252                    }
1253                });
1254
1255                Ok(heap.borrow_mut().insert_list(list))
1256            }
1257        }
1258    }
1259
1260    pub fn all() -> Vec<Self> {
1261        vec![
1262            Self::Sqrt,
1263            Self::Sin,
1264            Self::Cos,
1265            Self::Tan,
1266            Self::Asin,
1267            Self::Acos,
1268            Self::Atan,
1269            Self::Log,
1270            Self::Log10,
1271            Self::Exp,
1272            Self::Abs,
1273            Self::Floor,
1274            Self::Ceil,
1275            Self::Round,
1276            Self::Trunc,
1277            Self::Random,
1278            Self::Min,
1279            Self::Max,
1280            Self::Avg,
1281            Self::Sum,
1282            Self::Prod,
1283            Self::Median,
1284            Self::Percentile,
1285            Self::Range,
1286            Self::Len,
1287            Self::Head,
1288            Self::Tail,
1289            Self::Slice,
1290            Self::Concat,
1291            Self::Dot,
1292            Self::Unique,
1293            Self::Sort,
1294            Self::SortBy,
1295            Self::Reverse,
1296            Self::Any,
1297            Self::All,
1298            Self::Map,
1299            Self::Reduce,
1300            Self::Filter,
1301            Self::Every,
1302            Self::Some,
1303            Self::Split,
1304            Self::Join,
1305            Self::Replace,
1306            Self::Trim,
1307            Self::Uppercase,
1308            Self::Lowercase,
1309            Self::ToString,
1310            Self::ToNumber,
1311            Self::ToBool,
1312            Self::Convert,
1313            Self::Includes,
1314            Self::Format,
1315            Self::Typeof,
1316            Self::Arity,
1317            Self::Keys,
1318            Self::Values,
1319            Self::Entries,
1320            #[cfg(not(target_arch = "wasm32"))]
1321            Self::Print,
1322            #[cfg(not(target_arch = "wasm32"))]
1323            Self::TimeNow,
1324        ]
1325    }
1326
1327    pub fn all_names() -> Vec<&'static str> {
1328        Self::all().iter().map(|f| f.name()).collect()
1329    }
1330}
1331
1332impl FunctionDef {
1333    pub fn get_name(&self) -> String {
1334        match self {
1335            FunctionDef::BuiltIn(built_in) => {
1336                format!("built-in function \"{}\"", built_in.name())
1337            }
1338            FunctionDef::Lambda(LambdaDef { name, .. }) => name
1339                .clone()
1340                .map_or(String::from("anonymous function"), |n| {
1341                    format!("function \"{}\"", n)
1342                }),
1343        }
1344    }
1345
1346    pub fn arity(&self) -> FunctionArity {
1347        match self {
1348            FunctionDef::BuiltIn(built_in) => built_in.arity(),
1349            FunctionDef::Lambda(lambda_def) => lambda_def.get_arity(),
1350        }
1351    }
1352
1353    pub fn check_arity(&self, arg_count: usize) -> Result<()> {
1354        match self {
1355            FunctionDef::BuiltIn(built_in) => match built_in.arity() {
1356                FunctionArity::Exact(expected) => {
1357                    if arg_count == expected {
1358                        Ok(())
1359                    } else {
1360                        Err(anyhow!(
1361                            "{} takes exactly {} arguments, but {} were given",
1362                            self.get_name(),
1363                            expected,
1364                            arg_count
1365                        ))
1366                    }
1367                }
1368                FunctionArity::AtLeast(expected) => {
1369                    if arg_count >= expected {
1370                        Ok(())
1371                    } else {
1372                        Err(anyhow!(
1373                            "{} takes at least {} arguments, but {} were given",
1374                            self.get_name(),
1375                            expected,
1376                            arg_count
1377                        ))
1378                    }
1379                }
1380                FunctionArity::Between(min, max) => {
1381                    if arg_count >= min && arg_count <= max {
1382                        Ok(())
1383                    } else {
1384                        Err(anyhow!(
1385                            "{} takes between {} and {} arguments, but {} were given",
1386                            self.get_name(),
1387                            min,
1388                            max,
1389                            arg_count
1390                        ))
1391                    }
1392                }
1393            },
1394            FunctionDef::Lambda(def) => {
1395                let arity = def.get_arity();
1396
1397                match arity {
1398                    FunctionArity::Exact(expected) => {
1399                        if arg_count == expected {
1400                            Ok(())
1401                        } else {
1402                            Err(anyhow!(
1403                                "{} takes exactly {} arguments, but {} were given",
1404                                self.get_name(),
1405                                expected,
1406                                arg_count
1407                            ))
1408                        }
1409                    }
1410                    FunctionArity::AtLeast(expected) => {
1411                        if arg_count >= expected {
1412                            Ok(())
1413                        } else {
1414                            Err(anyhow!(
1415                                "{} takes at least {} arguments, but {} were given",
1416                                self.get_name(),
1417                                expected,
1418                                arg_count
1419                            ))
1420                        }
1421                    }
1422                    FunctionArity::Between(min, max) => {
1423                        if arg_count >= min && arg_count <= max {
1424                            Ok(())
1425                        } else {
1426                            Err(anyhow!(
1427                                "{} takes between {} and {} arguments, but {} were given",
1428                                self.get_name(),
1429                                min,
1430                                max,
1431                                arg_count
1432                            ))
1433                        }
1434                    }
1435                }
1436            }
1437        }
1438    }
1439
1440    pub fn call(
1441        &self,
1442        this_value: Value,
1443        args: Vec<Value>,
1444        heap: Rc<RefCell<Heap>>,
1445        bindings: Rc<RefCell<HashMap<String, Value>>>,
1446        call_depth: usize,
1447        source: &str,
1448    ) -> Result<Value> {
1449        #[cfg(not(target_arch = "wasm32"))]
1450        let start = std::time::Instant::now();
1451
1452        self.check_arity(args.len())?;
1453
1454        if call_depth > 1000 {
1455            return Err(anyhow!(
1456                "in {}: maximum call depth of 1000 exceeded",
1457                self.get_name()
1458            ));
1459        }
1460
1461        match self {
1462            FunctionDef::Lambda(LambdaDef {
1463                name,
1464                args: expected_args,
1465                body,
1466                scope,
1467                source: lambda_source,
1468            }) => {
1469                #[cfg(not(target_arch = "wasm32"))]
1470                let start_var_env = std::time::Instant::now();
1471
1472                // Start with current environment as fallback for late binding
1473                let mut new_bindings = bindings.borrow().clone();
1474
1475                // Override with captured scope (captured variables take precedence)
1476                for (key, value) in scope {
1477                    new_bindings.insert(key.clone(), *value);
1478                }
1479
1480                // Add self-reference if named
1481                if let Some(fn_name) = name {
1482                    new_bindings.insert(fn_name.clone(), this_value);
1483                }
1484
1485                // Preserve inputs if present
1486                if let Some(inputs) = new_bindings.get("inputs").copied() {
1487                    new_bindings.insert(String::from("inputs"), inputs);
1488                }
1489
1490                // Add function arguments (highest precedence)
1491                for (idx, expected_arg) in expected_args.iter().enumerate() {
1492                    match expected_arg {
1493                        LambdaArg::Required(arg_name) => {
1494                            new_bindings.insert(arg_name.clone(), args[idx]);
1495                        }
1496                        LambdaArg::Optional(arg_name) => {
1497                            new_bindings.insert(
1498                                arg_name.clone(),
1499                                args.get(idx).copied().unwrap_or(Value::Null),
1500                            );
1501                        }
1502                        LambdaArg::Rest(arg_name) => {
1503                            new_bindings.insert(
1504                                arg_name.clone(),
1505                                heap.borrow_mut()
1506                                    .insert_list(args.iter().skip(idx).copied().collect()),
1507                            );
1508                        }
1509                    }
1510                }
1511
1512                #[cfg(not(target_arch = "wasm32"))]
1513                let end_var_env = std::time::Instant::now();
1514
1515                let return_value = evaluate_ast(
1516                    body,
1517                    Rc::clone(&heap),
1518                    Rc::new(RefCell::new(new_bindings)),
1519                    call_depth + 1,
1520                    lambda_source.clone(),
1521                )
1522                .map_err(|error| anyhow!("in {}: {}", self.get_name(), error));
1523
1524                #[cfg(not(target_arch = "wasm32"))]
1525                FUNCTION_CALLS.lock().unwrap().push(FunctionCallStats {
1526                    name: self.get_name(),
1527                    start,
1528                    end: std::time::Instant::now(),
1529                    start_var_env: Some(start_var_env),
1530                    end_var_env: Some(end_var_env),
1531                });
1532
1533                return_value
1534            }
1535            FunctionDef::BuiltIn(built_in) => {
1536                let return_value = built_in
1537                    .call(args, heap, bindings, call_depth + 1, source)
1538                    .map_err(|error| anyhow!("in {}: {}", self.get_name(), error));
1539
1540                #[cfg(not(target_arch = "wasm32"))]
1541                FUNCTION_CALLS.lock().unwrap().push(FunctionCallStats {
1542                    name: self.get_name(),
1543                    start,
1544                    end: std::time::Instant::now(),
1545                    start_var_env: None,
1546                    end_var_env: None,
1547                });
1548
1549                return_value
1550            }
1551        }
1552    }
1553}
1554
1555pub fn is_built_in_function(ident: &str) -> bool {
1556    BuiltInFunction::from_ident(ident).is_some()
1557}
1558
1559pub fn get_built_in_function_def_by_ident(ident: &str) -> Option<FunctionDef> {
1560    BuiltInFunction::from_ident(ident).map(FunctionDef::BuiltIn)
1561}
1562
1563pub fn get_built_in_function_idents() -> Vec<&'static str> {
1564    BuiltInFunction::all_names()
1565}
1566
1567pub fn get_function_def(value: &Value, heap: &Heap) -> Option<FunctionDef> {
1568    match value {
1569        Value::Lambda(pointer) => Some(FunctionDef::Lambda(
1570            pointer.reify(heap).as_lambda().ok()?.clone(),
1571        )),
1572        Value::BuiltIn(built_in) => Some(FunctionDef::BuiltIn(*built_in)),
1573        _ => None,
1574    }
1575}
1576
1577#[cfg(test)]
1578mod tests {
1579    use super::*;
1580    use std::cell::RefCell;
1581    use std::collections::HashMap;
1582    use std::rc::Rc;
1583
1584    #[test]
1585    fn test_range_function() {
1586        // Test single argument
1587        let heap = Rc::new(RefCell::new(Heap::new()));
1588        let bindings = Rc::new(RefCell::new(HashMap::new()));
1589        let range_fn = BuiltInFunction::Range;
1590
1591        // Test range(4) - exclusive, so [0, 1, 2, 3]
1592        let args = vec![Value::Number(4.0)];
1593        let result = range_fn
1594            .call(args, heap.clone(), bindings.clone(), 0, "")
1595            .unwrap();
1596
1597        let heap_borrow = heap.borrow();
1598        let list = result.as_list(&heap_borrow).unwrap();
1599        assert_eq!(list.len(), 4);
1600        assert_eq!(list[0], Value::Number(0.0));
1601        assert_eq!(list[3], Value::Number(3.0));
1602    }
1603
1604    #[test]
1605    fn test_range_function_two_args() {
1606        // Test two arguments
1607        let heap = Rc::new(RefCell::new(Heap::new()));
1608        let bindings = Rc::new(RefCell::new(HashMap::new()));
1609        let range_fn = BuiltInFunction::Range;
1610
1611        // Test range(4, 10) - exclusive, so [4, 5, 6, 7, 8, 9]
1612        let args = vec![Value::Number(4.0), Value::Number(10.0)];
1613        let result = range_fn
1614            .call(args, heap.clone(), bindings.clone(), 0, "")
1615            .unwrap();
1616
1617        let heap_borrow = heap.borrow();
1618        let list = result.as_list(&heap_borrow).unwrap();
1619        assert_eq!(list.len(), 6);
1620        assert_eq!(list[0], Value::Number(4.0));
1621        assert_eq!(list[5], Value::Number(9.0));
1622    }
1623
1624    #[test]
1625    fn test_round_function_single_arg() {
1626        let heap = Rc::new(RefCell::new(Heap::new()));
1627        let bindings = Rc::new(RefCell::new(HashMap::new()));
1628        let round_fn = BuiltInFunction::Round;
1629
1630        // Test basic rounding
1631        let test_cases = vec![
1632            (42.4, 42.0),
1633            (42.5, 43.0),
1634            (42.6, 43.0),
1635            (-42.4, -42.0),
1636            (-42.5, -43.0),
1637            (-42.6, -43.0),
1638            (0.0, 0.0),
1639            (1.999, 2.0),
1640            (-1.999, -2.0),
1641        ];
1642
1643        for (input, expected) in test_cases {
1644            let args = vec![Value::Number(input)];
1645            let result = round_fn
1646                .call(args, heap.clone(), bindings.clone(), 0, "")
1647                .unwrap();
1648            assert_eq!(
1649                result,
1650                Value::Number(expected),
1651                "round({}) should be {}",
1652                input,
1653                expected
1654            );
1655        }
1656    }
1657
1658    #[test]
1659    fn test_round_function_with_decimal_places() {
1660        let heap = Rc::new(RefCell::new(Heap::new()));
1661        let bindings = Rc::new(RefCell::new(HashMap::new()));
1662        let round_fn = BuiltInFunction::Round;
1663
1664        // Test rounding to decimal places
1665        let test_cases = vec![
1666            (42.4543, 0.0, 42.0),
1667            (42.4543, 1.0, 42.5),
1668            (42.4543, 2.0, 42.45),
1669            (42.4543, 3.0, 42.454),
1670            (42.4543, 4.0, 42.4543),
1671            (4.14159, 4.0, 4.1416),
1672            (0.005, 2.0, 0.01),
1673            (0.995, 2.0, 1.0),
1674            // Note: 9.995 has floating point representation issues
1675            // In binary, it's actually slightly less than 9.995
1676            (9.995, 2.0, 9.99),
1677            (-9.995, 2.0, -9.99),
1678        ];
1679
1680        for (input, places, expected) in test_cases {
1681            let args = vec![Value::Number(input), Value::Number(places)];
1682            let result = round_fn
1683                .call(args, heap.clone(), bindings.clone(), 0, "")
1684                .unwrap();
1685
1686            // Use approximate comparison for floating point
1687            if let Value::Number(result_num) = result {
1688                assert!(
1689                    (result_num - expected).abs() < 1e-10,
1690                    "round({}, {}) = {} should be close to {}",
1691                    input,
1692                    places,
1693                    result_num,
1694                    expected
1695                );
1696            } else {
1697                panic!("Expected number result");
1698            }
1699        }
1700    }
1701
1702    #[test]
1703    fn test_round_function_negative_decimal_places() {
1704        let heap = Rc::new(RefCell::new(Heap::new()));
1705        let bindings = Rc::new(RefCell::new(HashMap::new()));
1706        let round_fn = BuiltInFunction::Round;
1707
1708        // Test rounding to tens, hundreds, etc.
1709        let test_cases = vec![
1710            (1234.567, -1.0, 1230.0),
1711            (1234.567, -2.0, 1200.0),
1712            (1234.567, -3.0, 1000.0),
1713            (1234.567, -4.0, 0.0),
1714            (5678.9, -1.0, 5680.0),
1715            (5678.9, -2.0, 5700.0),
1716            (5678.9, -3.0, 6000.0),
1717            (-1234.567, -1.0, -1230.0),
1718            (-1234.567, -2.0, -1200.0),
1719            (1500.0, -3.0, 2000.0),
1720            (-1500.0, -3.0, -2000.0),
1721        ];
1722
1723        for (input, places, expected) in test_cases {
1724            let args = vec![Value::Number(input), Value::Number(places)];
1725            let result = round_fn
1726                .call(args, heap.clone(), bindings.clone(), 0, "")
1727                .unwrap();
1728            assert_eq!(
1729                result,
1730                Value::Number(expected),
1731                "round({}, {}) should be {}",
1732                input,
1733                places,
1734                expected
1735            );
1736        }
1737    }
1738
1739    #[test]
1740    fn test_round_function_edge_cases() {
1741        let heap = Rc::new(RefCell::new(Heap::new()));
1742        let bindings = Rc::new(RefCell::new(HashMap::new()));
1743        let round_fn = BuiltInFunction::Round;
1744
1745        // Test edge cases
1746        let test_cases = vec![
1747            (f64::INFINITY, 0.0, f64::INFINITY),
1748            (f64::NEG_INFINITY, 0.0, f64::NEG_INFINITY),
1749            (0.0, 5.0, 0.0),
1750            (-0.0, 5.0, -0.0),
1751            (1e-10, 5.0, 0.0),
1752            (1e-10, 15.0, 1e-10),
1753        ];
1754
1755        for (input, places, expected) in test_cases {
1756            let args = vec![Value::Number(input), Value::Number(places)];
1757            let result = round_fn
1758                .call(args, heap.clone(), bindings.clone(), 0, "")
1759                .unwrap();
1760
1761            if let Value::Number(result_num) = result {
1762                if expected.is_infinite() {
1763                    assert!(
1764                        result_num.is_infinite()
1765                            && result_num.is_sign_positive() == expected.is_sign_positive(),
1766                        "round({}, {}) should be {}",
1767                        input,
1768                        places,
1769                        expected
1770                    );
1771                } else if expected == 0.0 || expected == -0.0 {
1772                    assert!(
1773                        result_num.abs() < 1e-10,
1774                        "round({}, {}) = {} should be close to 0",
1775                        input,
1776                        places,
1777                        result_num
1778                    );
1779                } else {
1780                    assert!(
1781                        (result_num - expected).abs() < 1e-15,
1782                        "round({}, {}) = {} should be close to {}",
1783                        input,
1784                        places,
1785                        result_num,
1786                        expected
1787                    );
1788                }
1789            } else {
1790                panic!("Expected number result");
1791            }
1792        }
1793    }
1794
1795    #[test]
1796    fn test_random_function_deterministic() {
1797        let heap = Rc::new(RefCell::new(Heap::new()));
1798        let bindings = Rc::new(RefCell::new(HashMap::new()));
1799        let random_fn = BuiltInFunction::Random;
1800
1801        // Test that same seed produces same result
1802        let seed = 42.0;
1803        let args1 = vec![Value::Number(seed)];
1804        let result1 = random_fn
1805            .call(args1, heap.clone(), bindings.clone(), 0, "")
1806            .unwrap();
1807
1808        let args2 = vec![Value::Number(seed)];
1809        let result2 = random_fn
1810            .call(args2, heap.clone(), bindings.clone(), 0, "")
1811            .unwrap();
1812
1813        assert_eq!(result1, result2, "Same seed should produce same result");
1814    }
1815
1816    #[test]
1817    fn test_random_function_different_seeds() {
1818        let heap = Rc::new(RefCell::new(Heap::new()));
1819        let bindings = Rc::new(RefCell::new(HashMap::new()));
1820        let random_fn = BuiltInFunction::Random;
1821
1822        // Test that different seeds produce different results
1823        let args1 = vec![Value::Number(42.0)];
1824        let result1 = random_fn
1825            .call(args1, heap.clone(), bindings.clone(), 0, "")
1826            .unwrap();
1827
1828        let args2 = vec![Value::Number(100.0)];
1829        let result2 = random_fn
1830            .call(args2, heap.clone(), bindings.clone(), 0, "")
1831            .unwrap();
1832
1833        assert_ne!(result1, result2, "Different seeds should produce different results");
1834    }
1835
1836    #[test]
1837    fn test_random_function_range() {
1838        let heap = Rc::new(RefCell::new(Heap::new()));
1839        let bindings = Rc::new(RefCell::new(HashMap::new()));
1840        let random_fn = BuiltInFunction::Random;
1841
1842        // Test that output is in range [0, 1)
1843        for seed in [0.0, 1.0, 42.0, 100.0, 999.0, 12345.0] {
1844            let args = vec![Value::Number(seed)];
1845            let result = random_fn
1846                .call(args, heap.clone(), bindings.clone(), 0, "")
1847                .unwrap();
1848
1849            if let Value::Number(num) = result {
1850                assert!(num >= 0.0 && num < 1.0, "Random value {} should be in range [0, 1)", num);
1851            } else {
1852                panic!("Expected number result");
1853            }
1854        }
1855    }
1856
1857    #[test]
1858    fn test_random_function_negative_seed() {
1859        let heap = Rc::new(RefCell::new(Heap::new()));
1860        let bindings = Rc::new(RefCell::new(HashMap::new()));
1861        let random_fn = BuiltInFunction::Random;
1862
1863        // Test with negative seeds (they get cast to u64)
1864        let args = vec![Value::Number(-42.0)];
1865        let result = random_fn
1866            .call(args, heap.clone(), bindings.clone(), 0, "")
1867            .unwrap();
1868
1869        if let Value::Number(num) = result {
1870            assert!(num >= 0.0 && num < 1.0, "Random value {} should be in range [0, 1)", num);
1871        } else {
1872            panic!("Expected number result");
1873        }
1874    }
1875
1876    #[test]
1877    fn test_map_with_index() {
1878        use crate::values::LambdaDef;
1879
1880        let heap = Rc::new(RefCell::new(Heap::new()));
1881        let bindings = Rc::new(RefCell::new(HashMap::new()));
1882
1883        // Create a list [10, 20, 30]
1884        let list = heap.borrow_mut().insert_list(vec![
1885            Value::Number(10.0),
1886            Value::Number(20.0),
1887            Value::Number(30.0),
1888        ]);
1889
1890        // Create a lambda (x, i) => x + i
1891        let lambda = LambdaDef {
1892            name: None,
1893            args: vec![
1894                crate::values::LambdaArg::Required("x".to_string()),
1895                crate::values::LambdaArg::Required("i".to_string()),
1896            ],
1897            body: crate::ast::Spanned::dummy(crate::ast::Expr::BinaryOp {
1898                op: crate::ast::BinaryOp::Add,
1899                left: Box::new(crate::ast::Spanned::dummy(crate::ast::Expr::Identifier(
1900                    "x".to_string(),
1901                ))),
1902                right: Box::new(crate::ast::Spanned::dummy(crate::ast::Expr::Identifier(
1903                    "i".to_string(),
1904                ))),
1905            }),
1906            scope: HashMap::new(),
1907            source: Rc::from(""),
1908        };
1909        let lambda_value = heap.borrow_mut().insert_lambda(lambda);
1910
1911        // Call map
1912        let result = BuiltInFunction::Map
1913            .call(
1914                vec![list, lambda_value],
1915                heap.clone(),
1916                bindings.clone(),
1917                0,
1918                "",
1919            )
1920            .unwrap();
1921
1922        // Verify result is [10, 21, 32]
1923        let heap_borrow = heap.borrow();
1924        let result_list = result.as_list(&heap_borrow).unwrap();
1925        assert_eq!(result_list.len(), 3);
1926        assert_eq!(result_list[0], Value::Number(10.0)); // 10 + 0
1927        assert_eq!(result_list[1], Value::Number(21.0)); // 20 + 1
1928        assert_eq!(result_list[2], Value::Number(32.0)); // 30 + 2
1929    }
1930
1931    #[test]
1932    fn test_filter_with_index() {
1933        use crate::values::LambdaDef;
1934
1935        let heap = Rc::new(RefCell::new(Heap::new()));
1936        let bindings = Rc::new(RefCell::new(HashMap::new()));
1937
1938        // Create a list [10, 20, 30, 40]
1939        let list = heap.borrow_mut().insert_list(vec![
1940            Value::Number(10.0),
1941            Value::Number(20.0),
1942            Value::Number(30.0),
1943            Value::Number(40.0),
1944        ]);
1945
1946        // Create a lambda (x, i) => i > 1
1947        let lambda = LambdaDef {
1948            name: None,
1949            args: vec![
1950                crate::values::LambdaArg::Required("x".to_string()),
1951                crate::values::LambdaArg::Required("i".to_string()),
1952            ],
1953            body: crate::ast::Spanned::dummy(crate::ast::Expr::BinaryOp {
1954                op: crate::ast::BinaryOp::Greater,
1955                left: Box::new(crate::ast::Spanned::dummy(crate::ast::Expr::Identifier(
1956                    "i".to_string(),
1957                ))),
1958                right: Box::new(crate::ast::Spanned::dummy(crate::ast::Expr::Number(1.0))),
1959            }),
1960            scope: HashMap::new(),
1961            source: Rc::from(""),
1962        };
1963        let lambda_value = heap.borrow_mut().insert_lambda(lambda);
1964
1965        // Call filter
1966        let result = BuiltInFunction::Filter
1967            .call(
1968                vec![list, lambda_value],
1969                heap.clone(),
1970                bindings.clone(),
1971                0,
1972                "",
1973            )
1974            .unwrap();
1975
1976        // Verify result is [30, 40] (indices 2 and 3)
1977        let heap_borrow = heap.borrow();
1978        let result_list = result.as_list(&heap_borrow).unwrap();
1979        assert_eq!(result_list.len(), 2);
1980        assert_eq!(result_list[0], Value::Number(30.0));
1981        assert_eq!(result_list[1], Value::Number(40.0));
1982    }
1983
1984    #[test]
1985    fn test_reduce_with_index() {
1986        use crate::values::LambdaDef;
1987
1988        let heap = Rc::new(RefCell::new(Heap::new()));
1989        let bindings = Rc::new(RefCell::new(HashMap::new()));
1990
1991        // Create a list [10, 20, 30]
1992        let list = heap.borrow_mut().insert_list(vec![
1993            Value::Number(10.0),
1994            Value::Number(20.0),
1995            Value::Number(30.0),
1996        ]);
1997
1998        // Create a lambda (acc, x, i) => acc + x + i
1999        let lambda = LambdaDef {
2000            name: None,
2001            args: vec![
2002                crate::values::LambdaArg::Required("acc".to_string()),
2003                crate::values::LambdaArg::Required("x".to_string()),
2004                crate::values::LambdaArg::Required("i".to_string()),
2005            ],
2006            body: crate::ast::Spanned::dummy(crate::ast::Expr::BinaryOp {
2007                op: crate::ast::BinaryOp::Add,
2008                left: Box::new(crate::ast::Spanned::dummy(crate::ast::Expr::BinaryOp {
2009                    op: crate::ast::BinaryOp::Add,
2010                    left: Box::new(crate::ast::Spanned::dummy(crate::ast::Expr::Identifier(
2011                        "acc".to_string(),
2012                    ))),
2013                    right: Box::new(crate::ast::Spanned::dummy(crate::ast::Expr::Identifier(
2014                        "x".to_string(),
2015                    ))),
2016                })),
2017                right: Box::new(crate::ast::Spanned::dummy(crate::ast::Expr::Identifier(
2018                    "i".to_string(),
2019                ))),
2020            }),
2021            scope: HashMap::new(),
2022            source: Rc::from(""),
2023        };
2024        let lambda_value = heap.borrow_mut().insert_lambda(lambda);
2025
2026        // Call reduce with initial value 0
2027        let result = BuiltInFunction::Reduce
2028            .call(
2029                vec![list, lambda_value, Value::Number(0.0)],
2030                heap.clone(),
2031                bindings.clone(),
2032                0,
2033                "",
2034            )
2035            .unwrap();
2036
2037        // Verify result is 63 (0 + 10+0 + 20+1 + 30+2)
2038        assert_eq!(result, Value::Number(63.0));
2039    }
2040
2041    #[test]
2042    fn test_map_backward_compatible() {
2043        use crate::values::LambdaDef;
2044
2045        let heap = Rc::new(RefCell::new(Heap::new()));
2046        let bindings = Rc::new(RefCell::new(HashMap::new()));
2047
2048        // Create a list [10, 20, 30]
2049        let list = heap.borrow_mut().insert_list(vec![
2050            Value::Number(10.0),
2051            Value::Number(20.0),
2052            Value::Number(30.0),
2053        ]);
2054
2055        // Create a lambda x => x * 2 (only one argument)
2056        let lambda = LambdaDef {
2057            name: None,
2058            args: vec![crate::values::LambdaArg::Required("x".to_string())],
2059            body: crate::ast::Spanned::dummy(crate::ast::Expr::BinaryOp {
2060                op: crate::ast::BinaryOp::Multiply,
2061                left: Box::new(crate::ast::Spanned::dummy(crate::ast::Expr::Identifier(
2062                    "x".to_string(),
2063                ))),
2064                right: Box::new(crate::ast::Spanned::dummy(crate::ast::Expr::Number(2.0))),
2065            }),
2066            scope: HashMap::new(),
2067            source: Rc::from(""),
2068        };
2069        let lambda_value = heap.borrow_mut().insert_lambda(lambda);
2070
2071        // Call map
2072        let result = BuiltInFunction::Map
2073            .call(
2074                vec![list, lambda_value],
2075                heap.clone(),
2076                bindings.clone(),
2077                0,
2078                "",
2079            )
2080            .unwrap();
2081
2082        // Verify result is [20, 40, 60] - backward compatible, no index passed
2083        let heap_borrow = heap.borrow();
2084        let result_list = result.as_list(&heap_borrow).unwrap();
2085        assert_eq!(result_list.len(), 3);
2086        assert_eq!(result_list[0], Value::Number(20.0));
2087        assert_eq!(result_list[1], Value::Number(40.0));
2088        assert_eq!(result_list[2], Value::Number(60.0));
2089    }
2090
2091    #[test]
2092    fn test_convert_length() {
2093        let heap = Rc::new(RefCell::new(Heap::new()));
2094        let bindings = Rc::new(RefCell::new(HashMap::new()));
2095        let convert_fn = BuiltInFunction::Convert;
2096
2097        // Test km to m
2098        let from_unit = heap.borrow_mut().insert_string("km".to_string());
2099        let to_unit = heap.borrow_mut().insert_string("m".to_string());
2100        let args = vec![Value::Number(1.0), from_unit, to_unit];
2101        let result = convert_fn
2102            .call(args, heap.clone(), bindings.clone(), 0, "")
2103            .unwrap();
2104        assert_eq!(result, Value::Number(1000.0));
2105
2106        // Test miles to km
2107        let from_unit = heap.borrow_mut().insert_string("miles".to_string());
2108        let to_unit = heap.borrow_mut().insert_string("km".to_string());
2109        let args = vec![Value::Number(1.0), from_unit, to_unit];
2110        let result = convert_fn
2111            .call(args, heap.clone(), bindings.clone(), 0, "")
2112            .unwrap();
2113        if let Value::Number(n) = result {
2114            assert!((n - 1.609344).abs() < 1e-6);
2115        } else {
2116            panic!("Expected number");
2117        }
2118    }
2119
2120    #[test]
2121    fn test_convert_temperature() {
2122        let heap = Rc::new(RefCell::new(Heap::new()));
2123        let bindings = Rc::new(RefCell::new(HashMap::new()));
2124        let convert_fn = BuiltInFunction::Convert;
2125
2126        // Test 0°C to °F (should be 32°F)
2127        let from_unit = heap.borrow_mut().insert_string("celsius".to_string());
2128        let to_unit = heap.borrow_mut().insert_string("fahrenheit".to_string());
2129        let args = vec![Value::Number(0.0), from_unit, to_unit];
2130        let result = convert_fn
2131            .call(args, heap.clone(), bindings.clone(), 0, "")
2132            .unwrap();
2133        if let Value::Number(n) = result {
2134            assert!((n - 32.0).abs() < 1e-10);
2135        }
2136
2137        // Test 100°C to °F (should be 212°F)
2138        let from_unit = heap.borrow_mut().insert_string("celsius".to_string());
2139        let to_unit = heap.borrow_mut().insert_string("fahrenheit".to_string());
2140        let args = vec![Value::Number(100.0), from_unit, to_unit];
2141        let result = convert_fn
2142            .call(args, heap.clone(), bindings.clone(), 0, "")
2143            .unwrap();
2144        if let Value::Number(n) = result {
2145            assert!((n - 212.0).abs() < 1e-10);
2146        }
2147    }
2148
2149    #[test]
2150    fn test_convert_mass() {
2151        let heap = Rc::new(RefCell::new(Heap::new()));
2152        let bindings = Rc::new(RefCell::new(HashMap::new()));
2153        let convert_fn = BuiltInFunction::Convert;
2154
2155        // Test 1 kg to lbs
2156        let from_unit = heap.borrow_mut().insert_string("kg".to_string());
2157        let to_unit = heap.borrow_mut().insert_string("lbs".to_string());
2158        let args = vec![Value::Number(1.0), from_unit, to_unit];
2159        let result = convert_fn
2160            .call(args, heap.clone(), bindings.clone(), 0, "")
2161            .unwrap();
2162        if let Value::Number(n) = result {
2163            assert!((n - 2.20462).abs() < 0.001);
2164        }
2165    }
2166
2167    #[test]
2168    fn test_convert_information_storage() {
2169        let heap = Rc::new(RefCell::new(Heap::new()));
2170        let bindings = Rc::new(RefCell::new(HashMap::new()));
2171        let convert_fn = BuiltInFunction::Convert;
2172
2173        // Test 1 kibibyte to bytes
2174        let from_unit = heap.borrow_mut().insert_string("kibibytes".to_string());
2175        let to_unit = heap.borrow_mut().insert_string("bytes".to_string());
2176        let args = vec![Value::Number(1.0), from_unit, to_unit];
2177        let result = convert_fn
2178            .call(args, heap.clone(), bindings.clone(), 0, "")
2179            .unwrap();
2180        assert_eq!(result, Value::Number(1024.0));
2181    }
2182
2183    #[test]
2184    fn test_convert_same_unit() {
2185        let heap = Rc::new(RefCell::new(Heap::new()));
2186        let bindings = Rc::new(RefCell::new(HashMap::new()));
2187        let convert_fn = BuiltInFunction::Convert;
2188
2189        // Test converting to same unit
2190        let from_unit = heap.borrow_mut().insert_string("meters".to_string());
2191        let to_unit = heap.borrow_mut().insert_string("m".to_string());
2192        let args = vec![Value::Number(42.0), from_unit, to_unit];
2193        let result = convert_fn
2194            .call(args, heap.clone(), bindings.clone(), 0, "")
2195            .unwrap();
2196        assert_eq!(result, Value::Number(42.0));
2197    }
2198
2199    #[test]
2200    fn test_convert_incompatible_units() {
2201        let heap = Rc::new(RefCell::new(Heap::new()));
2202        let bindings = Rc::new(RefCell::new(HashMap::new()));
2203        let convert_fn = BuiltInFunction::Convert;
2204
2205        // Test incompatible units
2206        let from_unit = heap.borrow_mut().insert_string("kg".to_string());
2207        let to_unit = heap.borrow_mut().insert_string("meters".to_string());
2208        let args = vec![Value::Number(1.0), from_unit, to_unit];
2209        let result = convert_fn.call(args, heap.clone(), bindings.clone(), 0, "");
2210        assert!(result.is_err());
2211        assert!(result.unwrap_err().to_string().contains("Cannot convert"));
2212    }
2213
2214    #[test]
2215    fn test_convert_unknown_unit() {
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 unknown unit
2221        let from_unit = heap.borrow_mut().insert_string("foobar".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("Unknown unit"));
2227    }
2228
2229    #[test]
2230    fn test_convert_case_insensitive() {
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 case insensitivity
2236        let from_unit = heap.borrow_mut().insert_string("KM".to_string());
2237        let to_unit = heap.borrow_mut().insert_string("M".to_string());
2238        let args = vec![Value::Number(1.0), from_unit, to_unit];
2239        let result = convert_fn
2240            .call(args, heap.clone(), bindings.clone(), 0, "")
2241            .unwrap();
2242        assert_eq!(result, Value::Number(1000.0));
2243    }
2244}