#[cfg(test)] mod test { use indoc::indoc; use pretty_assertions::assert_eq; #[test] fn test_tuple_literal_declaration() -> anyhow::Result<()> { let compiled = compile!( check r#" let (x, y) = (1, 2); "# ); assert!( compiled.errors.is_empty(), "Expected no errors, got: {:?}", compiled.errors ); assert_eq!( compiled.output, indoc! { " j main main: move r8 1 move r9 2 " } ); Ok(()) } #[test] fn test_tuple_literal_declaration_with_underscore() -> anyhow::Result<()> { let compiled = compile!( check r#" let (x, _) = (1, 2); "# ); assert!( compiled.errors.is_empty(), "Expected no errors, got: {:?}", compiled.errors ); assert_eq!( compiled.output, indoc! { " j main main: move r8 1 " } ); Ok(()) } #[test] fn test_tuple_literal_assignment() -> anyhow::Result<()> { let compiled = compile!( check r#" let x = 0; let y = 0; (x, y) = (5, 10); "# ); assert!( compiled.errors.is_empty(), "Expected no errors, got: {:?}", compiled.errors ); assert_eq!( compiled.output, indoc! { " j main main: move r8 0 move r9 0 move r8 5 move r9 10 " } ); Ok(()) } #[test] fn test_tuple_literal_with_variables() -> anyhow::Result<()> { let compiled = compile!( check r#" let a = 42; let b = 99; let (x, y) = (a, b); "# ); assert!( compiled.errors.is_empty(), "Expected no errors, got: {:?}", compiled.errors ); assert_eq!( compiled.output, indoc! { " j main main: move r8 42 move r9 99 move r10 r8 move r11 r9 " } ); Ok(()) } #[test] fn test_tuple_literal_three_elements() -> anyhow::Result<()> { let compiled = compile!( check r#" let (x, y, z) = (1, 2, 3); "# ); assert!( compiled.errors.is_empty(), "Expected no errors, got: {:?}", compiled.errors ); assert_eq!( compiled.output, indoc! { " j main main: move r8 1 move r9 2 move r10 3 " } ); Ok(()) } #[test] fn test_tuple_literal_assignment_with_underscore() -> anyhow::Result<()> { let compiled = compile!( check r#" let i = 0; let x = 123; (i, _) = (456, 789); "# ); assert!( compiled.errors.is_empty(), "Expected no errors, got: {:?}", compiled.errors ); assert_eq!( compiled.output, indoc! { " j main main: move r8 0 move r9 123 move r8 456 " } ); Ok(()) } #[test] fn test_tuple_return_simple() -> anyhow::Result<()> { let compiled = compile!( check r#" fn getPair() { return (10, 20); }; let (x, y) = getPair(); "# ); assert!( compiled.errors.is_empty(), "Expected no errors, got: {:?}", compiled.errors ); assert_eq!( compiled.output, indoc! { " j main getPair: push sp push ra push 10 push 20 sub r0 sp 4 get r0 db r0 move r15 r0 j __internal_L1 __internal_L1: sub r0 sp 3 get ra db r0 j ra main: jal getPair pop r9 pop r8 move sp r15 " } ); Ok(()) } #[test] fn test_tuple_return_with_underscore() -> anyhow::Result<()> { let compiled = compile!( check r#" fn getPair() { return (5, 15); }; let (x, _) = getPair(); "# ); assert!( compiled.errors.is_empty(), "Expected no errors, got: {:?}", compiled.errors ); assert_eq!( compiled.output, indoc! { " j main getPair: push sp push ra push 5 push 15 sub r0 sp 4 get r0 db r0 move r15 r0 j __internal_L1 __internal_L1: sub r0 sp 3 get ra db r0 j ra main: jal getPair pop r0 pop r8 move sp r15 " } ); Ok(()) } #[test] fn test_tuple_return_three_elements() -> anyhow::Result<()> { let compiled = compile!( check r#" fn getTriple() { return (1, 2, 3); }; let (a, b, c) = getTriple(); "# ); assert!( compiled.errors.is_empty(), "Expected no errors, got: {:?}", compiled.errors ); assert_eq!( compiled.output, indoc! { " j main getTriple: push sp push ra push 1 push 2 push 3 sub r0 sp 5 get r0 db r0 move r15 r0 j __internal_L1 __internal_L1: sub r0 sp 4 get ra db r0 j ra main: jal getTriple pop r10 pop r9 pop r8 move sp r15 " } ); Ok(()) } #[test] fn test_tuple_return_assignment() -> anyhow::Result<()> { let compiled = compile!( check r#" fn getPair() { return (42, 84); }; let i = 1; let j = 2; (i, j) = getPair(); "# ); assert!( compiled.errors.is_empty(), "Expected no errors, got: {:?}", compiled.errors ); assert_eq!( compiled.output, indoc! { " j main getPair: push sp push ra push 42 push 84 sub r0 sp 4 get r0 db r0 move r15 r0 j __internal_L1 __internal_L1: sub r0 sp 3 get ra db r0 j ra main: move r8 1 move r9 2 jal getPair pop r9 pop r8 move sp r15 " } ); Ok(()) } #[test] fn test_tuple_return_mismatch() -> anyhow::Result<()> { let errors = compile!( result r#" fn doSomething() { return (1, 2, 3); }; let (x, y) = doSomething(); "# ); // Should have exactly one error about tuple size mismatch assert_eq!(errors.len(), 1); // Check for the specific TupleSizeMismatch error match &errors[0] { crate::Error::TupleSizeMismatch(expected_size, actual_count, _) => { assert_eq!(*expected_size, 3); assert_eq!(*actual_count, 2); } e => panic!("Expected TupleSizeMismatch error, got: {:?}", e), } Ok(()) } #[test] fn test_tuple_return_called_by_non_tuple_return() -> anyhow::Result<()> { let compiled = compile!( check r#" fn doSomething() { return (1, 2); }; fn doSomethingElse() { let (x, y) = doSomething(); return y; }; let returnedValue = doSomethingElse(); "# ); assert!( compiled.errors.is_empty(), "Expected no errors, got: {:?}", compiled.errors ); assert_eq!( compiled.output, indoc! { " j main doSomething: push sp push ra push 1 push 2 sub r0 sp 4 get r0 db r0 move r15 r0 j __internal_L1 __internal_L1: sub r0 sp 3 get ra db r0 j ra doSomethingElse: push sp push ra jal doSomething pop r9 pop r8 move sp r15 move r15 r9 j __internal_L2 __internal_L2: pop ra pop sp j ra main: jal doSomethingElse move r8 r15 " } ); Ok(()) } #[test] fn test_non_tuple_return_called_by_tuple_return() -> anyhow::Result<()> { let compiled = compile!( check r#" fn getValue() { return 42; }; fn getTuple() { let x = getValue(); return (x, x); }; let (a, b) = getTuple(); "# ); assert!( compiled.errors.is_empty(), "Expected no errors, got: {:?}", compiled.errors ); assert_eq!( compiled.output, indoc! { " j main getValue: push sp push ra move r15 42 j __internal_L1 __internal_L1: pop ra pop sp j ra getTuple: push sp push ra jal getValue move r8 r15 push r8 push r8 sub r0 sp 4 get r0 db r0 move r15 r0 j __internal_L2 __internal_L2: sub r0 sp 3 get ra db r0 j ra main: jal getTuple pop r9 pop r8 move sp r15 " } ); Ok(()) } #[test] fn test_tuple_literal_size_mismatch() -> anyhow::Result<()> { let errors = compile!( result r#" let (x, y) = (1, 2, 3); "# ); // Should have exactly one error about tuple size mismatch assert_eq!(errors.len(), 1); assert!(matches!( errors[0], crate::Error::TupleSizeMismatch(_, _, _) )); Ok(()) } #[test] fn test_multiple_tuple_returns_in_function() -> anyhow::Result<()> { // Test multiple return paths in tuple-returning function let compiled = compile!( check r#" fn getValue(x) { if (x) { return (1, 2); } else { return (3, 4); } }; let (a, b) = getValue(1); "# ); assert!( compiled.errors.is_empty(), "Expected no errors, got: {:?}", compiled.errors ); assert_eq!( compiled.output, indoc! { " j main getValue: pop r8 push sp push ra beqz r8 __internal_L3 push 1 push 2 sub r0 sp 4 get r0 db r0 move r15 r0 j __internal_L1 j __internal_L2 __internal_L3: push 3 push 4 sub r0 sp 4 get r0 db r0 move r15 r0 j __internal_L1 __internal_L2: __internal_L1: sub r0 sp 3 get ra db r0 j ra main: push 1 jal getValue pop r9 pop r8 move sp r15 " }, ); Ok(()) } #[test] fn test_tuple_return_with_expression() -> anyhow::Result<()> { let compiled = compile!( check r#" fn add(x, y) { return (x, y); }; let (a, b) = add(5, 10); "# ); assert!( compiled.errors.is_empty(), "Expected no errors, got: {:?}", compiled.errors ); assert_eq!( compiled.output, indoc! { " j main add: pop r8 pop r9 push sp push ra push r9 push r8 sub r0 sp 4 get r0 db r0 move r15 r0 j __internal_L1 __internal_L1: sub r0 sp 3 get ra db r0 j ra main: push 5 push 10 jal add pop r9 pop r8 move sp r15 " } ); Ok(()) } #[test] fn test_nested_function_tuple_calls() -> anyhow::Result<()> { let compiled = compile!( check r#" fn inner() { return (1, 2); }; fn outer() { let (x, y) = inner(); return (y, x); }; let (a, b) = outer(); "# ); assert!( compiled.errors.is_empty(), "Expected no errors, got: {:?}", compiled.errors ); assert_eq!( compiled.output, indoc! { " j main inner: push sp push ra push 1 push 2 sub r0 sp 4 get r0 db r0 move r15 r0 j __internal_L1 __internal_L1: sub r0 sp 3 get ra db r0 j ra outer: push sp push ra jal inner pop r9 pop r8 move sp r15 push r9 push r8 sub r0 sp 4 get r0 db r0 move r15 r0 j __internal_L2 __internal_L2: sub r0 sp 3 get ra db r0 j ra main: jal outer pop r9 pop r8 move sp r15 " } ); Ok(()) } #[test] fn test_tuple_literal_with_constant_expressions() -> anyhow::Result<()> { let compiled = compile!( check r#" let (a, b) = (1 + 2, 3 * 4); "# ); assert!( compiled.errors.is_empty(), "Expected no errors, got: {:?}", compiled.errors ); assert_eq!( compiled.output, indoc! { " j main main: move r8 3 move r9 12 " } ); Ok(()) } #[test] fn test_tuple_literal_with_variable_expressions() -> anyhow::Result<()> { let compiled = compile!( check r#" let x = 5; let y = 10; let (a, b) = (x + 1, y * 2); "# ); assert!( compiled.errors.is_empty(), "Expected no errors, got: {:?}", compiled.errors ); assert_eq!( compiled.output, indoc! { " j main main: move r8 5 move r9 10 add r1 r8 1 move r10 r1 mul r2 r9 2 move r11 r2 " } ); Ok(()) } #[test] fn test_tuple_assignment_with_expressions() -> anyhow::Result<()> { let compiled = compile!( check r#" let a = 0; let b = 0; let x = 5; (a, b) = (x + 1, x * 2); "# ); assert!( compiled.errors.is_empty(), "Expected no errors, got: {:?}", compiled.errors ); assert_eq!( compiled.output, indoc! { " j main main: move r8 0 move r9 0 move r10 5 add r1 r10 1 move r8 r1 mul r2 r10 2 move r9 r2 " } ); Ok(()) } #[test] fn test_tuple_literal_with_function_calls() -> anyhow::Result<()> { let compiled = compile!( check r#" fn getValue() { return 42; }; fn getOther() { return 99; }; let (a, b) = (getValue(), getOther()); "# ); assert!( compiled.errors.is_empty(), "Expected no errors, got: {:?}", compiled.errors ); assert_eq!( compiled.output, indoc! { " j main getValue: push sp push ra move r15 42 j __internal_L1 __internal_L1: pop ra pop sp j ra getOther: push sp push ra move r15 99 j __internal_L2 __internal_L2: pop ra pop sp j ra main: push r8 jal getValue pop r8 move r1 r15 move r8 r1 push r8 push r9 jal getOther pop r9 pop r8 move r2 r15 move r9 r2 " } ); Ok(()) } #[test] fn test_tuple_with_logical_expressions() -> anyhow::Result<()> { let compiled = compile!( check r#" let x = 1; let y = 0; let (a, b) = (x && y, x || y); "# ); assert!( compiled.errors.is_empty(), "Expected no errors, got: {:?}", compiled.errors ); assert_eq!( compiled.output, indoc! { " j main main: move r8 1 move r9 0 and r1 r8 r9 move r10 r1 or r2 r8 r9 move r11 r2 " } ); Ok(()) } #[test] fn test_tuple_with_comparison_expressions() -> anyhow::Result<()> { let compiled = compile!( check r#" let x = 5; let y = 10; let (a, b) = (x > y, x < y); "# ); assert!( compiled.errors.is_empty(), "Expected no errors, got: {:?}", compiled.errors ); assert_eq!( compiled.output, indoc! { " j main main: move r8 5 move r9 10 sgt r1 r8 r9 move r10 r1 slt r2 r8 r9 move r11 r2 " } ); Ok(()) } #[test] fn test_tuple_with_device_property_access() -> anyhow::Result<()> { let compiled = compile!( check r#" device sensor = "d0"; device display = "d1"; let (temp, pressure) = (sensor.Temperature, sensor.Pressure); "# ); assert!( compiled.errors.is_empty(), "Expected no errors, got: {:?}", compiled.errors ); assert_eq!( compiled.output, indoc! { " j main main: l r1 d0 Temperature move r8 r1 l r2 d0 Pressure move r9 r2 " } ); Ok(()) } #[test] fn test_tuple_with_device_property_and_function_call() -> anyhow::Result<()> { let compiled = compile!( check r#" device self = "db"; fn getY() { return 42; } let (x, y) = (self.Setting, getY()); "# ); assert!( compiled.errors.is_empty(), "Expected no errors, got: {:?}", compiled.errors ); assert_eq!( compiled.output, indoc! { " j main getY: push sp push ra move r15 42 j __internal_L1 __internal_L1: pop ra pop sp j ra main: l r1 db Setting move r8 r1 push r8 push r9 jal getY pop r9 pop r8 move r2 r15 move r9 r2 " } ); Ok(()) } #[test] fn test_tuple_with_function_call_expressions() -> anyhow::Result<()> { let compiled = compile!( check r#" fn getValue() { return 10; } fn getOther() { return 20; } let (a, b) = (getValue() + 5, getOther() * 2); "# ); assert!( compiled.errors.is_empty(), "Expected no errors, got: {:?}", compiled.errors ); assert_eq!( compiled.output, indoc! { " j main getValue: push sp push ra move r15 10 j __internal_L1 __internal_L1: pop ra pop sp j ra getOther: push sp push ra move r15 20 j __internal_L2 __internal_L2: pop ra pop sp j ra main: push r8 jal getValue pop r8 move r1 r15 add r2 r1 5 move r8 r2 push r8 push r9 jal getOther pop r9 pop r8 move r3 r15 mul r4 r3 2 move r9 r4 " } ); Ok(()) } #[test] fn test_tuple_with_stack_spillover() -> anyhow::Result<()> { let compiled = compile!( check r#" fn get8() { return (1, 2, 3, 4, 5, 6, 7, 8); } let (a, b, c, d, e, f, g, h) = get8(); let sum = a + h; "# ); assert!( compiled.errors.is_empty(), "Expected no errors, got: {:?}", compiled.errors ); assert_eq!( compiled.output, indoc! { " j main get8: push sp push ra push 1 push 2 push 3 push 4 push 5 push 6 push 7 push 8 sub r0 sp 10 get r0 db r0 move r15 r0 j __internal_L1 __internal_L1: sub r0 sp 9 get ra db r0 j ra main: jal get8 pop r0 sub r0 sp 0 put db r0 r0 pop r14 pop r13 pop r12 pop r11 pop r10 pop r9 pop r8 move sp r15 sub r0 sp 1 get r1 db r0 add r2 r8 r1 push r2 sub sp sp 2 " } ); Ok(()) } #[test] fn test_tuple_return_in_loop() -> anyhow::Result<()> { let compiled = compile!( check r#" fn getValues(i) { return (i, i * 2); }; let sum = 0; let i = 0; loop { let (a, b) = getValues(i); sum = sum + a + b; i = i + 1; if (i > 3) { break; } } "# ); assert!( compiled.errors.is_empty(), "Expected no errors, got: {:?}", compiled.errors ); assert_eq!( compiled.output, indoc! { " j main getValues: pop r8 push sp push ra push r8 mul r1 r8 2 push r1 sub r0 sp 4 get r0 db r0 move r15 r0 j __internal_L1 __internal_L1: sub r0 sp 3 get ra db r0 j ra main: move r8 0 move r9 0 __internal_L2: push r9 jal getValues pop r11 pop r10 move sp r15 add r1 r8 r10 add r2 r1 r11 move r8 r2 add r3 r9 1 move r9 r3 sgt r4 r9 3 beqz r4 __internal_L4 j __internal_L3 __internal_L4: j __internal_L2 __internal_L3: " } ); Ok(()) } #[test] fn test_tuple_all_forms_combined() -> anyhow::Result<()> { // Test all three tuple forms in one test: // 1. Tuple literal declaration: let (x, y) = (1, 2); // 2. Tuple literal assignment: (x, y) = (3, 4); // 3. Function return tuple: let (a, b) = func(); let compiled = compile!( check r#" fn swap(x, y) { return (y, x); }; let (a, b) = (10, 20); // Literal declaration (a, b) = (30, 40); // Literal assignment let (c, d) = swap(a, b); // Function return "# ); assert!( compiled.errors.is_empty(), "Expected no errors, got: {:?}", compiled.errors ); assert_eq!( compiled.output, indoc! { " j main swap: pop r8 pop r9 push sp push ra push r8 push r9 sub r0 sp 4 get r0 db r0 move r15 r0 j __internal_L1 __internal_L1: sub r0 sp 3 get ra db r0 j ra main: move r8 10 move r9 20 move r8 30 move r9 40 push r8 push r9 jal swap pop r11 pop r10 move sp r15 " } ); Ok(()) } }