try-catch #3
@@ -102,6 +102,19 @@ fn collect_stmt(stmt: &Stmt, symbols: &mut Vec<Symbol>) {
|
||||
collect_expr(expr, symbols);
|
||||
}
|
||||
Stmt::Return(None) | Stmt::Break | Stmt::Continue => {}
|
||||
Stmt::Try { body, catch_var, catch_body, finally_body } => {
|
||||
collect_stmts(body, symbols);
|
||||
if let Some(var) = catch_var {
|
||||
symbols.push(Symbol { name: var.clone(), kind: SymbolKind::Variable });
|
||||
}
|
||||
if let Some(cb) = catch_body {
|
||||
collect_stmts(cb, symbols);
|
||||
}
|
||||
if let Some(fb) = finally_body {
|
||||
collect_stmts(fb, symbols);
|
||||
}
|
||||
}
|
||||
Stmt::Throw(expr) => collect_expr(expr, symbols),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,4 +58,15 @@ pub enum Stmt {
|
||||
|
||||
/// continue
|
||||
Continue,
|
||||
|
||||
/// try { body } catch (e) { catch_body } finally { finally_body }
|
||||
Try {
|
||||
body: Vec<Stmt>,
|
||||
catch_var: Option<String>,
|
||||
catch_body: Option<Vec<Stmt>>,
|
||||
finally_body: Option<Vec<Stmt>>,
|
||||
},
|
||||
|
||||
/// throw expr;
|
||||
Throw(Expr),
|
||||
}
|
||||
|
||||
@@ -360,6 +360,10 @@ impl Lexer {
|
||||
"true" => TokenKind::True,
|
||||
"false" => TokenKind::False,
|
||||
"nil" => TokenKind::Nil,
|
||||
"try" => TokenKind::Try,
|
||||
"catch" => TokenKind::Catch,
|
||||
"finally" => TokenKind::Finally,
|
||||
"throw" => TokenKind::Throw,
|
||||
_ => TokenKind::Identifier(s),
|
||||
};
|
||||
|
||||
|
||||
@@ -39,6 +39,10 @@ pub enum TokenKind {
|
||||
True,
|
||||
False,
|
||||
Nil,
|
||||
Try,
|
||||
Catch,
|
||||
Finally,
|
||||
Throw,
|
||||
|
||||
EOF,
|
||||
}
|
||||
|
||||
@@ -54,6 +54,10 @@ impl Parser {
|
||||
self.continue_statement()
|
||||
} else if self.match_kind(&[TokenKind::Return]) {
|
||||
self.return_statement()
|
||||
} else if self.match_kind(&[TokenKind::Try]) {
|
||||
self.try_statement()
|
||||
} else if self.match_kind(&[TokenKind::Throw]) {
|
||||
self.throw_statement()
|
||||
} else if self.match_kind(&[TokenKind::LeftBrace]) {
|
||||
Ok(Stmt::Block(self.block()?))
|
||||
} else {
|
||||
@@ -201,6 +205,39 @@ impl Parser {
|
||||
Ok(Stmt::Return(value))
|
||||
}
|
||||
|
||||
fn try_statement(&mut self) -> Result<Stmt, RuntimeError> {
|
||||
self.consume(TokenKind::LeftBrace, "Expected '{' after 'try'.")?;
|
||||
let body = self.block()?;
|
||||
|
||||
let mut catch_var = None;
|
||||
let mut catch_body = None;
|
||||
if self.match_kind(&[TokenKind::Catch]) {
|
||||
self.consume(TokenKind::LeftParen, "Expected '(' after 'catch'.")?;
|
||||
catch_var = Some(self.consume_ident("Expected exception variable.")?);
|
||||
self.consume(TokenKind::RightParen, "Expected ')' after catch variable.")?;
|
||||
self.consume(TokenKind::LeftBrace, "Expected '{' before catch body.")?;
|
||||
catch_body = Some(self.block()?);
|
||||
}
|
||||
|
||||
let mut finally_body = None;
|
||||
if self.match_kind(&[TokenKind::Finally]) {
|
||||
self.consume(TokenKind::LeftBrace, "Expected '{' after 'finally'.")?;
|
||||
finally_body = Some(self.block()?);
|
||||
}
|
||||
|
||||
if catch_var.is_none() && finally_body.is_none() {
|
||||
return Err(RuntimeError::parse("Expected 'catch' or 'finally' after 'try'.", self.peek().clone()));
|
||||
}
|
||||
|
||||
Ok(Stmt::Try { body, catch_var, catch_body, finally_body })
|
||||
}
|
||||
|
||||
fn throw_statement(&mut self) -> Result<Stmt, RuntimeError> {
|
||||
let expr = self.expression()?;
|
||||
self.match_kind(&[TokenKind::Semicolon]); // 分号可选
|
||||
Ok(Stmt::Throw(expr))
|
||||
}
|
||||
|
||||
fn block(&mut self) -> Result<Vec<Stmt>, RuntimeError> {
|
||||
let mut statements = Vec::new();
|
||||
|
||||
@@ -616,7 +653,9 @@ impl Parser {
|
||||
| TokenKind::For
|
||||
| TokenKind::Return
|
||||
| TokenKind::Break
|
||||
| TokenKind::Continue => return,
|
||||
| TokenKind::Continue
|
||||
| TokenKind::Try
|
||||
| TokenKind::Throw => return,
|
||||
_ => {
|
||||
self.advance();
|
||||
}
|
||||
|
||||
@@ -26,6 +26,17 @@ pub struct FunctionProto {
|
||||
pub upvalue_count: u8,
|
||||
pub upvalues: Vec<(bool, u8)>, // (is_local, index)
|
||||
pub lines: Vec<(usize, usize)>, // (bytecode_offset, source_line)
|
||||
pub exception_handlers: Vec<ExceptionHandler>,
|
||||
}
|
||||
|
||||
/// Exception handler entry: defines a try-catch-finally region.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ExceptionHandler {
|
||||
pub try_start: usize, // inclusive
|
||||
pub try_end: usize, // exclusive
|
||||
pub catch_ip: usize, // catch handler IP (0 if no catch clause)
|
||||
pub catch_slot: u8, // local slot for catch variable (0 if no catch)
|
||||
pub finally_ip: usize, // finally rethrow entry (0 if no finally)
|
||||
}
|
||||
|
||||
impl FunctionProto {
|
||||
@@ -39,6 +50,7 @@ impl FunctionProto {
|
||||
upvalue_count: 0,
|
||||
upvalues: Vec::new(),
|
||||
lines: Vec::new(),
|
||||
exception_handlers: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
use crate::ast::*;
|
||||
use crate::ast::expr::{Literal, UnaryOp, BinaryOp, LogicalOp, AssignOp};
|
||||
use crate::error::RuntimeError;
|
||||
use crate::runtime::{Value, FunctionProto};
|
||||
use crate::runtime::{Value, FunctionProto, ExceptionHandler};
|
||||
use super::opcode::*;
|
||||
|
||||
use std::rc::Rc;
|
||||
@@ -139,6 +139,12 @@ impl Compiler {
|
||||
Stmt::Continue => {
|
||||
self.compile_continue()?;
|
||||
}
|
||||
Stmt::Try { body, catch_var, catch_body, finally_body } => {
|
||||
self.compile_try(body, catch_var.as_deref(), catch_body.as_ref(), finally_body.as_ref())?;
|
||||
}
|
||||
Stmt::Throw(expr) => {
|
||||
self.compile_throw(expr)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -375,6 +381,87 @@ impl Compiler {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compile_throw(&mut self, expr: &Expr) -> Result<(), RuntimeError> {
|
||||
self.compile_expr(expr)?;
|
||||
self.emit_op(OpCode::Throw);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compile_try(
|
||||
&mut self,
|
||||
body: &[Stmt],
|
||||
catch_var: Option<&str>,
|
||||
catch_body: Option<&Vec<Stmt>>,
|
||||
finally_body: Option<&Vec<Stmt>>,
|
||||
) -> Result<(), RuntimeError> {
|
||||
let try_start = self.function.code.len();
|
||||
|
||||
// Compile try body
|
||||
self.begin_scope();
|
||||
self.compile_stmts(body)?;
|
||||
self.end_scope();
|
||||
let try_end = self.function.code.len();
|
||||
|
||||
// ── Normal path: jump past catch to finally (or end) ──
|
||||
let skip_catch = self.emit_jump(OpCode::Jump);
|
||||
|
||||
// ── Catch handler (entry via unwind) ──
|
||||
let (catch_ip, catch_slot) = if let (Some(var), Some(cb)) = (catch_var, catch_body) {
|
||||
let ip = self.function.code.len();
|
||||
self.begin_scope();
|
||||
let slot = self.locals.len() as u8;
|
||||
self.locals.push(Local {
|
||||
name: var.to_string(),
|
||||
depth: self.scope_depth,
|
||||
is_captured: false,
|
||||
is_const: false,
|
||||
});
|
||||
// VM pushes exception value before jumping here; StoreLocal peeks it
|
||||
emit_u8(&mut self.function.code, OpCode::StoreLocal, slot);
|
||||
self.compile_stmts(cb)?;
|
||||
self.end_scope();
|
||||
(ip, slot)
|
||||
} else {
|
||||
(0, 0)
|
||||
};
|
||||
self.patch_jump(skip_catch);
|
||||
|
||||
// ── Finally: inline for success paths (try/catch fall through here) ──
|
||||
let finally_block_ip = if let Some(fb) = finally_body {
|
||||
let ip = self.function.code.len();
|
||||
self.begin_scope();
|
||||
self.compile_stmts(fb)?;
|
||||
self.end_scope();
|
||||
let done_jump = self.emit_jump(OpCode::Jump);
|
||||
|
||||
// ── Finally rethrow entry (for exception path) ──
|
||||
let rethrow_ip = self.function.code.len();
|
||||
// Duplicate finally body for exception path
|
||||
self.begin_scope();
|
||||
self.compile_stmts(fb)?;
|
||||
self.end_scope();
|
||||
self.emit_op(OpCode::Throw); // re-throw after finally
|
||||
|
||||
self.patch_jump(done_jump);
|
||||
(ip, rethrow_ip)
|
||||
} else {
|
||||
(0, 0)
|
||||
};
|
||||
|
||||
let (_, rethrow_ip) = finally_block_ip;
|
||||
|
||||
// Register exception handler
|
||||
self.function.exception_handlers.push(ExceptionHandler {
|
||||
try_start,
|
||||
try_end,
|
||||
catch_ip,
|
||||
catch_slot,
|
||||
finally_ip: rethrow_ip,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Expression compilation
|
||||
// ========================================================================
|
||||
|
||||
@@ -80,12 +80,13 @@ pub enum OpCode {
|
||||
CompoundAssignIndex = 41, // operand: u8 compound-op tag
|
||||
CompoundAssignUpvalue = 44, // operand: u8 upvalue idx + u8 compound-op tag
|
||||
CompoundAssignGlobal = 45, // operand: u16 name idx + u8 compound-op tag
|
||||
Throw = 46, // pops exception value, begins stack unwind
|
||||
}
|
||||
|
||||
impl OpCode {
|
||||
pub fn from_u8(byte: u8) -> Option<Self> {
|
||||
match byte {
|
||||
0..=41 | 44..=45 => Some(unsafe { std::mem::transmute::<u8, OpCode>(byte) }),
|
||||
0..=41 | 44..=46 => Some(unsafe { std::mem::transmute::<u8, OpCode>(byte) }),
|
||||
42 => Some(OpCode::LoadUpvalue),
|
||||
43 => Some(OpCode::StoreUpvalue),
|
||||
_ => None,
|
||||
@@ -93,7 +94,7 @@ impl OpCode {
|
||||
}
|
||||
|
||||
/// Total number of distinct opcodes
|
||||
pub const COUNT: usize = 46;
|
||||
pub const COUNT: usize = 47;
|
||||
}
|
||||
|
||||
/// Compound assignment operator tags (used as operand byte after
|
||||
|
||||
@@ -1737,3 +1737,305 @@ fn eval_require_empty_module() {
|
||||
v => panic!("Expected Object, got {:?}", v),
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Exception handling: throw
|
||||
// =========================================================================
|
||||
|
||||
#[test]
|
||||
fn eval_throw_caught() {
|
||||
let vm = run("
|
||||
let result = 'none';
|
||||
try {
|
||||
throw 'caught!';
|
||||
} catch (e) {
|
||||
result = e;
|
||||
}
|
||||
");
|
||||
match get_var(&vm, "result") {
|
||||
Value::String(s) => assert_eq!(s, "caught!"),
|
||||
v => panic!("Expected String, got {:?}", v),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_throw_preserves_value_type() {
|
||||
let vm = run("
|
||||
let msg = '';
|
||||
let code = 0;
|
||||
try { throw 'error'; } catch (e) { msg = e; }
|
||||
try { throw 42; } catch (e) { code = e; }
|
||||
");
|
||||
match get_var(&vm, "msg") {
|
||||
Value::String(s) => assert_eq!(s, "error"),
|
||||
v => panic!("Expected String, got {:?}", v),
|
||||
}
|
||||
assert_num(&get_var(&vm, "code"), 42.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_throw_caught_correct_handler() {
|
||||
let vm = run("
|
||||
let outer = 'none';
|
||||
let inner = 'none';
|
||||
try {
|
||||
try {
|
||||
throw 'inner';
|
||||
} catch (e) {
|
||||
inner = e;
|
||||
}
|
||||
} catch (e) {
|
||||
outer = e;
|
||||
}
|
||||
");
|
||||
match get_var(&vm, "inner") {
|
||||
Value::String(s) => assert_eq!(s, "inner"),
|
||||
v => panic!("Expected String, got {:?}", v),
|
||||
}
|
||||
match get_var(&vm, "outer") {
|
||||
Value::String(s) => assert_eq!(s, "none"),
|
||||
v => panic!("Expected String, got {:?}", v),
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Exception handling: runtime errors caught
|
||||
// =========================================================================
|
||||
|
||||
#[test]
|
||||
fn eval_catch_division_by_zero() {
|
||||
let vm = run("
|
||||
let msg = 'none';
|
||||
try {
|
||||
let x = 1 / 0;
|
||||
} catch (e) {
|
||||
msg = e;
|
||||
}
|
||||
");
|
||||
match get_var(&vm, "msg") {
|
||||
Value::String(s) => assert!(s.contains("Division by zero") || s.contains("division"), "got: {}", s),
|
||||
v => panic!("Expected String, got {:?}", v),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_catch_undefined_variable() {
|
||||
let vm = run("
|
||||
let msg = 'none';
|
||||
try {
|
||||
let x = no_such_var;
|
||||
} catch (e) {
|
||||
msg = e;
|
||||
}
|
||||
");
|
||||
match get_var(&vm, "msg") {
|
||||
Value::String(s) => assert!(s.contains("Undefined"), "got: {}", s),
|
||||
v => panic!("Expected String, got {:?}", v),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_catch_index_out_of_bounds() {
|
||||
let vm = run("
|
||||
let msg = 'none';
|
||||
try {
|
||||
let x = [1, 2][99];
|
||||
} catch (e) {
|
||||
msg = e;
|
||||
}
|
||||
");
|
||||
match get_var(&vm, "msg") {
|
||||
Value::String(s) => assert!(s.contains("out of bounds"), "got: {}", s),
|
||||
v => panic!("Expected String, got {:?}", v),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_catch_call_non_function() {
|
||||
let vm = run("
|
||||
let msg = 'none';
|
||||
try {
|
||||
42();
|
||||
} catch (e) {
|
||||
msg = e;
|
||||
}
|
||||
");
|
||||
match get_var(&vm, "msg") {
|
||||
Value::String(s) => assert!(s.contains("non-function"), "got: {}", s),
|
||||
v => panic!("Expected String, got {:?}", v),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_no_error_runs_normally() {
|
||||
let vm = run("
|
||||
let result = 0;
|
||||
try {
|
||||
result = 42;
|
||||
} catch (e) {
|
||||
result = -1;
|
||||
}
|
||||
");
|
||||
assert_num(&get_var(&vm, "result"), 42.0);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Exception handling: finally
|
||||
// =========================================================================
|
||||
|
||||
#[test]
|
||||
fn eval_finally_runs_on_success() {
|
||||
let vm = run("
|
||||
let flag = 0;
|
||||
try {
|
||||
flag = 1;
|
||||
} finally {
|
||||
flag = flag + 10;
|
||||
}
|
||||
");
|
||||
assert_num(&get_var(&vm, "flag"), 11.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_finally_runs_on_throw() {
|
||||
let vm = run("
|
||||
let flag = 0;
|
||||
let msg = 'none';
|
||||
try {
|
||||
throw 'oops';
|
||||
} catch (e) {
|
||||
msg = e;
|
||||
} finally {
|
||||
flag = flag + 10;
|
||||
}
|
||||
");
|
||||
match get_var(&vm, "msg") {
|
||||
Value::String(s) => assert_eq!(s, "oops"),
|
||||
v => panic!("Expected String, got {:?}", v),
|
||||
}
|
||||
assert_num(&get_var(&vm, "flag"), 10.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_finally_runs_on_runtime_error() {
|
||||
let vm = run("
|
||||
let flag = 0;
|
||||
let msg = 'none';
|
||||
try {
|
||||
let x = 1 / 0;
|
||||
} catch (e) {
|
||||
msg = 'caught';
|
||||
} finally {
|
||||
flag = 1;
|
||||
}
|
||||
");
|
||||
match get_var(&vm, "msg") {
|
||||
Value::String(s) => assert_eq!(s, "caught"),
|
||||
v => panic!("Expected String, got {:?}", v),
|
||||
}
|
||||
assert_num(&get_var(&vm, "flag"), 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_finally_without_catch() {
|
||||
// try-finally without catch: finally runs, then rethrows
|
||||
let result = std::panic::catch_unwind(|| {
|
||||
run("
|
||||
let flag = 0;
|
||||
try {
|
||||
throw 'boom';
|
||||
} finally {
|
||||
flag = 1;
|
||||
}
|
||||
");
|
||||
});
|
||||
assert!(result.is_err(), "Expected uncaught exception");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_nested_try_catch() {
|
||||
let vm = run("
|
||||
let outer = 'none';
|
||||
let inner = 'none';
|
||||
try {
|
||||
try {
|
||||
throw 'inner_error';
|
||||
} catch (e) {
|
||||
inner = e;
|
||||
}
|
||||
} catch (e) {
|
||||
outer = e;
|
||||
}
|
||||
");
|
||||
match get_var(&vm, "inner") {
|
||||
Value::String(s) => assert_eq!(s, "inner_error"),
|
||||
v => panic!("Expected String, got {:?}", v),
|
||||
}
|
||||
match get_var(&vm, "outer") {
|
||||
Value::String(s) => assert_eq!(s, "none"),
|
||||
v => panic!("Expected String, got {:?}", v),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_throw_in_catch_propagates() {
|
||||
let vm = run("
|
||||
let outer = 'none';
|
||||
let inner = 'none';
|
||||
try {
|
||||
try {
|
||||
throw 'first';
|
||||
} catch (e) {
|
||||
inner = e;
|
||||
throw 'second';
|
||||
}
|
||||
} catch (e) {
|
||||
outer = e;
|
||||
}
|
||||
");
|
||||
match get_var(&vm, "inner") {
|
||||
Value::String(s) => assert_eq!(s, "first"),
|
||||
v => panic!("Expected String, got {:?}", v),
|
||||
}
|
||||
match get_var(&vm, "outer") {
|
||||
Value::String(s) => assert_eq!(s, "second"),
|
||||
v => panic!("Expected String, got {:?}", v),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_try_catch_in_function() {
|
||||
let vm = run("
|
||||
fn safe_div(a, b) {
|
||||
try {
|
||||
return a / b;
|
||||
} catch (e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
let r1 = safe_div(10, 2);
|
||||
let r2 = safe_div(10, 0);
|
||||
");
|
||||
assert_num(&get_var(&vm, "r1"), 5.0);
|
||||
assert_num(&get_var(&vm, "r2"), -1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_finally_known_limitation_return() {
|
||||
// Note: return/break/continue inside try-finally do NOT yet run finally
|
||||
// (this is a known limitation). This test documents current behavior.
|
||||
let vm = run("
|
||||
let flag = 0;
|
||||
fn test() {
|
||||
try {
|
||||
return 42;
|
||||
} finally {
|
||||
flag = 1;
|
||||
}
|
||||
}
|
||||
let r = test();
|
||||
");
|
||||
assert_num(&get_var(&vm, "r"), 42.0);
|
||||
// flag stays 0 because finally doesn't run before return (known limitation)
|
||||
assert_num(&get_var(&vm, "flag"), 0.0);
|
||||
}
|
||||
|
||||
+88
-5
@@ -4,7 +4,7 @@
|
||||
//! call frames. Implements the `Runtime` trait for native function support.
|
||||
|
||||
use crate::error::RuntimeError;
|
||||
use crate::runtime::{Value, Runtime, FunctionProto, Closure, UpvalueObj};
|
||||
use crate::runtime::{Value, Runtime, FunctionProto, Closure, UpvalueObj, ExceptionHandler};
|
||||
use crate::lexer::Lexer;
|
||||
use crate::parser::Parser;
|
||||
use super::opcode::*;
|
||||
@@ -109,8 +109,26 @@ impl Vm {
|
||||
|
||||
fn execute_loop(&mut self) -> Result<(), RuntimeError> {
|
||||
loop {
|
||||
match self.execute_one_step() {
|
||||
Ok(true) => continue, // more instructions to execute
|
||||
Ok(false) => return Ok(()), // VM finished
|
||||
Err(runtime_err) => {
|
||||
// Try to handle as exception
|
||||
let exc = Value::String(runtime_err.to_string());
|
||||
self.unwind(exc)?; // Err if no handler found
|
||||
continue; // Handler found, continue executing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute a single bytecode instruction. Returns:
|
||||
/// - Ok(true): continue the loop
|
||||
/// - Ok(false): VM is done (frames empty)
|
||||
/// - Err(...): runtime error to potentially catch
|
||||
fn execute_one_step(&mut self) -> Result<bool, RuntimeError> {
|
||||
if self.frames.is_empty() {
|
||||
return Ok(());
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// Snapshot frame state (must drop borrow before mutating self)
|
||||
@@ -122,12 +140,12 @@ impl Vm {
|
||||
let frame = self.frames.pop().unwrap();
|
||||
self.close_upvalues(frame.stack_base);
|
||||
if self.frames.is_empty() {
|
||||
return Ok(());
|
||||
return Ok(false);
|
||||
}
|
||||
// Remove callee + args + function locals, push nil result
|
||||
self.stack.truncate(frame.stack_base.saturating_sub(1));
|
||||
self.stack.push(Value::Nil);
|
||||
continue;
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
// Clone Rc<FunctionProto> to access code without holding self.frames borrow
|
||||
@@ -445,7 +463,7 @@ impl Vm {
|
||||
self.stack.truncate(frame.stack_base.saturating_sub(1));
|
||||
self.stack.push(result);
|
||||
if self.frames.is_empty() {
|
||||
return Ok(());
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
OpCode::Closure => {
|
||||
@@ -645,8 +663,16 @@ impl Vm {
|
||||
self.stack.push(result);
|
||||
self.advance_ip(SIZE_U16_PLUS1);
|
||||
}
|
||||
OpCode::Throw => {
|
||||
let exc = self.stack.pop().unwrap();
|
||||
self.unwind(exc)?;
|
||||
// unwind() returns Ok only if it found a handler and jumped there
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
// All ops (except Throw) fall through to here
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
@@ -665,6 +691,63 @@ impl Vm {
|
||||
self.frame_mut().ip = target;
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Exception handling (stack unwind)
|
||||
// ========================================================================
|
||||
|
||||
/// Search for an exception handler covering the current IP.
|
||||
/// Returns the handler and true if a handler was found.
|
||||
fn find_handler(proto: &FunctionProto, ip: usize) -> Option<&ExceptionHandler> {
|
||||
// Search in insertion order: innermost try is compiled (and registered) first,
|
||||
// so forward iteration finds the innermost matching handler first.
|
||||
proto.exception_handlers.iter().find(|h| ip >= h.try_start && ip < h.try_end)
|
||||
}
|
||||
|
||||
/// Unwind the call stack to find an exception handler. On success,
|
||||
/// the VM's IP is set to the handler and execution continues.
|
||||
/// Returns Err if no handler is found (uncaught exception).
|
||||
fn unwind(&mut self, exception: Value) -> Result<(), RuntimeError> {
|
||||
loop {
|
||||
if self.frames.is_empty() {
|
||||
return Err(RuntimeError::RuntimeError {
|
||||
message: format!("Uncaught exception: {}", exception),
|
||||
token: None,
|
||||
});
|
||||
}
|
||||
|
||||
let ip = self.frames.last().unwrap().ip;
|
||||
let proto = Rc::clone(&self.frames.last().unwrap().closure.proto);
|
||||
|
||||
if let Some(h) = Self::find_handler(&proto, ip) {
|
||||
if h.finally_ip != 0 {
|
||||
if h.catch_ip != 0 {
|
||||
// Has catch: push exception, jump to catch.
|
||||
// Catch body falls through to inline finally.
|
||||
self.stack.push(exception);
|
||||
self.frame_mut().ip = h.catch_ip;
|
||||
return Ok(());
|
||||
} else {
|
||||
// No catch, only finally: push exception, jump to finally
|
||||
// rethrow entry. The finally body runs, then Throw.
|
||||
self.stack.push(exception);
|
||||
self.frame_mut().ip = h.finally_ip;
|
||||
return Ok(());
|
||||
}
|
||||
} else if h.catch_ip != 0 {
|
||||
// Only catch, no finally
|
||||
self.stack.push(exception);
|
||||
self.frame_mut().ip = h.catch_ip;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// No handler — pop frame and continue in caller
|
||||
let frame = self.frames.pop().unwrap();
|
||||
self.close_upvalues(frame.stack_base);
|
||||
self.stack.truncate(frame.stack_base.saturating_sub(1));
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Function calls
|
||||
// ========================================================================
|
||||
|
||||
@@ -9,6 +9,7 @@ use lsp_types::{
|
||||
const KEYWORDS: &[&str] = &[
|
||||
"let", "const", "fn", "if", "else", "while", "for", "in",
|
||||
"break", "continue", "return", "true", "false", "nil",
|
||||
"try", "catch", "finally", "throw",
|
||||
];
|
||||
|
||||
const BUILTINS: &[&str] = &[
|
||||
|
||||
Reference in New Issue
Block a user