2025-09-19 08:34:29 +09:00
|
|
|
//! Control-flow entrypoints (if/loop/try/throw) centralized here.
|
2025-11-13 16:40:58 +09:00
|
|
|
use super::{Effect, EffectMask, MirInstruction, ValueId};
|
2025-09-19 08:34:29 +09:00
|
|
|
use crate::ast::ASTNode;
|
|
|
|
|
|
|
|
|
|
impl super::MirBuilder {
|
|
|
|
|
/// Control-flow: block
|
|
|
|
|
pub(super) fn cf_block(&mut self, statements: Vec<ASTNode>) -> Result<ValueId, String> {
|
|
|
|
|
// 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<ASTNode>,
|
|
|
|
|
) -> Result<ValueId, String> {
|
|
|
|
|
// current impl is a simple forward to canonical lowering
|
|
|
|
|
self.lower_if_form(condition, then_branch, else_branch)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Control-flow: loop
|
2025-11-28 19:12:59 +09:00
|
|
|
///
|
|
|
|
|
/// # 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.
|
2025-09-19 08:34:29 +09:00
|
|
|
pub(super) fn cf_loop(
|
|
|
|
|
&mut self,
|
|
|
|
|
condition: ASTNode,
|
|
|
|
|
body: Vec<ASTNode>,
|
|
|
|
|
) -> Result<ValueId, String> {
|
2025-11-28 19:12:59 +09:00
|
|
|
// Phase 49: Try JoinIR Frontend route for mainline targets
|
|
|
|
|
if let Some(result) = self.try_cf_loop_joinir(&condition, &body)? {
|
|
|
|
|
return Ok(result);
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-18 06:39:45 +09:00
|
|
|
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");
|
|
|
|
|
}
|
2025-09-19 08:34:29 +09:00
|
|
|
// Delegate to LoopBuilder for consistent handling
|
|
|
|
|
let mut loop_builder = crate::mir::loop_builder::LoopBuilder::new(self);
|
|
|
|
|
loop_builder.build_loop(condition, body)
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-28 19:12:59 +09:00
|
|
|
/// 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<Option<ValueId>, 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.as_str())
|
|
|
|
|
.unwrap_or("");
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() {
|
|
|
|
|
eprintln!(
|
|
|
|
|
"[cf_loop/joinir] Routing {} through JoinIR Frontend mainline",
|
|
|
|
|
func_name
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO(Phase 49-3): Implement actual JoinIR Frontend integration
|
|
|
|
|
// 1. Convert condition + body to AST JSON (Program)
|
|
|
|
|
// 2. Call AstToJoinIrLowerer::lower_program_json()
|
|
|
|
|
// 3. Convert JoinModule to MIR via convert_join_module_to_mir_with_meta()
|
|
|
|
|
// 4. Merge generated blocks into current_function
|
|
|
|
|
//
|
|
|
|
|
// For now, fall through to legacy path
|
|
|
|
|
Ok(None)
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-19 08:34:29 +09:00
|
|
|
/// Control-flow: try/catch/finally
|
|
|
|
|
pub(super) fn cf_try_catch(
|
|
|
|
|
&mut self,
|
|
|
|
|
try_body: Vec<ASTNode>,
|
|
|
|
|
catch_clauses: Vec<crate::ast::CatchClause>,
|
|
|
|
|
finally_body: Option<Vec<ASTNode>>,
|
|
|
|
|
) -> Result<ValueId, String> {
|
|
|
|
|
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;
|
|
|
|
|
|
2025-11-17 00:48:18 +09:00
|
|
|
let ret_slot = self.next_value_id();
|
2025-09-19 08:34:29 +09:00
|
|
|
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
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-11-17 00:48:18 +09:00
|
|
|
let exception_value = self.next_value_id();
|
2025-09-19 08:34:29 +09:00
|
|
|
self.emit_instruction(MirInstruction::Catch {
|
|
|
|
|
exception_type: catch_clause.exception_type.clone(),
|
|
|
|
|
exception_value,
|
|
|
|
|
handler_bb: catch_block,
|
|
|
|
|
})?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Enter try block
|
2025-09-28 20:38:09 +09:00
|
|
|
crate::mir::builder::emission::branch::emit_jump(self, try_block)?;
|
2025-09-19 08:34:29 +09:00
|
|
|
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);
|
2025-09-28 20:38:09 +09:00
|
|
|
crate::mir::builder::emission::branch::emit_jump(self, next_target)?;
|
2025-09-19 08:34:29 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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);
|
2025-09-28 20:38:09 +09:00
|
|
|
crate::mir::builder::emission::branch::emit_jump(self, next_target)?;
|
2025-09-19 08:34:29 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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 {
|
2025-09-28 20:38:09 +09:00
|
|
|
crate::mir::builder::emission::branch::emit_jump(self, exit_block)?;
|
2025-09-19 08:34:29 +09:00
|
|
|
}
|
|
|
|
|
self.in_cleanup_block = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Exit block
|
|
|
|
|
self.start_new_block(exit_block)?;
|
|
|
|
|
let result = if self.return_deferred_emitted && !cleanup_terminated {
|
2025-11-21 06:25:17 +09:00
|
|
|
self.emit_instruction(MirInstruction::Return {
|
|
|
|
|
value: Some(ret_slot),
|
|
|
|
|
})?;
|
2025-09-28 20:38:09 +09:00
|
|
|
crate::mir::builder::emission::constant::emit_void(self)
|
2025-09-19 08:34:29 +09:00
|
|
|
} else {
|
2025-09-28 20:38:09 +09:00
|
|
|
crate::mir::builder::emission::constant::emit_void(self)
|
2025-09-19 08:34:29 +09:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 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<ValueId, String> {
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|