blots_core/
functions.rs

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