From d83341d90b751a44e8912d4ee5f75ac3138428e6 Mon Sep 17 00:00:00 2001 From: Devin Bidwell Date: Tue, 30 Dec 2025 16:33:11 -0700 Subject: [PATCH] Update tuples to support member access and function calls --- .../libs/compiler/src/test/edge_cases.rs | 115 ++++++ .../libs/compiler/src/test/scoping.rs | 10 +- .../libs/compiler/src/test/tuple_literals.rs | 120 +++---- rust_compiler/libs/compiler/src/v1.rs | 338 ++++++------------ 4 files changed, 281 insertions(+), 302 deletions(-) diff --git a/rust_compiler/libs/compiler/src/test/edge_cases.rs b/rust_compiler/libs/compiler/src/test/edge_cases.rs index 2cff613..c7f4c4c 100644 --- a/rust_compiler/libs/compiler/src/test/edge_cases.rs +++ b/rust_compiler/libs/compiler/src/test/edge_cases.rs @@ -609,3 +609,118 @@ fn function_with_many_parameters() -> anyhow::Result<()> { Ok(()) } + +#[test] +fn tuple_declaration_with_functions() -> anyhow::Result<()> { + let compiled = compile! { + check + r#" + device self = "db"; + fn doSomething() { + return (self.Setting, self.Temperature); + } + + let (setting, temperature) = doSomething(); + "# + }; + + assert!( + compiled.errors.is_empty(), + "Expected no errors, got: {:?}", + compiled.errors + ); + + assert_eq!( + compiled.output, + indoc! {" + j main + doSomething: + push ra + l r0 db Setting + push r0 + l r0 db Temperature + push r0 + move r15 r0 + j __internal_L1 + __internal_L1: + pop ra + sub sp sp 2 + j ra + main: + jal doSomething + pop r9 + pop r8 + "} + ); + + Ok(()) +} + +#[test] +fn tuple_from_simple_function() -> anyhow::Result<()> { + let compiled = compile! { + check " + fn get_pair() { + return (1, 2); + } + + let (a, b) = get_pair(); + " + }; + + assert!( + compiled.errors.is_empty(), + "Expected no errors, got: {:?}", + compiled.errors + ); + + assert_eq!( + compiled.output, + indoc! {" + j main + get_pair: + push ra + push 1 + push 2 + move r15 2 + j __internal_L1 + __internal_L1: + pop ra + sub sp sp 2 + j ra + main: + jal get_pair + pop r9 + pop r8 + "} + ); + + Ok(()) +} + +#[test] +fn tuple_from_expression_not_function() -> anyhow::Result<()> { + let compiled = compile! { + check " + let (a, b) = (5 + 3, 10 * 2); + " + }; + + assert!( + compiled.errors.is_empty(), + "Expected no errors, got: {:?}", + compiled.errors + ); + + assert_eq!( + compiled.output, + indoc! {" + j main + main: + move r8 8 + move r9 20 + "} + ); + + Ok(()) +} diff --git a/rust_compiler/libs/compiler/src/test/scoping.rs b/rust_compiler/libs/compiler/src/test/scoping.rs index cf22b4d..ccc5935 100644 --- a/rust_compiler/libs/compiler/src/test/scoping.rs +++ b/rust_compiler/libs/compiler/src/test/scoping.rs @@ -390,19 +390,19 @@ fn tuple_unpacking_scope() -> anyhow::Result<()> { " j main pair: - move r15 sp push ra push 1 push 2 - move r15 1 - sub r0 sp 3 - get ra db r0 + move r15 2 + j __internal_L1 + __internal_L1: + pop ra + sub sp sp 2 j ra main: jal pair pop r9 pop r8 - move sp r15 add r1 r8 r9 move r10 r1 " diff --git a/rust_compiler/libs/compiler/src/test/tuple_literals.rs b/rust_compiler/libs/compiler/src/test/tuple_literals.rs index f93f1f5..241f2f1 100644 --- a/rust_compiler/libs/compiler/src/test/tuple_literals.rs +++ b/rust_compiler/libs/compiler/src/test/tuple_literals.rs @@ -218,19 +218,19 @@ mod test { " j main getPair: - move r15 sp push ra push 10 push 20 - move r15 1 - sub r0 sp 3 - get ra db r0 + move r15 20 + j __internal_L1 + __internal_L1: + pop ra + sub sp sp 2 j ra main: jal getPair pop r9 pop r8 - move sp r15 " } ); @@ -262,19 +262,19 @@ mod test { " j main getPair: - move r15 sp push ra push 5 push 15 - move r15 1 - sub r0 sp 3 - get ra db r0 + move r15 15 + j __internal_L1 + __internal_L1: + pop ra + sub sp sp 2 j ra main: jal getPair pop r0 pop r8 - move sp r15 " } ); @@ -306,21 +306,21 @@ mod test { " j main getTriple: - move r15 sp push ra push 1 push 2 push 3 - move r15 1 - sub r0 sp 4 - get ra db r0 + move r15 3 + j __internal_L1 + __internal_L1: + pop ra + sub sp sp 3 j ra main: jal getTriple pop r10 pop r9 pop r8 - move sp r15 " } ); @@ -354,13 +354,14 @@ mod test { " j main getPair: - move r15 sp push ra push 42 push 84 - move r15 1 - sub r0 sp 3 - get ra db r0 + move r15 84 + j __internal_L1 + __internal_L1: + pop ra + sub sp sp 2 j ra main: move r8 1 @@ -368,7 +369,6 @@ mod test { jal getPair pop r9 pop r8 - move sp r15 " } ); @@ -433,20 +433,20 @@ mod test { " j main doSomething: - move r15 sp push ra push 1 push 2 - move r15 1 - sub r0 sp 3 - get ra db r0 + move r15 2 + j __internal_L1 + __internal_L1: + pop ra + sub sp sp 2 j ra doSomethingElse: push ra jal doSomething pop r9 pop r8 - move sp r15 move r15 r9 j __internal_L2 __internal_L2: @@ -499,21 +499,21 @@ mod test { pop ra j ra getTuple: - move r15 sp push ra jal getValue move r8 r15 push r8 push r8 - move r15 1 - sub r0 sp 3 - get ra db r0 + move r15 r8 + j __internal_L2 + __internal_L2: + pop ra + sub sp sp 2 j ra main: jal getTuple pop r9 pop r8 - move sp r15 " } ); @@ -570,32 +570,28 @@ mod test { j main getValue: pop r8 - move r15 sp push ra beqz r8 __internal_L3 push 1 push 2 - move r15 0 - sub r0 sp 3 - get ra db r0 - j ra - sub sp sp 2 + move r15 2 + j __internal_L1 j __internal_L2 __internal_L3: push 3 push 4 - move r15 0 - sub r0 sp 3 - get ra db r0 - j ra - sub sp sp 2 + move r15 4 + j __internal_L1 __internal_L2: + __internal_L1: + pop ra + sub sp sp 2 + j ra main: push 1 jal getValue pop r9 pop r8 - move sp r15 " }, ); @@ -630,13 +626,14 @@ mod test { add: pop r8 pop r9 - move r15 sp push ra push r9 push r8 - move r15 1 - sub r0 sp 3 - get ra db r0 + move r15 r8 + j __internal_L1 + __internal_L1: + pop ra + sub sp sp 2 j ra main: push 5 @@ -644,7 +641,6 @@ mod test { jal add pop r9 pop r8 - move sp r15 " } ); @@ -682,32 +678,32 @@ mod test { " j main inner: - move r15 sp push ra push 1 push 2 - move r15 1 - sub r0 sp 3 - get ra db r0 + move r15 2 + j __internal_L1 + __internal_L1: + pop ra + sub sp sp 2 j ra outer: - move r15 sp push ra jal inner pop r9 pop r8 - move sp r15 push r9 push r8 - move r15 1 - sub r0 sp 3 - get ra db r0 + move r15 r8 + j __internal_L2 + __internal_L2: + pop ra + sub sp sp 2 j ra main: jal outer pop r9 pop r8 - move sp r15 " } ); @@ -1118,7 +1114,6 @@ mod test { " j main get8: - move r15 sp push ra push 1 push 2 @@ -1128,9 +1123,11 @@ mod test { push 6 push 7 push 8 - move r15 1 - sub r0 sp 9 - get ra db r0 + move r15 8 + j __internal_L1 + __internal_L1: + pop ra + sub sp sp 8 j ra main: jal get8 @@ -1144,7 +1141,6 @@ mod test { pop r10 pop r9 pop r8 - move sp r15 sub r0 sp 1 get r1 db r0 add r2 r8 r1 diff --git a/rust_compiler/libs/compiler/src/v1.rs b/rust_compiler/libs/compiler/src/v1.rs index c98b319..2fdc8d0 100644 --- a/rust_compiler/libs/compiler/src/v1.rs +++ b/rust_compiler/libs/compiler/src/v1.rs @@ -156,8 +156,8 @@ struct FunctionMetadata<'a> { current_name: Option>, /// Return label for the current function return_label: Option>, - /// Whether the current function returns a tuple - returns_tuple: bool, + /// Size of tuple return for the current function (0 if not returning tuple) + tuple_return_size: u16, /// Whether the SP (stack pointer) has been saved for the current function sp_saved: bool, } @@ -170,7 +170,7 @@ impl<'a> Default for FunctionMetadata<'a> { tuple_return_sizes: HashMap::new(), current_name: None, return_label: None, - returns_tuple: false, + tuple_return_size: 0, sp_saved: false, } } @@ -1301,15 +1301,6 @@ impl<'a> Compiler<'a> { // Pop tuple values from stack into variables self.pop_tuple_values(var_locations)?; - - // Restore stack pointer to value saved at function entry - self.write_instruction( - Instruction::Move( - Operand::StackPointer, - Operand::Register(VariableScope::RETURN_REGISTER), - ), - Some(value.span), - )?; } Expression::Tuple(tuple_expr) => { // Direct tuple literal: (value1, value2, ...) @@ -1402,15 +1393,6 @@ impl<'a> Compiler<'a> { // Pop tuple values from stack into variables self.pop_tuple_values(var_locations)?; - - // Restore stack pointer to value saved at function entry - self.write_instruction( - Instruction::Move( - Operand::StackPointer, - Operand::Register(VariableScope::RETURN_REGISTER), - ), - Some(value.span), - )?; } Expression::Tuple(tuple_expr) => { // Direct tuple literal: (value1, value2, ...) @@ -2524,32 +2506,13 @@ impl<'a> Compiler<'a> { let span = expr.span; let tuple_elements = &tuple_expr.node; - // Record the stack offset where the tuple will start - let tuple_start_offset = scope.stack_offset(); + // Track the last value for r15 + let mut last_value_operand: Option = None; - // First pass: Add temporary variables to scope for each tuple element - // This updates the scope's stack_offset so we can calculate ra position later - let mut temp_names = Vec::new(); - for (i, _element) in tuple_elements.iter().enumerate() { - let temp_name = format!("__tuple_ret_{}", i); - scope.add_variable( - temp_name.clone().into(), - LocationRequest::Stack, - Some(span), - )?; - temp_names.push(temp_name); - } - - // Second pass: Push the actual values onto the stack + // Push each tuple element onto the stack for element in tuple_elements.iter() { - match &element.node { - Expression::Literal(lit) => { - let value_operand = extract_literal(lit.node.clone(), false)?; - self.write_instruction( - Instruction::Push(value_operand), - Some(span), - )?; - } + let push_operand = match &element.node { + Expression::Literal(lit) => extract_literal(lit.node.clone(), false)?, Expression::Variable(var) => { let var_loc = match scope.get_location_of(&var.node, Some(var.span)) { @@ -2565,20 +2528,12 @@ impl<'a> Compiler<'a> { match &var_loc { VariableLocation::Temporary(reg) - | VariableLocation::Persistant(reg) => { - self.write_instruction( - Instruction::Push(Operand::Register(*reg)), - Some(span), - )?; - } + | VariableLocation::Persistant(reg) => Operand::Register(*reg), VariableLocation::Constant(lit) => { - let value_operand = extract_literal(lit.clone(), false)?; - self.write_instruction( - Instruction::Push(value_operand), - Some(span), - )?; + extract_literal(lit.clone(), false)? } VariableLocation::Stack(offset) => { + // Load from stack into temp register self.write_instruction( Instruction::Sub( Operand::Register( @@ -2601,12 +2556,7 @@ impl<'a> Compiler<'a> { ), Some(span), )?; - self.write_instruction( - Instruction::Push(Operand::Register( - VariableScope::TEMP_STACK_REGISTER, - )), - Some(span), - )?; + Operand::Register(VariableScope::TEMP_STACK_REGISTER) } VariableLocation::Device(_) => { return Err(Error::Unknown( @@ -2616,64 +2566,65 @@ impl<'a> Compiler<'a> { } } } - _ => { - // For complex expressions, just push 0 for now + Expression::MemberAccess(member_access) => { + // Compile member access (e.g., device.Property) + let member_span = element.span; + + // Get the device name from the object (should be a Variable expression) + let device_name = if let Expression::Variable(var) = + &member_access.node.object.node + { + &var.node + } else { + return Err(Error::Unknown( + "Member access must be on a device variable".into(), + Some(member_span), + )); + }; + + let property_name = &member_access.node.member.node; + + // Get device + let device = self.devices.get(device_name).ok_or_else(|| { + Error::UnknownIdentifier(device_name.clone(), member_span) + })?; + + // Load property into temp register self.write_instruction( - Instruction::Push(Operand::Number( - Number::Integer(0, Unit::None).into(), - )), - Some(span), + Instruction::Load( + Operand::Register(VariableScope::TEMP_STACK_REGISTER), + Operand::Device(device.clone()), + Operand::LogicType(property_name.clone()), + ), + Some(member_span), )?; + Operand::Register(VariableScope::TEMP_STACK_REGISTER) } - } - } + _ => { + // For other expression types, push 0 for now + // TODO: Support more expression types + Operand::Number(Number::Integer(0, Unit::None).into()) + } + }; - // Store the pointer to the tuple (stack offset) in r15 - self.write_instruction( - Instruction::Move( - Operand::Register(VariableScope::RETURN_REGISTER), - Operand::Number(tuple_start_offset.into()), - ), - Some(span), - )?; - - // For tuple returns, ra is buried under the tuple values on the stack. - // Stack layout: [ra, val0, val1, val2, ...] - // Instead of popping and pushing, use Get to read ra from its stack position - // while leaving the tuple values in place. - - // Calculate offset to ra from current stack position - // ra is at tuple_start_offset - 1, so offset = (current - tuple_start) + 1 - let current_offset = scope.stack_offset(); - let ra_offset_from_current = (current_offset - tuple_start_offset + 1) as i32; - - // Use a temp register to read ra from the stack - if ra_offset_from_current > 0 { self.write_instruction( - Instruction::Sub( - Operand::Register(VariableScope::TEMP_STACK_REGISTER), - Operand::StackPointer, - Operand::Number(ra_offset_from_current.into()), - ), + Instruction::Push(push_operand.clone()), Some(span), )?; + last_value_operand = Some(push_operand); + } + // Set r15 to the last pushed value (convention for tuple returns) + if let Some(last_op) = last_value_operand { self.write_instruction( - Instruction::Get( - Operand::ReturnAddress, - Operand::Device(Cow::from("db")), - Operand::Register(VariableScope::TEMP_STACK_REGISTER), + Instruction::Move( + Operand::Register(VariableScope::RETURN_REGISTER), + last_op, ), Some(span), )?; } - // Jump back to caller - self.write_instruction(Instruction::Jump(Operand::ReturnAddress), Some(span))?; - - // Mark that we had a tuple return so the function declaration can skip return label cleanup - self.function_meta.returns_tuple = true; - // Record the tuple return size for validation at call sites if let Some(func_name) = &self.function_meta.current_name { self.function_meta @@ -2681,8 +2632,8 @@ impl<'a> Compiler<'a> { .insert(func_name.clone(), tuple_elements.len()); } - // Early return to skip the normal return label processing - return Ok(VariableLocation::Persistant(VariableScope::RETURN_REGISTER)); + // Track tuple size for epilogue cleanup + self.function_meta.tuple_return_size = tuple_elements.len() as u16; } _ => { return Err(Error::Unknown( @@ -3312,78 +3263,6 @@ impl<'a> Compiler<'a> { } } - /// Check if a function body contains any tuple returns - fn has_tuple_return(body: &BlockExpression) -> bool { - for expr in &body.0 { - match &expr.node { - Expression::Return(Some(ret_expr)) => { - if let Expression::Tuple(_) = &ret_expr.node { - return true; - } - } - Expression::If(if_expr) => { - // Check the then block - if Self::has_tuple_return(&if_expr.node.body.node) { - return true; - } - // Check the else branch if it exists - if let Some(else_branch) = &if_expr.node.else_branch { - match &else_branch.node { - Expression::Block(block) => { - if Self::has_tuple_return(block) { - return true; - } - } - Expression::If(_) => { - // Handle else-if chains - if Self::has_tuple_return_in_expr(else_branch) { - return true; - } - } - _ => {} - } - } - } - Expression::While(while_expr) => { - if Self::has_tuple_return(&while_expr.node.body) { - return true; - } - } - Expression::Loop(loop_expr) => { - if Self::has_tuple_return(&loop_expr.node.body.node) { - return true; - } - } - Expression::Block(block) => { - if Self::has_tuple_return(block) { - return true; - } - } - _ => {} - } - } - false - } - - /// Helper to check for tuple returns in any expression - fn has_tuple_return_in_expr(expr: &Spanned) -> bool { - match &expr.node { - Expression::Block(block) => Self::has_tuple_return(block), - Expression::If(if_expr) => { - if Self::has_tuple_return(&if_expr.node.body.node) { - return true; - } - if let Some(else_branch) = &if_expr.node.else_branch { - return Self::has_tuple_return_in_expr(else_branch); - } - false - } - Expression::While(while_expr) => Self::has_tuple_return(&while_expr.node.body), - Expression::Loop(loop_expr) => Self::has_tuple_return(&loop_expr.node.body.node), - _ => false, - } - } - /// Compile a function declaration. /// Calees are responsible for backing up any registers they wish to use. fn expression_function( @@ -3476,18 +3355,6 @@ impl<'a> Compiler<'a> { )?; } - // If this function has tuple returns, save the SP to r15 before pushing ra - if Self::has_tuple_return(&body) { - self.write_instruction( - Instruction::Move( - Operand::Register(VariableScope::RETURN_REGISTER), - Operand::StackPointer, - ), - Some(span), - )?; - self.function_meta.sp_saved = true; - } - self.write_instruction(Instruction::Push(Operand::ReturnAddress), Some(span))?; let return_label = self.next_label_name(); @@ -3544,61 +3411,62 @@ impl<'a> Compiler<'a> { self.function_meta.return_label = prev_return_label; - // Only write the return label if this function doesn't have a tuple return - // (tuple returns handle their own pop ra and return) - if !self.function_meta.returns_tuple { - self.write_instruction(Instruction::LabelDef(return_label.clone()), Some(span))?; + // Write the return label and epilogue + self.write_instruction(Instruction::LabelDef(return_label.clone()), Some(span))?; - if ra_stack_offset == 1 { - self.write_instruction(Instruction::Pop(Operand::ReturnAddress), Some(span))?; + if ra_stack_offset == 1 { + self.write_instruction(Instruction::Pop(Operand::ReturnAddress), Some(span))?; - let remaining_cleanup = block_scope.stack_offset() - 1; - if remaining_cleanup > 0 { - self.write_instruction( - Instruction::Sub( - Operand::StackPointer, - Operand::StackPointer, - Operand::Number(remaining_cleanup.into()), - ), - Some(span), - )?; - } - } else { + // Calculate cleanup: scope variables + tuple return values + let remaining_cleanup = + (block_scope.stack_offset() - 1) + self.function_meta.tuple_return_size; + if remaining_cleanup > 0 { self.write_instruction( Instruction::Sub( - Operand::Register(VariableScope::TEMP_STACK_REGISTER), Operand::StackPointer, - Operand::Number(ra_stack_offset.into()), + Operand::StackPointer, + Operand::Number(remaining_cleanup.into()), ), Some(span), )?; - - self.write_instruction( - Instruction::Get( - Operand::ReturnAddress, - Operand::Device(Cow::from("db")), - Operand::Register(VariableScope::TEMP_STACK_REGISTER), - ), - Some(span), - )?; - - if block_scope.stack_offset() > 0 { - self.write_instruction( - Instruction::Sub( - Operand::StackPointer, - Operand::StackPointer, - Operand::Number(block_scope.stack_offset().into()), - ), - Some(span), - )?; - } } + } else { + self.write_instruction( + Instruction::Sub( + Operand::Register(VariableScope::TEMP_STACK_REGISTER), + Operand::StackPointer, + Operand::Number(ra_stack_offset.into()), + ), + Some(span), + )?; - self.write_instruction(Instruction::Jump(Operand::ReturnAddress), Some(span))?; + self.write_instruction( + Instruction::Get( + Operand::ReturnAddress, + Operand::Device(Cow::from("db")), + Operand::Register(VariableScope::TEMP_STACK_REGISTER), + ), + Some(span), + )?; + + // Clean up scope variables + tuple return values + let total_cleanup = block_scope.stack_offset() + self.function_meta.tuple_return_size; + if total_cleanup > 0 { + self.write_instruction( + Instruction::Sub( + Operand::StackPointer, + Operand::StackPointer, + Operand::Number(total_cleanup.into()), + ), + Some(span), + )?; + } } - // Reset the flag for the next function - self.function_meta.returns_tuple = false; + self.write_instruction(Instruction::Jump(Operand::ReturnAddress), Some(span))?; + + // Reset the flags for the next function + self.function_meta.tuple_return_size = 0; self.function_meta.sp_saved = false; self.function_meta.current_name = None; Ok(())