blots_core/
tests.rs

1// Tests for late binding in function calls
2#[cfg(test)]
3mod late_binding_tests {
4    use crate::environment::Environment;
5    use crate::expressions::evaluate_pairs;
6    use crate::heap::Heap;
7    use crate::parser::{Rule, get_pairs};
8    use crate::values::Value;
9    use std::cell::RefCell;
10    use std::rc::Rc;
11
12    fn parse_and_evaluate(
13        code: &str,
14        heap: Option<Rc<RefCell<Heap>>>,
15        bindings: Option<Rc<Environment>>,
16    ) -> Result<Value, crate::error::RuntimeError> {
17        let pairs = get_pairs(code).map_err(|e| crate::error::RuntimeError::new(e.to_string()))?;
18
19        let heap = heap.unwrap_or_else(|| Rc::new(RefCell::new(Heap::new())));
20        let bindings = bindings.unwrap_or_else(|| Rc::new(Environment::new()));
21
22        let mut result = Value::Null;
23        for pair in pairs {
24            match pair.as_rule() {
25                Rule::statement => {
26                    if let Some(inner_pair) = pair.into_inner().next() {
27                        match inner_pair.as_rule() {
28                            Rule::expression | Rule::assignment => {
29                                result = evaluate_pairs(
30                                    inner_pair.into_inner(),
31                                    Rc::clone(&heap),
32                                    Rc::clone(&bindings),
33                                    0,
34                                    code,
35                                )?;
36                            }
37                            _ => {}
38                        }
39                    }
40                }
41                Rule::EOI => {}
42                _ => {}
43            }
44        }
45        Ok(result)
46    }
47
48    #[test]
49    fn test_late_binding_simple() {
50        // Test that functions can reference variables defined after the function
51        let heap = Rc::new(RefCell::new(Heap::new()));
52        let bindings = Rc::new(Environment::new());
53
54        // Define function that references g
55        let _ = parse_and_evaluate(
56            "f = x => g(x)",
57            Some(Rc::clone(&heap)),
58            Some(Rc::clone(&bindings)),
59        )
60        .unwrap();
61
62        // Define g after f
63        let _ = parse_and_evaluate(
64            "g = x => x * 2",
65            Some(Rc::clone(&heap)),
66            Some(Rc::clone(&bindings)),
67        )
68        .unwrap();
69
70        // Call f(5) should work and return 10
71        let result = parse_and_evaluate("f(5)", Some(heap), Some(bindings)).unwrap();
72
73        assert_eq!(result, Value::Number(10.0));
74    }
75
76    #[test]
77    fn test_late_binding_with_closure() {
78        // Test that captured variables take precedence over late binding
79        let heap = Rc::new(RefCell::new(Heap::new()));
80        let bindings = Rc::new(Environment::new());
81
82        // Define y = 10
83        let _ = parse_and_evaluate("y = 10", Some(Rc::clone(&heap)), Some(Rc::clone(&bindings)))
84            .unwrap();
85
86        // Define function that captures y
87        let _ = parse_and_evaluate(
88            "f = x => x + y",
89            Some(Rc::clone(&heap)),
90            Some(Rc::clone(&bindings)),
91        )
92        .unwrap();
93
94        // Create a new scope with y2 = 20
95        let _ = parse_and_evaluate(
96            "y2 = 20",
97            Some(Rc::clone(&heap)),
98            Some(Rc::clone(&bindings)),
99        )
100        .unwrap();
101
102        // Call f(5) should use captured y=10
103        let result = parse_and_evaluate("f(5)", Some(heap), Some(bindings)).unwrap();
104
105        assert_eq!(result, Value::Number(15.0)); // 5 + 10
106    }
107
108    #[test]
109    fn test_late_binding_chain() {
110        // Test chained late binding
111        let heap = Rc::new(RefCell::new(Heap::new()));
112        let bindings = Rc::new(Environment::new());
113
114        // Define f that calls g
115        let _ = parse_and_evaluate(
116            "f = x => g(x) + 1",
117            Some(Rc::clone(&heap)),
118            Some(Rc::clone(&bindings)),
119        )
120        .unwrap();
121
122        // Define g that calls h
123        let _ = parse_and_evaluate(
124            "g = x => h(x) * 2",
125            Some(Rc::clone(&heap)),
126            Some(Rc::clone(&bindings)),
127        )
128        .unwrap();
129
130        // Define h
131        let _ = parse_and_evaluate(
132            "h = x => x + 3",
133            Some(Rc::clone(&heap)),
134            Some(Rc::clone(&bindings)),
135        )
136        .unwrap();
137
138        // Call f(5) should work: ((5 + 3) * 2) + 1 = 17
139        let result = parse_and_evaluate("f(5)", Some(heap), Some(bindings)).unwrap();
140
141        assert_eq!(result, Value::Number(17.0));
142    }
143
144    #[test]
145    fn test_late_binding_with_recursion() {
146        // Test that recursion still works with late binding
147        let heap = Rc::new(RefCell::new(Heap::new()));
148        let bindings = Rc::new(Environment::new());
149
150        // Define factorial
151        let _ = parse_and_evaluate(
152            "factorial = n => if n <= 1 then 1 else n * factorial(n - 1)",
153            Some(Rc::clone(&heap)),
154            Some(Rc::clone(&bindings)),
155        )
156        .unwrap();
157
158        // Call factorial(5)
159        let result = parse_and_evaluate("factorial(5)", Some(heap), Some(bindings)).unwrap();
160
161        assert_eq!(result, Value::Number(120.0));
162    }
163
164    #[test]
165    fn test_late_binding_undefined_still_fails() {
166        // Test that truly undefined variables still cause errors
167        let heap = Rc::new(RefCell::new(Heap::new()));
168        let bindings = Rc::new(Environment::new());
169
170        // Define function that references undefined h
171        let _ = parse_and_evaluate(
172            "f = x => h(x)",
173            Some(Rc::clone(&heap)),
174            Some(Rc::clone(&bindings)),
175        )
176        .unwrap();
177
178        // Calling f should fail because h is never defined
179        let result = parse_and_evaluate("f(5)", Some(heap), Some(bindings));
180
181        assert!(result.is_err());
182        assert!(
183            result
184                .unwrap_err()
185                .to_string()
186                .contains("unknown identifier: h")
187        );
188    }
189
190    #[test]
191    fn test_late_binding_with_redefinition() {
192        // Test that late binding uses the current definition
193        // Since Blots doesn't allow reassignment, we'll test with nested scopes
194        let heap = Rc::new(RefCell::new(Heap::new()));
195        let bindings = Rc::new(Environment::new());
196
197        // Define f that calls g
198        let _ = parse_and_evaluate(
199            "f = x => g(x)",
200            Some(Rc::clone(&heap)),
201            Some(Rc::clone(&bindings)),
202        )
203        .unwrap();
204
205        // Define g
206        let _ = parse_and_evaluate(
207            "g = x => x * 2",
208            Some(Rc::clone(&heap)),
209            Some(Rc::clone(&bindings)),
210        )
211        .unwrap();
212
213        // Call f(5) - should return 10
214        let result1 =
215            parse_and_evaluate("f(5)", Some(Rc::clone(&heap)), Some(Rc::clone(&bindings))).unwrap();
216        assert_eq!(result1, Value::Number(10.0));
217
218        // Test with a different g in a new context
219        let new_heap = Rc::new(RefCell::new(Heap::new()));
220        let new_bindings = Rc::new(Environment::new());
221
222        // Define f that calls g in new context
223        let _ = parse_and_evaluate(
224            "f = x => g(x)",
225            Some(Rc::clone(&new_heap)),
226            Some(Rc::clone(&new_bindings)),
227        )
228        .unwrap();
229
230        // Define g with different behavior
231        let _ = parse_and_evaluate(
232            "g = x => x * 3",
233            Some(Rc::clone(&new_heap)),
234            Some(Rc::clone(&new_bindings)),
235        )
236        .unwrap();
237
238        // Call f(5) - should return 15 with new g
239        let result2 = parse_and_evaluate("f(5)", Some(new_heap), Some(new_bindings)).unwrap();
240        assert_eq!(result2, Value::Number(15.0));
241    }
242
243    #[test]
244    fn test_argument_shadows_late_binding() {
245        // Test that function arguments shadow late-bound variables
246        let heap = Rc::new(RefCell::new(Heap::new()));
247        let bindings = Rc::new(Environment::new());
248
249        // Define g = 100
250        let _ = parse_and_evaluate(
251            "g = 100",
252            Some(Rc::clone(&heap)),
253            Some(Rc::clone(&bindings)),
254        )
255        .unwrap();
256
257        // Define function with parameter g that shadows outer g
258        let _ = parse_and_evaluate(
259            "f = g => g * 2",
260            Some(Rc::clone(&heap)),
261            Some(Rc::clone(&bindings)),
262        )
263        .unwrap();
264
265        // Call f(5) - should use parameter g=5, not outer g=100
266        let result = parse_and_evaluate("f(5)", Some(heap), Some(bindings)).unwrap();
267
268        assert_eq!(result, Value::Number(10.0));
269    }
270}
271
272// Tests for input reference syntax (#field)
273#[cfg(test)]
274mod input_reference_tests {
275    use crate::environment::Environment;
276    use crate::expressions::evaluate_pairs;
277    use crate::heap::{Heap, HeapPointer};
278    use crate::parser::{Rule, get_pairs};
279    use crate::values::Value;
280    use indexmap::IndexMap;
281    use std::cell::RefCell;
282    use std::rc::Rc;
283
284    fn parse_and_evaluate(
285        code: &str,
286        heap: Option<Rc<RefCell<Heap>>>,
287        bindings: Option<Rc<Environment>>,
288    ) -> Result<Value, crate::error::RuntimeError> {
289        let pairs = get_pairs(code).map_err(|e| crate::error::RuntimeError::new(e.to_string()))?;
290
291        let heap = heap.unwrap_or_else(|| Rc::new(RefCell::new(Heap::new())));
292        let bindings = bindings.unwrap_or_else(|| Rc::new(Environment::new()));
293
294        let mut result = Value::Null;
295        for pair in pairs {
296            match pair.as_rule() {
297                Rule::statement => {
298                    if let Some(inner_pair) = pair.into_inner().next() {
299                        match inner_pair.as_rule() {
300                            Rule::expression | Rule::assignment => {
301                                result = evaluate_pairs(
302                                    inner_pair.into_inner(),
303                                    Rc::clone(&heap),
304                                    Rc::clone(&bindings),
305                                    0,
306                                    code,
307                                )?;
308                            }
309                            _ => {}
310                        }
311                    }
312                }
313                Rule::EOI => {}
314                _ => {}
315            }
316        }
317        Ok(result)
318    }
319
320    fn setup_inputs(heap: &Rc<RefCell<Heap>>, bindings: &Rc<Environment>) {
321        // Create an inputs record with test data
322        let mut inputs_map = IndexMap::new();
323        inputs_map.insert("x".to_string(), Value::Number(42.0));
324        inputs_map.insert("y".to_string(), Value::Number(10.0));
325        inputs_map.insert(
326            "name".to_string(),
327            heap.borrow_mut().insert_string("Alice".to_string()),
328        );
329
330        let inputs_value = heap.borrow_mut().insert_record(inputs_map);
331        bindings.insert("inputs".to_string(), inputs_value);
332    }
333
334    #[test]
335    fn test_input_reference_simple() {
336        let heap = Rc::new(RefCell::new(Heap::new()));
337        let bindings = Rc::new(Environment::new());
338        setup_inputs(&heap, &bindings);
339
340        let result = parse_and_evaluate("#x", Some(heap), Some(bindings)).unwrap();
341        assert_eq!(result, Value::Number(42.0));
342    }
343
344    #[test]
345    fn test_input_reference_string() {
346        let heap = Rc::new(RefCell::new(Heap::new()));
347        let bindings = Rc::new(Environment::new());
348        setup_inputs(&heap, &bindings);
349
350        let result = parse_and_evaluate("#name", Some(Rc::clone(&heap)), Some(bindings)).unwrap();
351
352        if let Value::String(s) = result {
353            let borrowed_heap = heap.borrow();
354            let name = s.reify(&borrowed_heap).as_string().unwrap();
355            assert_eq!(name, "Alice");
356        } else {
357            panic!("Expected string value");
358        }
359    }
360
361    #[test]
362    fn test_input_reference_in_expression() {
363        let heap = Rc::new(RefCell::new(Heap::new()));
364        let bindings = Rc::new(Environment::new());
365        setup_inputs(&heap, &bindings);
366
367        let result = parse_and_evaluate("#x + #y", Some(heap), Some(bindings)).unwrap();
368        assert_eq!(result, Value::Number(52.0));
369    }
370
371    #[test]
372    fn test_input_reference_equivalence() {
373        // Test that #field is equivalent to inputs.field
374        let heap = Rc::new(RefCell::new(Heap::new()));
375        let bindings = Rc::new(Environment::new());
376        setup_inputs(&heap, &bindings);
377
378        let result1 =
379            parse_and_evaluate("#x", Some(Rc::clone(&heap)), Some(Rc::clone(&bindings))).unwrap();
380        let result2 = parse_and_evaluate("inputs.x", Some(heap), Some(bindings)).unwrap();
381
382        assert_eq!(result1, result2);
383    }
384
385    #[test]
386    fn test_input_reference_in_function() {
387        let heap = Rc::new(RefCell::new(Heap::new()));
388        let bindings = Rc::new(Environment::new());
389        setup_inputs(&heap, &bindings);
390
391        // Define a function that uses input reference
392        parse_and_evaluate(
393            "double_x = () => #x * 2",
394            Some(Rc::clone(&heap)),
395            Some(Rc::clone(&bindings)),
396        )
397        .unwrap();
398
399        let result = parse_and_evaluate("double_x()", Some(heap), Some(bindings)).unwrap();
400        assert_eq!(result, Value::Number(84.0));
401    }
402
403    #[test]
404    fn test_input_reference_multiple_uses() {
405        let heap = Rc::new(RefCell::new(Heap::new()));
406        let bindings = Rc::new(Environment::new());
407        setup_inputs(&heap, &bindings);
408
409        let result = parse_and_evaluate("#x + #x * #y", Some(heap), Some(bindings)).unwrap();
410        // 42 + (42 * 10) = 42 + 420 = 462
411        assert_eq!(result, Value::Number(462.0));
412    }
413
414    #[test]
415    fn test_input_reference_missing_field() {
416        // Missing fields should return null, consistent with inputs.field behavior
417        let heap = Rc::new(RefCell::new(Heap::new()));
418        let bindings = Rc::new(Environment::new());
419        setup_inputs(&heap, &bindings);
420
421        let result = parse_and_evaluate("#missing", Some(heap), Some(bindings)).unwrap();
422        assert_eq!(result, Value::Null);
423    }
424
425    #[test]
426    fn test_input_reference_no_inputs() {
427        let heap = Rc::new(RefCell::new(Heap::new()));
428        let bindings = Rc::new(Environment::new());
429        // Don't setup inputs
430
431        let result = parse_and_evaluate("#x", Some(heap), Some(bindings));
432
433        assert!(result.is_err());
434        assert!(result.unwrap_err().to_string().contains("inputs not found"));
435    }
436
437    #[test]
438    fn test_input_reference_in_conditional() {
439        let heap = Rc::new(RefCell::new(Heap::new()));
440        let bindings = Rc::new(Environment::new());
441        setup_inputs(&heap, &bindings);
442
443        let result = parse_and_evaluate(
444            "if #x > 40 then #x + 10 else #y",
445            Some(heap),
446            Some(bindings),
447        )
448        .unwrap();
449
450        assert_eq!(result, Value::Number(52.0)); // 42 + 10
451    }
452
453    #[test]
454    fn test_input_reference_with_underscore() {
455        let heap = Rc::new(RefCell::new(Heap::new()));
456        let bindings = Rc::new(Environment::new());
457
458        // Create inputs with underscore field
459        let mut inputs_map = IndexMap::new();
460        inputs_map.insert("my_value".to_string(), Value::Number(123.0));
461        let inputs_value = heap.borrow_mut().insert_record(inputs_map);
462        bindings.insert("inputs".to_string(), inputs_value);
463
464        let result = parse_and_evaluate("#my_value", Some(heap), Some(bindings)).unwrap();
465        assert_eq!(result, Value::Number(123.0));
466    }
467
468    #[test]
469    fn test_multiline_evaluation_with_inputs() {
470        let heap = Rc::new(RefCell::new(Heap::new()));
471        let bindings = Rc::new(Environment::new());
472        setup_inputs(&heap, &bindings);
473
474        // Test multiple statements with inputs
475        let code = "a = #x * 2\nb = #y + 10\na + b";
476        let result = parse_and_evaluate(code, Some(heap), Some(bindings)).unwrap();
477        // a = 42 * 2 = 84, b = 10 + 10 = 20, result = 84 + 20 = 104
478        assert_eq!(result, Value::Number(104.0));
479    }
480
481    #[test]
482    fn test_multiline_evaluation_with_comments() {
483        let heap = Rc::new(RefCell::new(Heap::new()));
484        let bindings = Rc::new(Environment::new());
485        setup_inputs(&heap, &bindings);
486
487        // Test multiple statements with comments interspersed
488        let code = "// Calculate doubled value\na = #x * 2\n// Calculate offset\nb = #y + 10\n// Return sum\na + b";
489        let result = parse_and_evaluate(code, Some(heap), Some(bindings)).unwrap();
490        // a = 42 * 2 = 84, b = 10 + 10 = 20, result = 84 + 20 = 104
491        assert_eq!(result, Value::Number(104.0));
492    }
493
494    #[test]
495    fn test_multiline_with_blank_lines_and_comments() {
496        let heap = Rc::new(RefCell::new(Heap::new()));
497        let bindings = Rc::new(Environment::new());
498        setup_inputs(&heap, &bindings);
499
500        // Test with blank lines and comments
501        let code = "x = #x * 2\ny = #y + 10\n\n// hi\nx + y";
502        let result = parse_and_evaluate(code, Some(heap), Some(bindings)).unwrap();
503        // x = 42 * 2 = 84, y = 10 + 10 = 20, result = 84 + 20 = 104
504        assert_eq!(result, Value::Number(104.0));
505    }
506
507    #[test]
508    fn test_comment_only_lines() {
509        let heap = Rc::new(RefCell::new(Heap::new()));
510        let bindings = Rc::new(Environment::new());
511
512        // Test with just comments and a final expression
513        let code = "// First comment\n// Second comment\n42";
514        let result = parse_and_evaluate(code, Some(heap), Some(bindings)).unwrap();
515        assert_eq!(result, Value::Number(42.0));
516    }
517}
518
519// Tests for lambda error reporting across contexts
520#[cfg(test)]
521mod lambda_error_context_tests {
522    use crate::environment::Environment;
523    use crate::expressions::evaluate_pairs;
524    use crate::heap::Heap;
525    use crate::parser::{Rule, get_pairs};
526    use crate::values::Value;
527    use std::cell::RefCell;
528    use std::rc::Rc;
529
530    fn parse_and_evaluate(
531        code: &str,
532        heap: Option<Rc<RefCell<Heap>>>,
533        bindings: Option<Rc<Environment>>,
534    ) -> Result<Value, crate::error::RuntimeError> {
535        let pairs = get_pairs(code).map_err(|e| crate::error::RuntimeError::new(e.to_string()))?;
536
537        let heap = heap.unwrap_or_else(|| Rc::new(RefCell::new(Heap::new())));
538        let bindings = bindings.unwrap_or_else(|| Rc::new(Environment::new()));
539
540        let mut result = Value::Null;
541        for pair in pairs {
542            match pair.as_rule() {
543                Rule::statement => {
544                    if let Some(inner_pair) = pair.into_inner().next() {
545                        match inner_pair.as_rule() {
546                            Rule::expression | Rule::assignment => {
547                                result = evaluate_pairs(
548                                    inner_pair.into_inner(),
549                                    Rc::clone(&heap),
550                                    Rc::clone(&bindings),
551                                    0,
552                                    code,
553                                )?;
554                            }
555                            _ => {}
556                        }
557                    }
558                }
559                Rule::EOI => {}
560                _ => {}
561            }
562        }
563        Ok(result)
564    }
565
566    #[test]
567    fn test_lambda_error_preserves_original_source() {
568        // Test that when a lambda is defined in one context and called in another,
569        // errors in the lambda body still reference the original source
570        let heap = Rc::new(RefCell::new(Heap::new()));
571        let bindings = Rc::new(Environment::new());
572
573        // Define a lambda that will error (references undefined variable)
574        let lambda_definition = "f = x => x + undefined_var";
575        parse_and_evaluate(
576            lambda_definition,
577            Some(Rc::clone(&heap)),
578            Some(Rc::clone(&bindings)),
579        )
580        .unwrap();
581
582        // Call the lambda in a completely different source context
583        let call_code = "f(5)";
584        let result = parse_and_evaluate(call_code, Some(heap), Some(bindings));
585
586        // The call should fail
587        assert!(result.is_err());
588
589        let error = result.unwrap_err();
590        let error_msg = error.to_string();
591
592        // The error should mention the undefined variable
593        assert!(error_msg.contains("undefined_var"));
594    }
595
596    #[test]
597    fn test_lambda_error_with_multiple_calls() {
598        // Test that multiple calls to the same lambda all report errors correctly
599        let heap = Rc::new(RefCell::new(Heap::new()));
600        let bindings = Rc::new(Environment::new());
601
602        // Define a lambda with a division by zero
603        let lambda_def = "divide = (a, b) => a / b";
604        parse_and_evaluate(
605            lambda_def,
606            Some(Rc::clone(&heap)),
607            Some(Rc::clone(&bindings)),
608        )
609        .unwrap();
610
611        // First call - should succeed
612        let call1 = "divide(10, 2)";
613        let result1 = parse_and_evaluate(call1, Some(Rc::clone(&heap)), Some(Rc::clone(&bindings)));
614        assert!(result1.is_ok());
615        assert_eq!(result1.unwrap(), Value::Number(5.0));
616
617        // Second call - with division by zero should succeed (returns infinity in floating point)
618        let call2 = "divide(10, 0)";
619        let result2 = parse_and_evaluate(call2, Some(heap), Some(bindings));
620        assert!(result2.is_ok());
621    }
622
623    #[test]
624    fn test_nested_lambda_error_reporting() {
625        // Test that nested lambdas report errors correctly
626        let heap = Rc::new(RefCell::new(Heap::new()));
627        let bindings = Rc::new(Environment::new());
628
629        // Define a lambda that returns another lambda
630        let outer_lambda = "outer = x => (y => x + y + undefined_nested)";
631        parse_and_evaluate(
632            outer_lambda,
633            Some(Rc::clone(&heap)),
634            Some(Rc::clone(&bindings)),
635        )
636        .unwrap();
637
638        // Call outer to get inner lambda
639        let get_inner = "inner = outer(5)";
640        parse_and_evaluate(
641            get_inner,
642            Some(Rc::clone(&heap)),
643            Some(Rc::clone(&bindings)),
644        )
645        .unwrap();
646
647        // Call inner lambda - should error
648        let call_inner = "inner(10)";
649        let result = parse_and_evaluate(call_inner, Some(heap), Some(bindings));
650
651        assert!(result.is_err());
652        let error = result.unwrap_err();
653        assert!(error.to_string().contains("undefined_nested"));
654    }
655}
656
657// Tests for lambda parameter isolation
658// Ensures that lambda parameters are not incorrectly captured from outer scope
659#[cfg(test)]
660mod lambda_parameter_isolation_tests {
661    use crate::environment::Environment;
662    use crate::expressions::evaluate_pairs;
663    use crate::heap::Heap;
664    use crate::parser::{Rule, get_pairs};
665    use crate::values::{SerializableValue, Value};
666    use std::cell::RefCell;
667    use std::rc::Rc;
668
669    fn parse_and_evaluate(
670        code: &str,
671        heap: Option<Rc<RefCell<Heap>>>,
672        bindings: Option<Rc<Environment>>,
673    ) -> Result<Value, crate::error::RuntimeError> {
674        let pairs = get_pairs(code).map_err(|e| crate::error::RuntimeError::new(e.to_string()))?;
675
676        let heap = heap.unwrap_or_else(|| Rc::new(RefCell::new(Heap::new())));
677        let bindings = bindings.unwrap_or_else(|| Rc::new(Environment::new()));
678
679        let mut result = Value::Null;
680        for pair in pairs {
681            match pair.as_rule() {
682                Rule::statement => {
683                    if let Some(inner_pair) = pair.into_inner().next() {
684                        match inner_pair.as_rule() {
685                            Rule::expression | Rule::assignment => {
686                                result = evaluate_pairs(
687                                    inner_pair.into_inner(),
688                                    Rc::clone(&heap),
689                                    Rc::clone(&bindings),
690                                    0,
691                                    code,
692                                )?;
693                            }
694                            _ => {}
695                        }
696                    }
697                }
698                Rule::EOI => {}
699                _ => {}
700            }
701        }
702        Ok(result)
703    }
704
705    #[test]
706    fn test_lambda_parameters_not_captured_from_outer_scope() {
707        // Regression test for bug where lambda parameters were incorrectly captured
708        // from outer scope instead of being excluded from closure
709        let heap = Rc::new(RefCell::new(Heap::new()));
710        let bindings = Rc::new(Environment::new());
711
712        // Define x in outer scope
713        parse_and_evaluate("x = 42", Some(Rc::clone(&heap)), Some(Rc::clone(&bindings))).unwrap();
714
715        // Define a lambda with parameter x - should NOT capture outer x
716        parse_and_evaluate(
717            "f = x => x + 1",
718            Some(Rc::clone(&heap)),
719            Some(Rc::clone(&bindings)),
720        )
721        .unwrap();
722
723        // Get the lambda value and check its scope
724        let f_value = bindings.get("f").unwrap();
725        {
726            let heap_ref = heap.borrow();
727            let lambda_def = f_value.as_lambda(&heap_ref).unwrap();
728
729            // The lambda's scope should NOT contain x
730            assert!(
731                !lambda_def.scope.contains_key("x"),
732                "Lambda should not capture its own parameter 'x' from outer scope"
733            );
734        }
735
736        // Call f(10) should return 11, not 43 (which would happen if outer x was captured)
737        let result = parse_and_evaluate("f(10)", Some(heap), Some(bindings)).unwrap();
738        assert_eq!(result, Value::Number(11.0));
739    }
740
741    #[test]
742    fn test_lambda_multiple_parameters_not_captured() {
743        // Test that all lambda parameters are excluded from closure
744        let heap = Rc::new(RefCell::new(Heap::new()));
745        let bindings = Rc::new(Environment::new());
746
747        // Define variables in outer scope with same names as parameters
748        parse_and_evaluate("a = 1", Some(Rc::clone(&heap)), Some(Rc::clone(&bindings))).unwrap();
749        parse_and_evaluate("b = 2", Some(Rc::clone(&heap)), Some(Rc::clone(&bindings))).unwrap();
750
751        // Define lambda with parameters a and b
752        parse_and_evaluate(
753            "add = (a, b) => a + b",
754            Some(Rc::clone(&heap)),
755            Some(Rc::clone(&bindings)),
756        )
757        .unwrap();
758
759        // Get the lambda value and check its scope
760        let add_value = bindings.get("add").unwrap();
761        {
762            let heap_ref = heap.borrow();
763            let lambda_def = add_value.as_lambda(&heap_ref).unwrap();
764
765            // The lambda's scope should NOT contain a or b
766            assert!(
767                !lambda_def.scope.contains_key("a"),
768                "Lambda should not capture its parameter 'a'"
769            );
770            assert!(
771                !lambda_def.scope.contains_key("b"),
772                "Lambda should not capture its parameter 'b'"
773            );
774        }
775
776        // Call add(10, 20) should return 30, not 3 (which would use captured values)
777        let result = parse_and_evaluate("add(10, 20)", Some(heap), Some(bindings)).unwrap();
778        assert_eq!(result, Value::Number(30.0));
779    }
780
781    #[test]
782    fn test_nested_lambda_parameters_not_captured() {
783        // Test that nested lambdas don't capture their own parameters
784        let heap = Rc::new(RefCell::new(Heap::new()));
785        let bindings = Rc::new(Environment::new());
786
787        // Define x in outer scope
788        parse_and_evaluate(
789            "x = 100",
790            Some(Rc::clone(&heap)),
791            Some(Rc::clone(&bindings)),
792        )
793        .unwrap();
794
795        // Define nested lambda - inner lambda should not capture its own x parameter
796        parse_and_evaluate(
797            "outer = x => (y => x + y)",
798            Some(Rc::clone(&heap)),
799            Some(Rc::clone(&bindings)),
800        )
801        .unwrap();
802
803        // Get outer lambda and check its scope
804        let outer_value = bindings.get("outer").unwrap();
805        {
806            let heap_ref = heap.borrow();
807            let outer_lambda = outer_value.as_lambda(&heap_ref).unwrap();
808
809            // Outer lambda should NOT capture x from global scope
810            assert!(
811                !outer_lambda.scope.contains_key("x"),
812                "Outer lambda should not capture its own parameter 'x'"
813            );
814        }
815
816        // Call outer(5) to get inner lambda
817        parse_and_evaluate(
818            "inner = outer(5)",
819            Some(Rc::clone(&heap)),
820            Some(Rc::clone(&bindings)),
821        )
822        .unwrap();
823
824        // Call inner(3) should return 8 (5 + 3), not 103 (100 + 3)
825        let result = parse_and_evaluate("inner(3)", Some(heap), Some(bindings)).unwrap();
826        assert_eq!(result, Value::Number(8.0));
827    }
828
829    #[test]
830    fn test_lambda_serialization_preserves_parameter_names() {
831        // Test that when serializing lambdas, parameter names are not substituted
832        let heap = Rc::new(RefCell::new(Heap::new()));
833        let bindings = Rc::new(Environment::new());
834
835        // Define x in outer scope
836        parse_and_evaluate("x = 42", Some(Rc::clone(&heap)), Some(Rc::clone(&bindings))).unwrap();
837
838        // Define lambda with parameter x
839        parse_and_evaluate(
840            "increment = x => x + 1",
841            Some(Rc::clone(&heap)),
842            Some(Rc::clone(&bindings)),
843        )
844        .unwrap();
845
846        // Get the lambda and serialize it
847        let increment_value = bindings.get("increment").unwrap();
848        let serialized = increment_value
849            .to_serializable_value(&heap.borrow())
850            .unwrap();
851
852        // Check that the serialized body contains "x", not "42"
853        if let SerializableValue::Lambda(lambda_def) = serialized {
854            assert!(
855                lambda_def.body.contains("x"),
856                "Serialized lambda body should contain parameter name 'x', got: {}",
857                lambda_def.body
858            );
859            assert!(
860                !lambda_def.body.contains("42"),
861                "Serialized lambda body should not contain value '42', got: {}",
862                lambda_def.body
863            );
864            assert_eq!(
865                lambda_def.body, "x + 1",
866                "Serialized lambda body should be 'x + 1', got: {}",
867                lambda_def.body
868            );
869        } else {
870            panic!("Expected Lambda, got: {:?}", serialized);
871        }
872    }
873
874    #[test]
875    fn test_curried_function_captures_correctly() {
876        // Test that curried functions capture outer parameters but not their own
877        let heap = Rc::new(RefCell::new(Heap::new()));
878        let bindings = Rc::new(Environment::new());
879
880        // Define curried add function
881        parse_and_evaluate(
882            "curry_add = x => y => x + y",
883            Some(Rc::clone(&heap)),
884            Some(Rc::clone(&bindings)),
885        )
886        .unwrap();
887
888        // Call curry_add(5) to get a partially applied function
889        parse_and_evaluate(
890            "add_five = curry_add(5)",
891            Some(Rc::clone(&heap)),
892            Some(Rc::clone(&bindings)),
893        )
894        .unwrap();
895
896        // Get add_five and check its scope
897        let add_five_value = bindings.get("add_five").unwrap();
898        {
899            let heap_ref = heap.borrow();
900            let add_five_lambda = add_five_value.as_lambda(&heap_ref).unwrap();
901
902            // add_five should capture x=5 from outer scope
903            assert!(
904                add_five_lambda.scope.contains_key("x"),
905                "add_five should capture 'x' from outer scope"
906            );
907            assert_eq!(
908                add_five_lambda.scope.get("x").unwrap().as_number().unwrap(),
909                5.0
910            );
911
912            // but should NOT capture its own parameter y
913            assert!(
914                !add_five_lambda.scope.contains_key("y"),
915                "add_five should not capture its own parameter 'y'"
916            );
917        }
918
919        // Serialize and check body
920        let serialized = add_five_value
921            .to_serializable_value(&heap.borrow())
922            .unwrap();
923
924        if let SerializableValue::Lambda(lambda_def) = serialized {
925            // Body should be "5 + y" (x captured as 5, y preserved as parameter)
926            assert_eq!(
927                lambda_def.body, "5 + y",
928                "Serialized body should be '5 + y', got: {}",
929                lambda_def.body
930            );
931        } else {
932            panic!("Expected Lambda, got: {:?}", serialized);
933        }
934    }
935}
936
937// Tests for the where operator
938#[cfg(test)]
939mod where_operator_tests {
940    use crate::environment::Environment;
941    use crate::expressions::evaluate_pairs;
942    use crate::heap::{Heap, HeapPointer};
943    use crate::parser::{Rule, get_pairs};
944    use crate::values::Value;
945    use std::cell::RefCell;
946    use std::rc::Rc;
947
948    fn parse_and_evaluate(
949        code: &str,
950    ) -> Result<(Value, Rc<RefCell<Heap>>), crate::error::RuntimeError> {
951        let pairs = get_pairs(code).map_err(|e| crate::error::RuntimeError::new(e.to_string()))?;
952
953        let heap = Rc::new(RefCell::new(Heap::new()));
954        let bindings = Rc::new(Environment::new());
955
956        let mut result = Value::Null;
957        for pair in pairs {
958            match pair.as_rule() {
959                Rule::statement => {
960                    if let Some(inner_pair) = pair.into_inner().next() {
961                        match inner_pair.as_rule() {
962                            Rule::expression | Rule::assignment => {
963                                result = evaluate_pairs(
964                                    inner_pair.into_inner(),
965                                    Rc::clone(&heap),
966                                    Rc::clone(&bindings),
967                                    0,
968                                    code,
969                                )?;
970                            }
971                            _ => {}
972                        }
973                    }
974                }
975                Rule::EOI => {}
976                _ => {}
977            }
978        }
979        Ok((result, heap))
980    }
981
982    #[test]
983    fn test_where_basic_filtering() {
984        let (result, heap) = parse_and_evaluate("[1, 2, 3, 4, 5] where x => x > 3").unwrap();
985        let borrowed_heap = heap.borrow();
986        let list = result.as_list(&borrowed_heap).unwrap();
987        assert_eq!(list.len(), 2);
988        assert_eq!(list[0], Value::Number(4.0));
989        assert_eq!(list[1], Value::Number(5.0));
990    }
991
992    #[test]
993    fn test_where_with_index() {
994        let (result, heap) =
995            parse_and_evaluate("[10, 20, 30, 40] where (val, idx) => idx > 0").unwrap();
996        let borrowed_heap = heap.borrow();
997        let list = result.as_list(&borrowed_heap).unwrap();
998        assert_eq!(list.len(), 3);
999        assert_eq!(list[0], Value::Number(20.0));
1000        assert_eq!(list[1], Value::Number(30.0));
1001        assert_eq!(list[2], Value::Number(40.0));
1002    }
1003
1004    #[test]
1005    fn test_where_empty_list() {
1006        let (result, heap) = parse_and_evaluate("[] where x => x > 3").unwrap();
1007        let borrowed_heap = heap.borrow();
1008        let list = result.as_list(&borrowed_heap).unwrap();
1009        assert_eq!(list.len(), 0);
1010    }
1011
1012    #[test]
1013    fn test_where_all_filtered_out() {
1014        let (result, heap) = parse_and_evaluate("[1, 2, 3] where x => x > 10").unwrap();
1015        let borrowed_heap = heap.borrow();
1016        let list = result.as_list(&borrowed_heap).unwrap();
1017        assert_eq!(list.len(), 0);
1018    }
1019
1020    #[test]
1021    fn test_where_none_filtered_out() {
1022        let (result, heap) = parse_and_evaluate("[1, 2, 3] where x => true").unwrap();
1023        let borrowed_heap = heap.borrow();
1024        let list = result.as_list(&borrowed_heap).unwrap();
1025        assert_eq!(list.len(), 3);
1026        assert_eq!(list[0], Value::Number(1.0));
1027        assert_eq!(list[1], Value::Number(2.0));
1028        assert_eq!(list[2], Value::Number(3.0));
1029    }
1030
1031    #[test]
1032    fn test_where_scalar_error() {
1033        let result = parse_and_evaluate("5 where x => x > 3");
1034        assert!(result.is_err());
1035        let error_msg = result.unwrap_err().to_string();
1036        assert!(error_msg.contains("where operator requires a list on the left side"));
1037    }
1038
1039    #[test]
1040    fn test_where_non_function_error() {
1041        let result = parse_and_evaluate("[1, 2, 3] where 5");
1042        assert!(result.is_err());
1043        let error_msg = result.unwrap_err().to_string();
1044        assert!(error_msg.contains("can't call a non-function"));
1045    }
1046
1047    #[test]
1048    fn test_where_non_boolean_return_error() {
1049        let result = parse_and_evaluate("[1, 2, 3] where x => x");
1050        assert!(result.is_err());
1051        let error_msg = result.unwrap_err().to_string();
1052        assert!(error_msg.contains("expected a boolean"));
1053    }
1054
1055    #[test]
1056    fn test_where_with_string_comparison() {
1057        let heap = Rc::new(RefCell::new(Heap::new()));
1058        let pairs = crate::parser::get_pairs(
1059            "[\"apple\", \"banana\", \"cherry\"] where s => s == \"banana\"",
1060        )
1061        .unwrap();
1062        let bindings = Rc::new(Environment::new());
1063
1064        let mut result = Value::Null;
1065        for pair in pairs {
1066            if let crate::parser::Rule::statement = pair.as_rule() {
1067                if let Some(inner_pair) = pair.into_inner().next() {
1068                    if let crate::parser::Rule::expression = inner_pair.as_rule() {
1069                        result = crate::expressions::evaluate_pairs(
1070                            inner_pair.into_inner(),
1071                            Rc::clone(&heap),
1072                            Rc::clone(&bindings),
1073                            0,
1074                            "[\"apple\", \"banana\", \"cherry\"] where s => s == \"banana\"",
1075                        )
1076                        .unwrap();
1077                    }
1078                }
1079            }
1080        }
1081
1082        let borrowed_heap = heap.borrow();
1083        let list = result.as_list(&borrowed_heap).unwrap();
1084        assert_eq!(list.len(), 1);
1085
1086        if let Value::String(s) = list[0] {
1087            let string_value = s.reify(&borrowed_heap).as_string().unwrap();
1088            assert_eq!(string_value, "banana");
1089        } else {
1090            panic!("Expected string value");
1091        }
1092    }
1093
1094    #[test]
1095    fn test_where_complex_predicate() {
1096        let (result, heap) =
1097            parse_and_evaluate("[1, 2, 3, 4, 5, 6] where x => x % 2 == 0").unwrap();
1098        let borrowed_heap = heap.borrow();
1099        let list = result.as_list(&borrowed_heap).unwrap();
1100        assert_eq!(list.len(), 3);
1101        assert_eq!(list[0], Value::Number(2.0));
1102        assert_eq!(list[1], Value::Number(4.0));
1103        assert_eq!(list[2], Value::Number(6.0));
1104    }
1105
1106    #[test]
1107    fn test_where_with_built_in_function() {
1108        let (result, heap) = parse_and_evaluate("[1, -2, 3, -4, 5] where x => x > 0").unwrap();
1109        let borrowed_heap = heap.borrow();
1110        let list = result.as_list(&borrowed_heap).unwrap();
1111        assert_eq!(list.len(), 3);
1112        assert_eq!(list[0], Value::Number(1.0));
1113        assert_eq!(list[1], Value::Number(3.0));
1114        assert_eq!(list[2], Value::Number(5.0));
1115    }
1116}
1117
1118// Tests for flat chaining of via/into/where operators
1119#[cfg(test)]
1120mod flat_chaining_tests {
1121    use crate::environment::Environment;
1122    use crate::expressions::evaluate_pairs;
1123    use crate::heap::Heap;
1124    use crate::parser::{Rule, get_pairs};
1125    use crate::values::Value;
1126    use std::cell::RefCell;
1127    use std::rc::Rc;
1128
1129    fn parse_and_evaluate(
1130        code: &str,
1131    ) -> Result<(Value, Rc<RefCell<Heap>>), crate::error::RuntimeError> {
1132        let pairs = get_pairs(code).map_err(|e| crate::error::RuntimeError::new(e.to_string()))?;
1133
1134        let heap = Rc::new(RefCell::new(Heap::new()));
1135        let bindings = Rc::new(Environment::new());
1136
1137        let mut result = Value::Null;
1138        for pair in pairs {
1139            match pair.as_rule() {
1140                Rule::statement => {
1141                    if let Some(inner_pair) = pair.into_inner().next() {
1142                        match inner_pair.as_rule() {
1143                            Rule::expression | Rule::assignment => {
1144                                result = evaluate_pairs(
1145                                    inner_pair.into_inner(),
1146                                    Rc::clone(&heap),
1147                                    Rc::clone(&bindings),
1148                                    0,
1149                                    code,
1150                                )?;
1151                            }
1152                            _ => {}
1153                        }
1154                    }
1155                }
1156                Rule::EOI => {}
1157                _ => {}
1158            }
1159        }
1160        Ok((result, heap))
1161    }
1162
1163    #[test]
1164    fn test_via_then_where() {
1165        let (result, heap) =
1166            parse_and_evaluate("[1, 2, 3, 4, 5] via x => x * 2 where y => y > 5").unwrap();
1167        let borrowed_heap = heap.borrow();
1168        let list = result.as_list(&borrowed_heap).unwrap();
1169        assert_eq!(list.len(), 3);
1170        assert_eq!(list[0], Value::Number(6.0));
1171        assert_eq!(list[1], Value::Number(8.0));
1172        assert_eq!(list[2], Value::Number(10.0));
1173    }
1174
1175    #[test]
1176    fn test_where_then_via() {
1177        let (result, heap) =
1178            parse_and_evaluate("[1, 2, 3, 4, 5] where x => x > 3 via y => y * 10").unwrap();
1179        let borrowed_heap = heap.borrow();
1180        let list = result.as_list(&borrowed_heap).unwrap();
1181        assert_eq!(list.len(), 2);
1182        assert_eq!(list[0], Value::Number(40.0));
1183        assert_eq!(list[1], Value::Number(50.0));
1184    }
1185
1186    #[test]
1187    fn test_via_where_via_chain() {
1188        let (result, heap) =
1189            parse_and_evaluate("[1, 2, 3, 4, 5, 6] via x => x * 2 where y => y > 5 via z => z + 1")
1190                .unwrap();
1191        let borrowed_heap = heap.borrow();
1192        let list = result.as_list(&borrowed_heap).unwrap();
1193        assert_eq!(list.len(), 4);
1194        assert_eq!(list[0], Value::Number(7.0));
1195        assert_eq!(list[1], Value::Number(9.0));
1196        assert_eq!(list[2], Value::Number(11.0));
1197        assert_eq!(list[3], Value::Number(13.0));
1198    }
1199
1200    #[test]
1201    fn test_via_where_into() {
1202        let (result, _heap) =
1203            parse_and_evaluate("[1, 2, 3, 4, 5] via x => x * 2 where x => x > 5 into sum").unwrap();
1204        assert_eq!(result, Value::Number(24.0));
1205    }
1206
1207    #[test]
1208    fn test_where_with_index_then_via() {
1209        let (result, heap) =
1210            parse_and_evaluate("[10, 20, 30, 40] where (val, idx) => idx > 0 via x => x / 10")
1211                .unwrap();
1212        let borrowed_heap = heap.borrow();
1213        let list = result.as_list(&borrowed_heap).unwrap();
1214        assert_eq!(list.len(), 3);
1215        assert_eq!(list[0], Value::Number(2.0));
1216        assert_eq!(list[1], Value::Number(3.0));
1217        assert_eq!(list[2], Value::Number(4.0));
1218    }
1219
1220    #[test]
1221    fn test_nested_via_requires_parens() {
1222        // This should work with parens
1223        let (result, heap) =
1224            parse_and_evaluate("[[1, 2], [3, 4]] via row => (row via x => x * 10)").unwrap();
1225        let borrowed_heap = heap.borrow();
1226        let outer_list = result.as_list(&borrowed_heap).unwrap();
1227        assert_eq!(outer_list.len(), 2);
1228
1229        let first_row = outer_list[0].as_list(&borrowed_heap).unwrap();
1230        assert_eq!(first_row[0], Value::Number(10.0));
1231        assert_eq!(first_row[1], Value::Number(20.0));
1232
1233        let second_row = outer_list[1].as_list(&borrowed_heap).unwrap();
1234        assert_eq!(second_row[0], Value::Number(30.0));
1235        assert_eq!(second_row[1], Value::Number(40.0));
1236    }
1237
1238    #[test]
1239    fn test_complex_chain_with_different_param_names() {
1240        let (result, heap) = parse_and_evaluate(
1241            "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] where a => a % 2 == 0 via b => b * 3 where c => c > 10"
1242        ).unwrap();
1243        let borrowed_heap = heap.borrow();
1244        let list = result.as_list(&borrowed_heap).unwrap();
1245        assert_eq!(list.len(), 4);
1246        assert_eq!(list[0], Value::Number(12.0)); // 4 * 3
1247        assert_eq!(list[1], Value::Number(18.0)); // 6 * 3
1248        assert_eq!(list[2], Value::Number(24.0)); // 8 * 3
1249        assert_eq!(list[3], Value::Number(30.0)); // 10 * 3
1250    }
1251}
1252
1253// Tests for via and into operator error messages
1254#[cfg(test)]
1255mod via_into_error_message_tests {
1256    use crate::environment::Environment;
1257    use crate::expressions::evaluate_pairs;
1258    use crate::heap::Heap;
1259    use crate::parser::{Rule, get_pairs};
1260    use crate::values::Value;
1261    use std::cell::RefCell;
1262    use std::rc::Rc;
1263
1264    fn parse_and_evaluate(code: &str) -> Result<Value, crate::error::RuntimeError> {
1265        let pairs = get_pairs(code).map_err(|e| crate::error::RuntimeError::new(e.to_string()))?;
1266
1267        let heap = Rc::new(RefCell::new(Heap::new()));
1268        let bindings = Rc::new(Environment::new());
1269
1270        let mut result = Value::Null;
1271        for pair in pairs {
1272            match pair.as_rule() {
1273                Rule::statement => {
1274                    if let Some(inner_pair) = pair.into_inner().next() {
1275                        match inner_pair.as_rule() {
1276                            Rule::expression | Rule::assignment => {
1277                                result = evaluate_pairs(
1278                                    inner_pair.into_inner(),
1279                                    Rc::clone(&heap),
1280                                    Rc::clone(&bindings),
1281                                    0,
1282                                    code,
1283                                )?;
1284                            }
1285                            _ => {}
1286                        }
1287                    }
1288                }
1289                Rule::EOI => {}
1290                _ => {}
1291            }
1292        }
1293        Ok(result)
1294    }
1295
1296    #[test]
1297    fn test_via_callback_error_message_with_list() {
1298        // Test that via errors say "in via callback" not "in built-in function map"
1299        let result = parse_and_evaluate("[1, 2, null] via x => x + 1");
1300        assert!(result.is_err());
1301        let error_msg = result.unwrap_err().to_string();
1302        assert!(
1303            error_msg.contains("in \"via\" callback"),
1304            "Error should mention 'via callback', got: {}",
1305            error_msg
1306        );
1307        assert!(
1308            !error_msg.contains("in built-in function \"map\""),
1309            "Error should not mention 'built-in function map', got: {}",
1310            error_msg
1311        );
1312    }
1313
1314    #[test]
1315    fn test_via_callback_error_message_scalar() {
1316        // Test scalar via also shows "via callback" context
1317        let result = parse_and_evaluate("null via x => x + 1");
1318        assert!(result.is_err());
1319        let error_msg = result.unwrap_err().to_string();
1320        assert!(
1321            error_msg.contains("in \"via\" callback"),
1322            "Error should mention 'via callback', got: {}",
1323            error_msg
1324        );
1325    }
1326
1327    #[test]
1328    fn test_via_callback_error_message_list_to_list() {
1329        // Test list-to-list via (zipped) also shows "via callback" context
1330        let result = parse_and_evaluate("[1, 2] via [x => x + 1, x => x + null]");
1331        assert!(result.is_err());
1332        let error_msg = result.unwrap_err().to_string();
1333        assert!(
1334            error_msg.contains("in \"via\" callback"),
1335            "Error should mention 'via callback', got: {}",
1336            error_msg
1337        );
1338    }
1339
1340    #[test]
1341    fn test_via_callback_error_includes_function_context() {
1342        // Test that the error also includes the function name context
1343        let result = parse_and_evaluate("[1, 2, null] via x => x + 1");
1344        assert!(result.is_err());
1345        let error_msg = result.unwrap_err().to_string();
1346        assert!(
1347            error_msg.contains("in anonymous function"),
1348            "Error should mention 'anonymous function', got: {}",
1349            error_msg
1350        );
1351    }
1352
1353    #[test]
1354    fn test_via_callback_error_with_named_function() {
1355        // Test that named functions show their name in the error
1356        let result = parse_and_evaluate("add_one = x => x + 1\n[1, 2, null] via add_one");
1357        assert!(result.is_err());
1358        let error_msg = result.unwrap_err().to_string();
1359        assert!(
1360            error_msg.contains("in \"via\" callback"),
1361            "Error should mention 'via callback', got: {}",
1362            error_msg
1363        );
1364        assert!(
1365            error_msg.contains("function \"add_one\""),
1366            "Error should mention function name, got: {}",
1367            error_msg
1368        );
1369    }
1370
1371    #[test]
1372    fn test_via_callback_error_with_index_parameter() {
1373        // Test that via with index parameter also shows proper error context
1374        let result = parse_and_evaluate("[1, 2, null] via (x, idx) => x + idx");
1375        assert!(result.is_err());
1376        let error_msg = result.unwrap_err().to_string();
1377        assert!(
1378            error_msg.contains("in \"via\" callback"),
1379            "Error should mention 'via callback', got: {}",
1380            error_msg
1381        );
1382    }
1383
1384    #[test]
1385    fn test_via_with_index_parameter_works() {
1386        // Test that via with index parameter actually works (regression test)
1387        let (result, heap) = {
1388            let pairs = crate::parser::get_pairs("[10, 20, 30] via (x, idx) => x + idx").unwrap();
1389            let heap = Rc::new(RefCell::new(crate::heap::Heap::new()));
1390            let bindings = Rc::new(Environment::new());
1391            let mut result = Value::Null;
1392            for pair in pairs {
1393                if let crate::parser::Rule::statement = pair.as_rule() {
1394                    if let Some(inner_pair) = pair.into_inner().next() {
1395                        result = crate::expressions::evaluate_pairs(
1396                            inner_pair.into_inner(),
1397                            Rc::clone(&heap),
1398                            Rc::clone(&bindings),
1399                            0,
1400                            "[10, 20, 30] via (x, idx) => x + idx",
1401                        )
1402                        .unwrap();
1403                    }
1404                }
1405            }
1406            (result, heap)
1407        };
1408        let borrowed_heap = heap.borrow();
1409        let list = result.as_list(&borrowed_heap).unwrap();
1410        assert_eq!(list.len(), 3);
1411        assert_eq!(list[0], Value::Number(10.0)); // 10 + 0
1412        assert_eq!(list[1], Value::Number(21.0)); // 20 + 1
1413        assert_eq!(list[2], Value::Number(32.0)); // 30 + 2
1414    }
1415
1416    #[test]
1417    fn test_into_callback_error_message_with_list() {
1418        // Test that into errors say "in into callback"
1419        let result = parse_and_evaluate("[1, 2, 3] into list => list + \"oops\"");
1420        assert!(result.is_err());
1421        let error_msg = result.unwrap_err().to_string();
1422        assert!(
1423            error_msg.contains("in \"into\" callback"),
1424            "Error should mention 'into callback', got: {}",
1425            error_msg
1426        );
1427    }
1428
1429    #[test]
1430    fn test_into_callback_error_message_scalar() {
1431        // Test scalar into also shows "into callback" context
1432        let result = parse_and_evaluate("5 into x => x + \"oops\"");
1433        assert!(result.is_err());
1434        let error_msg = result.unwrap_err().to_string();
1435        assert!(
1436            error_msg.contains("in \"into\" callback"),
1437            "Error should mention 'into callback', got: {}",
1438            error_msg
1439        );
1440    }
1441
1442    #[test]
1443    fn test_into_callback_error_includes_function_context() {
1444        // Test that the error also includes the function name context
1445        let result = parse_and_evaluate("[1, 2, 3] into list => list + \"oops\"");
1446        assert!(result.is_err());
1447        let error_msg = result.unwrap_err().to_string();
1448        assert!(
1449            error_msg.contains("in anonymous function"),
1450            "Error should mention 'anonymous function', got: {}",
1451            error_msg
1452        );
1453    }
1454
1455    #[test]
1456    fn test_into_callback_error_with_named_function() {
1457        // Test that named functions show their name in the error
1458        let result = parse_and_evaluate("bad_fn = list => list + \"oops\"\n[1, 2, 3] into bad_fn");
1459        assert!(result.is_err());
1460        let error_msg = result.unwrap_err().to_string();
1461        assert!(
1462            error_msg.contains("in \"into\" callback"),
1463            "Error should mention 'into callback', got: {}",
1464            error_msg
1465        );
1466        assert!(
1467            error_msg.contains("function \"bad_fn\""),
1468            "Error should mention function name, got: {}",
1469            error_msg
1470        );
1471    }
1472
1473    #[test]
1474    fn test_where_callback_error_message_with_list() {
1475        // Test that where errors say "in where callback" not "in built-in function filter"
1476        // Use x + 1 > 0 because x > 0 with null returns false (doesn't error)
1477        let result = parse_and_evaluate("[1, 2, null] where x => x + 1 > 0");
1478        assert!(result.is_err());
1479        let error_msg = result.unwrap_err().to_string();
1480        assert!(
1481            error_msg.contains("in \"where\" callback"),
1482            "Error should mention 'where callback', got: {}",
1483            error_msg
1484        );
1485        assert!(
1486            !error_msg.contains("in built-in function \"filter\""),
1487            "Error should not mention 'built-in function filter', got: {}",
1488            error_msg
1489        );
1490    }
1491
1492    #[test]
1493    fn test_where_callback_error_includes_function_context() {
1494        // Test that the error also includes the function name context
1495        let result = parse_and_evaluate("[1, 2, null] where x => x + 1 > 0");
1496        assert!(result.is_err());
1497        let error_msg = result.unwrap_err().to_string();
1498        assert!(
1499            error_msg.contains("in anonymous function"),
1500            "Error should mention 'anonymous function', got: {}",
1501            error_msg
1502        );
1503    }
1504
1505    #[test]
1506    fn test_where_callback_error_with_named_function() {
1507        // Test that named functions show their name in the error
1508        let result =
1509            parse_and_evaluate("is_positive = x => x + 1 > 0\n[1, 2, null] where is_positive");
1510        assert!(result.is_err());
1511        let error_msg = result.unwrap_err().to_string();
1512        assert!(
1513            error_msg.contains("in \"where\" callback"),
1514            "Error should mention 'where callback', got: {}",
1515            error_msg
1516        );
1517        assert!(
1518            error_msg.contains("function \"is_positive\""),
1519            "Error should mention function name, got: {}",
1520            error_msg
1521        );
1522    }
1523
1524    #[test]
1525    fn test_where_callback_error_with_index_parameter() {
1526        // Test that where with index parameter also shows proper error context
1527        let result = parse_and_evaluate("[1, 2, null] where (x, idx) => x + 1 > idx");
1528        assert!(result.is_err());
1529        let error_msg = result.unwrap_err().to_string();
1530        assert!(
1531            error_msg.contains("in \"where\" callback"),
1532            "Error should mention 'where callback', got: {}",
1533            error_msg
1534        );
1535    }
1536
1537    #[test]
1538    fn test_where_callback_error_on_non_boolean_return() {
1539        // Test that where errors on non-boolean return include "where callback" context
1540        let result = parse_and_evaluate("[1, 2, 3] where x => x * 2");
1541        assert!(result.is_err());
1542        let error_msg = result.unwrap_err().to_string();
1543        assert!(
1544            error_msg.contains("in \"where\" callback"),
1545            "Error should mention 'where callback', got: {}",
1546            error_msg
1547        );
1548    }
1549}