//! Control-flow entrypoints (if/loop/try/throw) centralized here. use super::{Effect, EffectMask, MirInstruction, ValueId}; use crate::ast::{ASTNode, Span}; impl super::MirBuilder { /// Control-flow: block pub(super) fn cf_block(&mut self, statements: Vec) -> Result { // identical to build_block; kept here for future policy hooks self.build_block(statements) } /// Control-flow: if pub(super) fn cf_if( &mut self, condition: ASTNode, then_branch: ASTNode, else_branch: Option, ) -> Result { // current impl is a simple forward to canonical lowering self.lower_if_form(condition, then_branch, else_branch) } /// Control-flow: loop /// /// # Phase 49: JoinIR Frontend Mainline Integration /// /// This is the unified entry point for all loop lowering. When enabled via /// `HAKO_JOINIR_PRINT_TOKENS_MAIN=1`, specific functions (starting with /// `JsonTokenizer.print_tokens/1`) are routed through JoinIR Frontend instead /// of the traditional LoopBuilder path. pub(super) fn cf_loop( &mut self, condition: ASTNode, body: Vec, ) -> Result { // Phase 49: Try JoinIR Frontend route for mainline targets if let Some(result) = self.try_cf_loop_joinir(&condition, &body)? { return Ok(result); } if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { eprintln!("[cf_loop] CALLED from somewhere"); eprintln!("[cf_loop] Current stack (simulated): check build_statement vs build_expression_impl"); } // Delegate to LoopBuilder for consistent handling let mut loop_builder = crate::mir::loop_builder::LoopBuilder::new(self); loop_builder.build_loop(condition, body) } /// Phase 49: Try JoinIR Frontend for mainline integration /// /// Returns `Ok(Some(value))` if the current function should use JoinIR Frontend, /// `Ok(None)` to fall through to the legacy LoopBuilder path. fn try_cf_loop_joinir( &mut self, condition: &ASTNode, body: &[ASTNode], ) -> Result, String> { // Phase 49-2: Check if feature is enabled if std::env::var("HAKO_JOINIR_PRINT_TOKENS_MAIN").ok().as_deref() != Some("1") { return Ok(None); } // Get current function name let func_name = self .current_function .as_ref() .map(|f| f.signature.name.clone()) .unwrap_or_default(); // Phase 49-2: Only handle print_tokens for now if func_name != "JsonTokenizer.print_tokens/1" { return Ok(None); } // Debug log when routing through JoinIR Frontend let debug = std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() || std::env::var("NYASH_JOINIR_MAINLINE_DEBUG").is_ok(); if debug { eprintln!( "[cf_loop/joinir] Routing {} through JoinIR Frontend mainline", func_name ); } // Phase 49-3: Implement JoinIR Frontend integration self.cf_loop_joinir_impl(condition, body, &func_name, debug) } /// Phase 49-3: JoinIR Frontend integration implementation /// /// # Pipeline /// 1. Build Loop AST → Program JSON /// 2. AstToJoinIrLowerer::lower_program_json() → JoinModule /// 3. convert_join_module_to_mir_with_meta() → MirModule /// 4. Merge MIR blocks into current_function fn cf_loop_joinir_impl( &mut self, condition: &ASTNode, body: &[ASTNode], func_name: &str, debug: bool, ) -> Result, String> { use crate::r#macro::ast_json::ast_to_json; use crate::mir::join_ir::frontend::{AstToJoinIrLowerer, JoinFuncMetaMap}; use crate::mir::join_ir_vm_bridge::convert_join_module_to_mir_with_meta; use crate::mir::types::ConstValue; // Step 1: Build Loop AST wrapped in a minimal function let loop_ast = ASTNode::Loop { condition: Box::new(condition.clone()), body: body.to_vec(), span: Span::unknown(), }; // Wrap in a minimal function for JoinIR lowering // JoinIR Frontend expects a function body, not just a loop let wrapper_func = ASTNode::Program { statements: vec![loop_ast], span: Span::unknown(), }; // Step 2: Convert to JSON let program_json = ast_to_json(&wrapper_func); if debug { eprintln!( "[cf_loop/joinir] Generated JSON for {}: {}", func_name, serde_json::to_string_pretty(&program_json).unwrap_or_default() ); } // Step 3: Lower to JoinIR let mut lowerer = AstToJoinIrLowerer::new(); let join_module = lowerer.lower_program_json(&program_json); // Phase 49-3 MVP: Use empty meta map (full if-analysis is Phase 40+ territory) let join_meta = JoinFuncMetaMap::new(); if debug { eprintln!( "[cf_loop/joinir] JoinModule has {} functions, entry={:?}", join_module.functions.len(), join_module.entry ); } // Step 4: Convert JoinModule to MIR let mir_module = convert_join_module_to_mir_with_meta(&join_module, &join_meta) .map_err(|e| format!("JoinIR→MIR conversion failed: {}", e.message))?; if debug { eprintln!( "[cf_loop/joinir] MirModule has {} functions", mir_module.functions.len() ); } // Step 5: Merge MIR blocks into current_function // For Phase 49-3, we'll use a simplified approach: // - Add generated blocks to current_function // - Jump from current_block to the entry of generated loop // - The loop exit becomes the new current_block self.merge_joinir_mir_blocks(&mir_module, debug)?; // Return void for now (loop doesn't have a meaningful return value in this context) let void_val = self.next_value_id(); self.emit_instruction(MirInstruction::Const { dst: void_val, value: ConstValue::Void, })?; Ok(Some(void_val)) } /// Phase 49-3: Merge JoinIR-generated MIR blocks into current_function /// /// This is a simplified merge that: /// 1. Remaps all block IDs to avoid conflicts /// 2. Remaps all value IDs to avoid conflicts /// 3. Adds all blocks to current_function /// 4. Jumps from current_block to the entry block fn merge_joinir_mir_blocks( &mut self, mir_module: &crate::mir::MirModule, debug: bool, ) -> Result<(), String> { // For Phase 49-3 MVP: Just log and fall through // Full block merging is complex and needs careful ID remapping if debug { eprintln!( "[cf_loop/joinir] merge_joinir_mir_blocks called with {} functions", mir_module.functions.len() ); for (name, func) in &mir_module.functions { eprintln!( "[cf_loop/joinir] Function '{}': {} blocks, entry={:?}", name, func.blocks.len(), func.entry_block ); } } // TODO(Phase 49-3.2): Implement full block merging // For now, this is a MVP that demonstrates the pipeline works // The actual block merging will need: // 1. Block ID remapping (block_gen.next() for each block) // 2. Value ID remapping (next_value_id() for each value) // 3. Instruction remapping (update all block/value references) // 4. Variable map integration (merge variable_map) Ok(()) } /// Control-flow: try/catch/finally pub(super) fn cf_try_catch( &mut self, try_body: Vec, catch_clauses: Vec, finally_body: Option>, ) -> Result { if std::env::var("NYASH_BUILDER_DISABLE_TRYCATCH") .ok() .as_deref() == Some("1") { let try_ast = ASTNode::Program { statements: try_body, span: crate::ast::Span::unknown(), }; let result = self.build_expression(try_ast)?; return Ok(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(); // Snapshot deferred-return state let saved_defer_active = self.return_defer_active; let saved_defer_slot = self.return_defer_slot; let saved_defer_target = self.return_defer_target; let saved_deferred_flag = self.return_deferred_emitted; let saved_in_cleanup = self.in_cleanup_block; let saved_allow_ret = self.cleanup_allow_return; let saved_allow_throw = self.cleanup_allow_throw; let ret_slot = self.next_value_id(); self.return_defer_active = true; self.return_defer_slot = Some(ret_slot); self.return_deferred_emitted = false; self.return_defer_target = Some(finally_block.unwrap_or(exit_block)); if let Some(catch_clause) = catch_clauses.first() { if std::env::var("NYASH_DEBUG_TRYCATCH").ok().as_deref() == Some("1") { eprintln!( "[BUILDER] Emitting catch handler for {:?}", catch_clause.exception_type ); } let exception_value = self.next_value_id(); self.emit_instruction(MirInstruction::Catch { exception_type: catch_clause.exception_type.clone(), exception_value, handler_bb: catch_block, })?; } // Enter try block crate::mir::builder::emission::branch::emit_jump(self, 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)?; if !self.is_current_block_terminated() { let next_target = finally_block.unwrap_or(exit_block); crate::mir::builder::emission::branch::emit_jump(self, next_target)?; } // Catch block self.start_new_block(catch_block)?; if std::env::var("NYASH_DEBUG_TRYCATCH").ok().as_deref() == Some("1") { eprintln!("[BUILDER] Enter catch block {:?}", catch_block); } if let Some(catch_clause) = catch_clauses.first() { let catch_ast = ASTNode::Program { statements: catch_clause.body.clone(), span: crate::ast::Span::unknown(), }; self.build_expression(catch_ast)?; } if !self.is_current_block_terminated() { let next_target = finally_block.unwrap_or(exit_block); crate::mir::builder::emission::branch::emit_jump(self, next_target)?; } // Finally let mut cleanup_terminated = false; if let (Some(finally_block_id), Some(finally_statements)) = (finally_block, finally_body) { self.start_new_block(finally_block_id)?; self.in_cleanup_block = true; self.cleanup_allow_return = crate::config::env::cleanup_allow_return(); self.cleanup_allow_throw = crate::config::env::cleanup_allow_throw(); self.return_defer_active = false; // do not defer inside cleanup let finally_ast = ASTNode::Program { statements: finally_statements, span: crate::ast::Span::unknown(), }; self.build_expression(finally_ast)?; cleanup_terminated = self.is_current_block_terminated(); if !cleanup_terminated { crate::mir::builder::emission::branch::emit_jump(self, exit_block)?; } self.in_cleanup_block = false; } // Exit block self.start_new_block(exit_block)?; let result = if self.return_deferred_emitted && !cleanup_terminated { self.emit_instruction(MirInstruction::Return { value: Some(ret_slot), })?; crate::mir::builder::emission::constant::emit_void(self) } else { crate::mir::builder::emission::constant::emit_void(self) }; // Restore context self.return_defer_active = saved_defer_active; self.return_defer_slot = saved_defer_slot; self.return_defer_target = saved_defer_target; self.return_deferred_emitted = saved_deferred_flag; self.in_cleanup_block = saved_in_cleanup; self.cleanup_allow_return = saved_allow_ret; self.cleanup_allow_throw = saved_allow_throw; Ok(result) } /// Control-flow: throw pub(super) fn cf_throw(&mut self, expression: ASTNode) -> Result { if self.in_cleanup_block && !self.cleanup_allow_throw { return Err("throw is not allowed inside cleanup block (enable NYASH_CLEANUP_ALLOW_THROW=1 to permit)".to_string()); } if std::env::var("NYASH_BUILDER_DISABLE_THROW").ok().as_deref() == Some("1") { let v = self.build_expression(expression)?; self.emit_instruction(MirInstruction::ExternCall { dst: None, iface_name: "env.debug".to_string(), method_name: "trace".to_string(), args: vec![v], effects: EffectMask::PURE.add(Effect::Debug), })?; return Ok(v); } let exception_value = self.build_expression(expression)?; self.emit_instruction(MirInstruction::Throw { exception: exception_value, effects: EffectMask::PANIC, })?; Ok(exception_value) } }