0.5.0 -- tuples and more optimizations #12

Merged
dbidwell merged 34 commits from 43-tuple-return into master 2025-12-31 17:03:51 -07:00
4 changed files with 200 additions and 241 deletions
Showing only changes of commit 6d8a22459c - Show all commits

View File

@@ -333,8 +333,7 @@ mod test {
// Check for the specific TupleSizeMismatch error // Check for the specific TupleSizeMismatch error
match &errors[0] { match &errors[0] {
crate::Error::TupleSizeMismatch(func_name, expected_size, actual_count, _) => { crate::Error::TupleSizeMismatch(expected_size, actual_count, _) => {
assert_eq!(func_name.as_ref(), "doSomething");
assert_eq!(*expected_size, 3); assert_eq!(*expected_size, 3);
assert_eq!(*actual_count, 2); assert_eq!(*actual_count, 2);
} }
@@ -461,7 +460,10 @@ mod test {
// Should have exactly one error about tuple size mismatch // Should have exactly one error about tuple size mismatch
assert_eq!(errors.len(), 1); assert_eq!(errors.len(), 1);
assert!(matches!(errors[0], crate::Error::Unknown(_, _))); assert!(matches!(
errors[0],
crate::Error::TupleSizeMismatch(_, _, _)
));
Ok(()) Ok(())
} }
@@ -483,11 +485,42 @@ mod test {
"# "#
); );
println!("Generated code:\n{}", compiled); assert_eq!(
compiled,
// Both returns are 2-tuples, should compile successfully indoc! {
assert!(compiled.contains("getValue:")); "
assert!(compiled.contains("move r15 ")); 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
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
__internal_L2:
main:
push 1
jal getValue
pop r9
pop r8
move sp r15
"
},
);
Ok(()) Ok(())
} }

View File

@@ -64,10 +64,8 @@ pub enum Error<'a> {
#[error("Attempted to re-assign a value to a device const `{0}`")] #[error("Attempted to re-assign a value to a device const `{0}`")]
DeviceAssignment(Cow<'a, str>, Span), DeviceAssignment(Cow<'a, str>, Span),
#[error( #[error("Expected a {0}-tuple, but you're trying to destructure into {1} variables")]
"Function '{0}' returns a {1}-tuple, but you're trying to destructure into {2} variables" TupleSizeMismatch(usize, usize, Span),
)]
TupleSizeMismatch(Cow<'a, str>, usize, usize, Span),
#[error("{0}")] #[error("{0}")]
Unknown(String, Option<Span>), Unknown(String, Option<Span>),
@@ -91,7 +89,7 @@ impl<'a> From<Error<'a>> for lsp_types::Diagnostic {
| ConstAssignment(_, span) | ConstAssignment(_, span)
| DeviceAssignment(_, span) | DeviceAssignment(_, span)
| AgrumentMismatch(_, span) | AgrumentMismatch(_, span)
| TupleSizeMismatch(_, _, _, span) => Diagnostic { | TupleSizeMismatch(_, _, span) => Diagnostic {
range: span.into(), range: span.into(),
message: value.to_string(), message: value.to_string(),
severity: Some(DiagnosticSeverity::ERROR), severity: Some(DiagnosticSeverity::ERROR),
@@ -1117,20 +1115,19 @@ impl<'a> Compiler<'a> {
// For function calls returning tuples: // For function calls returning tuples:
// r15 = pointer to beginning of tuple on stack // r15 = pointer to beginning of tuple on stack
// r14, r13, ... contain the tuple elements, or they're on the stack // r14, r13, ... contain the tuple elements, or they're on the stack
match &value.node { match value.node {
Expression::Invocation(invoke_expr) => { Expression::Invocation(invoke_expr) => {
// Execute the function call // Execute the function call
// Tuple values are on the stack, sp points after the last pushed value // Tuple values are on the stack, sp points after the last pushed value
// Pop them in reverse order (from end to beginning) // Pop them in reverse order (from end to beginning)
// We don't need to backup registers for tuple returns // We don't need to backup registers for tuple returns
self.expression_function_invocation_with_invocation(invoke_expr, scope, false)?; self.expression_function_invocation_with_invocation(&invoke_expr, scope, false)?;
// Validate tuple return size matches the declaration // Validate tuple return size matches the declaration
let func_name = &invoke_expr.node.name.node; let func_name = &invoke_expr.node.name.node;
if let Some(&expected_size) = self.function_tuple_return_sizes.get(func_name) { if let Some(&expected_size) = self.function_tuple_return_sizes.get(func_name) {
if names.len() != expected_size { if names.len() != expected_size {
self.errors.push(Error::TupleSizeMismatch( self.errors.push(Error::TupleSizeMismatch(
func_name.clone(),
expected_size, expected_size,
names.len(), names.len(),
value.span, value.span,
@@ -1194,24 +1191,19 @@ impl<'a> Compiler<'a> {
} }
Expression::Tuple(tuple_expr) => { Expression::Tuple(tuple_expr) => {
// Direct tuple literal: (value1, value2, ...) // Direct tuple literal: (value1, value2, ...)
let tuple_elements = &tuple_expr.node; let tuple_elements = tuple_expr.node;
// Validate tuple size matches names // Validate tuple size matches names
if tuple_elements.len() != names.len() { if tuple_elements.len() != names.len() {
return Err(Error::Unknown( return Err(Error::TupleSizeMismatch(
format!(
"Tuple size mismatch: expected {} elements, got {}",
names.len(), names.len(),
tuple_elements.len() tuple_elements.len(),
), value.span,
Some(value.span),
)); ));
} }
// Compile each element and assign to corresponding variable // Compile each element and assign to corresponding variable
for (_index, (name_spanned, element)) in for (name_spanned, element) in names.into_iter().zip(tuple_elements.into_iter()) {
names.iter().zip(tuple_elements.iter()).enumerate()
{
// Skip underscores // Skip underscores
if name_spanned.node.as_ref() == "_" { if name_spanned.node.as_ref() == "_" {
continue; continue;
@@ -1224,65 +1216,13 @@ impl<'a> Compiler<'a> {
Some(name_spanned.span), Some(name_spanned.span),
)?; )?;
// Compile the element expression - handle common cases directly // Compile the element expression - use compile_operand to handle all expression types
match &element.node { let (value_operand, cleanup) = self.compile_operand(element, scope)?;
Expression::Literal(lit) => {
let value_operand = extract_literal(lit.node.clone(), false)?;
self.emit_variable_assignment(&var_location, value_operand)?; self.emit_variable_assignment(&var_location, value_operand)?;
}
Expression::Variable(var) => {
let var_loc = match scope.get_location_of(&var.node, Some(var.span)) {
Ok(l) => l,
Err(_) => {
self.errors
.push(Error::UnknownIdentifier(var.node.clone(), var.span));
VariableLocation::Temporary(0)
}
};
let value_operand = match &var_loc { // Clean up any temporary registers used for complex expressions
VariableLocation::Temporary(reg) if let Some(temp_name) = cleanup {
| VariableLocation::Persistant(reg) => Operand::Register(*reg), scope.free_temp(temp_name, None)?;
VariableLocation::Constant(lit) => {
extract_literal(lit.clone(), false)?
}
VariableLocation::Stack(offset) => {
self.write_instruction(
Instruction::Sub(
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
Operand::StackPointer,
Operand::Number((*offset).into()),
),
Some(var.span),
)?;
self.write_instruction(
Instruction::Get(
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
Operand::Device(Cow::from("db")),
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
),
Some(var.span),
)?;
Operand::Register(VariableScope::TEMP_STACK_REGISTER)
}
VariableLocation::Device(_) => {
return Err(Error::Unknown(
"Device values not supported in tuple literals".into(),
Some(var.span),
));
}
};
self.emit_variable_assignment(&var_location, value_operand)?;
}
_ => {
return Err(Error::Unknown(
"Complex expressions in tuple literals not yet supported".into(),
Some(element.span),
));
}
} }
} }
} }
@@ -1306,20 +1246,19 @@ impl<'a> Compiler<'a> {
let TupleAssignmentExpression { names, value } = tuple_assign; let TupleAssignmentExpression { names, value } = tuple_assign;
// Similar to tuple declaration, but variables must already exist // Similar to tuple declaration, but variables must already exist
match &value.node { match value.node {
Expression::Invocation(invoke_expr) => { Expression::Invocation(invoke_expr) => {
// Execute the function call // Execute the function call
// Tuple values are on the stack, sp points after the last pushed value // Tuple values are on the stack, sp points after the last pushed value
// Pop them in reverse order (from end to beginning) // Pop them in reverse order (from end to beginning)
// We don't need to backup registers for tuple returns // We don't need to backup registers for tuple returns
self.expression_function_invocation_with_invocation(invoke_expr, scope, false)?; self.expression_function_invocation_with_invocation(&invoke_expr, scope, false)?;
// Validate tuple return size matches the assignment // Validate tuple return size matches the assignment
let func_name = &invoke_expr.node.name.node; let func_name = &invoke_expr.node.name.node;
if let Some(&expected_size) = self.function_tuple_return_sizes.get(func_name) { if let Some(&expected_size) = self.function_tuple_return_sizes.get(func_name) {
if names.len() != expected_size { if names.len() != expected_size {
self.errors.push(Error::TupleSizeMismatch( self.errors.push(Error::TupleSizeMismatch(
func_name.clone(),
expected_size, expected_size,
names.len(), names.len(),
value.span, value.span,
@@ -1419,24 +1358,19 @@ impl<'a> Compiler<'a> {
} }
Expression::Tuple(tuple_expr) => { Expression::Tuple(tuple_expr) => {
// Direct tuple literal: (value1, value2, ...) // Direct tuple literal: (value1, value2, ...)
let tuple_elements = &tuple_expr.node; let tuple_elements = tuple_expr.node;
// Validate tuple size matches names // Validate tuple size matches names
if tuple_elements.len() != names.len() { if tuple_elements.len() != names.len() {
return Err(Error::Unknown( return Err(Error::TupleSizeMismatch(
format!( tuple_elements.len(),
"Tuple size mismatch: expected {} elements, got {}",
names.len(), names.len(),
tuple_elements.len() value.span,
),
Some(value.span),
)); ));
} }
// Compile each element and assign to corresponding variable // Compile each element and assign to corresponding variable
for (_index, (name_spanned, element)) in for (name_spanned, element) in names.into_iter().zip(tuple_elements.into_iter()) {
names.iter().zip(tuple_elements.iter()).enumerate()
{
// Skip underscores // Skip underscores
if name_spanned.node.as_ref() == "_" { if name_spanned.node.as_ref() == "_" {
continue; continue;
@@ -1455,13 +1389,12 @@ impl<'a> Compiler<'a> {
} }
}; };
// Compile the element expression - handle common cases directly // Compile the element expression - use compile_operand to handle all expression types
match &element.node { let (value_operand, cleanup) = self.compile_operand(element, scope)?;
Expression::Literal(lit) => {
let value_operand = extract_literal(lit.node.clone(), false)?; // Assign the compiled value to the target variable location
match &var_location { match &var_location {
VariableLocation::Temporary(reg) VariableLocation::Temporary(reg) | VariableLocation::Persistant(reg) => {
| VariableLocation::Persistant(reg) => {
self.write_instruction( self.write_instruction(
Instruction::Move(Operand::Register(*reg), value_operand), Instruction::Move(Operand::Register(*reg), value_operand),
Some(name_spanned.span), Some(name_spanned.span),
@@ -1499,99 +1432,10 @@ impl<'a> Compiler<'a> {
)); ));
} }
} }
}
Expression::Variable(var) => {
let var_loc = match scope.get_location_of(&var.node, Some(var.span)) {
Ok(l) => l,
Err(_) => {
self.errors
.push(Error::UnknownIdentifier(var.node.clone(), var.span));
VariableLocation::Temporary(0)
}
};
let value_operand = match &var_loc { // Clean up any temporary registers used for complex expressions
VariableLocation::Temporary(reg) if let Some(temp_name) = cleanup {
| VariableLocation::Persistant(reg) => Operand::Register(*reg), scope.free_temp(temp_name, None)?;
VariableLocation::Constant(lit) => {
extract_literal(lit.clone(), false)?
}
VariableLocation::Stack(offset) => {
self.write_instruction(
Instruction::Sub(
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
Operand::StackPointer,
Operand::Number((*offset).into()),
),
Some(var.span),
)?;
self.write_instruction(
Instruction::Get(
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
Operand::Device(Cow::from("db")),
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
),
Some(var.span),
)?;
Operand::Register(VariableScope::TEMP_STACK_REGISTER)
}
VariableLocation::Device(_) => {
return Err(Error::Unknown(
"Device values not supported in tuple literals".into(),
Some(var.span),
));
}
};
match &var_location {
VariableLocation::Temporary(reg)
| VariableLocation::Persistant(reg) => {
self.write_instruction(
Instruction::Move(Operand::Register(*reg), value_operand),
Some(name_spanned.span),
)?;
}
VariableLocation::Stack(offset) => {
self.write_instruction(
Instruction::Sub(
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
Operand::StackPointer,
Operand::Number((*offset).into()),
),
Some(name_spanned.span),
)?;
self.write_instruction(
Instruction::Put(
Operand::Device(Cow::from("db")),
Operand::Register(VariableScope::TEMP_STACK_REGISTER),
value_operand,
),
Some(name_spanned.span),
)?;
}
VariableLocation::Constant(_) => {
return Err(Error::ConstAssignment(
name_spanned.node.clone(),
name_spanned.span,
));
}
VariableLocation::Device(_) => {
return Err(Error::DeviceAssignment(
name_spanned.node.clone(),
name_spanned.span,
));
}
}
}
_ => {
return Err(Error::Unknown(
"Complex expressions in tuple literals not yet supported".into(),
Some(element.span),
));
}
} }
} }
} }
@@ -3421,14 +3265,69 @@ impl<'a> Compiler<'a> {
return true; return true;
} }
} }
_ => { Expression::If(if_expr) => {
// Could recursively check nested blocks, but for now just check direct returns // 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 false
} }
/// Helper to check for tuple returns in any expression
fn has_tuple_return_in_expr(expr: &Spanned<Expression>) -> 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. /// Compile a function declaration.
/// Calees are responsible for backing up any registers they wish to use. /// Calees are responsible for backing up any registers they wish to use.
fn expression_function( fn expression_function(

View File

@@ -1118,13 +1118,7 @@ impl<'a> Parser<'a> {
}) })
}; };
expressions.insert( expressions.insert(i, Spanned { span, node });
i,
Spanned {
span,
node,
},
);
} }
} }
operators.retain(|symbol| !matches!(symbol, Symbol::Assign)); operators.retain(|symbol| !matches!(symbol, Symbol::Assign));
@@ -1188,7 +1182,6 @@ impl<'a> Parser<'a> {
// Next toekn is a comma, we need to consume it and advance 1 more time. // Next toekn is a comma, we need to consume it and advance 1 more time.
self.assign_next()?; self.assign_next()?;
self.assign_next()?; self.assign_next()?;
println!("{:?}", self.current_token);
items.push(self.expression()?.ok_or(Error::UnexpectedEOF)?); items.push(self.expression()?.ok_or(Error::UnexpectedEOF)?);
} }

View File

@@ -253,3 +253,37 @@ fn test_tuple_assignment_with_function_call_with_underscore() -> Result<()> {
Ok(()) Ok(())
} }
#[test]
fn test_tuple_declaration_with_complex_expressions() -> Result<()> {
let expr = parser!("let (x, y) = (1 + 1, doSomething());")
.parse()?
.unwrap();
assert_eq!("(let (x, y) = ((1 + 1), doSomething()))", expr.to_string());
Ok(())
}
#[test]
fn test_tuple_assignment_with_complex_expressions() -> Result<()> {
let expr = parser!("(x, y) = (doSomething(), 123 / someValue.Setting);")
.parse()?
.unwrap();
assert_eq!(
"((x, y) = (doSomething(), (123 / someValue.Setting)))",
expr.to_string()
);
Ok(())
}
#[test]
fn test_tuple_declaration_all_complex_expressions() -> Result<()> {
let expr = parser!("let (x, y) = (a + b, c * d);").parse()?.unwrap();
assert_eq!("(let (x, y) = ((a + b), (c * d)))", expr.to_string());
Ok(())
}