uni_core/primitives/
plus.rs

1// RUST CONCEPT: Modular primitive organization
2// Each primitive gets its own file with implementation and tests
3use crate::compat::{format, ToString};
4use crate::interpreter::Interpreter;
5use crate::primitives::numeric_promotion::promote_pair;
6use crate::value::{RuntimeError, Value};
7
8// RUST CONCEPT: Polymorphic addition - multiple numeric types and string concatenation
9// Stack-based addition: ( n1 n2 -- sum ) or ( str1 any -- concat ) or ( any str2 -- concat )
10// Supports automatic type promotion: Integer < Rational < GaussianInt < Number < Complex
11pub fn add_builtin(interp: &mut Interpreter) -> Result<(), RuntimeError> {
12    let b =
13        interp.pop_with_context("'+' requires exactly 2 values on the stack (e.g., '5 3 +')")?;
14    let a =
15        interp.pop_with_context("'+' requires exactly 2 values on the stack (e.g., '5 3 +')")?;
16
17    // Handle string concatenation first
18    match (&a, &b) {
19        (Value::String(_), _) | (_, Value::String(_)) => {
20            let str_a = match &a {
21                Value::String(s) => s.as_ref(),
22                _ => &a.to_string(),
23            };
24            let str_b = match &b {
25                Value::String(s) => s.as_ref(),
26                _ => &b.to_string(),
27            };
28            let result = format!("{}{}", str_a, str_b);
29            interp.push(Value::String(result.into()));
30            return Ok(());
31        }
32        _ => {}
33    }
34
35    // For numeric types, use type promotion
36    let (pa, pb) = promote_pair(&a, &b);
37
38    let result = match (&pa, &pb) {
39        // RUST CONCEPT: Fast path for Int32 - checked arithmetic for embedded safety
40        (Value::Int32(i1), Value::Int32(i2)) => {
41            match i1.checked_add(*i2) {
42                Some(result) => Value::Int32(result),
43                // Overflow: promote to BigInt
44                None => Value::Integer(num_bigint::BigInt::from(*i1) + num_bigint::BigInt::from(*i2)),
45            }
46        }
47        (Value::Integer(i1), Value::Integer(i2)) => Value::Integer(i1 + i2),
48        (Value::Rational(r1), Value::Rational(r2)) => Value::Rational(r1 + r2).demote(),
49        (Value::Number(n1), Value::Number(n2)) => Value::Number(n1 + n2),
50        #[cfg(feature = "complex_numbers")]
51        (Value::GaussianInt(re1, im1), Value::GaussianInt(re2, im2)) => {
52            Value::GaussianInt(re1 + re2, im1 + im2).demote()
53        }
54        #[cfg(feature = "complex_numbers")]
55        (Value::Complex(c1), Value::Complex(c2)) => Value::Complex(c1 + c2),
56        _ => {
57            return Err(RuntimeError::TypeError(format!(
58                "Cannot add {:?} and {:?}",
59                a, b
60            )))
61        }
62    };
63
64    interp.push(result);
65    Ok(())
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71    use crate::value::Value;
72
73    fn setup_interpreter() -> Interpreter {
74        Interpreter::new()
75    }
76
77    #[test]
78    fn test_add_builtin() {
79        let mut interp = setup_interpreter();
80
81        // Test basic addition
82        interp.push(Value::Number(3.0));
83        interp.push(Value::Number(5.0));
84        add_builtin(&mut interp).unwrap();
85
86        let result = interp.pop().unwrap();
87        assert!(matches!(result, Value::Number(n) if n == 8.0));
88
89        // Test with negative numbers
90        interp.push(Value::Number(-2.0));
91        interp.push(Value::Number(7.0));
92        add_builtin(&mut interp).unwrap();
93
94        let result = interp.pop().unwrap();
95        assert!(matches!(result, Value::Number(n) if n == 5.0));
96
97        // Test with zero
98        interp.push(Value::Number(0.0));
99        interp.push(Value::Number(42.0));
100        add_builtin(&mut interp).unwrap();
101
102        let result = interp.pop().unwrap();
103        assert!(matches!(result, Value::Number(n) if n == 42.0));
104    }
105
106    #[test]
107    fn test_add_builtin_stack_underflow() {
108        let mut interp = setup_interpreter();
109
110        // Test with empty stack
111        let result = add_builtin(&mut interp);
112        assert!(matches!(result, Err(RuntimeError::StackUnderflow)));
113
114        // Test with only one element
115        interp.push(Value::Number(5.0));
116        let result = add_builtin(&mut interp);
117        assert!(matches!(result, Err(RuntimeError::StackUnderflow)));
118    }
119
120    #[test]
121    fn test_add_builtin_string_concatenation() {
122        let mut interp = setup_interpreter();
123
124        // Test string + string
125        interp.push(Value::String("Hello ".into()));
126        interp.push(Value::String("World".into()));
127        add_builtin(&mut interp).unwrap();
128
129        let result = interp.pop().unwrap();
130        assert!(matches!(result, Value::String(s) if s.as_ref() == "Hello World"));
131
132        // Test string + number
133        interp.push(Value::String("Count: ".into()));
134        interp.push(Value::Number(42.0));
135        add_builtin(&mut interp).unwrap();
136
137        let result = interp.pop().unwrap();
138        assert!(matches!(result, Value::String(s) if s.as_ref() == "Count: 42"));
139
140        // Test number + string
141        interp.push(Value::Number(3.14));
142        interp.push(Value::String(" is pi".into()));
143        add_builtin(&mut interp).unwrap();
144
145        let result = interp.pop().unwrap();
146        assert!(matches!(result, Value::String(s) if s.as_ref() == "3.14 is pi"));
147
148        // Test string + boolean
149        interp.push(Value::String("Result: ".into()));
150        interp.push(Value::Boolean(true));
151        add_builtin(&mut interp).unwrap();
152
153        let result = interp.pop().unwrap();
154        assert!(matches!(result, Value::String(s) if s.as_ref() == "Result: true"));
155    }
156
157    #[test]
158    fn test_add_builtin_type_error() {
159        let mut interp = setup_interpreter();
160
161        // Test with incompatible types (no numbers or strings)
162        interp.push(Value::Boolean(true));
163        interp.push(Value::Boolean(false));
164        let result = add_builtin(&mut interp);
165        assert!(matches!(result, Err(RuntimeError::TypeError(_))));
166    }
167
168    #[test]
169    fn test_add_builtin_position_aware_error() {
170        use crate::tokenizer::SourcePos;
171        let mut interp = setup_interpreter();
172
173        // Set up a source position for error context (mock for testing)
174        let pos = SourcePos::new(2, 15, 20); // Line 2, column 15, offset 20
175        interp.current_pos = Some(pos);
176
177        // Test stack underflow with position information
178        let result = add_builtin(&mut interp);
179        assert!(result.is_err());
180
181        match result.unwrap_err() {
182            RuntimeError::StackUnderflowAt { pos, context } => {
183                assert_eq!(pos.line, 2);
184                assert_eq!(pos.column, 15);
185                assert_eq!(pos.offset, 20);
186                assert!(context.contains("'+' requires exactly 2 values"));
187            }
188            _ => panic!("Expected StackUnderflowAt error"),
189        }
190
191        // Test with only one element on stack
192        interp.push(Value::Number(42.0));
193        let result = add_builtin(&mut interp);
194        assert!(result.is_err());
195
196        match result.unwrap_err() {
197            RuntimeError::StackUnderflowAt { pos, context } => {
198                assert_eq!(pos.line, 2);
199                assert_eq!(pos.column, 15);
200                assert!(context.contains("'+' requires exactly 2 values"));
201            }
202            _ => panic!("Expected StackUnderflowAt error"),
203        }
204    }
205
206    #[test]
207    fn test_demonstrate_formatted_error_output() {
208        use crate::tokenizer::SourcePos;
209        let mut interp = setup_interpreter();
210
211        // Set up a source position that represents where '+' appears in source code
212        let pos = SourcePos::new(3, 8, 45); // Line 3, column 8, offset 45
213        interp.current_pos = Some(pos);
214
215        // Try to add without enough values on stack
216        let result = add_builtin(&mut interp);
217        assert!(result.is_err());
218
219        // Show the formatted error message
220        let error = result.unwrap_err();
221        let formatted_error = format!("{}", error);
222
223        // Print to demonstrate nice formatting (won't show in normal test run)
224        println!("Demo error message: {}", formatted_error);
225
226        // Verify the error message contains expected components
227        assert!(formatted_error.contains("line 3, column 8"));
228        assert!(formatted_error.contains("'+' requires exactly 2 values"));
229        assert!(formatted_error.contains("Stack underflow"));
230    }
231
232    // ========== TESTS FOR NEW NUMBER TYPES ==========
233
234    #[test]
235    fn test_add_bigint() {
236        use num_bigint::BigInt;
237        let mut interp = setup_interpreter();
238
239        // Test BigInt + BigInt
240        interp.push(Value::Integer(BigInt::from(123)));
241        interp.push(Value::Integer(BigInt::from(456)));
242        add_builtin(&mut interp).unwrap();
243
244        let result = interp.pop().unwrap();
245        assert!(matches!(result, Value::Integer(i) if i == BigInt::from(579)));
246
247        // Test large BigInt addition
248        let large1 = BigInt::parse_bytes(b"123456789012345678901234567890", 10).unwrap();
249        let large2 = BigInt::parse_bytes(b"987654321098765432109876543210", 10).unwrap();
250        let expected = BigInt::parse_bytes(b"1111111110111111111011111111100", 10).unwrap();
251
252        interp.push(Value::Integer(large1));
253        interp.push(Value::Integer(large2));
254        add_builtin(&mut interp).unwrap();
255
256        let result = interp.pop().unwrap();
257        assert!(matches!(result, Value::Integer(i) if i == expected));
258    }
259
260    #[test]
261    fn test_add_rational() {
262        use num_bigint::BigInt;
263        use num_rational::BigRational;
264        let mut interp = setup_interpreter();
265
266        // Test Rational + Rational: 1/2 + 1/3 = 5/6
267        let r1 = BigRational::new(BigInt::from(1), BigInt::from(2));
268        let r2 = BigRational::new(BigInt::from(1), BigInt::from(3));
269        let expected = BigRational::new(BigInt::from(5), BigInt::from(6));
270
271        interp.push(Value::Rational(r1));
272        interp.push(Value::Rational(r2));
273        add_builtin(&mut interp).unwrap();
274
275        let result = interp.pop().unwrap();
276        assert!(matches!(result, Value::Rational(r) if r == expected));
277
278        // Test Rational + Rational: 1/3 + 1/3 = 2/3
279        let r1 = BigRational::new(BigInt::from(1), BigInt::from(3));
280        let r2 = BigRational::new(BigInt::from(1), BigInt::from(3));
281        let expected = BigRational::new(BigInt::from(2), BigInt::from(3));
282
283        interp.push(Value::Rational(r1));
284        interp.push(Value::Rational(r2));
285        add_builtin(&mut interp).unwrap();
286
287        let result = interp.pop().unwrap();
288        assert!(matches!(result, Value::Rational(r) if r == expected));
289    }
290
291    #[test]
292    #[cfg(feature = "complex_numbers")]
293    fn test_add_complex() {
294        use num_complex::Complex64;
295        let mut interp = setup_interpreter();
296
297        // Test Complex + Complex: (3+4i) + (1+2i) = (4+6i)
298        let c1 = Complex64::new(3.0, 4.0);
299        let c2 = Complex64::new(1.0, 2.0);
300        let expected = Complex64::new(4.0, 6.0);
301
302        interp.push(Value::Complex(c1));
303        interp.push(Value::Complex(c2));
304        add_builtin(&mut interp).unwrap();
305
306        let result = interp.pop().unwrap();
307        assert!(matches!(result, Value::Complex(c) if c == expected));
308
309        // Test pure imaginary: 5i + 3i = 8i
310        let c1 = Complex64::new(0.0, 5.0);
311        let c2 = Complex64::new(0.0, 3.0);
312        let expected = Complex64::new(0.0, 8.0);
313
314        interp.push(Value::Complex(c1));
315        interp.push(Value::Complex(c2));
316        add_builtin(&mut interp).unwrap();
317
318        let result = interp.pop().unwrap();
319        assert!(matches!(result, Value::Complex(c) if c == expected));
320    }
321
322    #[test]
323    fn test_add_mixed_float_bigint() {
324        use num_bigint::BigInt;
325        let mut interp = setup_interpreter();
326
327        // Test float + BigInt: 5.0 + 10 = 15.0 (promotes to float since floats are inexact)
328        interp.push(Value::Number(5.0));
329        interp.push(Value::Integer(BigInt::from(10)));
330        add_builtin(&mut interp).unwrap();
331
332        let result = interp.pop().unwrap();
333        assert!(matches!(result, Value::Number(n) if n == 15.0));
334
335        // Test BigInt + float (reversed order)
336        interp.push(Value::Integer(BigInt::from(20)));
337        interp.push(Value::Number(7.0));
338        add_builtin(&mut interp).unwrap();
339
340        let result = interp.pop().unwrap();
341        assert!(matches!(result, Value::Number(n) if n == 27.0));
342    }
343
344    #[test]
345    fn test_add_mixed_float_rational() {
346        use num_bigint::BigInt;
347        use num_rational::BigRational;
348        let mut interp = setup_interpreter();
349
350        // Test float + Rational: 0.5 + 1/2 = 1.0 (promotes to float since floats are inexact)
351        interp.push(Value::Number(0.5));
352        interp.push(Value::Rational(BigRational::new(BigInt::from(1), BigInt::from(2))));
353        add_builtin(&mut interp).unwrap();
354
355        let result = interp.pop().unwrap();
356        // Result should be Number (float) since mixing exact and inexact promotes to inexact
357        assert!(matches!(result, Value::Number(n) if n == 1.0));
358    }
359
360    #[test]
361    #[cfg(feature = "complex_numbers")]
362    fn test_add_mixed_float_complex() {
363        use num_complex::Complex64;
364        let mut interp = setup_interpreter();
365
366        // Test float + Complex: 3.0 + (2+5i) = (5+5i)
367        interp.push(Value::Number(3.0));
368        interp.push(Value::Complex(Complex64::new(2.0, 5.0)));
369        add_builtin(&mut interp).unwrap();
370
371        let result = interp.pop().unwrap();
372        assert!(matches!(result, Value::Complex(c) if c == Complex64::new(5.0, 5.0)));
373    }
374
375    #[test]
376    fn test_add_mixed_bigint_rational() {
377        use num_bigint::BigInt;
378        use num_rational::BigRational;
379        let mut interp = setup_interpreter();
380
381        // Test BigInt + Rational: 2n + 1/2 = 5/2
382        interp.push(Value::Integer(BigInt::from(2)));
383        interp.push(Value::Rational(BigRational::new(BigInt::from(1), BigInt::from(2))));
384        add_builtin(&mut interp).unwrap();
385
386        let result = interp.pop().unwrap();
387        let expected = BigRational::new(BigInt::from(5), BigInt::from(2));
388        assert!(matches!(result, Value::Rational(r) if r == expected));
389    }
390
391    #[test]
392    #[cfg(feature = "complex_numbers")]
393    fn test_add_mixed_bigint_complex() {
394        use num_bigint::BigInt;
395        use num_complex::Complex64;
396        let mut interp = setup_interpreter();
397
398        // Test BigInt + Complex: 5n + (1+2i) = (6+2i)
399        interp.push(Value::Integer(BigInt::from(5)));
400        interp.push(Value::Complex(Complex64::new(1.0, 2.0)));
401        add_builtin(&mut interp).unwrap();
402
403        let result = interp.pop().unwrap();
404        assert!(matches!(result, Value::Complex(c) if c == Complex64::new(6.0, 2.0)));
405    }
406
407    #[test]
408    #[cfg(feature = "complex_numbers")]
409    fn test_add_mixed_rational_complex() {
410        use num_bigint::BigInt;
411        use num_complex::Complex64;
412        use num_rational::BigRational;
413        let mut interp = setup_interpreter();
414
415        // Test Rational + Complex: 1/2 + (3+4i)
416        interp.push(Value::Rational(BigRational::new(BigInt::from(1), BigInt::from(2))));
417        interp.push(Value::Complex(Complex64::new(3.0, 4.0)));
418        add_builtin(&mut interp).unwrap();
419
420        let result = interp.pop().unwrap();
421        assert!(matches!(result, Value::Complex(c) if c == Complex64::new(3.5, 4.0)));
422    }
423
424    // ========== EDGE CASE TESTS ==========
425
426    #[test]
427    fn test_add_bigint_zero() {
428        use num_bigint::BigInt;
429        let mut interp = setup_interpreter();
430
431        // Test 0n + 0n = 0n
432        interp.push(Value::Integer(BigInt::from(0)));
433        interp.push(Value::Integer(BigInt::from(0)));
434        add_builtin(&mut interp).unwrap();
435
436        let result = interp.pop().unwrap();
437        assert!(matches!(result, Value::Integer(i) if i == BigInt::from(0)));
438
439        // Test 0n + positive
440        interp.push(Value::Integer(BigInt::from(0)));
441        interp.push(Value::Integer(BigInt::from(42)));
442        add_builtin(&mut interp).unwrap();
443
444        let result = interp.pop().unwrap();
445        assert!(matches!(result, Value::Integer(i) if i == BigInt::from(42)));
446    }
447
448    #[test]
449    fn test_add_bigint_negative() {
450        use num_bigint::BigInt;
451        let mut interp = setup_interpreter();
452
453        // Test negative + negative
454        interp.push(Value::Integer(BigInt::from(-5)));
455        interp.push(Value::Integer(BigInt::from(-3)));
456        add_builtin(&mut interp).unwrap();
457
458        let result = interp.pop().unwrap();
459        assert!(matches!(result, Value::Integer(i) if i == BigInt::from(-8)));
460
461        // Test positive + negative
462        interp.push(Value::Integer(BigInt::from(10)));
463        interp.push(Value::Integer(BigInt::from(-7)));
464        add_builtin(&mut interp).unwrap();
465
466        let result = interp.pop().unwrap();
467        assert!(matches!(result, Value::Integer(i) if i == BigInt::from(3)));
468    }
469
470    #[test]
471    fn test_add_rational_zero() {
472        use num_bigint::BigInt;
473        use num_rational::BigRational;
474        let mut interp = setup_interpreter();
475
476        // Test 0/1 + 0/1 (should demote to Int32(0))
477        let zero = BigRational::new(BigInt::from(0), BigInt::from(1));
478        interp.push(Value::Rational(zero.clone()));
479        interp.push(Value::Rational(zero.clone()));
480        add_builtin(&mut interp).unwrap();
481
482        let result = interp.pop().unwrap();
483        assert!(matches!(result, Value::Int32(0)));
484
485        // Test 0/1 + 3/4
486        let frac = BigRational::new(BigInt::from(3), BigInt::from(4));
487        interp.push(Value::Rational(zero));
488        interp.push(Value::Rational(frac.clone()));
489        add_builtin(&mut interp).unwrap();
490
491        let result = interp.pop().unwrap();
492        assert!(matches!(result, Value::Rational(r) if r == frac));
493    }
494
495    #[test]
496    fn test_add_rational_negative() {
497        use num_bigint::BigInt;
498        use num_rational::BigRational;
499        let mut interp = setup_interpreter();
500
501        // Test -1/2 + 1/2 = 0 (demoted to Int32)
502        let neg_half = BigRational::new(BigInt::from(-1), BigInt::from(2));
503        let pos_half = BigRational::new(BigInt::from(1), BigInt::from(2));
504        interp.push(Value::Rational(neg_half));
505        interp.push(Value::Rational(pos_half));
506        add_builtin(&mut interp).unwrap();
507
508        let result = interp.pop().unwrap();
509        assert!(matches!(result, Value::Int32(0)));
510
511        // Test -3/4 + -1/4 = -1 (demoted to Int32)
512        let r1 = BigRational::new(BigInt::from(-3), BigInt::from(4));
513        let r2 = BigRational::new(BigInt::from(-1), BigInt::from(4));
514        interp.push(Value::Rational(r1));
515        interp.push(Value::Rational(r2));
516        add_builtin(&mut interp).unwrap();
517
518        let result = interp.pop().unwrap();
519        assert!(matches!(result, Value::Int32(-1)));
520    }
521
522    #[test]
523    fn test_add_rational_simplification() {
524        use num_bigint::BigInt;
525        use num_rational::BigRational;
526        let mut interp = setup_interpreter();
527
528        // Test 1/4 + 1/4 = 1/2 (auto-simplifies from 2/4)
529        let quarter = BigRational::new(BigInt::from(1), BigInt::from(4));
530        let half = BigRational::new(BigInt::from(1), BigInt::from(2));
531
532        interp.push(Value::Rational(quarter.clone()));
533        interp.push(Value::Rational(quarter));
534        add_builtin(&mut interp).unwrap();
535
536        let result = interp.pop().unwrap();
537        assert!(matches!(result, Value::Rational(r) if r == half));
538    }
539
540    #[test]
541    #[cfg(feature = "complex_numbers")]
542    fn test_add_complex_zero() {
543        use num_complex::Complex64;
544        let mut interp = setup_interpreter();
545
546        // Test 0+0i + 0+0i
547        let zero = Complex64::new(0.0, 0.0);
548        interp.push(Value::Complex(zero));
549        interp.push(Value::Complex(zero));
550        add_builtin(&mut interp).unwrap();
551
552        let result = interp.pop().unwrap();
553        assert!(matches!(result, Value::Complex(c) if c == zero));
554
555        // Test 0+0i + 3+4i
556        let c = Complex64::new(3.0, 4.0);
557        interp.push(Value::Complex(zero));
558        interp.push(Value::Complex(c));
559        add_builtin(&mut interp).unwrap();
560
561        let result = interp.pop().unwrap();
562        assert!(matches!(result, Value::Complex(c) if c == Complex64::new(3.0, 4.0)));
563    }
564
565    #[test]
566    #[cfg(feature = "complex_numbers")]
567    fn test_add_complex_negative() {
568        use num_complex::Complex64;
569        let mut interp = setup_interpreter();
570
571        // Test (3+4i) + (-3-4i) = 0+0i
572        let c1 = Complex64::new(3.0, 4.0);
573        let c2 = Complex64::new(-3.0, -4.0);
574        let zero = Complex64::new(0.0, 0.0);
575
576        interp.push(Value::Complex(c1));
577        interp.push(Value::Complex(c2));
578        add_builtin(&mut interp).unwrap();
579
580        let result = interp.pop().unwrap();
581        assert!(matches!(result, Value::Complex(c) if c == zero));
582
583        // Test negative real only: (-5+2i) + (3-1i)
584        let c1 = Complex64::new(-5.0, 2.0);
585        let c2 = Complex64::new(3.0, -1.0);
586        let expected = Complex64::new(-2.0, 1.0);
587
588        interp.push(Value::Complex(c1));
589        interp.push(Value::Complex(c2));
590        add_builtin(&mut interp).unwrap();
591
592        let result = interp.pop().unwrap();
593        assert!(matches!(result, Value::Complex(c) if c == expected));
594    }
595
596    #[test]
597    #[cfg(feature = "complex_numbers")]
598    fn test_add_complex_pure_real() {
599        use num_complex::Complex64;
600        let mut interp = setup_interpreter();
601
602        // Test (5+0i) + (3+0i) = (8+0i)
603        let c1 = Complex64::new(5.0, 0.0);
604        let c2 = Complex64::new(3.0, 0.0);
605        let expected = Complex64::new(8.0, 0.0);
606
607        interp.push(Value::Complex(c1));
608        interp.push(Value::Complex(c2));
609        add_builtin(&mut interp).unwrap();
610
611        let result = interp.pop().unwrap();
612        assert!(matches!(result, Value::Complex(c) if c == expected));
613    }
614
615    #[test]
616    #[cfg(feature = "complex_numbers")]
617    fn test_add_complex_pure_imaginary() {
618        use num_complex::Complex64;
619        let mut interp = setup_interpreter();
620
621        // Test (0+5i) + (0+3i) = (0+8i)
622        let c1 = Complex64::new(0.0, 5.0);
623        let c2 = Complex64::new(0.0, 3.0);
624        let expected = Complex64::new(0.0, 8.0);
625
626        interp.push(Value::Complex(c1));
627        interp.push(Value::Complex(c2));
628        add_builtin(&mut interp).unwrap();
629
630        let result = interp.pop().unwrap();
631        assert!(matches!(result, Value::Complex(c) if c == expected));
632    }
633
634    #[test]
635    fn test_add_mixed_float_bigint_fractional() {
636        use num_bigint::BigInt;
637        let mut interp = setup_interpreter();
638
639        // Test 3.5 + 10 = 13.5 (promotes to float since floats are inexact)
640        interp.push(Value::Number(3.5));
641        interp.push(Value::Integer(BigInt::from(10)));
642        add_builtin(&mut interp).unwrap();
643
644        let result = interp.pop().unwrap();
645        // Should be Number (float) since mixing exact and inexact promotes to inexact
646        assert!(matches!(result, Value::Number(n) if n == 13.5));
647    }
648
649    #[test]
650    #[cfg(feature = "complex_numbers")]
651    fn test_add_mixed_types_commutativity() {
652        use num_bigint::BigInt;
653        use num_complex::Complex64;
654        let mut interp = setup_interpreter();
655
656        // Test that mixed type addition is commutative: a + b == b + a
657        // BigInt + Complex
658        interp.push(Value::Integer(BigInt::from(5)));
659        interp.push(Value::Complex(Complex64::new(1.0, 2.0)));
660        add_builtin(&mut interp).unwrap();
661        let result1 = interp.pop().unwrap();
662
663        // Complex + BigInt (reversed)
664        interp.push(Value::Complex(Complex64::new(1.0, 2.0)));
665        interp.push(Value::Integer(BigInt::from(5)));
666        add_builtin(&mut interp).unwrap();
667        let result2 = interp.pop().unwrap();
668
669        // Both should be Complex(6.0, 2.0)
670        assert!(matches!(result1, Value::Complex(c) if c == Complex64::new(6.0, 2.0)));
671        assert!(matches!(result2, Value::Complex(c) if c == Complex64::new(6.0, 2.0)));
672    }
673
674    #[test]
675    #[cfg(feature = "complex_numbers")]
676    fn test_add_type_error_with_new_types() {
677        let mut interp = setup_interpreter();
678
679        // Test BigInt + Atom (should error)
680        use num_bigint::BigInt;
681        interp.push(Value::Integer(BigInt::from(5)));
682        interp.push(Value::Atom("foo".into()));
683        let result = add_builtin(&mut interp);
684        assert!(matches!(result, Err(RuntimeError::TypeError(_))));
685
686        // Test Rational + Boolean (should error)
687        use num_rational::BigRational;
688        interp.stack.clear();
689        interp.push(Value::Rational(BigRational::new(BigInt::from(1), BigInt::from(2))));
690        interp.push(Value::Boolean(true));
691        let result = add_builtin(&mut interp);
692        assert!(matches!(result, Err(RuntimeError::TypeError(_))));
693
694        // Test Complex + Nil (should error)
695        use num_complex::Complex64;
696        interp.stack.clear();
697        interp.push(Value::Complex(Complex64::new(1.0, 2.0)));
698        interp.push(Value::Nil);
699        let result = add_builtin(&mut interp);
700        assert!(matches!(result, Err(RuntimeError::TypeError(_))));
701    }
702
703    #[test]
704    fn test_add_very_large_bigint() {
705        use num_bigint::BigInt;
706        let mut interp = setup_interpreter();
707
708        // Test addition of very large numbers that would overflow i64
709        let large1 = BigInt::parse_bytes(b"99999999999999999999999999999999", 10).unwrap();
710        let large2 = BigInt::parse_bytes(b"11111111111111111111111111111111", 10).unwrap();
711        let expected = BigInt::parse_bytes(b"111111111111111111111111111111110", 10).unwrap();
712
713        interp.push(Value::Integer(large1));
714        interp.push(Value::Integer(large2));
715        add_builtin(&mut interp).unwrap();
716
717        let result = interp.pop().unwrap();
718        assert!(matches!(result, Value::Integer(i) if i == expected));
719    }
720
721    #[test]
722    fn test_add_infinity_and_nan() {
723        let mut interp = setup_interpreter();
724
725        // Test infinity + number
726        interp.push(Value::Number(f64::INFINITY));
727        interp.push(Value::Number(42.0));
728        add_builtin(&mut interp).unwrap();
729
730        let result = interp.pop().unwrap();
731        assert!(matches!(result, Value::Number(n) if n.is_infinite()));
732
733        // Test NaN + number
734        interp.push(Value::Number(f64::NAN));
735        interp.push(Value::Number(42.0));
736        add_builtin(&mut interp).unwrap();
737
738        let result = interp.pop().unwrap();
739        assert!(matches!(result, Value::Number(n) if n.is_nan()));
740    }
741
742    // ========== GAUSSIAN INTEGER TESTS ==========
743
744    #[test]
745    #[cfg(feature = "complex_numbers")]
746    #[cfg(feature = "complex_numbers")]
747    fn test_add_gaussian_int() {
748        use num_bigint::BigInt;
749        let mut interp = setup_interpreter();
750
751        // Test GaussianInt + GaussianInt: (3+4i) + (1+2i) = (4+6i)
752        interp.push(Value::GaussianInt(BigInt::from(3), BigInt::from(4)));
753        interp.push(Value::GaussianInt(BigInt::from(1), BigInt::from(2)));
754        add_builtin(&mut interp).unwrap();
755
756        let result = interp.pop().unwrap();
757        assert!(matches!(
758            result,
759            Value::GaussianInt(re, im) if re == BigInt::from(4) && im == BigInt::from(6)
760        ));
761
762        // Test negative imaginary: (5+3i) + (2-7i) = (7-4i)
763        interp.push(Value::GaussianInt(BigInt::from(5), BigInt::from(3)));
764        interp.push(Value::GaussianInt(BigInt::from(2), BigInt::from(-7)));
765        add_builtin(&mut interp).unwrap();
766
767        let result = interp.pop().unwrap();
768        assert!(matches!(
769            result,
770            Value::GaussianInt(re, im) if re == BigInt::from(7) && im == BigInt::from(-4)
771        ));
772    }
773
774    #[test]
775    #[cfg(feature = "complex_numbers")]
776    fn test_add_gaussian_int_zero() {
777        use num_bigint::BigInt;
778        let mut interp = setup_interpreter();
779
780        // Test 0+0i + 0+0i = 0 (demoted to Int32)
781        interp.push(Value::GaussianInt(BigInt::from(0), BigInt::from(0)));
782        interp.push(Value::GaussianInt(BigInt::from(0), BigInt::from(0)));
783        add_builtin(&mut interp).unwrap();
784
785        let result = interp.pop().unwrap();
786        assert!(matches!(result, Value::Int32(0)));
787
788        // Test 0+0i + 3+4i = 3+4i
789        interp.push(Value::GaussianInt(BigInt::from(0), BigInt::from(0)));
790        interp.push(Value::GaussianInt(BigInt::from(3), BigInt::from(4)));
791        add_builtin(&mut interp).unwrap();
792
793        let result = interp.pop().unwrap();
794        assert!(matches!(
795            result,
796            Value::GaussianInt(re, im) if re == BigInt::from(3) && im == BigInt::from(4)
797        ));
798    }
799
800    #[test]
801    #[cfg(feature = "complex_numbers")]
802    fn test_add_gaussian_int_pure_real() {
803        use num_bigint::BigInt;
804        let mut interp = setup_interpreter();
805
806        // Test (5+0i) + (3+0i) = 8 (demoted to Int32)
807        interp.push(Value::GaussianInt(BigInt::from(5), BigInt::from(0)));
808        interp.push(Value::GaussianInt(BigInt::from(3), BigInt::from(0)));
809        add_builtin(&mut interp).unwrap();
810
811        let result = interp.pop().unwrap();
812        assert!(matches!(result, Value::Int32(8)));
813    }
814
815    #[test]
816    #[cfg(feature = "complex_numbers")]
817    fn test_add_gaussian_int_pure_imaginary() {
818        use num_bigint::BigInt;
819        let mut interp = setup_interpreter();
820
821        // Test (0+5i) + (0+3i) = (0+8i)
822        interp.push(Value::GaussianInt(BigInt::from(0), BigInt::from(5)));
823        interp.push(Value::GaussianInt(BigInt::from(0), BigInt::from(3)));
824        add_builtin(&mut interp).unwrap();
825
826        let result = interp.pop().unwrap();
827        assert!(matches!(
828            result,
829            Value::GaussianInt(re, im) if re == BigInt::from(0) && im == BigInt::from(8)
830        ));
831    }
832
833    #[test]
834    #[cfg(feature = "complex_numbers")]
835    fn test_add_gaussian_int_with_bigint() {
836        use num_bigint::BigInt;
837        let mut interp = setup_interpreter();
838
839        // Test GaussianInt + BigInt: (3+4i) + 5 = (8+4i)
840        interp.push(Value::GaussianInt(BigInt::from(3), BigInt::from(4)));
841        interp.push(Value::Integer(BigInt::from(5)));
842        add_builtin(&mut interp).unwrap();
843
844        let result = interp.pop().unwrap();
845        assert!(matches!(
846            result,
847            Value::GaussianInt(re, im) if re == BigInt::from(8) && im == BigInt::from(4)
848        ));
849
850        // Test BigInt + GaussianInt (reversed): 7 + (2+3i) = (9+3i)
851        interp.push(Value::Integer(BigInt::from(7)));
852        interp.push(Value::GaussianInt(BigInt::from(2), BigInt::from(3)));
853        add_builtin(&mut interp).unwrap();
854
855        let result = interp.pop().unwrap();
856        assert!(matches!(
857            result,
858            Value::GaussianInt(re, im) if re == BigInt::from(9) && im == BigInt::from(3)
859        ));
860    }
861
862    #[test]
863    #[cfg(feature = "complex_numbers")]
864    #[cfg(feature = "complex_numbers")]
865    fn test_add_gaussian_int_with_float_promotes_to_complex() {
866        use num_bigint::BigInt;
867        use num_complex::Complex64;
868        let mut interp = setup_interpreter();
869
870        // Test GaussianInt + Float promotes to Complex64: (3+4i) + 2.5 = (5.5+4i)
871        interp.push(Value::GaussianInt(BigInt::from(3), BigInt::from(4)));
872        interp.push(Value::Number(2.5));
873        add_builtin(&mut interp).unwrap();
874
875        let result = interp.pop().unwrap();
876        assert!(matches!(result, Value::Complex(c) if c == Complex64::new(5.5, 4.0)));
877
878        // Test Float + GaussianInt (reversed): 1.5 + (2+3i) = (3.5+3i)
879        interp.push(Value::Number(1.5));
880        interp.push(Value::GaussianInt(BigInt::from(2), BigInt::from(3)));
881        add_builtin(&mut interp).unwrap();
882
883        let result = interp.pop().unwrap();
884        assert!(matches!(result, Value::Complex(c) if c == Complex64::new(3.5, 3.0)));
885    }
886
887    #[test]
888    #[cfg(feature = "complex_numbers")]
889    #[cfg(feature = "complex_numbers")]
890    fn test_add_gaussian_int_with_rational_promotes_to_complex() {
891        use num_bigint::BigInt;
892        use num_complex::Complex64;
893        use num_rational::BigRational;
894        let mut interp = setup_interpreter();
895
896        // Test GaussianInt + Rational promotes to Complex64: (3+4i) + 1/2 = (3.5+4i)
897        interp.push(Value::GaussianInt(BigInt::from(3), BigInt::from(4)));
898        interp.push(Value::Rational(BigRational::new(BigInt::from(1), BigInt::from(2))));
899        add_builtin(&mut interp).unwrap();
900
901        let result = interp.pop().unwrap();
902        assert!(matches!(result, Value::Complex(c) if c == Complex64::new(3.5, 4.0)));
903
904        // Test Rational + GaussianInt (reversed): 1/4 + (2+5i) = (2.25+5i)
905        interp.push(Value::Rational(BigRational::new(BigInt::from(1), BigInt::from(4))));
906        interp.push(Value::GaussianInt(BigInt::from(2), BigInt::from(5)));
907        add_builtin(&mut interp).unwrap();
908
909        let result = interp.pop().unwrap();
910        assert!(matches!(result, Value::Complex(c) if c == Complex64::new(2.25, 5.0)));
911    }
912
913    #[test]
914    #[cfg(feature = "complex_numbers")]
915    #[cfg(feature = "complex_numbers")]
916    fn test_add_gaussian_int_with_complex() {
917        use num_bigint::BigInt;
918        use num_complex::Complex64;
919        let mut interp = setup_interpreter();
920
921        // Test GaussianInt + Complex: (3+4i) + (1.5+2.5i) = (4.5+6.5i)
922        interp.push(Value::GaussianInt(BigInt::from(3), BigInt::from(4)));
923        interp.push(Value::Complex(Complex64::new(1.5, 2.5)));
924        add_builtin(&mut interp).unwrap();
925
926        let result = interp.pop().unwrap();
927        assert!(matches!(result, Value::Complex(c) if c == Complex64::new(4.5, 6.5)));
928
929        // Test Complex + GaussianInt (reversed): (2.5+1.5i) + (5+3i) = (7.5+4.5i)
930        interp.push(Value::Complex(Complex64::new(2.5, 1.5)));
931        interp.push(Value::GaussianInt(BigInt::from(5), BigInt::from(3)));
932        add_builtin(&mut interp).unwrap();
933
934        let result = interp.pop().unwrap();
935        assert!(matches!(result, Value::Complex(c) if c == Complex64::new(7.5, 4.5)));
936    }
937
938    #[test]
939    #[cfg(feature = "complex_numbers")]
940    fn test_add_gaussian_int_large_values() {
941        use num_bigint::BigInt;
942        let mut interp = setup_interpreter();
943
944        // Test with large Gaussian integers
945        let large_re1 = BigInt::parse_bytes(b"123456789012345", 10).unwrap();
946        let large_im1 = BigInt::parse_bytes(b"987654321098765", 10).unwrap();
947        let large_re2 = BigInt::parse_bytes(b"111111111111111", 10).unwrap();
948        let large_im2 = BigInt::parse_bytes(b"222222222222222", 10).unwrap();
949
950        let expected_re = BigInt::parse_bytes(b"234567900123456", 10).unwrap();
951        let expected_im = BigInt::parse_bytes(b"1209876543320987", 10).unwrap();
952
953        interp.push(Value::GaussianInt(large_re1, large_im1));
954        interp.push(Value::GaussianInt(large_re2, large_im2));
955        add_builtin(&mut interp).unwrap();
956
957        let result = interp.pop().unwrap();
958        assert!(matches!(
959            result,
960            Value::GaussianInt(re, im) if re == expected_re && im == expected_im
961        ));
962    }
963
964    #[test]
965    #[cfg(feature = "complex_numbers")]
966    fn test_add_gaussian_int_negative_parts() {
967        use num_bigint::BigInt;
968        let mut interp = setup_interpreter();
969
970        // Test (-3+4i) + (5-2i) = (2+2i)
971        interp.push(Value::GaussianInt(BigInt::from(-3), BigInt::from(4)));
972        interp.push(Value::GaussianInt(BigInt::from(5), BigInt::from(-2)));
973        add_builtin(&mut interp).unwrap();
974
975        let result = interp.pop().unwrap();
976        assert!(matches!(
977            result,
978            Value::GaussianInt(re, im) if re == BigInt::from(2) && im == BigInt::from(2)
979        ));
980
981        // Test (-5-3i) + (-2-7i) = (-7-10i)
982        interp.push(Value::GaussianInt(BigInt::from(-5), BigInt::from(-3)));
983        interp.push(Value::GaussianInt(BigInt::from(-2), BigInt::from(-7)));
984        add_builtin(&mut interp).unwrap();
985
986        let result = interp.pop().unwrap();
987        assert!(matches!(
988            result,
989            Value::GaussianInt(re, im) if re == BigInt::from(-7) && im == BigInt::from(-10)
990        ));
991    }
992}