use crate::{
bytecompiler::{ByteCompiler, Register, ToJsString, jump_control::JumpControlInfoFlags},
vm::opcode::BindingOpcode,
};
use boa_ast::{
Statement, StatementListItem,
declaration::Binding,
statement::{Block, Catch, Finally, Try},
};
enum TryVariant<'a> {
Catch(&'a Catch),
Finally((&'a Finally, Register, Register)),
CatchFinally((&'a Catch, &'a Finally, Register, Register)),
}
impl TryVariant<'_> {
fn finaly_re_throw_register(&self) -> Option<(&Register, &Register)> {
match self {
TryVariant::Catch(_) => None,
TryVariant::Finally((_, f, i)) | TryVariant::CatchFinally((_, _, f, i)) => Some((f, i)),
}
}
}
impl ByteCompiler<'_> {
pub(crate) fn compile_try(&mut self, t: &Try, use_expr: bool) {
let variant = match (t.catch(), t.finally()) {
(Some(catch), Some(finally)) => {
let finally_re_throw = self.register_allocator.alloc();
let finally_jump_index = self.register_allocator.alloc();
self.bytecode.emit_push_true(finally_re_throw.variable());
self.bytecode.emit_push_zero(finally_jump_index.variable());
self.push_try_with_finally_control_info(
&finally_re_throw,
&finally_jump_index,
use_expr,
);
TryVariant::CatchFinally((catch, finally, finally_re_throw, finally_jump_index))
}
(Some(catch), None) => TryVariant::Catch(catch),
(None, Some(finally)) => {
let finally_re_throw = self.register_allocator.alloc();
let finally_jump_index = self.register_allocator.alloc();
self.bytecode.emit_push_true(finally_re_throw.variable());
self.bytecode.emit_push_zero(finally_jump_index.variable());
self.push_try_with_finally_control_info(
&finally_re_throw,
&finally_jump_index,
use_expr,
);
TryVariant::Finally((finally, finally_re_throw, finally_jump_index))
}
(None, None) => unreachable!("try statement must have either catch or finally"),
};
let try_handler = self.push_handler();
self.compile_block(t.block(), use_expr);
if let Some((finally_re_throw, _)) = variant.finaly_re_throw_register() {
self.bytecode.emit_push_false(finally_re_throw.variable());
}
let finally = self.jump();
self.patch_handler(try_handler);
match variant {
TryVariant::Catch(c) => {
let error = self.register_allocator.alloc();
self.bytecode.emit_exception(error.variable());
self.compile_catch_stmt(c, &error, use_expr);
self.register_allocator.dealloc(error);
self.patch_jump(finally);
}
TryVariant::CatchFinally((c, f, finally_re_throw, finally_jump_index)) => {
let catch_handler = self.push_handler();
let error = self.register_allocator.alloc();
self.bytecode.emit_exception(error.variable());
self.compile_catch_stmt(c, &error, use_expr);
self.bytecode.emit_push_false(finally_re_throw.variable());
let no_throw = self.jump();
self.patch_handler(catch_handler);
self.bytecode.emit_push_true(finally_re_throw.variable());
self.patch_jump(no_throw);
self.patch_jump(finally);
let finally_start = self.next_opcode_location();
self.jump_info
.last_mut()
.expect("there should be a try block")
.flags |= JumpControlInfoFlags::IN_FINALLY;
self.compile_finally_stmt(f);
self.register_allocator.dealloc(error);
let do_not_throw_exit = self.jump_if_false(&finally_re_throw);
self.bytecode.emit_re_throw();
self.patch_jump(do_not_throw_exit);
self.pop_try_with_finally_control_info(finally_start);
self.register_allocator.dealloc(finally_re_throw);
self.register_allocator.dealloc(finally_jump_index);
}
TryVariant::Finally((f, finally_re_throw, finally_jump_index))
if self.is_generator() =>
{
let catch_handler = self.push_handler();
let error = self.register_allocator.alloc();
self.bytecode.emit_exception(error.variable());
let re_throw_generator = self.register_allocator.alloc();
self.bytecode.emit_push_false(re_throw_generator.variable());
self.bytecode.emit_push_true(finally_re_throw.variable());
let no_throw = self.jump();
self.patch_handler(catch_handler);
self.bytecode.emit_push_true(re_throw_generator.variable());
self.patch_jump(no_throw);
self.patch_jump(finally);
let finally_start = self.next_opcode_location();
self.jump_info
.last_mut()
.expect("there should be a try block")
.flags |= JumpControlInfoFlags::IN_FINALLY;
self.compile_finally_stmt(f);
let do_not_throw_exit = self.jump_if_false(&finally_re_throw);
let is_generator_exit = self.jump_if_true(&re_throw_generator);
self.bytecode.emit_throw(error.variable());
self.register_allocator.dealloc(error);
self.patch_jump(is_generator_exit);
self.bytecode.emit_re_throw();
self.patch_jump(do_not_throw_exit);
self.register_allocator.dealloc(re_throw_generator);
self.pop_try_with_finally_control_info(finally_start);
self.register_allocator.dealloc(finally_re_throw);
self.register_allocator.dealloc(finally_jump_index);
}
TryVariant::Finally((f, finally_re_throw, finally_jump_index)) => {
let catch_handler = self.push_handler();
let error = self.register_allocator.alloc();
self.bytecode.emit_exception(error.variable());
self.bytecode.emit_push_true(finally_re_throw.variable());
let no_throw = self.jump();
self.patch_handler(catch_handler);
self.patch_jump(no_throw);
self.patch_jump(finally);
let finally_start = self.next_opcode_location();
self.jump_info
.last_mut()
.expect("there should be a try block")
.flags |= JumpControlInfoFlags::IN_FINALLY;
self.compile_finally_stmt(f);
let do_not_throw_exit = self.jump_if_false(&finally_re_throw);
self.bytecode.emit_throw(error.variable());
self.register_allocator.dealloc(error);
self.patch_jump(do_not_throw_exit);
self.pop_try_with_finally_control_info(finally_start);
self.register_allocator.dealloc(finally_re_throw);
self.register_allocator.dealloc(finally_jump_index);
}
}
}
pub(crate) fn compile_catch_stmt(&mut self, catch: &Catch, error: &Register, use_expr: bool) {
let outer_scope = self.push_declarative_scope(Some(catch.scope()));
if let Some(binding) = catch.parameter() {
match binding {
Binding::Identifier(ident) => {
let ident = ident.to_js_string(self.interner());
self.emit_binding(BindingOpcode::InitLexical, ident, error);
}
Binding::Pattern(pattern) => {
self.compile_declaration_pattern(pattern, BindingOpcode::InitLexical, error);
}
}
}
self.compile_catch_finally_block(catch.block(), use_expr);
self.pop_declarative_scope(outer_scope);
}
pub(crate) fn compile_finally_stmt(&mut self, finally: &Finally) {
let value = self.register_allocator.alloc();
self.bytecode
.emit_set_register_from_accumulator(value.variable());
self.compile_catch_finally_block(finally.block(), false);
self.bytecode.emit_set_accumulator(value.variable());
self.register_allocator.dealloc(value);
}
fn compile_catch_finally_block(&mut self, block: &Block, use_expr: bool) {
let mut b = block;
while let Some(statement) = b.statement_list().first() {
match statement {
StatementListItem::Statement(statement) => match statement.as_ref() {
Statement::Break(_) | Statement::Continue(_) => {
let value = self.register_allocator.alloc();
self.bytecode.emit_push_undefined(value.variable());
self.bytecode.emit_set_accumulator(value.variable());
self.register_allocator.dealloc(value);
break;
}
Statement::Block(block) => b = block,
_ => break,
},
StatementListItem::Declaration(_) => break,
}
}
self.compile_block(block, use_expr);
}
}