refactor: Extract exception handling from control_flow.rs (Phase 5)
- Created exception/ directory with try_catch.rs, throw.rs, mod.rs - Extracted ~180 lines of exception handling logic - control_flow/mod.rs now delegates to exception module - All builds pass, no behavior changes
This commit is contained in:
36
src/mir/builder/control_flow/exception/mod.rs
Normal file
36
src/mir/builder/control_flow/exception/mod.rs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
//! Exception handling implementation.
|
||||||
|
//!
|
||||||
|
//! This module provides exception handling control flow primitives:
|
||||||
|
//! - Try/catch/finally blocks
|
||||||
|
//! - Throw statements
|
||||||
|
//!
|
||||||
|
//! # Architecture
|
||||||
|
//!
|
||||||
|
//! Exception handling integrates with the MIR builder's cleanup and
|
||||||
|
//! deferred return mechanisms to ensure proper resource management
|
||||||
|
//! and control flow even in the presence of exceptions.
|
||||||
|
//!
|
||||||
|
//! ## Try/Catch/Finally
|
||||||
|
//!
|
||||||
|
//! The try/catch/finally implementation creates multiple basic blocks:
|
||||||
|
//! - Try block: Normal execution path
|
||||||
|
//! - Catch block: Exception handler
|
||||||
|
//! - Finally block: Cleanup code (optional)
|
||||||
|
//! - Exit block: Continuation after exception handling
|
||||||
|
//!
|
||||||
|
//! ## Cleanup Blocks
|
||||||
|
//!
|
||||||
|
//! Cleanup blocks (finally blocks) have special restrictions:
|
||||||
|
//! - Return statements require `NYASH_CLEANUP_ALLOW_RETURN=1`
|
||||||
|
//! - Throw statements require `NYASH_CLEANUP_ALLOW_THROW=1`
|
||||||
|
//!
|
||||||
|
//! # Modules
|
||||||
|
//!
|
||||||
|
//! - `try_catch`: Try/catch/finally block implementation
|
||||||
|
//! - `throw`: Throw statement implementation
|
||||||
|
|
||||||
|
mod throw;
|
||||||
|
mod try_catch;
|
||||||
|
|
||||||
|
pub(in crate::mir::builder) use throw::cf_throw;
|
||||||
|
pub(in crate::mir::builder) use try_catch::cf_try_catch;
|
||||||
41
src/mir/builder/control_flow/exception/throw.rs
Normal file
41
src/mir/builder/control_flow/exception/throw.rs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
//! Throw statement implementation.
|
||||||
|
//!
|
||||||
|
//! This module implements the throw statement for exception raising,
|
||||||
|
//! with proper cleanup block validation.
|
||||||
|
|
||||||
|
use crate::ast::ASTNode;
|
||||||
|
use crate::mir::builder::{Effect, EffectMask, MirInstruction, ValueId};
|
||||||
|
|
||||||
|
/// Control-flow: throw
|
||||||
|
///
|
||||||
|
/// Raises an exception with the given expression value.
|
||||||
|
///
|
||||||
|
/// # Cleanup Block Validation
|
||||||
|
///
|
||||||
|
/// Throwing inside cleanup blocks is controlled by the
|
||||||
|
/// `NYASH_CLEANUP_ALLOW_THROW=1` environment variable.
|
||||||
|
pub(in crate::mir::builder) fn cf_throw(
|
||||||
|
builder: &mut super::super::super::MirBuilder,
|
||||||
|
expression: ASTNode,
|
||||||
|
) -> Result<ValueId, String> {
|
||||||
|
if builder.in_cleanup_block && !builder.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 = builder.build_expression(expression)?;
|
||||||
|
builder.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 = builder.build_expression(expression)?;
|
||||||
|
builder.emit_instruction(MirInstruction::Throw {
|
||||||
|
exception: exception_value,
|
||||||
|
effects: EffectMask::PANIC,
|
||||||
|
})?;
|
||||||
|
Ok(exception_value)
|
||||||
|
}
|
||||||
146
src/mir/builder/control_flow/exception/try_catch.rs
Normal file
146
src/mir/builder/control_flow/exception/try_catch.rs
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
//! Try/catch/finally exception handling implementation.
|
||||||
|
//!
|
||||||
|
//! This module implements the control flow for try/catch/finally blocks,
|
||||||
|
//! including proper handling of deferred returns and cleanup blocks.
|
||||||
|
|
||||||
|
use crate::ast::ASTNode;
|
||||||
|
use crate::mir::builder::{Effect, EffectMask, MirInstruction, ValueId};
|
||||||
|
|
||||||
|
/// Control-flow: try/catch/finally
|
||||||
|
///
|
||||||
|
/// Implements exception handling with:
|
||||||
|
/// - Try block execution
|
||||||
|
/// - Catch clause handling
|
||||||
|
/// - Finally cleanup block
|
||||||
|
/// - Deferred return state management
|
||||||
|
pub(in crate::mir::builder) fn cf_try_catch(
|
||||||
|
builder: &mut super::super::super::MirBuilder,
|
||||||
|
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 = builder.build_expression(try_ast)?;
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
let try_block = builder.block_gen.next();
|
||||||
|
let catch_block = builder.block_gen.next();
|
||||||
|
let finally_block = if finally_body.is_some() {
|
||||||
|
Some(builder.block_gen.next())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let exit_block = builder.block_gen.next();
|
||||||
|
|
||||||
|
// Snapshot deferred-return state
|
||||||
|
let saved_defer_active = builder.return_defer_active;
|
||||||
|
let saved_defer_slot = builder.return_defer_slot;
|
||||||
|
let saved_defer_target = builder.return_defer_target;
|
||||||
|
let saved_deferred_flag = builder.return_deferred_emitted;
|
||||||
|
let saved_in_cleanup = builder.in_cleanup_block;
|
||||||
|
let saved_allow_ret = builder.cleanup_allow_return;
|
||||||
|
let saved_allow_throw = builder.cleanup_allow_throw;
|
||||||
|
|
||||||
|
let ret_slot = builder.next_value_id();
|
||||||
|
builder.return_defer_active = true;
|
||||||
|
builder.return_defer_slot = Some(ret_slot);
|
||||||
|
builder.return_deferred_emitted = false;
|
||||||
|
builder.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 = builder.next_value_id();
|
||||||
|
builder.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(builder, try_block)?;
|
||||||
|
builder.start_new_block(try_block)?;
|
||||||
|
let try_ast = ASTNode::Program {
|
||||||
|
statements: try_body,
|
||||||
|
span: crate::ast::Span::unknown(),
|
||||||
|
};
|
||||||
|
let _try_result = builder.build_expression(try_ast)?;
|
||||||
|
if !builder.is_current_block_terminated() {
|
||||||
|
let next_target = finally_block.unwrap_or(exit_block);
|
||||||
|
crate::mir::builder::emission::branch::emit_jump(builder, next_target)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Catch block
|
||||||
|
builder.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(),
|
||||||
|
};
|
||||||
|
builder.build_expression(catch_ast)?;
|
||||||
|
}
|
||||||
|
if !builder.is_current_block_terminated() {
|
||||||
|
let next_target = finally_block.unwrap_or(exit_block);
|
||||||
|
crate::mir::builder::emission::branch::emit_jump(builder, next_target)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally
|
||||||
|
let mut cleanup_terminated = false;
|
||||||
|
if let (Some(finally_block_id), Some(finally_statements)) = (finally_block, finally_body) {
|
||||||
|
builder.start_new_block(finally_block_id)?;
|
||||||
|
builder.in_cleanup_block = true;
|
||||||
|
builder.cleanup_allow_return = crate::config::env::cleanup_allow_return();
|
||||||
|
builder.cleanup_allow_throw = crate::config::env::cleanup_allow_throw();
|
||||||
|
builder.return_defer_active = false; // do not defer inside cleanup
|
||||||
|
|
||||||
|
let finally_ast = ASTNode::Program {
|
||||||
|
statements: finally_statements,
|
||||||
|
span: crate::ast::Span::unknown(),
|
||||||
|
};
|
||||||
|
builder.build_expression(finally_ast)?;
|
||||||
|
cleanup_terminated = builder.is_current_block_terminated();
|
||||||
|
if !cleanup_terminated {
|
||||||
|
crate::mir::builder::emission::branch::emit_jump(builder, exit_block)?;
|
||||||
|
}
|
||||||
|
builder.in_cleanup_block = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit block
|
||||||
|
builder.start_new_block(exit_block)?;
|
||||||
|
let result = if builder.return_deferred_emitted && !cleanup_terminated {
|
||||||
|
builder.emit_instruction(MirInstruction::Return {
|
||||||
|
value: Some(ret_slot),
|
||||||
|
})?;
|
||||||
|
crate::mir::builder::emission::constant::emit_void(builder)
|
||||||
|
} else {
|
||||||
|
crate::mir::builder::emission::constant::emit_void(builder)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Restore context
|
||||||
|
builder.return_defer_active = saved_defer_active;
|
||||||
|
builder.return_defer_slot = saved_defer_slot;
|
||||||
|
builder.return_defer_target = saved_defer_target;
|
||||||
|
builder.return_deferred_emitted = saved_deferred_flag;
|
||||||
|
builder.in_cleanup_block = saved_in_cleanup;
|
||||||
|
builder.cleanup_allow_return = saved_allow_ret;
|
||||||
|
builder.cleanup_allow_throw = saved_allow_throw;
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
@ -4,17 +4,21 @@
|
|||||||
//! - Phase 1: Debug utilities (debug.rs) ✅
|
//! - Phase 1: Debug utilities (debug.rs) ✅
|
||||||
//! - Phase 2: Pattern lowerers (joinir/patterns/) ✅
|
//! - Phase 2: Pattern lowerers (joinir/patterns/) ✅
|
||||||
//! - Phase 3: JoinIR routing (joinir/routing.rs) ✅
|
//! - Phase 3: JoinIR routing (joinir/routing.rs) ✅
|
||||||
//! - Phase 4-19: Additional modularization (future)
|
//! - Phase 4: Merge implementation (joinir/merge/) ✅
|
||||||
|
//! - Phase 5: Exception handling (exception/) ✅
|
||||||
|
|
||||||
use super::{Effect, EffectMask, MirInstruction, ValueId};
|
use super::ValueId;
|
||||||
use crate::ast::ASTNode;
|
use crate::ast::ASTNode;
|
||||||
|
|
||||||
// Phase 1: Debug utilities
|
// Phase 1: Debug utilities
|
||||||
pub(in crate::mir::builder) mod debug;
|
pub(in crate::mir::builder) mod debug;
|
||||||
|
|
||||||
// Phase 2: JoinIR integration (patterns)
|
// Phase 2-4: JoinIR integration (patterns, routing, merge)
|
||||||
pub(in crate::mir::builder) mod joinir;
|
pub(in crate::mir::builder) mod joinir;
|
||||||
|
|
||||||
|
// Phase 5: Exception handling
|
||||||
|
pub(in crate::mir::builder) mod exception;
|
||||||
|
|
||||||
impl super::MirBuilder {
|
impl super::MirBuilder {
|
||||||
/// Control-flow: block
|
/// Control-flow: block
|
||||||
pub(super) fn cf_block(&mut self, statements: Vec<ASTNode>) -> Result<ValueId, String> {
|
pub(super) fn cf_block(&mut self, statements: Vec<ASTNode>) -> Result<ValueId, String> {
|
||||||
@ -118,136 +122,15 @@ impl super::MirBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Control-flow: try/catch/finally
|
/// Control-flow: try/catch/finally
|
||||||
|
///
|
||||||
|
/// Delegates to exception::cf_try_catch for implementation.
|
||||||
pub(super) fn cf_try_catch(
|
pub(super) fn cf_try_catch(
|
||||||
&mut self,
|
&mut self,
|
||||||
try_body: Vec<ASTNode>,
|
try_body: Vec<ASTNode>,
|
||||||
catch_clauses: Vec<crate::ast::CatchClause>,
|
catch_clauses: Vec<crate::ast::CatchClause>,
|
||||||
finally_body: Option<Vec<ASTNode>>,
|
finally_body: Option<Vec<ASTNode>>,
|
||||||
) -> Result<ValueId, String> {
|
) -> Result<ValueId, String> {
|
||||||
if std::env::var("NYASH_BUILDER_DISABLE_TRYCATCH")
|
exception::cf_try_catch(self, try_body, catch_clauses, finally_body)
|
||||||
.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
|
/// Phase 188-Impl-2: Extract loop variable name from condition
|
||||||
@ -287,26 +170,9 @@ impl super::MirBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Control-flow: throw
|
/// Control-flow: throw
|
||||||
|
///
|
||||||
|
/// Delegates to exception::cf_throw for implementation.
|
||||||
pub(super) fn cf_throw(&mut self, expression: ASTNode) -> Result<ValueId, String> {
|
pub(super) fn cf_throw(&mut self, expression: ASTNode) -> Result<ValueId, String> {
|
||||||
if self.in_cleanup_block && !self.cleanup_allow_throw {
|
exception::cf_throw(self, expression)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user