From e2a45f0d05ab29c84d4b70d5b340815604e56b30 Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Tue, 30 Dec 2025 11:49:42 -0700 Subject: [PATCH] Added more tests and updated existing to use snapshot style testing --- .../libs/compiler/src/test/device_access.rs | 224 ++++++++ .../libs/compiler/src/test/edge_cases.rs | 500 ++++++++++++++++++ .../libs/compiler/src/test/error_handling.rs | 197 +++++++ rust_compiler/libs/compiler/src/test/mod.rs | 5 + .../compiler/src/test/negation_priority.rs | 304 +++++++++++ .../libs/compiler/src/test/scoping.rs | 428 +++++++++++++++ rust_compiler/libs/compiler/src/v1.rs | 29 +- 7 files changed, 1681 insertions(+), 6 deletions(-) create mode 100644 rust_compiler/libs/compiler/src/test/device_access.rs create mode 100644 rust_compiler/libs/compiler/src/test/edge_cases.rs create mode 100644 rust_compiler/libs/compiler/src/test/error_handling.rs create mode 100644 rust_compiler/libs/compiler/src/test/negation_priority.rs create mode 100644 rust_compiler/libs/compiler/src/test/scoping.rs diff --git a/rust_compiler/libs/compiler/src/test/device_access.rs b/rust_compiler/libs/compiler/src/test/device_access.rs new file mode 100644 index 0000000..e7afd82 --- /dev/null +++ b/rust_compiler/libs/compiler/src/test/device_access.rs @@ -0,0 +1,224 @@ +use indoc::indoc; +use pretty_assertions::assert_eq; + +#[test] +fn device_declaration() -> anyhow::Result<()> { + let compiled = compile! { + debug " + device d0 = \"d0\"; + " + }; + + // Declaration only emits the jump label header + assert_eq!(compiled, "j main\n"); + + Ok(()) +} + +#[test] +fn device_property_read() -> anyhow::Result<()> { + let compiled = compile! { + debug " + device ac = \"d0\"; + let temp = ac.Temperature; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + l r1 d0 Temperature + move r8 r1 + " + } + ); + + Ok(()) +} + +#[test] +fn device_property_write() -> anyhow::Result<()> { + let compiled = compile! { + debug " + device ac = \"d0\"; + ac.On = 1; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + s d0 On 1 + " + } + ); + + Ok(()) +} + +#[test] +fn multiple_device_declarations() -> anyhow::Result<()> { + let compiled = compile! { + debug " + device d0 = \"d0\"; + device d1 = \"d1\"; + device d2 = \"d2\"; + " + }; + + // Declarations only emit the header when unused + assert_eq!(compiled, "j main\n"); + + Ok(()) +} + +#[test] +fn device_with_variable_interaction() -> anyhow::Result<()> { + let compiled = compile! { + debug " + device sensor = \"d0\"; + let reading = sensor.Temperature; + let threshold = 373.15; + let alert = reading > threshold; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + l r1 d0 Temperature + move r8 r1 + move r9 373.15 + sgt r2 r8 r9 + move r10 r2 + " + } + ); + + Ok(()) +} + +#[test] +fn device_property_in_arithmetic() -> anyhow::Result<()> { + let compiled = compile! { + debug " + device d0 = \"d0\"; + let result = d0.Temperature + 100; + " + }; + + // Verify that we load property, add 100, and move to result + assert_eq!( + compiled, + indoc! { + " + j main + main: + l r1 d0 Temperature + add r2 r1 100 + move r8 r2 + " + } + ); + + Ok(()) +} + +#[test] +fn device_used_in_function() -> anyhow::Result<()> { + let compiled = compile! { + debug " + device d0 = \"d0\"; + + fn check_power() { + return d0.On; + }; + + let powered = check_power(); + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + check_power: + push ra + l r1 d0 On + move r15 r1 + j __internal_L1 + __internal_L1: + pop ra + j ra + main: + jal check_power + move r8 r15 + " + } + ); + + Ok(()) +} + +#[test] +fn device_in_conditional() -> anyhow::Result<()> { + let compiled = compile! { + debug " + device d0 = \"d0\"; + + if (d0.On) { + let x = 1; + } + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + l r1 d0 On + beqz r1 __internal_L1 + move r8 1 + __internal_L1: + " + } + ); + + Ok(()) +} + +#[test] +fn device_property_with_underscore_name() -> anyhow::Result<()> { + let compiled = compile! { + debug " + device cool_device = \"d0\"; + let value = cool_device.SomeProperty; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + l r1 d0 SomeProperty + move r8 r1 + " + } + ); + + Ok(()) +} diff --git a/rust_compiler/libs/compiler/src/test/edge_cases.rs b/rust_compiler/libs/compiler/src/test/edge_cases.rs new file mode 100644 index 0000000..b0a3b4a --- /dev/null +++ b/rust_compiler/libs/compiler/src/test/edge_cases.rs @@ -0,0 +1,500 @@ +use indoc::indoc; +use pretty_assertions::assert_eq; + +#[test] +fn zero_value_handling() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let x = 0; + let y = x + 0; + let z = x * 100; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 0 + add r1 r8 0 + move r9 r1 + mul r2 r8 100 + move r10 r2 + " + } + ); + + Ok(()) +} + +#[test] +fn negative_number_handling() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let x = -100; + let y = -x; + let z = -(-50); + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 -100 + move r9 50 + " + } + ); + + Ok(()) +} + +#[test] +fn large_number_constants() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let x = 999999999; + let y = x + 1; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 999999999 + add r1 r8 1 + move r9 r1 + " + } + ); + + Ok(()) +} + +#[test] +fn floating_point_precision() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let pi = 3.14159265; + let e = 2.71828182; + let sum = pi + e; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 3.14159265 + move r9 2.71828182 + add r1 r8 r9 + move r10 r1 + " + } + ); + + Ok(()) +} + +#[test] +fn temperature_unit_conversion() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let celsius = 20c; + let fahrenheit = 68f; + let kelvin = 293.15k; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 293.15 + move r9 293.15 + move r10 293.15 + " + } + ); + + Ok(()) +} + +#[test] +fn mixed_temperature_units() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let c = 0c; + let f = 32f; + let k = 273.15k; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 273.15 + move r9 273.15 + move r10 273.15 + " + } + ); + + Ok(()) +} + +#[test] +fn boolean_constant_folding() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let x = true; + let y = false; + let z = true && true; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 1 + move r9 0 + and r1 1 1 + move r10 r1 + " + } + ); + + Ok(()) +} + +#[test] +fn empty_block() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let x = 5; + { + } + let y = x; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 5 + move r9 r8 + " + } + ); + + Ok(()) +} + +#[test] +fn multiple_statements_same_line() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let x = 1; let y = 2; let z = 3; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 1 + move r9 2 + move r10 3 + " + } + ); + + Ok(()) +} + +#[test] +fn function_with_no_return() -> anyhow::Result<()> { + let compiled = compile! { + debug " + fn no_return() { + let x = 5; + }; + + no_return(); + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + no_return: + push ra + move r8 5 + __internal_L1: + pop ra + j ra + main: + jal no_return + move r1 r15 + " + } + ); + + Ok(()) +} + +#[test] +fn deeply_nested_expressions() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let x = ((((((((1 + 2) + 3) + 4) + 5) + 6) + 7) + 8) + 9); + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 45 + " + } + ); + + Ok(()) +} + +#[test] +fn constant_folding_with_operations() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let x = 10 * 5 + 3 - 2; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 51 + " + } + ); + + Ok(()) +} + +#[test] +fn constant_folding_with_division() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let x = 100 / 2 / 5; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 10 + " + } + ); + + Ok(()) +} + +#[test] +fn modulo_operation() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let x = 17 % 5; + let y = 10 % 3; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 2 + move r9 1 + " + } + ); + + Ok(()) +} + +#[test] +fn exponentiation() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let x = 2 ^ 8; + let y = 3 ^ 3; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r0 3 + move r1 3 + " + } + ); + + Ok(()) +} + +#[test] +fn comparison_with_zero() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let x = 0 == 0; + let y = 0 < 1; + let z = 0 > -1; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + seq r1 0 0 + move r8 r1 + slt r2 0 1 + move r9 r2 + sgt r3 0 -1 + move r10 r3 + " + } + ); + + Ok(()) +} + +#[test] +fn boolean_negation_edge_cases() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let x = !0; + let y = !1; + let z = !100; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + seq r1 0 0 + move r8 r1 + seq r2 1 0 + move r9 r2 + seq r3 100 0 + move r10 r3 + " + } + ); + + Ok(()) +} + +#[test] +fn function_with_many_parameters() -> anyhow::Result<()> { + let compiled = compile! { + debug " + fn many_params(a, b, c, d, e, f, g, h) { + return a + b + c + d + e + f + g + h; + }; + + let result = many_params(1, 2, 3, 4, 5, 6, 7, 8); + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + many_params: + pop r8 + pop r9 + pop r10 + pop r11 + pop r12 + pop r13 + pop r14 + push ra + sub r0 sp 2 + get r1 db r0 + add r2 r1 r14 + add r3 r2 r13 + add r4 r3 r12 + add r5 r4 r11 + add r6 r5 r10 + add r7 r6 r9 + add r1 r7 r8 + move r15 r1 + j __internal_L1 + __internal_L1: + pop ra + sub sp sp 1 + j ra + main: + push 1 + push 2 + push 3 + push 4 + push 5 + push 6 + push 7 + push 8 + jal many_params + move r8 r15 + " + } + ); + + Ok(()) +} diff --git a/rust_compiler/libs/compiler/src/test/error_handling.rs b/rust_compiler/libs/compiler/src/test/error_handling.rs new file mode 100644 index 0000000..116c2a3 --- /dev/null +++ b/rust_compiler/libs/compiler/src/test/error_handling.rs @@ -0,0 +1,197 @@ +use crate::Error; +use crate::variable_manager::Error as ScopeError; + +#[test] +fn unknown_identifier_error() { + let errors = compile! { + result "let x = unknown_var;" + }; + + assert_eq!(errors.len(), 1); + match &errors[0] { + Error::UnknownIdentifier(name, _) => { + assert_eq!(name.as_ref(), "unknown_var"); + } + _ => panic!("Expected UnknownIdentifier error, got {:?}", errors[0]), + } +} + +#[test] +fn duplicate_identifier_error() { + let errors = compile! { + result " + let x = 5; + let x = 10; + " + }; + + assert_eq!(errors.len(), 1); + match &errors[0] { + Error::Scope(ScopeError::DuplicateVariable(name, _)) => { + assert_eq!(name.as_ref(), "x"); + } + _ => panic!("Expected DuplicateIdentifier error, got {:?}", errors[0]), + } +} + +#[test] +fn const_reassignment_error() { + let errors = compile! { + result " + const PI = 3.14; + PI = 2.71; + " + }; + + assert_eq!(errors.len(), 1); + match &errors[0] { + Error::ConstAssignment(name, _) => { + assert_eq!(name.as_ref(), "PI"); + } + _ => panic!("Expected ConstAssignment error, got {:?}", errors[0]), + } +} + +#[test] +fn unknown_function_call_error() { + let errors = compile! { + result " + let result = unknown_function(); + " + }; + + assert_eq!(errors.len(), 1); + match &errors[0] { + Error::UnknownIdentifier(name, _) => { + assert_eq!(name.as_ref(), "unknown_function"); + } + _ => panic!("Expected UnknownIdentifier error, got {:?}", errors[0]), + } +} + +#[test] +fn argument_mismatch_error() { + let errors = compile! { + result " + fn add(a, b) { + return a + b; + }; + + let result = add(1); + " + }; + + // The error should be an AgrumentMismatch + assert!( + errors + .iter() + .any(|e| matches!(e, Error::AgrumentMismatch(_, _))) + ); +} + +#[test] +fn tuple_size_mismatch_error() { + let errors = compile! { + result " + fn pair() { + return (1, 2); + }; + + let (x, y, z) = pair(); + " + }; + + assert!( + errors + .iter() + .any(|e| matches!(e, Error::TupleSizeMismatch(2, 3, _))) + ); +} + +#[test] +fn multiple_errors_reported() { + let errors = compile! { + result " + let x = unknown1; + let x = 5; + let y = unknown2; + " + }; + + // Should have at least 3 errors + assert!( + errors.len() >= 2, + "Expected at least 2 errors, got {}", + errors.len() + ); +} + +#[test] +fn return_outside_function_error() { + let errors = compile! { + result " + let x = 5; + return x; + " + }; + + // Should have an error about return outside function + assert!( + !errors.is_empty(), + "Expected error for return outside function" + ); +} + +#[test] +fn break_outside_loop_error() { + let errors = compile! { + result " + break; + " + }; + + assert!(!errors.is_empty(), "Expected error for break outside loop"); +} + +#[test] +fn continue_outside_loop_error() { + let errors = compile! { + result " + continue; + " + }; + + assert!( + !errors.is_empty(), + "Expected error for continue outside loop" + ); +} + +#[test] +fn device_reassignment_error() { + let errors = compile! { + result " + device d0 = \"d0\"; + device d0 = \"d1\"; + " + }; + + assert!( + errors + .iter() + .any(|e| matches!(e, Error::DuplicateIdentifier(_, _))) + ); +} + +#[test] +fn invalid_device_error() { + let errors = compile! { + result " + device d0 = \"d0\"; + d0 = \"d1\"; + " + }; + + // Device reassignment should fail + assert!(!errors.is_empty(), "Expected error for device reassignment"); +} diff --git a/rust_compiler/libs/compiler/src/test/mod.rs b/rust_compiler/libs/compiler/src/test/mod.rs index 0f99991..5a0ac55 100644 --- a/rust_compiler/libs/compiler/src/test/mod.rs +++ b/rust_compiler/libs/compiler/src/test/mod.rs @@ -42,9 +42,14 @@ mod binary_expression; mod branching; mod declaration_function_invocation; mod declaration_literal; +mod device_access; +mod edge_cases; +mod error_handling; mod function_declaration; mod logic_expression; mod loops; mod math_syscall; +mod negation_priority; +mod scoping; mod syscall; mod tuple_literals; diff --git a/rust_compiler/libs/compiler/src/test/negation_priority.rs b/rust_compiler/libs/compiler/src/test/negation_priority.rs new file mode 100644 index 0000000..2cf2950 --- /dev/null +++ b/rust_compiler/libs/compiler/src/test/negation_priority.rs @@ -0,0 +1,304 @@ +use indoc::indoc; +use pretty_assertions::assert_eq; + +#[test] +fn simple_negation() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let x = -5; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 -5 + " + } + ); + + Ok(()) +} + +#[test] +fn negation_of_variable() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let x = 10; + let y = -x; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 10 + " + } + ); + + Ok(()) +} + +#[test] +fn double_negation() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let x = -(-5); + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 5 + " + } + ); + + Ok(()) +} + +#[test] +fn negation_in_expression() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let x = 10 + (-5); + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 5 + " + } + ); + + Ok(()) +} + +#[test] +fn negation_with_multiplication() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let x = -3 * 4; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 -12 + " + } + ); + + Ok(()) +} + +#[test] +fn parentheses_priority() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let x = (2 + 3) * 4; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 20 + " + } + ); + + Ok(()) +} + +#[test] +fn nested_parentheses() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let x = ((2 + 3) * (4 - 1)); + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 15 + " + } + ); + + Ok(()) +} + +#[test] +fn parentheses_with_variables() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let a = 5; + let b = 10; + let c = (a + b) * 2; + " + }; + + // Should calculate (5 + 10) * 2 = 30 + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 5 + move r9 10 + add r1 r8 r9 + mul r2 r1 2 + move r10 r2 + " + } + ); + + Ok(()) +} + +#[test] +fn priority_affects_result() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let with_priority = (2 + 3) * 4; + let without_priority = 2 + 3 * 4; + " + }; + + // with_priority should be 20, without_priority should be 14 + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 20 + move r9 14 + " + } + ); + + Ok(()) +} + +#[test] +fn negation_of_expression() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let x = -(2 + 3); + " + }; + + // Should be -5 + assert_eq!( + compiled, + indoc! { + " + j main + main: + " + } + ); + + Ok(()) +} + +#[test] +fn complex_negation_and_priority() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let x = -((10 - 5) * 2); + " + }; + + // Should be -(5 * 2) = -10 + assert_eq!( + compiled, + indoc! { + " + j main + main: + " + } + ); + + Ok(()) +} + +#[test] +fn negation_in_logical_expression() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let x = !(-5); + " + }; + + // -5 is truthy, so !(-5) should be 0 + assert_eq!( + compiled, + indoc! { + " + j main + main: + sub r1 0 5 + seq r2 r1 0 + move r8 r2 + " + } + ); + + Ok(()) +} + +#[test] +fn parentheses_in_comparison() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let x = (10 + 5) > (3 * 4); + " + }; + + // (10 + 5) = 15 > (3 * 4) = 12, so true (1) + assert_eq!( + compiled, + indoc! { + " + j main + main: + sgt r1 15 12 + move r8 r1 + " + } + ); + + Ok(()) +} diff --git a/rust_compiler/libs/compiler/src/test/scoping.rs b/rust_compiler/libs/compiler/src/test/scoping.rs new file mode 100644 index 0000000..e734c33 --- /dev/null +++ b/rust_compiler/libs/compiler/src/test/scoping.rs @@ -0,0 +1,428 @@ +use indoc::indoc; +use pretty_assertions::assert_eq; + +#[test] +fn block_scope() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let x = 10; + { + let y = 20; + let z = x + y; + } + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 10 + move r9 20 + add r1 r8 r9 + move r10 r1 + " + } + ); + + Ok(()) +} + +#[test] +fn variable_scope_isolation() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let x = 10; + { + let x = 20; + let y = x; + } + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 10 + move r9 20 + move r10 r9 + " + } + ); + + Ok(()) +} + +#[test] +fn function_parameter_scope() -> anyhow::Result<()> { + let compiled = compile! { + debug " + fn double(x) { + return x * 2; + }; + + let result = double(5); + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + double: + pop r8 + push ra + mul r1 r8 2 + move r15 r1 + j __internal_L1 + __internal_L1: + pop ra + j ra + main: + push 5 + jal double + move r8 r15 + " + } + ); + + Ok(()) +} + +#[test] +fn function_local_variables() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let global = 100; + + fn test() { + let local = 50; + return local + global; + }; + + let result = test(); + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + test: + push ra + move r8 50 + add r1 r8 r0 + move r15 r1 + j __internal_L1 + __internal_L1: + pop ra + j ra + main: + move r8 100 + push r8 + jal test + pop r8 + move r9 r15 + " + } + ); + + Ok(()) +} + +#[test] +fn nested_block_scopes() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let x = 1; + { + let x = 2; + { + let x = 3; + let y = x; + } + let z = x; + } + let w = x; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 1 + move r9 2 + move r10 3 + move r11 r10 + move r10 r9 + move r9 r8 + " + } + ); + + Ok(()) +} + +#[test] +fn variable_shadowing_in_conditional() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let x = 10; + + if (true) { + let x = 20; + } + + let y = x; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 10 + beqz 1 __internal_L1 + move r9 20 + __internal_L1: + move r9 r8 + " + } + ); + + Ok(()) +} + +#[test] +fn variable_shadowing_in_loop() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let x = 0; + + loop { + let x = x + 1; + if (x > 5) { + break; + } + } + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 0 + __internal_L1: + add r1 r8 1 + move r9 r1 + sgt r2 r9 5 + beqz r2 __internal_L3 + j __internal_L2 + __internal_L3: + j __internal_L1 + __internal_L2: + " + } + ); + + Ok(()) +} + +#[test] +fn const_scope() -> anyhow::Result<()> { + let compiled = compile! { + debug " + const PI = 3.14; + + { + const PI = 2.71; + let x = PI; + } + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 2.71 + " + } + ); + + Ok(()) +} + +#[test] +fn device_in_scope() -> anyhow::Result<()> { + let compiled = compile! { + debug " + device d0 = \"d0\"; + + { + let value = d0.Temperature; + } + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + l r1 d0 Temperature + move r8 r1 + " + } + ); + + Ok(()) +} + +#[test] +fn function_scope_isolation() -> anyhow::Result<()> { + let compiled = compile! { + debug " + fn func1() { + let x = 10; + return x; + }; + + fn func2() { + let x = 20; + return x; + }; + + let a = func1(); + let b = func2(); + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + func1: + push ra + move r8 10 + move r15 r8 + j __internal_L1 + __internal_L1: + pop ra + j ra + func2: + push ra + move r8 20 + move r15 r8 + j __internal_L2 + __internal_L2: + pop ra + j ra + main: + jal func1 + move r8 r15 + push r8 + jal func2 + pop r8 + move r9 r15 + " + } + ); + + Ok(()) +} + +#[test] +fn tuple_unpacking_scope() -> anyhow::Result<()> { + let compiled = compile! { + debug " + fn pair() { + return (1, 2); + }; + + { + let (x, y) = pair(); + let z = x + y; + } + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + pair: + move r15 sp + push ra + push 1 + push 2 + move r15 1 + sub r0 sp 3 + get ra db r0 + j ra + main: + jal pair + pop r9 + pop r8 + move sp r15 + add r1 r8 r9 + move r10 r1 + " + } + ); + + Ok(()) +} + +#[test] +fn shadowing_doesnt_affect_outer() -> anyhow::Result<()> { + let compiled = compile! { + debug " + let x = 5; + let y = x; + { + let x = 10; + let z = x; + } + let w = x + y; + " + }; + + assert_eq!( + compiled, + indoc! { + " + j main + main: + move r8 5 + move r9 r8 + move r10 10 + move r11 r10 + add r1 r8 r9 + move r10 r1 + " + } + ); + + Ok(()) +} diff --git a/rust_compiler/libs/compiler/src/v1.rs b/rust_compiler/libs/compiler/src/v1.rs index 510fa2e..d768815 100644 --- a/rust_compiler/libs/compiler/src/v1.rs +++ b/rust_compiler/libs/compiler/src/v1.rs @@ -603,15 +603,13 @@ impl<'a> Compiler<'a> { let name_str = var_name.node; let name_span = var_name.span; - // optimization. Check for a negated numeric literal - if let Expression::Negation(box_expr) = &expr.node - && let Expression::Literal(spanned_lit) = &box_expr.node - && let Literal::Number(neg_num) = &spanned_lit.node - { + // optimization. Check for a negated numeric literal (including nested negations) + // e.g., -5, -(-5), -(-(5)), etc. + if let Some(num) = self.try_fold_negation(&expr.node) { let loc = scope.add_variable(name_str.clone(), LocationRequest::Persist, Some(name_span))?; - self.emit_variable_assignment(&loc, Operand::Number((-*neg_num).into()))?; + self.emit_variable_assignment(&loc, Operand::Number(num.into()))?; return Ok(Some(CompileLocation { location: loc, temp_name: None, @@ -2034,6 +2032,25 @@ impl<'a> Compiler<'a> { ) } + /// Recursively fold negations of numeric literals, e.g., -5 => 5, -(-5) => 5 + fn try_fold_negation(&self, expr: &Expression) -> Option { + match expr { + // Base case: plain number literal + Expression::Literal(lit) => { + if let Literal::Number(n) = lit.node { + Some(n) + } else { + None + } + } + // Recursive case: negation of something foldable + Expression::Negation(inner) => self.try_fold_negation(&inner.node).map(|n| -n), + // Parentheses just pass through + Expression::Priority(inner) => self.try_fold_negation(&inner.node), + _ => None, + } + } + fn expression_binary( &mut self, expr: Spanned>,