diff --git a/src/backend/vm.rs b/src/backend/vm.rs index 3fc58241..f94f4a48 100644 --- a/src/backend/vm.rs +++ b/src/backend/vm.rs @@ -256,6 +256,33 @@ impl VM { Ok(ControlFlow::Continue) }, + MirInstruction::Nop => { + // No-op instruction + Ok(ControlFlow::Continue) + }, + + // Phase 5: Control flow & exception handling + MirInstruction::Throw { exception, effects: _ } => { + let exception_val = self.get_value(*exception)?; + // For now, convert throw to error return (simplified exception handling) + // In a full implementation, this would unwind the stack looking for catch handlers + println!("Exception thrown: {}", exception_val.to_string()); + Err(VMError::InvalidInstruction(format!("Unhandled exception: {}", exception_val.to_string()))) + }, + + MirInstruction::Catch { exception_type: _, exception_value, handler_bb: _ } => { + // For now, catch is a no-op since we don't have full exception handling + // In a real implementation, this would set up exception handling metadata + self.values.insert(*exception_value, VMValue::Void); + Ok(ControlFlow::Continue) + }, + + MirInstruction::Safepoint => { + // Safepoint is a no-op for now + // In a real implementation, this could trigger GC, debugging, etc. + Ok(ControlFlow::Continue) + }, + _ => { Err(VMError::InvalidInstruction(format!("Unsupported instruction: {:?}", instruction))) } diff --git a/src/mir/builder.rs b/src/mir/builder.rs index 10fc9913..bd46fcc8 100644 --- a/src/mir/builder.rs +++ b/src/mir/builder.rs @@ -72,6 +72,9 @@ impl MirBuilder { self.current_function = Some(main_function); self.current_block = Some(entry_block); + // Add safepoint at function entry + self.emit_instruction(MirInstruction::Safepoint)?; + // Convert AST to MIR let result_value = self.build_expression(ast)?; @@ -160,6 +163,18 @@ impl MirBuilder { ) }, + ASTNode::Loop { condition, body, .. } => { + self.build_loop_statement(*condition.clone(), body.clone()) + }, + + ASTNode::TryCatch { try_body, catch_clauses, finally_body, .. } => { + self.build_try_catch_statement(try_body.clone(), catch_clauses.clone(), finally_body.clone()) + }, + + ASTNode::Throw { expression, .. } => { + self.build_throw_statement(*expression.clone()) + }, + _ => { Err(format!("Unsupported AST node type: {:?}", ast)) } @@ -391,6 +406,160 @@ impl MirBuilder { } } + /// Build a loop statement: loop(condition) { body } + fn build_loop_statement(&mut self, condition: ASTNode, body: Vec) -> Result { + // Add safepoint at loop entry + self.emit_instruction(MirInstruction::Safepoint)?; + + let loop_header = self.block_gen.next(); + let loop_body = self.block_gen.next(); + let loop_exit = self.block_gen.next(); + + // Jump to loop header + self.emit_instruction(MirInstruction::Jump { target: loop_header })?; + + // Create loop header block + self.start_new_block(loop_header)?; + + // Evaluate condition + let condition_value = self.build_expression(condition)?; + + // Branch based on condition + self.emit_instruction(MirInstruction::Branch { + condition: condition_value, + then_bb: loop_body, + else_bb: loop_exit, + })?; + + // Create loop body block + self.start_new_block(loop_body)?; + + // Add safepoint at loop body start + self.emit_instruction(MirInstruction::Safepoint)?; + + // Build loop body + let body_ast = ASTNode::Program { + statements: body, + span: crate::ast::Span::unknown(), + }; + self.build_expression(body_ast)?; + + // Jump back to loop header + self.emit_instruction(MirInstruction::Jump { target: loop_header })?; + + // Create exit block + self.start_new_block(loop_exit)?; + + // Return void value + let void_dst = self.value_gen.next(); + self.emit_instruction(MirInstruction::Const { + dst: void_dst, + value: ConstValue::Void, + })?; + + Ok(void_dst) + } + + /// Build a try/catch statement + fn build_try_catch_statement(&mut self, try_body: Vec, catch_clauses: Vec, finally_body: Option>) -> Result { + let try_block = self.block_gen.next(); + let catch_block = self.block_gen.next(); + let finally_block = if finally_body.is_some() { Some(self.block_gen.next()) } else { None }; + let exit_block = self.block_gen.next(); + + // Jump to try block + self.emit_instruction(MirInstruction::Jump { target: try_block })?; + + // Build try block + self.start_new_block(try_block)?; + + let try_ast = ASTNode::Program { + statements: try_body, + span: crate::ast::Span::unknown(), + }; + let try_result = self.build_expression(try_ast)?; + + // Jump to finally or exit + let next_target = finally_block.unwrap_or(exit_block); + self.emit_instruction(MirInstruction::Jump { target: next_target })?; + + // Build catch block + self.start_new_block(catch_block)?; + + // For now, handle first catch clause only (simplified) + if let Some(catch_clause) = catch_clauses.first() { + let exception_value = self.value_gen.next(); + + // Set up catch handler + self.emit_instruction(MirInstruction::Catch { + exception_type: catch_clause.exception_type.clone(), + exception_value, + handler_bb: catch_block, + })?; + + // Build catch body + let catch_ast = ASTNode::Program { + statements: catch_clause.body.clone(), + span: crate::ast::Span::unknown(), + }; + self.build_expression(catch_ast)?; + } + + // Jump to finally or exit + let next_target = finally_block.unwrap_or(exit_block); + self.emit_instruction(MirInstruction::Jump { target: next_target })?; + + // Build finally block if present + if let (Some(finally_block_id), Some(finally_statements)) = (finally_block, finally_body) { + self.start_new_block(finally_block_id)?; + + let finally_ast = ASTNode::Program { + statements: finally_statements, + span: crate::ast::Span::unknown(), + }; + self.build_expression(finally_ast)?; + + self.emit_instruction(MirInstruction::Jump { target: exit_block })?; + } + + // Create exit block + self.start_new_block(exit_block)?; + + // Return the try result (simplified - in real implementation would need phi node) + Ok(try_result) + } + + /// Build a throw statement + fn build_throw_statement(&mut self, expression: ASTNode) -> Result { + let exception_value = self.build_expression(expression)?; + + // Emit throw instruction with PANIC effect + self.emit_instruction(MirInstruction::Throw { + exception: exception_value, + effects: EffectMask::PANIC, + })?; + + // Throw doesn't return normally, but we need to return a value for the type system + let void_dst = self.value_gen.next(); + self.emit_instruction(MirInstruction::Const { + dst: void_dst, + value: ConstValue::Void, + })?; + + Ok(void_dst) + } + + /// Start a new basic block + fn start_new_block(&mut self, block_id: BasicBlockId) -> Result<(), String> { + if let Some(ref mut function) = self.current_function { + function.add_block(BasicBlock::new(block_id)); + self.current_block = Some(block_id); + Ok(()) + } else { + Err("No current function".to_string()) + } + } + /// Convert AST binary operator to MIR operator fn convert_binary_operator(&self, op: BinaryOperator) -> Result { match op { diff --git a/src/mir/effect.rs b/src/mir/effect.rs index 6a1d6176..adeaee91 100644 --- a/src/mir/effect.rs +++ b/src/mir/effect.rs @@ -55,6 +55,9 @@ impl EffectMask { /// P2P communication effects pub const P2P: Self = Self(Effect::P2P as u16); + /// Panic/exception effects + pub const PANIC: Self = Self(Effect::Panic as u16); + /// All effects - maximum side effects pub const ALL: Self = Self(0xFFFF); diff --git a/src/mir/instruction.rs b/src/mir/instruction.rs index 56b9ee75..73bac4b0 100644 --- a/src/mir/instruction.rs +++ b/src/mir/instruction.rs @@ -178,6 +178,27 @@ pub enum MirInstruction { /// No-op instruction (for optimization placeholders) Nop, + + // === Control Flow & Exception Handling (Phase 5) === + + /// Throw an exception + /// `throw %exception_value` + Throw { + exception: ValueId, + effects: EffectMask, + }, + + /// Catch handler setup (landing pad for exceptions) + /// `catch %exception_type -> %handler_bb` + Catch { + exception_type: Option, // None = catch-all + exception_value: ValueId, // Where to store caught exception + handler_bb: super::BasicBlockId, + }, + + /// Safepoint instruction (no-op for now, can be used for GC/debugging) + /// `safepoint` + Safepoint, } /// Constant values in MIR @@ -274,6 +295,11 @@ impl MirInstruction { // Print has external write effect MirInstruction::Print { effects, .. } => *effects, + + // Phase 5: Control flow & exception handling + MirInstruction::Throw { effects, .. } => *effects, + MirInstruction::Catch { .. } => EffectMask::PURE, // Setting up handler is pure + MirInstruction::Safepoint => EffectMask::PURE, // No-op for now } } @@ -302,7 +328,11 @@ impl MirInstruction { MirInstruction::ArraySet { .. } | MirInstruction::Debug { .. } | MirInstruction::Print { .. } | + MirInstruction::Throw { .. } | + MirInstruction::Safepoint | MirInstruction::Nop => None, + + MirInstruction::Catch { exception_value, .. } => Some(*exception_value), } } @@ -352,6 +382,11 @@ impl MirInstruction { MirInstruction::Phi { inputs, .. } => { inputs.iter().map(|(_, value)| *value).collect() }, + + // Phase 5: Control flow & exception handling + MirInstruction::Throw { exception, .. } => vec![*exception], + MirInstruction::Catch { .. } => Vec::new(), // Handler setup doesn't use values + MirInstruction::Safepoint => Vec::new(), } } } diff --git a/src/mir/mod.rs b/src/mir/mod.rs index 7d1ab4cf..78c6bff3 100644 --- a/src/mir/mod.rs +++ b/src/mir/mod.rs @@ -110,4 +110,97 @@ mod tests { assert!(!mir_dump.is_empty(), "MIR dump should not be empty"); assert!(mir_dump.contains("function"), "MIR dump should contain function information"); } + + #[test] + fn test_throw_compilation() { + let mut compiler = MirCompiler::new(); + + let throw_ast = ASTNode::Throw { + expression: Box::new(ASTNode::Literal { + value: LiteralValue::String("Test exception".to_string()), + span: crate::ast::Span::unknown(), + }), + span: crate::ast::Span::unknown(), + }; + + let result = compiler.compile(throw_ast); + assert!(result.is_ok(), "Throw compilation should succeed"); + + let compile_result = result.unwrap(); + let mir_dump = compiler.dump_mir(&compile_result.module); + assert!(mir_dump.contains("throw"), "MIR should contain throw instruction"); + assert!(mir_dump.contains("safepoint"), "MIR should contain safepoint instruction"); + } + + #[test] + fn test_loop_compilation() { + let mut compiler = MirCompiler::new(); + + let loop_ast = ASTNode::Loop { + condition: Box::new(ASTNode::Literal { + value: LiteralValue::Bool(true), + span: crate::ast::Span::unknown(), + }), + body: vec![ + ASTNode::Print { + expression: Box::new(ASTNode::Literal { + value: LiteralValue::String("Loop body".to_string()), + span: crate::ast::Span::unknown(), + }), + span: crate::ast::Span::unknown(), + } + ], + span: crate::ast::Span::unknown(), + }; + + let result = compiler.compile(loop_ast); + assert!(result.is_ok(), "Loop compilation should succeed"); + + let compile_result = result.unwrap(); + let mir_dump = compiler.dump_mir(&compile_result.module); + assert!(mir_dump.contains("br"), "MIR should contain branch instructions"); + assert!(mir_dump.contains("safepoint"), "MIR should contain safepoint instructions"); + } + + #[test] + fn test_try_catch_compilation() { + let mut compiler = MirCompiler::new(); + + let try_catch_ast = ASTNode::TryCatch { + try_body: vec![ + ASTNode::Print { + expression: Box::new(ASTNode::Literal { + value: LiteralValue::String("Try block".to_string()), + span: crate::ast::Span::unknown(), + }), + span: crate::ast::Span::unknown(), + } + ], + catch_clauses: vec![ + crate::ast::CatchClause { + exception_type: Some("Exception".to_string()), + variable_name: Some("e".to_string()), + body: vec![ + ASTNode::Print { + expression: Box::new(ASTNode::Literal { + value: LiteralValue::String("Catch block".to_string()), + span: crate::ast::Span::unknown(), + }), + span: crate::ast::Span::unknown(), + } + ], + span: crate::ast::Span::unknown(), + } + ], + finally_body: None, + span: crate::ast::Span::unknown(), + }; + + let result = compiler.compile(try_catch_ast); + assert!(result.is_ok(), "TryCatch compilation should succeed"); + + let compile_result = result.unwrap(); + let mir_dump = compiler.dump_mir(&compile_result.module); + assert!(mir_dump.contains("catch"), "MIR should contain catch instruction"); + } } \ No newline at end of file diff --git a/src/mir/printer.rs b/src/mir/printer.rs index 636c7892..21ef98f1 100644 --- a/src/mir/printer.rs +++ b/src/mir/printer.rs @@ -301,6 +301,23 @@ impl MirPrinter { MirInstruction::Nop => { "nop".to_string() }, + + // Phase 5: Control flow & exception handling + MirInstruction::Throw { exception, effects: _ } => { + format!("throw {}", exception) + }, + + MirInstruction::Catch { exception_type, exception_value, handler_bb } => { + if let Some(ref exc_type) = exception_type { + format!("catch {} {} -> {}", exc_type, exception_value, handler_bb) + } else { + format!("catch * {} -> {}", exception_value, handler_bb) + } + }, + + MirInstruction::Safepoint => { + "safepoint".to_string() + }, } } diff --git a/test_mir_control_flow.nyash b/test_mir_control_flow.nyash new file mode 100644 index 00000000..5182e954 --- /dev/null +++ b/test_mir_control_flow.nyash @@ -0,0 +1,21 @@ +// Test MIR control flow and exception handling - Phase 5 + +// Test loop functionality +local counter +counter = 0 + +loop(counter < 3) { + print(counter) + counter = counter + 1 +} + +// Test try/catch functionality +try { + print("In try block") + throw "Test exception" + print("This should not execute") +} catch (exception) { + print("Caught exception") +} + +print("Program completed") \ No newline at end of file diff --git a/test_mir_phase5.rs b/test_mir_phase5.rs new file mode 100644 index 00000000..51e1b22c --- /dev/null +++ b/test_mir_phase5.rs @@ -0,0 +1,103 @@ +use nyash_rust::mir::{MirCompiler}; +use nyash_rust::ast::{ASTNode, LiteralValue, Span}; + +fn main() { + println!("=== Testing MIR Control Flow Compilation ===\n"); + + // Test 1: Basic Throw instruction + println!("Test 1: Basic Throw Instruction"); + let throw_ast = ASTNode::Throw { + expression: Box::new(ASTNode::Literal { + value: LiteralValue::String("Test exception".to_string()), + span: Span::unknown(), + }), + span: Span::unknown(), + }; + + let mut compiler = MirCompiler::new(); + match compiler.compile(throw_ast) { + Ok(result) => { + println!("✓ Throw compilation successful"); + let mir_dump = compiler.dump_mir(&result.module); + println!("MIR Output:\n{}", mir_dump); + }, + Err(e) => println!("✗ Throw compilation failed: {}", e), + } + + println!("\n" + &"=".repeat(50) + "\n"); + + // Test 2: Basic Loop instruction + println!("Test 2: Basic Loop Instruction"); + let loop_ast = ASTNode::Loop { + condition: Box::new(ASTNode::Literal { + value: LiteralValue::Bool(true), + span: Span::unknown(), + }), + body: vec![ + ASTNode::Print { + expression: Box::new(ASTNode::Literal { + value: LiteralValue::String("Hello from loop".to_string()), + span: Span::unknown(), + }), + span: Span::unknown(), + } + ], + span: Span::unknown(), + }; + + let mut compiler2 = MirCompiler::new(); + match compiler2.compile(loop_ast) { + Ok(result) => { + println!("✓ Loop compilation successful"); + let mir_dump = compiler2.dump_mir(&result.module); + println!("MIR Output:\n{}", mir_dump); + }, + Err(e) => println!("✗ Loop compilation failed: {}", e), + } + + println!("\n" + &"=".repeat(50) + "\n"); + + // Test 3: TryCatch compilation + println!("Test 3: TryCatch Instruction"); + let try_catch_ast = ASTNode::TryCatch { + try_body: vec![ + ASTNode::Print { + expression: Box::new(ASTNode::Literal { + value: LiteralValue::String("In try block".to_string()), + span: Span::unknown(), + }), + span: Span::unknown(), + } + ], + catch_clauses: vec![ + nyash_rust::ast::CatchClause { + exception_type: Some("Exception".to_string()), + variable_name: Some("e".to_string()), + body: vec![ + ASTNode::Print { + expression: Box::new(ASTNode::Literal { + value: LiteralValue::String("In catch block".to_string()), + span: Span::unknown(), + }), + span: Span::unknown(), + } + ], + span: Span::unknown(), + } + ], + finally_body: None, + span: Span::unknown(), + }; + + let mut compiler3 = MirCompiler::new(); + match compiler3.compile(try_catch_ast) { + Ok(result) => { + println!("✓ TryCatch compilation successful"); + let mir_dump = compiler3.dump_mir(&result.module); + println!("MIR Output:\n{}", mir_dump); + }, + Err(e) => println!("✗ TryCatch compilation failed: {}", e), + } + + println!("\n=== All tests completed ==="); +} \ No newline at end of file