use super::{ConstValue, Effect, EffectMask, MirInstruction, ValueId}; use crate::ast::{ASTNode, CallExpr}; use crate::mir::loop_builder::LoopBuilder; use crate::mir::TypeOpKind; impl super::MirBuilder { // Print statement: env.console.log(value) with early TypeOp handling pub(super) fn build_print_statement(&mut self, expression: ASTNode) -> Result { super::utils::builder_debug_log("enter build_print_statement"); // Prefer wrapper for simple function-call pattern (non-breaking refactor) if let Ok(call) = CallExpr::try_from(expression.clone()) { if (call.name == "isType" || call.name == "asType") && call.arguments.len() == 2 { super::utils::builder_debug_log("pattern: print(FunctionCall isType|asType) [via wrapper]"); if let Some(type_name) = super::MirBuilder::extract_string_literal(&call.arguments[1]) { super::utils::builder_debug_log(&format!("extract_string_literal OK: {}", type_name)); let val = self.build_expression(call.arguments[0].clone())?; let ty = super::MirBuilder::parse_type_name_to_mir(&type_name); let dst = self.value_gen.next(); let op = if call.name == "isType" { TypeOpKind::Check } else { TypeOpKind::Cast }; super::utils::builder_debug_log(&format!("emit TypeOp {:?} value={} dst= {}", op, val, dst)); self.emit_instruction(MirInstruction::TypeOp { dst, op, value: val, ty })?; self.emit_instruction(MirInstruction::ExternCall { dst: None, iface_name: "env.console".to_string(), method_name: "log".to_string(), args: vec![dst], effects: EffectMask::PURE.add(Effect::Io), })?; return Ok(dst); } else { super::utils::builder_debug_log("extract_string_literal FAIL [via wrapper]"); } } } match &expression { // print(isType(val, "Type")) / print(asType(...)) ASTNode::FunctionCall { name, arguments, .. } if (name == "isType" || name == "asType") && arguments.len() == 2 => { super::utils::builder_debug_log("pattern: print(FunctionCall isType|asType)"); if let Some(type_name) = super::MirBuilder::extract_string_literal(&arguments[1]) { super::utils::builder_debug_log(&format!( "extract_string_literal OK: {}", type_name )); let val = self.build_expression(arguments[0].clone())?; let ty = super::MirBuilder::parse_type_name_to_mir(&type_name); let dst = self.value_gen.next(); let op = if name == "isType" { TypeOpKind::Check } else { TypeOpKind::Cast }; super::utils::builder_debug_log(&format!( "emit TypeOp {:?} value={} dst= {}", op, val, dst )); self.emit_instruction(MirInstruction::TypeOp { dst, op, value: val, ty, })?; self.emit_instruction(MirInstruction::ExternCall { dst: None, iface_name: "env.console".to_string(), method_name: "log".to_string(), args: vec![dst], effects: EffectMask::PURE.add(Effect::Io), })?; return Ok(dst); } else { super::utils::builder_debug_log("extract_string_literal FAIL"); } } // print(obj.is("Type")) / print(obj.as("Type")) ASTNode::MethodCall { object, method, arguments, .. } if (method == "is" || method == "as") && arguments.len() == 1 => { super::utils::builder_debug_log("pattern: print(MethodCall is|as)"); if let Some(type_name) = super::MirBuilder::extract_string_literal(&arguments[0]) { super::utils::builder_debug_log(&format!( "extract_string_literal OK: {}", type_name )); let obj_val = self.build_expression(*object.clone())?; let ty = super::MirBuilder::parse_type_name_to_mir(&type_name); let dst = self.value_gen.next(); let op = if method == "is" { TypeOpKind::Check } else { TypeOpKind::Cast }; super::utils::builder_debug_log(&format!( "emit TypeOp {:?} obj={} dst= {}", op, obj_val, dst )); self.emit_instruction(MirInstruction::TypeOp { dst, op, value: obj_val, ty, })?; self.emit_instruction(MirInstruction::ExternCall { dst: None, iface_name: "env.console".to_string(), method_name: "log".to_string(), args: vec![dst], effects: EffectMask::PURE.add(Effect::Io), })?; return Ok(dst); } else { super::utils::builder_debug_log("extract_string_literal FAIL"); } } _ => {} } let value = self.build_expression(expression)?; super::utils::builder_debug_log(&format!("fallback print value={}", value)); self.emit_instruction(MirInstruction::ExternCall { dst: None, iface_name: "env.console".to_string(), method_name: "log".to_string(), args: vec![value], effects: EffectMask::PURE.add(Effect::Io), })?; Ok(value) } // Block: sequentially build statements and return last value or Void pub(super) fn build_block(&mut self, statements: Vec) -> Result { let mut last_value = None; for statement in statements { last_value = Some(self.build_expression(statement)?); // If the current block was terminated by this statement (e.g., return/throw), // do not emit any further instructions for this block. if self.is_current_block_terminated() { break; } } Ok(last_value.unwrap_or_else(|| { let void_val = self.value_gen.next(); self.emit_instruction(MirInstruction::Const { dst: void_val, value: ConstValue::Void, }) .unwrap(); void_val })) } // If: lower to Branch + Phi, bind reassigned var name if identical pub(super) fn build_if_statement( &mut self, condition: ASTNode, then_branch: ASTNode, else_branch: Option, ) -> Result { self.lower_if_form(condition, then_branch, else_branch) } // Loop: delegate to LoopBuilder pub(super) fn build_loop_statement( &mut self, condition: ASTNode, body: Vec, ) -> Result { let mut loop_builder = LoopBuilder::new(self); loop_builder.build_loop(condition, body) } // Try/Catch/Finally lowering with basic Extern semantics when disabled pub(super) fn build_try_catch_statement( &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 and enable deferred-return mode for try/catch 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.value_gen.next(); 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.value_gen.next(); self.emit_instruction(MirInstruction::Catch { exception_type: catch_clause.exception_type.clone(), exception_value, handler_bb: catch_block, })?; } self.emit_instruction(MirInstruction::Jump { target: 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); self.emit_instruction(MirInstruction::Jump { target: next_target, })?; } 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() { if std::env::var("NYASH_DEBUG_TRYCATCH").ok().as_deref() == Some("1") { eprintln!( "[BUILDER] Emitting catch handler for {:?}", catch_clause.exception_type ); } 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); self.emit_instruction(MirInstruction::Jump { target: next_target, })?; } 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)?; // Enter cleanup mode; returns may or may not be allowed by policy 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(); // Inside cleanup, do not defer returns; either forbid or emit real Return self.return_defer_active = false; let finally_ast = ASTNode::Program { statements: finally_statements, span: crate::ast::Span::unknown(), }; self.build_expression(finally_ast)?; // Do not emit a Jump if the finally block already terminated (e.g., via return/throw) cleanup_terminated = self.is_current_block_terminated(); if !cleanup_terminated { self.emit_instruction(MirInstruction::Jump { target: exit_block })?; } self.in_cleanup_block = false; } self.start_new_block(exit_block)?; // If any deferred return occurred in try/catch and cleanup did not already return, // finalize with a Return of the slot; otherwise emit a dummy const/ret to satisfy structure. let result = if self.return_deferred_emitted && !cleanup_terminated { self.emit_instruction(MirInstruction::Return { value: Some(ret_slot) })?; // Emit a symbolic const to satisfy return type inference paths when inspecting non-terminated blocks (not used here) let r = self.value_gen.next(); self.emit_instruction(MirInstruction::Const { dst: r, value: ConstValue::Void })?; r } else { let r = self.value_gen.next(); self.emit_instruction(MirInstruction::Const { dst: r, value: ConstValue::Void })?; r }; // Restore deferred-return 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) } // Throw: emit Throw or fallback to env.debug.trace when disabled pub(super) fn build_throw_statement(&mut self, expression: ASTNode) -> Result { // Enforce cleanup policy 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) } // Local declarations with optional initializers pub(super) fn build_local_statement( &mut self, variables: Vec, initial_values: Vec>>, ) -> Result { let mut last_value = None; for (i, var_name) in variables.iter().enumerate() { let value_id = if i < initial_values.len() && initial_values[i].is_some() { let init_expr = initial_values[i].as_ref().unwrap(); self.build_expression(*init_expr.clone())? } else { self.value_gen.next() }; self.variable_map.insert(var_name.clone(), value_id); last_value = Some(value_id); } Ok(last_value.unwrap_or_else(|| self.value_gen.next())) } // Return statement pub(super) fn build_return_statement( &mut self, value: Option>, ) -> Result { // Enforce cleanup policy if self.in_cleanup_block && !self.cleanup_allow_return { return Err("return is not allowed inside cleanup block (enable NYASH_CLEANUP_ALLOW_RETURN=1 to permit)".to_string()); } let return_value = if let Some(expr) = value { self.build_expression(*expr)? } else { let void_dst = self.value_gen.next(); self.emit_instruction(MirInstruction::Const { dst: void_dst, value: ConstValue::Void })?; void_dst }; if self.return_defer_active { // Defer: copy into slot and jump to target if let (Some(slot), Some(target)) = (self.return_defer_slot, self.return_defer_target) { self.return_deferred_emitted = true; self.emit_instruction(MirInstruction::Copy { dst: slot, src: return_value })?; if !self.is_current_block_terminated() { self.emit_instruction(MirInstruction::Jump { target })?; } Ok(return_value) } else { // Fallback: no configured slot/target; emit a real return self.emit_instruction(MirInstruction::Return { value: Some(return_value) })?; Ok(return_value) } } else { // Normal return self.emit_instruction(MirInstruction::Return { value: Some(return_value) })?; Ok(return_value) } } // Nowait: prefer env.future.spawn_instance if method call; else FutureNew pub(super) fn build_nowait_statement( &mut self, variable: String, expression: ASTNode, ) -> Result { if let ASTNode::MethodCall { object, method, arguments, .. } = expression.clone() { let recv_val = self.build_expression(*object)?; let mname_id = self.value_gen.next(); self.emit_instruction(MirInstruction::Const { dst: mname_id, value: super::ConstValue::String(method.clone()), })?; let mut arg_vals: Vec = Vec::with_capacity(2 + arguments.len()); arg_vals.push(recv_val); arg_vals.push(mname_id); for a in arguments.into_iter() { arg_vals.push(self.build_expression(a)?); } let future_id = self.value_gen.next(); self.emit_instruction(MirInstruction::ExternCall { dst: Some(future_id), iface_name: "env.future".to_string(), method_name: "spawn_instance".to_string(), args: arg_vals, effects: crate::mir::effect::EffectMask::PURE.add(crate::mir::effect::Effect::Io), })?; self.variable_map.insert(variable.clone(), future_id); return Ok(future_id); } let expression_value = self.build_expression(expression)?; let future_id = self.value_gen.next(); self.emit_instruction(MirInstruction::FutureNew { dst: future_id, value: expression_value, })?; self.variable_map.insert(variable.clone(), future_id); Ok(future_id) } // Await: insert Safepoint before/after and emit Await pub(super) fn build_await_expression( &mut self, expression: ASTNode, ) -> Result { let future_value = self.build_expression(expression)?; self.emit_instruction(MirInstruction::Safepoint)?; let result_id = self.value_gen.next(); self.emit_instruction(MirInstruction::Await { dst: result_id, future: future_value, })?; self.emit_instruction(MirInstruction::Safepoint)?; Ok(result_id) } // me: resolve to param if present; else symbolic const (stable mapping) pub(super) fn build_me_expression(&mut self) -> Result { if let Some(id) = self.variable_map.get("me").cloned() { return Ok(id); } let me_value = self.value_gen.next(); let me_tag = if let Some(ref cls) = self.current_static_box { cls.clone() } else { "__me__".to_string() }; self.emit_instruction(MirInstruction::Const { dst: me_value, value: ConstValue::String(me_tag), })?; self.variable_map.insert("me".to_string(), me_value); Ok(me_value) } } // use crate::mir::loop_api::LoopBuilderApi; // no longer needed here