//! Control-flow entrypoints (if/loop/try/throw) centralized here. use super::{Effect, EffectMask, MirInstruction, ValueId}; use crate::ast::ASTNode; 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) fn try_cf_loop_joinir( &mut self, condition: &ASTNode, body: &[ASTNode], ) -> Result, String> { // Get current function name let func_name = self .current_function .as_ref() .map(|f| f.signature.name.clone()) .unwrap_or_default(); // Phase 49-4 + Phase 80: Multi-target routing // - Core ON なら代表2本(print_tokens / ArrayExt.filter)は JoinIR を優先し、失敗したら LoopBuilder へフォールバック // - Core OFF では従来通り dev フラグで opt-in // Note: Arity does NOT include implicit `me` receiver let core_on = crate::config::env::joinir_core_enabled(); let is_target = match func_name.as_str() { "JsonTokenizer.print_tokens/0" => { if core_on { true } else { std::env::var("HAKO_JOINIR_PRINT_TOKENS_MAIN") .ok() .as_deref() == Some("1") } } "ArrayExtBox.filter/2" => { if core_on { true } else { std::env::var("HAKO_JOINIR_ARRAY_FILTER_MAIN") .ok() .as_deref() == Some("1") } } _ => false, }; if !is_target { 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 → JSON v0 format (with "defs" array) /// 2. AstToJoinIrLowerer::lower_program_json() → JoinModule /// 3. convert_join_module_to_mir_with_meta() → MirModule /// 4. Merge MIR blocks into current_function /// /// # Phase 49-4 Note /// /// JoinIR Frontend expects a complete function definition with: /// - local variable initializations /// - loop body /// - return statement /// /// Since cf_loop only has access to the loop condition and body, /// we construct a minimal JSON v0 wrapper with function name "simple" /// to match the JoinIR Frontend's expected pattern. fn cf_loop_joinir_impl( &mut self, condition: &ASTNode, body: &[ASTNode], func_name: &str, debug: bool, ) -> Result, String> { use super::loop_frontend_binding::LoopFrontendBinding; 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; use crate::r#macro::ast_json::ast_to_json; // Phase 50: Create appropriate binding based on function name let binding = match func_name { "JsonTokenizer.print_tokens/0" => LoopFrontendBinding::for_print_tokens(), "ArrayExtBox.filter/2" => LoopFrontendBinding::for_array_filter(), _ => { if debug { eprintln!( "[cf_loop/joinir] No binding defined for {}, falling back", func_name ); } return Ok(None); } }; if debug { eprintln!( "[cf_loop/joinir] Using binding: counter={}, acc={:?}, pattern={:?}", binding.counter_var, binding.accumulator_var, binding.pattern ); } // Step 1: Convert condition and body to JSON let condition_json = ast_to_json(condition); let mut body_json: Vec = body.iter().map(|s| ast_to_json(s)).collect(); // Phase 50: Rename variables in body (e.g., "out" → "acc" for filter) binding.rename_body_variables(&mut body_json); // Phase 50: Generate Local declarations from binding let (i_local, acc_local, n_local) = binding.generate_local_declarations(); // Phase 52/56: Build params from external_refs // Instance methods need `me`, static methods need their parameters (arr, pred, etc.) let mut params: Vec = Vec::new(); // Phase 52: Add 'me' for instance methods if binding.needs_me_receiver() { if debug { eprintln!("[cf_loop/joinir] Adding 'me' to params (instance method)"); } params.push(serde_json::json!("me")); } // Phase 56: Add external_refs as parameters (arr, pred for filter) for ext_ref in &binding.external_refs { // Skip "me" and "me.*" as they're handled above if ext_ref == "me" || ext_ref.starts_with("me.") { continue; } if debug { eprintln!( "[cf_loop/joinir] Adding '{}' to params (external_ref)", ext_ref ); } params.push(serde_json::json!(ext_ref)); } // Step 2: Construct JSON v0 format with "defs" array // The function is named "simple" to match JoinIR Frontend's pattern matching // Phase 50: Include i/acc/n Local declarations to satisfy JoinIR Frontend expectations let program_json = serde_json::json!({ "defs": [ { "name": "simple", "params": params, "body": { "type": "Block", "body": [ // Phase 50: Inject i/acc/n Local declarations i_local, acc_local, n_local, { "type": "Loop", "cond": condition_json, // JoinIR Frontend expects "cond" not "condition" "body": body_json }, // Return the accumulator (or null for side-effect loops) { "type": "Return", "value": { "kind": "Variable", "name": "acc" } } ] } } ] }); if debug { eprintln!( "[cf_loop/joinir] Generated JSON v0 for {}: {}", func_name, serde_json::to_string_pretty(&program_json).unwrap_or_default() ); } // Step 3: Lower to JoinIR // Phase 49-4: Use catch_unwind for graceful fallback on unsupported patterns // The JoinIR Frontend may panic if the loop doesn't match expected patterns // (e.g., missing variable initializations like "i must be initialized") let join_module = { let json_clone = program_json.clone(); let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { let mut lowerer = AstToJoinIrLowerer::new(); lowerer.lower_program_json(&json_clone) })); match result { Ok(module) => module, Err(e) => { // Extract panic message for debugging let panic_msg = if let Some(s) = e.downcast_ref::<&str>() { s.to_string() } else if let Some(s) = e.downcast_ref::() { s.clone() } else { "unknown panic".to_string() }; if debug { eprintln!( "[cf_loop/joinir] JoinIR lowering failed for {}: {}, falling back to legacy", func_name, panic_msg ); } // Return None to fall back to legacy LoopBuilder return Ok(None); } } }; // 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.2: Merge JoinIR-generated MIR blocks into current_function /// /// This merges JoinIR-generated blocks by: /// 1. Remapping all block IDs to avoid conflicts /// 2. Remapping all value IDs to avoid conflicts /// 3. Adding all blocks to current_function /// 4. Jumping from current_block to the entry block /// 5. Converting Return → Jump to exit block fn merge_joinir_mir_blocks( &mut self, mir_module: &crate::mir::MirModule, debug: bool, ) -> Result<(), String> { use crate::mir::{BasicBlock, BasicBlockId, MirInstruction, ValueId}; use std::collections::HashMap; if debug { eprintln!( "[cf_loop/joinir] merge_joinir_mir_blocks called with {} functions", mir_module.functions.len() ); } // Get the first (and typically only) function from JoinIR output let join_func = mir_module .functions .values() .next() .ok_or("JoinIR module has no functions")?; if debug { eprintln!( "[cf_loop/joinir] Merging function with {} blocks, entry={:?}", join_func.blocks.len(), join_func.entry_block ); } // Phase 49-3.2: Block ID and Value ID remapping let mut block_map: HashMap = HashMap::new(); let mut value_map: HashMap = HashMap::new(); // 1. Allocate new block IDs for all JoinIR blocks for old_block_id in join_func.blocks.keys() { let new_block_id = self.block_gen.next(); block_map.insert(*old_block_id, new_block_id); if debug { eprintln!( "[cf_loop/joinir] Block remap: {:?} → {:?}", old_block_id, new_block_id ); } } // 2. Create exit block for Return conversion let exit_block_id = self.block_gen.next(); if debug { eprintln!("[cf_loop/joinir] Exit block: {:?}", exit_block_id); } // 3. Collect all ValueIds used in JoinIR function let mut used_values: std::collections::BTreeSet = std::collections::BTreeSet::new(); for block in join_func.blocks.values() { Self::collect_values_in_block(block, &mut used_values); } // Also collect parameter ValueIds for param in &join_func.params { used_values.insert(*param); } // 4. Allocate new ValueIds for old_value in used_values { let new_value = self.next_value_id(); value_map.insert(old_value, new_value); if debug { eprintln!( "[cf_loop/joinir] Value remap: {:?} → {:?}", old_value, new_value ); } } // 5. Clone and remap all blocks for (old_block_id, old_block) in &join_func.blocks { let new_block_id = block_map[old_block_id]; let mut new_block = BasicBlock::new(new_block_id); // Remap instructions for inst in &old_block.instructions { let remapped = Self::remap_instruction(inst, &value_map, &block_map); new_block.instructions.push(remapped); } new_block.instruction_spans = old_block.instruction_spans.clone(); // Remap terminator (convert Return → Jump to exit) if let Some(ref term) = old_block.terminator { let remapped_term = match term { MirInstruction::Return { value } => { // Convert Return to Jump to exit block // If there's a return value, we need to store it first if let Some(ret_val) = value { let remapped_val = value_map.get(ret_val).copied().unwrap_or(*ret_val); // Store the return value for later use // For now, just jump to exit (value handling in Phase 49-4) if debug { eprintln!( "[cf_loop/joinir] Return({:?}) → Jump to exit", remapped_val ); } } MirInstruction::Jump { target: exit_block_id, } } _ => Self::remap_instruction(term, &value_map, &block_map), }; new_block.terminator = Some(remapped_term); } // Add block to current function if let Some(ref mut func) = self.current_function { func.add_block(new_block); } } // 6. Create exit block (empty for now, will be populated after loop) if let Some(ref mut func) = self.current_function { let exit_block = BasicBlock::new(exit_block_id); func.add_block(exit_block); } // 7. Jump from current block to JoinIR entry let entry_block = block_map[&join_func.entry_block]; crate::mir::builder::emission::branch::emit_jump(self, entry_block)?; // 8. Switch to exit block for subsequent code self.start_new_block(exit_block_id)?; if debug { eprintln!( "[cf_loop/joinir] Merge complete: {} blocks added, continuing from {:?}", join_func.blocks.len(), exit_block_id ); } Ok(()) } /// Collect all ValueIds used in a block fn collect_values_in_block( block: &crate::mir::BasicBlock, values: &mut std::collections::BTreeSet, ) { for inst in &block.instructions { Self::collect_values_in_instruction(inst, values); } if let Some(ref term) = block.terminator { Self::collect_values_in_instruction(term, values); } } /// Collect all ValueIds used in an instruction fn collect_values_in_instruction( inst: &crate::mir::MirInstruction, values: &mut std::collections::BTreeSet, ) { use crate::mir::MirInstruction; match inst { MirInstruction::Const { dst, .. } => { values.insert(*dst); } MirInstruction::BinOp { dst, lhs, rhs, .. } => { values.insert(*dst); values.insert(*lhs); values.insert(*rhs); } MirInstruction::UnaryOp { dst, operand, .. } => { values.insert(*dst); values.insert(*operand); } MirInstruction::Compare { dst, lhs, rhs, .. } => { values.insert(*dst); values.insert(*lhs); values.insert(*rhs); } MirInstruction::Load { dst, ptr } => { values.insert(*dst); values.insert(*ptr); } MirInstruction::Store { value, ptr } => { values.insert(*value); values.insert(*ptr); } MirInstruction::Call { dst, func, args, .. } => { if let Some(d) = dst { values.insert(*d); } values.insert(*func); for arg in args { values.insert(*arg); } } MirInstruction::BoxCall { dst, box_val, args, .. } => { if let Some(d) = dst { values.insert(*d); } values.insert(*box_val); for arg in args { values.insert(*arg); } } MirInstruction::Branch { condition, .. } => { values.insert(*condition); } MirInstruction::Return { value } => { if let Some(v) = value { values.insert(*v); } } MirInstruction::Phi { dst, inputs, type_hint: None, } => { values.insert(*dst); for (_, val) in inputs { values.insert(*val); } } MirInstruction::Copy { dst, src } => { values.insert(*dst); values.insert(*src); } MirInstruction::NewBox { dst, args, .. } => { values.insert(*dst); for arg in args { values.insert(*arg); } } MirInstruction::Print { value, .. } => { values.insert(*value); } _ => { // Other instructions: skip for now } } } /// Remap an instruction's ValueIds and BlockIds fn remap_instruction( inst: &crate::mir::MirInstruction, value_map: &std::collections::HashMap, block_map: &std::collections::HashMap, ) -> crate::mir::MirInstruction { use crate::mir::MirInstruction; let remap_value = |v: super::ValueId| value_map.get(&v).copied().unwrap_or(v); let remap_block = |b: crate::mir::BasicBlockId| block_map.get(&b).copied().unwrap_or(b); match inst { MirInstruction::Const { dst, value } => MirInstruction::Const { dst: remap_value(*dst), value: value.clone(), }, MirInstruction::BinOp { dst, op, lhs, rhs } => MirInstruction::BinOp { dst: remap_value(*dst), op: *op, lhs: remap_value(*lhs), rhs: remap_value(*rhs), }, MirInstruction::UnaryOp { dst, op, operand } => MirInstruction::UnaryOp { dst: remap_value(*dst), op: *op, operand: remap_value(*operand), }, MirInstruction::Compare { dst, op, lhs, rhs } => MirInstruction::Compare { dst: remap_value(*dst), op: *op, lhs: remap_value(*lhs), rhs: remap_value(*rhs), }, MirInstruction::Load { dst, ptr } => MirInstruction::Load { dst: remap_value(*dst), ptr: remap_value(*ptr), }, MirInstruction::Store { value, ptr } => MirInstruction::Store { value: remap_value(*value), ptr: remap_value(*ptr), }, MirInstruction::Call { dst, func, callee, args, effects, } => MirInstruction::Call { dst: dst.map(remap_value), func: remap_value(*func), callee: callee.clone(), args: args.iter().map(|a| remap_value(*a)).collect(), effects: *effects, }, MirInstruction::BoxCall { dst, box_val, method, method_id, args, effects, } => MirInstruction::BoxCall { dst: dst.map(remap_value), box_val: remap_value(*box_val), method: method.clone(), method_id: *method_id, args: args.iter().map(|a| remap_value(*a)).collect(), effects: *effects, }, MirInstruction::Branch { condition, then_bb, else_bb, } => MirInstruction::Branch { condition: remap_value(*condition), then_bb: remap_block(*then_bb), else_bb: remap_block(*else_bb), }, MirInstruction::Jump { target } => MirInstruction::Jump { target: remap_block(*target), }, MirInstruction::Return { value } => MirInstruction::Return { value: value.map(remap_value), }, MirInstruction::Phi { dst, inputs, type_hint: None, } => MirInstruction::Phi { dst: remap_value(*dst), inputs: inputs .iter() .map(|(bb, val)| (remap_block(*bb), remap_value(*val))) .collect(), type_hint: None, // Phase 63-6: Preserve no type hint during remapping }, MirInstruction::Copy { dst, src } => MirInstruction::Copy { dst: remap_value(*dst), src: remap_value(*src), }, MirInstruction::NewBox { dst, box_type, args, } => MirInstruction::NewBox { dst: remap_value(*dst), box_type: box_type.clone(), args: args.iter().map(|a| remap_value(*a)).collect(), }, MirInstruction::Print { value, effects } => MirInstruction::Print { value: remap_value(*value), effects: *effects, }, // Pass through other instructions unchanged other => other.clone(), } } /// 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) } }