//! Control-flow entrypoints (if/loop/try/throw) centralized here. //! //! This module is being modularized in phases: //! - Phase 1: Debug utilities (debug.rs) ✅ //! - Phase 2: Pattern lowerers (joinir/patterns/) ✅ //! - Phase 3: JoinIR routing (joinir/routing.rs) ✅ //! - Phase 4-19: Additional modularization (future) use super::{Effect, EffectMask, MirInstruction, ValueId}; use crate::ast::ASTNode; // Phase 1: Debug utilities pub(in crate::mir::builder) mod debug; // Phase 2: JoinIR integration (patterns) pub(in crate::mir::builder) mod joinir; 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 /// /// # Phase 124: JoinIR-Only (hako_check専用化完了) /// /// If statements are now always routed through the canonical lowering path /// (lower_if_form), which internally uses JoinIR-based PHI generation. /// /// Phase 123 の環境変数による分岐は削除済み。 pub(super) fn cf_if( &mut self, condition: ASTNode, then_branch: ASTNode, else_branch: Option, ) -> Result { // Phase 124: JoinIR-only path (環境変数分岐削除) // lower_if_form は JoinIR ベースの PHI 生成を使用 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. Specific functions /// are routed through JoinIR Frontend instead of the traditional LoopBuilder path /// when enabled via dev flags (Phase 49) or Core policy (Phase 80): /// /// - Core ON (`joinir_core_enabled()`): print_tokens / ArrayExt.filter はまず JoinIR Frontend を試す /// - Dev フラグ(既存): /// - `HAKO_JOINIR_PRINT_TOKENS_MAIN=1`: JsonTokenizer.print_tokens/0 /// - `HAKO_JOINIR_ARRAY_FILTER_MAIN=1`: ArrayExtBox.filter/2 /// /// Note: Arity does NOT include implicit `me` receiver. pub(super) fn cf_loop( &mut self, condition: ASTNode, body: Vec, ) -> Result { // Phase 49/80: 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"); } // Phase 186: LoopBuilder Hard Freeze - Legacy path disabled // Phase 187-2: LoopBuilder module removed - all loops must use JoinIR return Err(format!( "[joinir/freeze] Loop lowering failed: JoinIR does not support this pattern, and LoopBuilder has been removed.\n\ Function: {}\n\ Hint: This loop pattern is not supported. All loops must use JoinIR lowering.", self.current_function.as_ref().map(|f| f.signature.name.as_str()).unwrap_or("") )); } /// 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. /// /// # Phase 49-4: Multi-target support /// /// Targets are enabled via separate dev flags: /// - `HAKO_JOINIR_PRINT_TOKENS_MAIN=1`: JsonTokenizer.print_tokens/0 /// - `HAKO_JOINIR_ARRAY_FILTER_MAIN=1`: ArrayExtBox.filter/2 /// /// Note: Arity in function names does NOT include implicit `me` receiver. /// - Instance method `print_tokens()` → `/0` (no explicit params) /// - Static method `filter(arr, pred)` → `/2` (two params) /// Phase 49-3.2: Merge JoinIR-generated MIR blocks into current_function /// /// **Phase 4 Refactoring Complete**: This function now delegates to the modular /// merge implementation in `joinir::merge::merge_joinir_mir_blocks()`. /// /// The original 714-line implementation has been broken down into 6 focused modules: /// 1. block_allocator.rs - Block ID allocation /// 2. value_collector.rs - Value collection /// 3. ID remapping (using JoinIrIdRemapper) /// 4. instruction_rewriter.rs - Instruction rewriting /// 5. exit_phi_builder.rs - Exit PHI construction /// 6. Boundary reconnection (in merge/mod.rs) fn merge_joinir_mir_blocks( &mut self, mir_module: &crate::mir::MirModule, boundary: Option<&crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary>, debug: bool, ) -> Result, String> { // Phase 4: Delegate to modular implementation joinir::merge::merge_joinir_mir_blocks(self, mir_module, boundary, debug) } /// 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) } /// Phase 188-Impl-2: Extract loop variable name from condition /// /// For `i < 3`, extracts `i`. /// For `arr.length() > 0`, extracts `arr`. /// /// This is a minimal implementation that handles simple comparison patterns. fn extract_loop_variable_from_condition(&self, condition: &ASTNode) -> Result { use crate::ast::BinaryOperator; match condition { ASTNode::BinaryOp { operator, left, .. } if matches!( operator, BinaryOperator::Less | BinaryOperator::Greater | BinaryOperator::LessEqual | BinaryOperator::GreaterEqual ) => { // Binary comparison: extract variable from left side match &**left { ASTNode::Variable { name, .. } => Ok(name.clone()), _ => Err(format!( "[cf_loop/pattern1] Cannot extract loop variable from condition: {:?}", condition )), } } _ => Err(format!( "[cf_loop/pattern1] Unsupported loop condition pattern: {:?}", condition )), } } /// 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) } }