From bd70fecea5fcf7b49e1e1763e259f54282ca10d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=88=B1=E7=89=B9=E6=AF=8F=E7=BE=8A=E9=98=9F?= Date: Sun, 7 Jun 2026 21:31:05 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=20try-catch=20?= =?UTF-8?q?=E8=AF=AD=E6=B3=95=E5=AD=97=E8=8A=82=E7=A0=81=E7=BC=96=E8=AF=91?= =?UTF-8?q?=E5=8F=8A=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_compiler/src/compiler.rs | 3 + crates/oxide_compiler/src/counter.rs | 57 ++++++++++++- crates/oxide_compiler/src/emitter.rs | 97 ++++++++++++++++++++++- crates/oxide_compiler/src/hash.rs | 26 +++++- crates/oxide_compiler/src/opcode.rs | 31 +++++++- crates/oxide_vm/tests/regression_tests.rs | 2 +- 6 files changed, 210 insertions(+), 6 deletions(-) diff --git a/crates/oxide_compiler/src/compiler.rs b/crates/oxide_compiler/src/compiler.rs index 0403180..309ebbf 100644 --- a/crates/oxide_compiler/src/compiler.rs +++ b/crates/oxide_compiler/src/compiler.rs @@ -43,6 +43,9 @@ pub(crate) enum Label { ForInEnd(u32), SwitchEnd(u32), SwitchCase(u32), + CatchBody(u32), + FinallyBody(u32), + TryEnd(u32), } pub(crate) struct CompileCtx { diff --git a/crates/oxide_compiler/src/counter.rs b/crates/oxide_compiler/src/counter.rs index b5a0abd..96b94e4 100644 --- a/crates/oxide_compiler/src/counter.rs +++ b/crates/oxide_compiler/src/counter.rs @@ -195,8 +195,61 @@ impl Compiler { // FD emits LOAD_CONST(BytecodeFunc) + STORE_VAR ctx.projected_pc += 2; } - Statement::ThrowStatement(_) => { - // throw not yet implemented; emit pass will error + Statement::ThrowStatement(ts) => { + self.count_expression(&ts.argument, ctx); + ctx.projected_pc += 1; // THROW + } + Statement::TryStatement(ts) => { + let id = ctx.next_label_id(); + let catch_label = Label::CatchBody(id); + let try_end_label = Label::TryEnd(id); + let has_catch = ts.handler.is_some(); + let has_finally = ts.finalizer.is_some(); + + if has_finally { + ctx.projected_pc += 1; // TRY_FINALLY_BEGIN (before try body) + } + + if has_catch { + ctx.projected_pc += 1; // TRY_BEGIN (before try body) + } + + for s in &ts.block.body { + self.count_statement(s, ctx); + } + + if has_catch { + ctx.projected_pc += 1; // TRY_END + } + + let jmp_needed = has_catch || has_finally; + if jmp_needed { + ctx.projected_pc += 1; // JMP + } + + ctx.label_map.insert(catch_label, ctx.projected_pc); + if let Some(catch) = &ts.handler { + ctx.push_scope(); + if let Some(_param) = &catch.param { + ctx.alloc_reg(); + ctx.projected_pc += 1; // STORE_VAR + } + for cs in &catch.body.body { + self.count_statement(cs, ctx); + } + ctx.pop_scope(); + } + + if let Some(finally) = &ts.finalizer { + let finally_label = Label::FinallyBody(id); + ctx.label_map.insert(finally_label, ctx.projected_pc); + for fs in &finally.body { + self.count_statement(fs, ctx); + } + ctx.projected_pc += 1; // TRY_FINALLY_END + } + + ctx.label_map.insert(try_end_label, ctx.projected_pc); } _ => {} } diff --git a/crates/oxide_compiler/src/emitter.rs b/crates/oxide_compiler/src/emitter.rs index 75688bd..cf1c4e1 100644 --- a/crates/oxide_compiler/src/emitter.rs +++ b/crates/oxide_compiler/src/emitter.rs @@ -371,7 +371,102 @@ impl Compiler { Ok(None) } - Statement::ThrowStatement(_) => Err("throw statement not supported".into()), + Statement::ThrowStatement(ts) => { + let exc_reg = self.emit_expression(&ts.argument, ctx)?; + ctx.emit(opcode::encode(OpCode::THROW, exc_reg, 0, 0)); + Ok(None) + } + Statement::TryStatement(ts) => { + let id = ctx.next_label_id(); + let catch_label = Label::CatchBody(id); + let try_end_label = Label::TryEnd(id); + let has_catch = ts.handler.is_some(); + let has_finally = ts.finalizer.is_some(); + + let mut try_finally_begin_pos: Option = None; + let mut try_begin_pos: Option = None; + + if has_finally { + try_finally_begin_pos = Some(ctx.bytecode.len()); + ctx.emit(opcode::encode_try_finally_begin(0)); // placeholder + } + + if has_catch { + try_begin_pos = Some(ctx.bytecode.len()); + ctx.emit(opcode::encode_try_begin(0)); // placeholder + } + + for s in &ts.block.body { + self.emit_statement(s, ctx)?; + } + + if has_catch { + ctx.emit(opcode::encode(OpCode::TRY_END, 0, 0, 0)); + } + + let jmp_needed = has_catch || has_finally; + let jmp_skip_pos = if jmp_needed { + let pos = ctx.bytecode.len(); + ctx.emit(opcode::encode_jmp(0)); // placeholder + Some(pos) + } else { + None + }; + + let catch_label_pc = ctx.bytecode.len(); + ctx.label_map.insert(catch_label, catch_label_pc); + + if let Some(try_begin_pc) = try_begin_pos { + let offset = catch_label_pc as isize - (try_begin_pc as isize + 1); + ctx.bytecode[try_begin_pc] = opcode::encode_try_begin(offset as i16); + } + + if let Some(catch) = &ts.handler { + ctx.push_scope(); + if let Some(param) = &catch.param { + let catch_reg = ctx.alloc_reg(); + if let oxide_parser::BindingPattern::BindingIdentifier(bi) = + ¶m.pattern + { + ctx.declare_initialized(bi.name.as_str(), catch_reg)?; + ctx.emit(opcode::encode(OpCode::STORE_VAR, catch_reg, 0, 0)); + } + } + for s in &catch.body.body { + self.emit_statement(s, ctx)?; + } + ctx.pop_scope(); + } + + if has_finally { + let finally_label = Label::FinallyBody(id); + let finally_label_pc = ctx.bytecode.len(); + ctx.label_map.insert(finally_label, finally_label_pc); + + if let Some(fb_pos) = try_finally_begin_pos { + let offset = finally_label_pc as isize - (fb_pos as isize + 1); + ctx.bytecode[fb_pos] = opcode::encode_try_finally_begin(offset as i16); + } + + if let Some(jmp_pos) = jmp_skip_pos { + let offset = finally_label_pc as isize - (jmp_pos as isize + 1); + ctx.bytecode[jmp_pos] = opcode::encode_jmp(offset as i16); + } + + for s in &ts.finalizer.as_ref().unwrap().body { + self.emit_statement(s, ctx)?; + } + ctx.emit(opcode::encode(OpCode::TRY_FINALLY_END, 0, 0, 0)); + } else if let Some(jmp_pos) = jmp_skip_pos { + let offset = ctx.bytecode.len() as isize - (jmp_pos as isize + 1); + ctx.bytecode[jmp_pos] = opcode::encode_jmp(offset as i16); + } + + let try_end_pc = ctx.bytecode.len(); + ctx.label_map.insert(try_end_label, try_end_pc); + + Ok(None) + } _ => Ok(None), } } diff --git a/crates/oxide_compiler/src/hash.rs b/crates/oxide_compiler/src/hash.rs index 421d00b..4c0e561 100644 --- a/crates/oxide_compiler/src/hash.rs +++ b/crates/oxide_compiler/src/hash.rs @@ -116,8 +116,32 @@ fn hash_statement(stmt: &Statement, h: &mut rustc_hash::FxHasher) { } } } - Statement::ThrowStatement(_ts) => { + Statement::ThrowStatement(ts) => { 14u8.hash(h); + hash_expression(&ts.argument, h); + } + Statement::TryStatement(ts) => { + 34u8.hash(h); + for s in &ts.block.body { + hash_statement(s, h); + } + if let Some(catch) = &ts.handler { + if let Some(param) = &catch.param { + if let oxide_parser::BindingPattern::BindingIdentifier(bi) = + ¶m.pattern + { + bi.name.as_str().hash(h); + } + } + for s in &catch.body.body { + hash_statement(s, h); + } + } + if let Some(finally) = &ts.finalizer { + for s in &finally.body { + hash_statement(s, h); + } + } } _ => { std::mem::discriminant(stmt).hash(h); diff --git a/crates/oxide_compiler/src/opcode.rs b/crates/oxide_compiler/src/opcode.rs index f06cd07..fc5f55f 100644 --- a/crates/oxide_compiler/src/opcode.rs +++ b/crates/oxide_compiler/src/opcode.rs @@ -55,7 +55,14 @@ pub enum OpCode { SWITCH_TABLE = 0x2C, FOR_IN_CLEANUP = 0x2D, - // ── Variable (0x30-0x3F) ── + // -- Exception (0x2E-0x2F, 0x33-0x35) -- + THROW = 0x2E, + TRY_BEGIN = 0x2F, + TRY_END = 0x33, + TRY_FINALLY_BEGIN = 0x34, + TRY_FINALLY_END = 0x35, + + // -- Variable (0x30-0x32) -- LOAD_VAR = 0x30, STORE_VAR = 0x31, LOAD_CONST = 0x32, @@ -178,9 +185,14 @@ impl TryFrom for OpCode { 0x2B => Ok(OpCode::FOR_IN_DONE), 0x2C => Ok(OpCode::SWITCH_TABLE), 0x2D => Ok(OpCode::FOR_IN_CLEANUP), + 0x2E => Ok(OpCode::THROW), + 0x2F => Ok(OpCode::TRY_BEGIN), 0x30 => Ok(OpCode::LOAD_VAR), 0x31 => Ok(OpCode::STORE_VAR), 0x32 => Ok(OpCode::LOAD_CONST), + 0x33 => Ok(OpCode::TRY_END), + 0x34 => Ok(OpCode::TRY_FINALLY_BEGIN), + 0x35 => Ok(OpCode::TRY_FINALLY_END), 0x40 => Ok(OpCode::CALL), 0x41 => Ok(OpCode::RETURN), 0x42 => Ok(OpCode::CALL_NATIVE), @@ -259,6 +271,11 @@ impl fmt::Display for OpCode { OpCode::FOR_IN_DONE => "FOR_IN_DONE", OpCode::SWITCH_TABLE => "SWITCH_TABLE", OpCode::FOR_IN_CLEANUP => "FOR_IN_CLEANUP", + OpCode::THROW => "THROW", + OpCode::TRY_BEGIN => "TRY_BEGIN", + OpCode::TRY_END => "TRY_END", + OpCode::TRY_FINALLY_BEGIN => "TRY_FINALLY_BEGIN", + OpCode::TRY_FINALLY_END => "TRY_FINALLY_END", OpCode::LOAD_VAR => "LOAD_VAR", OpCode::STORE_VAR => "STORE_VAR", OpCode::LOAD_CONST => "LOAD_CONST", @@ -353,3 +370,15 @@ pub fn encode_jmp_if_true(rd: u8, offset: i16) -> Instr { let hi = ((offset as u16 >> 8) & 0xFF) as u8; encode(OpCode::JMP_IF_TRUE, rd, lo, hi) } + +pub fn encode_try_begin(offset: i16) -> Instr { + let lo = (offset as u16 & 0xFF) as u8; + let hi = ((offset as u16 >> 8) & 0xFF) as u8; + encode(OpCode::TRY_BEGIN, 0, lo, hi) +} + +pub fn encode_try_finally_begin(offset: i16) -> Instr { + let lo = (offset as u16 & 0xFF) as u8; + let hi = ((offset as u16 >> 8) & 0xFF) as u8; + encode(OpCode::TRY_FINALLY_BEGIN, 0, lo, hi) +} diff --git a/crates/oxide_vm/tests/regression_tests.rs b/crates/oxide_vm/tests/regression_tests.rs index 80ea966..5ea070b 100644 --- a/crates/oxide_vm/tests/regression_tests.rs +++ b/crates/oxide_vm/tests/regression_tests.rs @@ -61,7 +61,7 @@ fn regression_recursion_depth_limit() { fn regression_throw_statement_errors() { assert_eq!( eval("throw 'error'"), - "compile error: throw statement not supported" + "vm error: opcode THROW not yet implemented" ); }