use crate::{
bytecompiler::{jump_control::JumpControlInfoFlags, ByteCompiler, ToJsString},
vm::{BindingOpcode, Opcode},
};
use boa_ast::{
declaration::Binding,
statement::{Block, Catch, Finally, Try},
Statement, StatementListItem,
};
impl ByteCompiler<'_> {
pub(crate) fn compile_try(&mut self, t: &Try, use_expr: bool) {
let has_catch = t.catch().is_some();
let has_finally = t.finally().is_some();
if has_finally {
self.push_try_with_finally_control_info(use_expr);
}
let try_handler = self.push_handler();
self.compile_block(t.block(), use_expr);
if has_finally {
self.emit_opcode(Opcode::PushZero);
self.emit_opcode(Opcode::PushFalse);
}
let finally = self.jump();
self.patch_handler(try_handler);
let catch_handler = if has_finally && (self.is_generator() || has_catch) {
self.current_stack_value_count += 2;
Some(self.push_handler())
} else {
None
};
self.emit_opcode(Opcode::Exception);
if let Some(catch) = t.catch() {
self.compile_catch_stmt(catch, has_finally, use_expr);
} else {
if self.is_generator() && has_finally {
self.emit_opcode(Opcode::PushFalse);
}
self.emit_opcode(Opcode::PushTrue);
}
if has_finally {
if has_catch {
self.emit_opcode(Opcode::PushZero);
self.emit_opcode(Opcode::PushFalse);
}
let exit = self.jump();
if let Some(catch_handler) = catch_handler {
self.current_stack_value_count -= 2;
self.patch_handler(catch_handler);
}
if !has_catch && self.is_generator() {
self.emit_opcode(Opcode::PushTrue);
}
self.emit_opcode(Opcode::PushTrue);
self.patch_jump(exit);
}
self.patch_jump(finally);
let finally_start = self.next_opcode_location();
if let Some(finally) = t.finally() {
self.jump_info
.last_mut()
.expect("there should be a try block")
.flags |= JumpControlInfoFlags::IN_FINALLY;
self.current_stack_value_count += 2;
self.compile_finally_stmt(finally, has_catch);
self.current_stack_value_count -= 2;
}
if has_finally {
self.pop_try_with_finally_control_info(finally_start);
}
}
pub(crate) fn compile_catch_stmt(&mut self, catch: &Catch, _has_finally: bool, 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);
}
Binding::Pattern(pattern) => {
self.compile_declaration_pattern(pattern, BindingOpcode::InitLexical);
}
}
} else {
self.emit_opcode(Opcode::Pop);
}
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, has_catch: bool) {
self.current_stack_value_count += 1;
self.emit_opcode(Opcode::GetReturnValue);
self.compile_catch_finally_block(finally.block(), true);
self.emit_opcode(Opcode::SetReturnValue);
self.current_stack_value_count -= 1;
let do_not_throw_exit = self.jump_if_false();
if has_catch {
self.emit_opcode(Opcode::ReThrow);
} else if self.is_generator() {
let is_generator_exit = self.jump_if_true();
self.emit_opcode(Opcode::Throw);
self.patch_jump(is_generator_exit);
self.emit_opcode(Opcode::ReThrow);
} else {
self.emit_opcode(Opcode::Throw);
}
self.patch_jump(do_not_throw_exit);
}
fn compile_catch_finally_block(&mut self, block: &Block, use_expr: bool) {
let mut b = block;
loop {
match b.statement_list().first() {
Some(StatementListItem::Statement(
Statement::Break(_) | Statement::Continue(_),
)) => {
self.emit_opcode(Opcode::PushUndefined);
self.emit_opcode(Opcode::SetReturnValue);
break;
}
Some(StatementListItem::Statement(Statement::Block(block))) => {
b = block;
}
_ => {
break;
}
}
}
self.compile_block(block, use_expr);
}
}