fix: try/finally 支持 break/continue/return 穿越 finally(completion 机制)

This commit is contained in:
爱特每羊队
2026-08-08 12:38:25 +08:00
parent 6c7b10d6c5
commit a814dd0700
18 changed files with 529 additions and 39 deletions
+22 -4
View File
@@ -143,6 +143,8 @@ pub struct LabelScope {
pub(crate) name: String,
pub(crate) break_label: LabelId,
pub(crate) continue_label: Option<LabelId>,
/// 标签打开时嵌套的 finally 域数:break/continue 跨越 finally 的计数依据。
pub(crate) finally_depth_at_open: usize,
}
/// 单函数编译上下文:执行流 + 作用域 + 闭包捕获的聚合状态。
@@ -233,6 +235,7 @@ impl CompileCtx {
switch_stack: Vec::new(),
label_scopes: Vec::new(),
pending_loop_labels: Vec::new(),
finally_depth: 0,
label_counter: 0,
},
scopes: ScopeCtx {
@@ -375,39 +378,52 @@ impl CompileCtx {
}
pub(crate) fn push_loop(&mut self, break_label: LabelId, continue_label: LabelId) {
self.labels.loop_stack.push((break_label, continue_label));
let fd = self.labels.finally_depth;
self.labels.loop_stack.push((break_label, continue_label, fd));
}
pub(crate) fn pop_loop(&mut self) {
self.labels.loop_stack.pop();
}
pub(crate) fn current_loop(&self) -> Option<&(LabelId, LabelId)> {
pub(crate) fn current_loop(&self) -> Option<&(LabelId, LabelId, usize)> {
self.labels.loop_stack.last()
}
pub(crate) fn push_switch(&mut self, break_label: LabelId) {
self.labels.switch_stack.push(break_label);
let fd = self.labels.finally_depth;
self.labels.switch_stack.push((break_label, fd));
}
pub(crate) fn pop_switch(&mut self) {
self.labels.switch_stack.pop();
}
pub(crate) fn current_switch(&self) -> Option<&LabelId> {
pub(crate) fn current_switch(&self) -> Option<&(LabelId, usize)> {
self.labels.switch_stack.last()
}
/// 进入/离开一个 try/finally 域:break/continue 跨越 finally 计数用。
pub(crate) fn push_finally_domain(&mut self) {
self.labels.finally_depth += 1;
}
pub(crate) fn pop_finally_domain(&mut self) {
self.labels.finally_depth -= 1;
}
pub(crate) fn push_label_scope(
&mut self, name: &str, break_label: LabelId, continue_label: Option<LabelId>,
) -> Result<(), String> {
if self.labels.label_scopes.iter().any(|s| s.name == name) {
return Err(format!("SyntaxError: Label '{name}' has already been declared"));
}
let fd = self.labels.finally_depth;
self.labels.label_scopes.push(LabelScope {
name: name.to_string(),
break_label,
continue_label,
finally_depth_at_open: fd,
});
Ok(())
}
@@ -437,11 +453,13 @@ impl CompileCtx {
pub(crate) fn take_pending_loop_labels(&mut self, break_label: LabelId, continue_label: LabelId) -> usize {
let names = std::mem::take(&mut self.labels.pending_loop_labels);
let count = names.len();
let fd = self.labels.finally_depth;
for name in names {
self.labels.label_scopes.push(LabelScope {
name,
break_label,
continue_label: Some(continue_label),
finally_depth_at_open: fd,
});
}
count
+6 -2
View File
@@ -13,12 +13,16 @@ use oxide_ir::operand::LabelId;
pub(crate) struct LabelCtx {
/// label id → 指令下标。id 连续递增,Vec 索引即 id;写入前须扩容。
pub(crate) label_pos: Vec<Option<usize>>,
pub(crate) loop_stack: Vec<(LabelId, LabelId)>,
pub(crate) switch_stack: Vec<LabelId>,
/// 每个条目记录循环打开时嵌套的 finally 域数(break/continue 跨越 finally 计数用)。
pub(crate) loop_stack: Vec<(LabelId, LabelId, usize)>,
/// 每个条目记录 switch 打开时嵌套的 finally 域数。
pub(crate) switch_stack: Vec<(LabelId, usize)>,
/// 活动标签语句作用域(解析 `break label` / `continue label`)。
pub(crate) label_scopes: Vec<LabelScope>,
/// 等待绑定到下一个循环 continue 目标的标签名。
pub(crate) pending_loop_labels: Vec<String>,
/// 当前打开(正在 emit)的 try/finally 域数。
pub(crate) finally_depth: usize,
pub(crate) label_counter: u32,
}
@@ -1,4 +1,8 @@
//! break/continue 语句 emit:解析标签与最近循环的跳转目标。
//!
//! 计算 break/continue 逃出的 finally 域数(目标打开时的 finally 深度与当前深度之差):
//! 逃出 ≥1 个 finally → 发 BREAK/CONTINUE(运行时穿越 finally 后再跳转);
//! 不逃出(目标在 try 域内,如 switch/循环内部的 break)→ 发普通 JMP。
use crate::{CompileCtx, Emitter};
use oxide_ir::inst::Inst;
@@ -7,38 +11,51 @@ impl Emitter {
pub(crate) fn emit_break_statement(
&self, stmt: &oxide_parser::BreakStatement, ctx: &mut CompileCtx,
) -> Result<Option<u32>, String> {
let break_label = if let Some(label) = &stmt.label {
let (break_label, fd_at_open) = if let Some(label) = &stmt.label {
let name = label.name.as_str();
let scope = ctx
.find_label(name)
.ok_or_else(|| format!("SyntaxError: Undefined label '{name}'"))?;
scope.break_label
} else if let Some(sw_label) = ctx.current_switch() {
*sw_label
(scope.break_label, scope.finally_depth_at_open)
} else if let Some((sw_label, fd)) = ctx.current_switch() {
(*sw_label, *fd)
} else {
let (bl, _) = ctx.current_loop().ok_or("break outside switch or loop".to_string())?;
*bl
let (bl, _, fd) = ctx.current_loop().ok_or("break outside switch or loop".to_string())?;
(*bl, *fd)
};
ctx.inst(Inst::jmp(break_label));
let crossed = ctx.labels.finally_depth.saturating_sub(fd_at_open) as u16;
if crossed > 0 {
ctx.inst(Inst::brk(break_label, crossed));
} else {
ctx.inst(Inst::jmp(break_label));
}
Ok(None)
}
pub(crate) fn emit_continue_statement(
&self, stmt: &oxide_parser::ContinueStatement, ctx: &mut CompileCtx,
) -> Result<Option<u32>, String> {
let continue_label = if let Some(label) = &stmt.label {
let (continue_label, fd_at_open) = if let Some(label) = &stmt.label {
let name = label.name.as_str();
let scope = ctx
.find_label(name)
.ok_or_else(|| format!("SyntaxError: Undefined label '{name}'"))?;
scope.continue_label.ok_or_else(|| {
format!("SyntaxError: Illegal continue statement: '{name}' does not denote an iteration statement")
})?
(
scope.continue_label.ok_or_else(|| {
format!("SyntaxError: Illegal continue statement: '{name}' does not denote an iteration statement")
})?,
scope.finally_depth_at_open,
)
} else {
let (_, cl) = ctx.current_loop().ok_or("continue outside loop".to_string())?;
*cl
let (_, cl, fd) = ctx.current_loop().ok_or("continue outside loop".to_string())?;
(*cl, *fd)
};
ctx.inst(Inst::jmp(continue_label));
let crossed = ctx.labels.finally_depth.saturating_sub(fd_at_open) as u16;
if crossed > 0 {
ctx.inst(Inst::cont(continue_label, crossed));
} else {
ctx.inst(Inst::jmp(continue_label));
}
Ok(None)
}
}
@@ -18,6 +18,9 @@ impl Emitter {
let has_finally = ts.finalizer.is_some();
let result_reg = ctx.alloc_reg();
if has_finally {
// finally 域跨整个语句(try 体 + catch 体 + finally 体):期间 break/continue
// 逃出语句时须穿越本 finally,词法上记录域深度供跨越计数。
ctx.push_finally_domain();
ctx.inst(Inst::try_finally_begin(finally_label));
}
if has_catch {
@@ -81,6 +84,7 @@ impl Emitter {
Operand::None,
));
ctx.inst(Inst::new(OpCode::TRY_FINALLY_END, Operand::None, Operand::None, Operand::None));
ctx.pop_finally_domain();
}
ctx.labels.set_label_pos(try_end_label, ctx.insts.len());
Ok(Some(result_reg))