mirror of https://github.com/boa-dev/boa.git
Browse Source
This Pull Request is currently unfinished but will fix/close #1808 after some review and more work It changes the following: - Divides byte compiler logic into separate files I would like some review on the current code I have to know if the patterns I'm using are acceptable for the codebase, if everything looks good I will try to separate more code into different small modules to finish the work here.pull/2489/head
e-codes-stuff
2 years ago
13 changed files with 2417 additions and 2200 deletions
@ -0,0 +1,537 @@ |
|||||||
|
use boa_ast::{ |
||||||
|
declaration::Binding, |
||||||
|
expression::Identifier, |
||||||
|
function::{Class, ClassElement}, |
||||||
|
operations::bound_names, |
||||||
|
property::{MethodDefinition, PropertyName}, |
||||||
|
}; |
||||||
|
use boa_gc::Gc; |
||||||
|
use boa_interner::Sym; |
||||||
|
use rustc_hash::FxHashMap; |
||||||
|
|
||||||
|
use crate::{ |
||||||
|
vm::{BindingOpcode, CodeBlock, Opcode}, |
||||||
|
JsResult, |
||||||
|
}; |
||||||
|
|
||||||
|
use super::{ByteCompiler, Literal, NodeKind}; |
||||||
|
|
||||||
|
impl ByteCompiler<'_> { |
||||||
|
/// This function compiles a class declaration or expression.
|
||||||
|
///
|
||||||
|
/// The compilation of a class declaration and expression is mostly equal.
|
||||||
|
/// A class declaration binds the resulting class object to it's identifier.
|
||||||
|
/// A class expression leaves the resulting class object on the stack for following operations.
|
||||||
|
pub(crate) fn compile_class(&mut self, class: &Class, expression: bool) -> JsResult<()> { |
||||||
|
let code = CodeBlock::new( |
||||||
|
class.name().map_or(Sym::EMPTY_STRING, Identifier::sym), |
||||||
|
0, |
||||||
|
true, |
||||||
|
); |
||||||
|
let mut compiler = ByteCompiler { |
||||||
|
code_block: code, |
||||||
|
literals_map: FxHashMap::default(), |
||||||
|
names_map: FxHashMap::default(), |
||||||
|
bindings_map: FxHashMap::default(), |
||||||
|
jump_info: Vec::new(), |
||||||
|
in_async_generator: false, |
||||||
|
json_parse: self.json_parse, |
||||||
|
context: self.context, |
||||||
|
}; |
||||||
|
compiler.context.push_compile_time_environment(true); |
||||||
|
|
||||||
|
if let Some(expr) = class.constructor() { |
||||||
|
compiler.code_block.length = expr.parameters().length(); |
||||||
|
compiler.code_block.params = expr.parameters().clone(); |
||||||
|
compiler |
||||||
|
.context |
||||||
|
.create_mutable_binding(Sym::ARGUMENTS.into(), false, false); |
||||||
|
compiler.code_block.arguments_binding = Some( |
||||||
|
compiler |
||||||
|
.context |
||||||
|
.initialize_mutable_binding(Sym::ARGUMENTS.into(), false), |
||||||
|
); |
||||||
|
for parameter in expr.parameters().as_ref() { |
||||||
|
if parameter.is_rest_param() { |
||||||
|
compiler.emit_opcode(Opcode::RestParameterInit); |
||||||
|
} |
||||||
|
|
||||||
|
match parameter.variable().binding() { |
||||||
|
Binding::Identifier(ident) => { |
||||||
|
compiler |
||||||
|
.context |
||||||
|
.create_mutable_binding(*ident, false, false); |
||||||
|
if let Some(init) = parameter.variable().init() { |
||||||
|
let skip = |
||||||
|
compiler.emit_opcode_with_operand(Opcode::JumpIfNotUndefined); |
||||||
|
compiler.compile_expr(init, true)?; |
||||||
|
compiler.patch_jump(skip); |
||||||
|
} |
||||||
|
compiler.emit_binding(BindingOpcode::InitArg, *ident); |
||||||
|
} |
||||||
|
Binding::Pattern(pattern) => { |
||||||
|
for ident in bound_names(pattern) { |
||||||
|
compiler.context.create_mutable_binding(ident, false, false); |
||||||
|
} |
||||||
|
compiler.compile_declaration_pattern(pattern, BindingOpcode::InitArg)?; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
if !expr.parameters().has_rest_parameter() { |
||||||
|
compiler.emit_opcode(Opcode::RestParameterPop); |
||||||
|
} |
||||||
|
let env_label = if expr.parameters().has_expressions() { |
||||||
|
compiler.code_block.num_bindings = compiler.context.get_binding_number(); |
||||||
|
compiler.context.push_compile_time_environment(true); |
||||||
|
compiler.code_block.function_environment_push_location = |
||||||
|
compiler.next_opcode_location(); |
||||||
|
Some(compiler.emit_opcode_with_two_operands(Opcode::PushFunctionEnvironment)) |
||||||
|
} else { |
||||||
|
None |
||||||
|
}; |
||||||
|
compiler.create_decls(expr.body(), false); |
||||||
|
compiler.compile_statement_list(expr.body(), false, false)?; |
||||||
|
if let Some(env_label) = env_label { |
||||||
|
let (num_bindings, compile_environment) = |
||||||
|
compiler.context.pop_compile_time_environment(); |
||||||
|
let index_compile_environment = |
||||||
|
compiler.push_compile_environment(compile_environment); |
||||||
|
compiler.patch_jump_with_target(env_label.0, num_bindings as u32); |
||||||
|
compiler.patch_jump_with_target(env_label.1, index_compile_environment as u32); |
||||||
|
let (_, compile_environment) = compiler.context.pop_compile_time_environment(); |
||||||
|
compiler.push_compile_environment(compile_environment); |
||||||
|
} else { |
||||||
|
let (num_bindings, compile_environment) = |
||||||
|
compiler.context.pop_compile_time_environment(); |
||||||
|
compiler.push_compile_environment(compile_environment); |
||||||
|
compiler.code_block.num_bindings = num_bindings; |
||||||
|
compiler.code_block.is_class_constructor = true; |
||||||
|
} |
||||||
|
} else { |
||||||
|
if class.super_ref().is_some() { |
||||||
|
compiler.emit_opcode(Opcode::SuperCallDerived); |
||||||
|
} |
||||||
|
let (num_bindings, compile_environment) = |
||||||
|
compiler.context.pop_compile_time_environment(); |
||||||
|
compiler.push_compile_environment(compile_environment); |
||||||
|
compiler.code_block.num_bindings = num_bindings; |
||||||
|
compiler.code_block.is_class_constructor = true; |
||||||
|
} |
||||||
|
|
||||||
|
compiler.emit_opcode(Opcode::PushUndefined); |
||||||
|
compiler.emit_opcode(Opcode::Return); |
||||||
|
|
||||||
|
let code = Gc::new(compiler.finish()); |
||||||
|
let index = self.code_block.functions.len() as u32; |
||||||
|
self.code_block.functions.push(code); |
||||||
|
self.emit(Opcode::GetFunction, &[index]); |
||||||
|
|
||||||
|
self.emit_opcode(Opcode::Dup); |
||||||
|
if let Some(node) = class.super_ref() { |
||||||
|
self.compile_expr(node, true)?; |
||||||
|
self.emit_opcode(Opcode::PushClassPrototype); |
||||||
|
} else { |
||||||
|
self.emit_opcode(Opcode::PushUndefined); |
||||||
|
} |
||||||
|
self.emit_opcode(Opcode::SetClassPrototype); |
||||||
|
self.emit_opcode(Opcode::Swap); |
||||||
|
|
||||||
|
// TODO: set function name for getter and setters
|
||||||
|
for element in class.elements() { |
||||||
|
match element { |
||||||
|
ClassElement::StaticMethodDefinition(name, method_definition) => { |
||||||
|
self.emit_opcode(Opcode::Dup); |
||||||
|
match &method_definition { |
||||||
|
MethodDefinition::Get(expr) => match name { |
||||||
|
PropertyName::Literal(name) => { |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::DefineClassGetterByName, &[index]); |
||||||
|
} |
||||||
|
PropertyName::Computed(ref name_node) => { |
||||||
|
self.compile_expr(name_node, true)?; |
||||||
|
self.emit_opcode(Opcode::ToPropertyKey); |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
self.emit_opcode(Opcode::DefineClassGetterByValue); |
||||||
|
} |
||||||
|
}, |
||||||
|
MethodDefinition::Set(expr) => match name { |
||||||
|
PropertyName::Literal(name) => { |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::DefineClassSetterByName, &[index]); |
||||||
|
} |
||||||
|
PropertyName::Computed(name_node) => { |
||||||
|
self.compile_expr(name_node, true)?; |
||||||
|
self.emit_opcode(Opcode::ToPropertyKey); |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
self.emit_opcode(Opcode::DefineClassSetterByValue); |
||||||
|
} |
||||||
|
}, |
||||||
|
MethodDefinition::Ordinary(expr) => match name { |
||||||
|
PropertyName::Literal(name) => { |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::DefineClassMethodByName, &[index]); |
||||||
|
} |
||||||
|
PropertyName::Computed(name_node) => { |
||||||
|
self.compile_expr(name_node, true)?; |
||||||
|
self.emit_opcode(Opcode::ToPropertyKey); |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
self.emit_opcode(Opcode::DefineClassMethodByValue); |
||||||
|
} |
||||||
|
}, |
||||||
|
MethodDefinition::Async(expr) => match name { |
||||||
|
PropertyName::Literal(name) => { |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::DefineClassMethodByName, &[index]); |
||||||
|
} |
||||||
|
PropertyName::Computed(name_node) => { |
||||||
|
self.compile_expr(name_node, true)?; |
||||||
|
self.emit_opcode(Opcode::ToPropertyKey); |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
self.emit_opcode(Opcode::DefineClassMethodByValue); |
||||||
|
} |
||||||
|
}, |
||||||
|
MethodDefinition::Generator(expr) => match name { |
||||||
|
PropertyName::Literal(name) => { |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::DefineClassMethodByName, &[index]); |
||||||
|
} |
||||||
|
PropertyName::Computed(name_node) => { |
||||||
|
self.compile_expr(name_node, true)?; |
||||||
|
self.emit_opcode(Opcode::ToPropertyKey); |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
self.emit_opcode(Opcode::DefineClassMethodByValue); |
||||||
|
} |
||||||
|
}, |
||||||
|
MethodDefinition::AsyncGenerator(expr) => match name { |
||||||
|
PropertyName::Literal(name) => { |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::DefineClassMethodByName, &[index]); |
||||||
|
} |
||||||
|
PropertyName::Computed(name_node) => { |
||||||
|
self.compile_expr(name_node, true)?; |
||||||
|
self.emit_opcode(Opcode::ToPropertyKey); |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
self.emit_opcode(Opcode::DefineClassMethodByValue); |
||||||
|
} |
||||||
|
}, |
||||||
|
} |
||||||
|
} |
||||||
|
// TODO: set names for private methods
|
||||||
|
ClassElement::PrivateStaticMethodDefinition(name, method_definition) => { |
||||||
|
self.emit_opcode(Opcode::Dup); |
||||||
|
match &method_definition { |
||||||
|
MethodDefinition::Get(expr) => { |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::SetPrivateGetter, &[index]); |
||||||
|
} |
||||||
|
MethodDefinition::Set(expr) => { |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::SetPrivateSetter, &[index]); |
||||||
|
} |
||||||
|
MethodDefinition::Ordinary(expr) => { |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::SetPrivateMethod, &[index]); |
||||||
|
} |
||||||
|
MethodDefinition::Async(expr) => { |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::SetPrivateMethod, &[index]); |
||||||
|
} |
||||||
|
MethodDefinition::Generator(expr) => { |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::SetPrivateMethod, &[index]); |
||||||
|
} |
||||||
|
MethodDefinition::AsyncGenerator(expr) => { |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::SetPrivateMethod, &[index]); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
ClassElement::FieldDefinition(name, field) => { |
||||||
|
self.emit_opcode(Opcode::Dup); |
||||||
|
match name { |
||||||
|
PropertyName::Literal(name) => { |
||||||
|
self.emit_push_literal(Literal::String( |
||||||
|
self.interner().resolve_expect(*name).into_common(false), |
||||||
|
)); |
||||||
|
} |
||||||
|
PropertyName::Computed(name) => { |
||||||
|
self.compile_expr(name, true)?; |
||||||
|
} |
||||||
|
} |
||||||
|
let field_code = CodeBlock::new(Sym::EMPTY_STRING, 0, true); |
||||||
|
let mut field_compiler = ByteCompiler { |
||||||
|
code_block: field_code, |
||||||
|
literals_map: FxHashMap::default(), |
||||||
|
names_map: FxHashMap::default(), |
||||||
|
bindings_map: FxHashMap::default(), |
||||||
|
jump_info: Vec::new(), |
||||||
|
in_async_generator: false, |
||||||
|
json_parse: self.json_parse, |
||||||
|
context: self.context, |
||||||
|
}; |
||||||
|
field_compiler.context.push_compile_time_environment(true); |
||||||
|
if let Some(node) = field { |
||||||
|
field_compiler.compile_expr(node, true)?; |
||||||
|
} else { |
||||||
|
field_compiler.emit_opcode(Opcode::PushUndefined); |
||||||
|
} |
||||||
|
let (num_bindings, compile_environment) = |
||||||
|
field_compiler.context.pop_compile_time_environment(); |
||||||
|
field_compiler.push_compile_environment(compile_environment); |
||||||
|
field_compiler.code_block.num_bindings = num_bindings; |
||||||
|
field_compiler.emit_opcode(Opcode::Return); |
||||||
|
|
||||||
|
let code = Gc::new(field_compiler.finish()); |
||||||
|
let index = self.code_block.functions.len() as u32; |
||||||
|
self.code_block.functions.push(code); |
||||||
|
self.emit(Opcode::GetFunction, &[index]); |
||||||
|
self.emit_opcode(Opcode::PushClassField); |
||||||
|
} |
||||||
|
ClassElement::PrivateFieldDefinition(name, field) => { |
||||||
|
self.emit_opcode(Opcode::Dup); |
||||||
|
let name_index = self.get_or_insert_name((*name).into()); |
||||||
|
let field_code = CodeBlock::new(Sym::EMPTY_STRING, 0, true); |
||||||
|
let mut field_compiler = ByteCompiler { |
||||||
|
code_block: field_code, |
||||||
|
literals_map: FxHashMap::default(), |
||||||
|
names_map: FxHashMap::default(), |
||||||
|
bindings_map: FxHashMap::default(), |
||||||
|
jump_info: Vec::new(), |
||||||
|
in_async_generator: false, |
||||||
|
json_parse: self.json_parse, |
||||||
|
context: self.context, |
||||||
|
}; |
||||||
|
field_compiler.context.push_compile_time_environment(true); |
||||||
|
if let Some(node) = field { |
||||||
|
field_compiler.compile_expr(node, true)?; |
||||||
|
} else { |
||||||
|
field_compiler.emit_opcode(Opcode::PushUndefined); |
||||||
|
} |
||||||
|
let (num_bindings, compile_environment) = |
||||||
|
field_compiler.context.pop_compile_time_environment(); |
||||||
|
field_compiler.push_compile_environment(compile_environment); |
||||||
|
field_compiler.code_block.num_bindings = num_bindings; |
||||||
|
field_compiler.emit_opcode(Opcode::Return); |
||||||
|
|
||||||
|
let code = Gc::new(field_compiler.finish()); |
||||||
|
let index = self.code_block.functions.len() as u32; |
||||||
|
self.code_block.functions.push(code); |
||||||
|
self.emit(Opcode::GetFunction, &[index]); |
||||||
|
self.emit(Opcode::PushClassFieldPrivate, &[name_index]); |
||||||
|
} |
||||||
|
ClassElement::StaticFieldDefinition(name, field) => { |
||||||
|
self.emit_opcode(Opcode::Dup); |
||||||
|
match name { |
||||||
|
PropertyName::Literal(name) => { |
||||||
|
if let Some(node) = field { |
||||||
|
self.compile_expr(node, true)?; |
||||||
|
} else { |
||||||
|
self.emit_opcode(Opcode::PushUndefined); |
||||||
|
} |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::DefineOwnPropertyByName, &[index]); |
||||||
|
} |
||||||
|
PropertyName::Computed(name_node) => { |
||||||
|
self.compile_expr(name_node, true)?; |
||||||
|
self.emit_opcode(Opcode::ToPropertyKey); |
||||||
|
if let Some(node) = field { |
||||||
|
self.compile_expr(node, true)?; |
||||||
|
} else { |
||||||
|
self.emit_opcode(Opcode::PushUndefined); |
||||||
|
} |
||||||
|
self.emit_opcode(Opcode::DefineOwnPropertyByValue); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
ClassElement::PrivateStaticFieldDefinition(name, field) => { |
||||||
|
self.emit_opcode(Opcode::Dup); |
||||||
|
if let Some(node) = field { |
||||||
|
self.compile_expr(node, true)?; |
||||||
|
} else { |
||||||
|
self.emit_opcode(Opcode::PushUndefined); |
||||||
|
} |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::SetPrivateField, &[index]); |
||||||
|
} |
||||||
|
ClassElement::StaticBlock(statement_list) => { |
||||||
|
self.emit_opcode(Opcode::Dup); |
||||||
|
let mut compiler = |
||||||
|
ByteCompiler::new(Sym::EMPTY_STRING, true, false, self.context); |
||||||
|
compiler.context.push_compile_time_environment(true); |
||||||
|
compiler.create_decls(statement_list, false); |
||||||
|
compiler.compile_statement_list(statement_list, false, false)?; |
||||||
|
let (num_bindings, compile_environment) = |
||||||
|
compiler.context.pop_compile_time_environment(); |
||||||
|
compiler.push_compile_environment(compile_environment); |
||||||
|
compiler.code_block.num_bindings = num_bindings; |
||||||
|
|
||||||
|
let code = Gc::new(compiler.finish()); |
||||||
|
let index = self.code_block.functions.len() as u32; |
||||||
|
self.code_block.functions.push(code); |
||||||
|
self.emit(Opcode::GetFunction, &[index]); |
||||||
|
self.emit_opcode(Opcode::SetHomeObject); |
||||||
|
self.emit(Opcode::Call, &[0]); |
||||||
|
self.emit_opcode(Opcode::Pop); |
||||||
|
} |
||||||
|
// TODO: set names for private methods
|
||||||
|
ClassElement::PrivateMethodDefinition(name, method_definition) => { |
||||||
|
self.emit_opcode(Opcode::Dup); |
||||||
|
match method_definition { |
||||||
|
MethodDefinition::Get(expr) => { |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::PushClassPrivateGetter, &[index]); |
||||||
|
} |
||||||
|
MethodDefinition::Set(expr) => { |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::PushClassPrivateSetter, &[index]); |
||||||
|
} |
||||||
|
MethodDefinition::Ordinary(expr) => { |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::PushClassPrivateMethod, &[index]); |
||||||
|
} |
||||||
|
MethodDefinition::Async(expr) => { |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::PushClassPrivateMethod, &[index]); |
||||||
|
} |
||||||
|
MethodDefinition::Generator(expr) => { |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::PushClassPrivateMethod, &[index]); |
||||||
|
} |
||||||
|
MethodDefinition::AsyncGenerator(expr) => { |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::PushClassPrivateMethod, &[index]); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
ClassElement::MethodDefinition(..) => {} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
self.emit_opcode(Opcode::Swap); |
||||||
|
|
||||||
|
for element in class.elements() { |
||||||
|
match element { |
||||||
|
ClassElement::MethodDefinition(name, method_definition) => { |
||||||
|
self.emit_opcode(Opcode::Dup); |
||||||
|
// TODO: set names for getters and setters
|
||||||
|
match method_definition { |
||||||
|
MethodDefinition::Get(expr) => match name { |
||||||
|
PropertyName::Literal(name) => { |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::DefineClassGetterByName, &[index]); |
||||||
|
} |
||||||
|
PropertyName::Computed(name_node) => { |
||||||
|
self.compile_expr(name_node, true)?; |
||||||
|
self.emit_opcode(Opcode::ToPropertyKey); |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
self.emit_opcode(Opcode::DefineClassGetterByValue); |
||||||
|
} |
||||||
|
}, |
||||||
|
MethodDefinition::Set(expr) => match name { |
||||||
|
PropertyName::Literal(name) => { |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::DefineClassSetterByName, &[index]); |
||||||
|
} |
||||||
|
PropertyName::Computed(name_node) => { |
||||||
|
self.compile_expr(name_node, true)?; |
||||||
|
self.emit_opcode(Opcode::ToPropertyKey); |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
self.emit_opcode(Opcode::DefineClassSetterByValue); |
||||||
|
} |
||||||
|
}, |
||||||
|
MethodDefinition::Ordinary(expr) => match name { |
||||||
|
PropertyName::Literal(name) => { |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::DefineClassMethodByName, &[index]); |
||||||
|
} |
||||||
|
PropertyName::Computed(name_node) => { |
||||||
|
self.compile_expr(name_node, true)?; |
||||||
|
self.emit_opcode(Opcode::ToPropertyKey); |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
self.emit_opcode(Opcode::DefineClassMethodByValue); |
||||||
|
} |
||||||
|
}, |
||||||
|
MethodDefinition::Async(expr) => match name { |
||||||
|
PropertyName::Literal(name) => { |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::DefineClassMethodByName, &[index]); |
||||||
|
} |
||||||
|
PropertyName::Computed(name_node) => { |
||||||
|
self.compile_expr(name_node, true)?; |
||||||
|
self.emit_opcode(Opcode::ToPropertyKey); |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
self.emit_opcode(Opcode::DefineClassMethodByValue); |
||||||
|
} |
||||||
|
}, |
||||||
|
MethodDefinition::Generator(expr) => match name { |
||||||
|
PropertyName::Literal(name) => { |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::DefineClassMethodByName, &[index]); |
||||||
|
} |
||||||
|
PropertyName::Computed(name_node) => { |
||||||
|
self.compile_expr(name_node, true)?; |
||||||
|
self.emit_opcode(Opcode::ToPropertyKey); |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
self.emit_opcode(Opcode::DefineClassMethodByValue); |
||||||
|
} |
||||||
|
}, |
||||||
|
MethodDefinition::AsyncGenerator(expr) => match name { |
||||||
|
PropertyName::Literal(name) => { |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::DefineClassMethodByName, &[index]); |
||||||
|
} |
||||||
|
PropertyName::Computed(name_node) => { |
||||||
|
self.compile_expr(name_node, true)?; |
||||||
|
self.emit_opcode(Opcode::ToPropertyKey); |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
self.emit_opcode(Opcode::DefineClassMethodByValue); |
||||||
|
} |
||||||
|
}, |
||||||
|
} |
||||||
|
} |
||||||
|
ClassElement::PrivateMethodDefinition(..) |
||||||
|
| ClassElement::PrivateFieldDefinition(..) |
||||||
|
| ClassElement::StaticFieldDefinition(..) |
||||||
|
| ClassElement::PrivateStaticFieldDefinition(..) |
||||||
|
| ClassElement::StaticMethodDefinition(..) |
||||||
|
| ClassElement::PrivateStaticMethodDefinition(..) |
||||||
|
| ClassElement::StaticBlock(..) |
||||||
|
| ClassElement::FieldDefinition(..) => {} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
self.emit_opcode(Opcode::Pop); |
||||||
|
|
||||||
|
if !expression { |
||||||
|
self.emit_binding( |
||||||
|
BindingOpcode::InitVar, |
||||||
|
class.name().expect("class statements must have a name"), |
||||||
|
); |
||||||
|
} |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,258 @@ |
|||||||
|
use boa_ast::{ |
||||||
|
pattern::{ArrayPatternElement, ObjectPatternElement, Pattern}, |
||||||
|
property::PropertyName, |
||||||
|
}; |
||||||
|
|
||||||
|
use crate::{ |
||||||
|
bytecompiler::{Access, ByteCompiler, Literal}, |
||||||
|
vm::{BindingOpcode, Opcode}, |
||||||
|
JsResult, |
||||||
|
}; |
||||||
|
|
||||||
|
impl ByteCompiler<'_> { |
||||||
|
pub(crate) fn compile_declaration_pattern_impl( |
||||||
|
&mut self, |
||||||
|
pattern: &Pattern, |
||||||
|
def: BindingOpcode, |
||||||
|
) -> JsResult<()> { |
||||||
|
match pattern { |
||||||
|
Pattern::Object(pattern) => { |
||||||
|
self.emit_opcode(Opcode::ValueNotNullOrUndefined); |
||||||
|
|
||||||
|
self.emit_opcode(Opcode::RequireObjectCoercible); |
||||||
|
|
||||||
|
let mut additional_excluded_keys_count = 0; |
||||||
|
let rest_exits = pattern.has_rest(); |
||||||
|
|
||||||
|
for binding in pattern.bindings() { |
||||||
|
use ObjectPatternElement::{ |
||||||
|
AssignmentPropertyAccess, AssignmentRestPropertyAccess, Pattern, |
||||||
|
RestProperty, SingleName, |
||||||
|
}; |
||||||
|
|
||||||
|
match binding { |
||||||
|
// SingleNameBinding : BindingIdentifier Initializer[opt]
|
||||||
|
SingleName { |
||||||
|
ident, |
||||||
|
name, |
||||||
|
default_init, |
||||||
|
} => { |
||||||
|
self.emit_opcode(Opcode::Dup); |
||||||
|
match name { |
||||||
|
PropertyName::Literal(name) => { |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::GetPropertyByName, &[index]); |
||||||
|
} |
||||||
|
PropertyName::Computed(node) => { |
||||||
|
self.compile_expr(node, true)?; |
||||||
|
if rest_exits { |
||||||
|
self.emit_opcode(Opcode::GetPropertyByValuePush); |
||||||
|
} else { |
||||||
|
self.emit_opcode(Opcode::GetPropertyByValue); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if let Some(init) = default_init { |
||||||
|
let skip = |
||||||
|
self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined); |
||||||
|
self.compile_expr(init, true)?; |
||||||
|
self.patch_jump(skip); |
||||||
|
} |
||||||
|
self.emit_binding(def, *ident); |
||||||
|
|
||||||
|
if rest_exits && name.computed().is_some() { |
||||||
|
self.emit_opcode(Opcode::Swap); |
||||||
|
additional_excluded_keys_count += 1; |
||||||
|
} |
||||||
|
} |
||||||
|
// BindingRestProperty : ... BindingIdentifier
|
||||||
|
RestProperty { |
||||||
|
ident, |
||||||
|
excluded_keys, |
||||||
|
} => { |
||||||
|
self.emit_opcode(Opcode::PushEmptyObject); |
||||||
|
|
||||||
|
for key in excluded_keys { |
||||||
|
self.emit_push_literal(Literal::String( |
||||||
|
self.interner().resolve_expect(key.sym()).into_common(false), |
||||||
|
)); |
||||||
|
} |
||||||
|
|
||||||
|
self.emit( |
||||||
|
Opcode::CopyDataProperties, |
||||||
|
&[excluded_keys.len() as u32, additional_excluded_keys_count], |
||||||
|
); |
||||||
|
self.emit_binding(def, *ident); |
||||||
|
} |
||||||
|
AssignmentRestPropertyAccess { |
||||||
|
access, |
||||||
|
excluded_keys, |
||||||
|
} => { |
||||||
|
self.emit_opcode(Opcode::Dup); |
||||||
|
self.emit_opcode(Opcode::PushEmptyObject); |
||||||
|
for key in excluded_keys { |
||||||
|
self.emit_push_literal(Literal::String( |
||||||
|
self.interner().resolve_expect(key.sym()).into_common(false), |
||||||
|
)); |
||||||
|
} |
||||||
|
self.emit(Opcode::CopyDataProperties, &[excluded_keys.len() as u32, 0]); |
||||||
|
self.access_set( |
||||||
|
Access::Property { access }, |
||||||
|
false, |
||||||
|
ByteCompiler::access_set_top_of_stack_expr_fn, |
||||||
|
)?; |
||||||
|
} |
||||||
|
AssignmentPropertyAccess { |
||||||
|
name, |
||||||
|
access, |
||||||
|
default_init, |
||||||
|
} => { |
||||||
|
self.emit_opcode(Opcode::Dup); |
||||||
|
match name { |
||||||
|
PropertyName::Literal(name) => { |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::GetPropertyByName, &[index]); |
||||||
|
} |
||||||
|
PropertyName::Computed(node) => { |
||||||
|
self.compile_expr(node, true)?; |
||||||
|
if rest_exits { |
||||||
|
self.emit_opcode(Opcode::GetPropertyByValuePush); |
||||||
|
} else { |
||||||
|
self.emit_opcode(Opcode::GetPropertyByValue); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if let Some(init) = default_init { |
||||||
|
let skip = |
||||||
|
self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined); |
||||||
|
self.compile_expr(init, true)?; |
||||||
|
self.patch_jump(skip); |
||||||
|
} |
||||||
|
|
||||||
|
self.access_set( |
||||||
|
Access::Property { access }, |
||||||
|
false, |
||||||
|
ByteCompiler::access_set_top_of_stack_expr_fn, |
||||||
|
)?; |
||||||
|
|
||||||
|
if rest_exits && name.computed().is_some() { |
||||||
|
self.emit_opcode(Opcode::Swap); |
||||||
|
additional_excluded_keys_count += 1; |
||||||
|
} |
||||||
|
} |
||||||
|
Pattern { |
||||||
|
name, |
||||||
|
pattern, |
||||||
|
default_init, |
||||||
|
} => { |
||||||
|
self.emit_opcode(Opcode::Dup); |
||||||
|
match name { |
||||||
|
PropertyName::Literal(name) => { |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::GetPropertyByName, &[index]); |
||||||
|
} |
||||||
|
PropertyName::Computed(node) => { |
||||||
|
self.compile_expr(node, true)?; |
||||||
|
self.emit_opcode(Opcode::GetPropertyByValue); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if let Some(init) = default_init { |
||||||
|
let skip = |
||||||
|
self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined); |
||||||
|
self.compile_expr(init, true)?; |
||||||
|
self.patch_jump(skip); |
||||||
|
} |
||||||
|
|
||||||
|
self.compile_declaration_pattern(pattern, def)?; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if !rest_exits { |
||||||
|
self.emit_opcode(Opcode::Pop); |
||||||
|
} |
||||||
|
} |
||||||
|
Pattern::Array(pattern) => { |
||||||
|
self.emit_opcode(Opcode::ValueNotNullOrUndefined); |
||||||
|
self.emit_opcode(Opcode::InitIterator); |
||||||
|
|
||||||
|
for binding in pattern.bindings().iter() { |
||||||
|
use ArrayPatternElement::{ |
||||||
|
Elision, Pattern, PatternRest, PropertyAccess, PropertyAccessRest, |
||||||
|
SingleName, SingleNameRest, |
||||||
|
}; |
||||||
|
|
||||||
|
match binding { |
||||||
|
// ArrayBindingPattern : [ Elision ]
|
||||||
|
Elision => { |
||||||
|
self.emit_opcode(Opcode::IteratorNext); |
||||||
|
self.emit_opcode(Opcode::Pop); |
||||||
|
} |
||||||
|
// SingleNameBinding : BindingIdentifier Initializer[opt]
|
||||||
|
SingleName { |
||||||
|
ident, |
||||||
|
default_init, |
||||||
|
} => { |
||||||
|
self.emit_opcode(Opcode::IteratorNext); |
||||||
|
if let Some(init) = default_init { |
||||||
|
let skip = |
||||||
|
self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined); |
||||||
|
self.compile_expr(init, true)?; |
||||||
|
self.patch_jump(skip); |
||||||
|
} |
||||||
|
self.emit_binding(def, *ident); |
||||||
|
} |
||||||
|
PropertyAccess { access } => { |
||||||
|
self.emit_opcode(Opcode::IteratorNext); |
||||||
|
self.access_set( |
||||||
|
Access::Property { access }, |
||||||
|
false, |
||||||
|
ByteCompiler::access_set_top_of_stack_expr_fn, |
||||||
|
)?; |
||||||
|
} |
||||||
|
// BindingElement : BindingPattern Initializer[opt]
|
||||||
|
Pattern { |
||||||
|
pattern, |
||||||
|
default_init, |
||||||
|
} => { |
||||||
|
self.emit_opcode(Opcode::IteratorNext); |
||||||
|
|
||||||
|
if let Some(init) = default_init { |
||||||
|
let skip = |
||||||
|
self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined); |
||||||
|
self.compile_expr(init, true)?; |
||||||
|
self.patch_jump(skip); |
||||||
|
} |
||||||
|
|
||||||
|
self.compile_declaration_pattern(pattern, def)?; |
||||||
|
} |
||||||
|
// BindingRestElement : ... BindingIdentifier
|
||||||
|
SingleNameRest { ident } => { |
||||||
|
self.emit_opcode(Opcode::IteratorToArray); |
||||||
|
self.emit_binding(def, *ident); |
||||||
|
} |
||||||
|
PropertyAccessRest { access } => { |
||||||
|
self.emit_opcode(Opcode::IteratorToArray); |
||||||
|
self.access_set( |
||||||
|
Access::Property { access }, |
||||||
|
false, |
||||||
|
ByteCompiler::access_set_top_of_stack_expr_fn, |
||||||
|
)?; |
||||||
|
} |
||||||
|
// BindingRestElement : ... BindingPattern
|
||||||
|
PatternRest { pattern } => { |
||||||
|
self.emit_opcode(Opcode::IteratorToArray); |
||||||
|
self.compile_declaration_pattern(pattern, def)?; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
self.emit_opcode(Opcode::IteratorClose); |
||||||
|
} |
||||||
|
} |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1 @@ |
|||||||
|
mod declaration_pattern; |
@ -0,0 +1,90 @@ |
|||||||
|
use boa_ast::expression::operator::{assign::AssignOp, Assign}; |
||||||
|
|
||||||
|
use crate::{ |
||||||
|
bytecompiler::{Access, ByteCompiler}, |
||||||
|
vm::{BindingOpcode, Opcode}, |
||||||
|
JsResult, |
||||||
|
}; |
||||||
|
|
||||||
|
impl ByteCompiler<'_> { |
||||||
|
pub(crate) fn compile_assign(&mut self, assign: &Assign, use_expr: bool) -> JsResult<()> { |
||||||
|
if assign.op() == AssignOp::Assign { |
||||||
|
match Access::from_assign_target(assign.lhs()) { |
||||||
|
Ok(access) => self.access_set(access, use_expr, |compiler, _| { |
||||||
|
compiler.compile_expr(assign.rhs(), true)?; |
||||||
|
Ok(()) |
||||||
|
})?, |
||||||
|
Err(pattern) => { |
||||||
|
self.compile_expr(assign.rhs(), true)?; |
||||||
|
if use_expr { |
||||||
|
self.emit_opcode(Opcode::Dup); |
||||||
|
} |
||||||
|
self.compile_declaration_pattern(pattern, BindingOpcode::SetName)?; |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
let access = Access::from_assign_target(assign.lhs()) |
||||||
|
.expect("patterns should throw early errors on complex assignment operators"); |
||||||
|
|
||||||
|
let shortcircuit_operator_compilation = |
||||||
|
|compiler: &mut ByteCompiler<'_>, opcode: Opcode| -> JsResult<()> { |
||||||
|
let (early_exit, pop_count) = |
||||||
|
compiler.access_set(access, use_expr, |compiler, level| { |
||||||
|
compiler.access_get(access, true)?; |
||||||
|
let early_exit = compiler.emit_opcode_with_operand(opcode); |
||||||
|
compiler.compile_expr(assign.rhs(), true)?; |
||||||
|
Ok((early_exit, level)) |
||||||
|
})?; |
||||||
|
if pop_count == 0 { |
||||||
|
compiler.patch_jump(early_exit); |
||||||
|
} else { |
||||||
|
let exit = compiler.emit_opcode_with_operand(Opcode::Jump); |
||||||
|
compiler.patch_jump(early_exit); |
||||||
|
for _ in 0..pop_count { |
||||||
|
compiler.emit_opcode(Opcode::Swap); |
||||||
|
compiler.emit_opcode(Opcode::Pop); |
||||||
|
} |
||||||
|
compiler.patch_jump(exit); |
||||||
|
} |
||||||
|
Ok(()) |
||||||
|
}; |
||||||
|
|
||||||
|
let opcode = match assign.op() { |
||||||
|
AssignOp::Assign => unreachable!(), |
||||||
|
AssignOp::Add => Opcode::Add, |
||||||
|
AssignOp::Sub => Opcode::Sub, |
||||||
|
AssignOp::Mul => Opcode::Mul, |
||||||
|
AssignOp::Div => Opcode::Div, |
||||||
|
AssignOp::Mod => Opcode::Mod, |
||||||
|
AssignOp::Exp => Opcode::Pow, |
||||||
|
AssignOp::And => Opcode::BitAnd, |
||||||
|
AssignOp::Or => Opcode::BitOr, |
||||||
|
AssignOp::Xor => Opcode::BitXor, |
||||||
|
AssignOp::Shl => Opcode::ShiftLeft, |
||||||
|
AssignOp::Shr => Opcode::ShiftRight, |
||||||
|
AssignOp::Ushr => Opcode::UnsignedShiftRight, |
||||||
|
AssignOp::BoolAnd => { |
||||||
|
shortcircuit_operator_compilation(self, Opcode::LogicalAnd)?; |
||||||
|
return Ok(()); |
||||||
|
} |
||||||
|
AssignOp::BoolOr => { |
||||||
|
shortcircuit_operator_compilation(self, Opcode::LogicalOr)?; |
||||||
|
return Ok(()); |
||||||
|
} |
||||||
|
AssignOp::Coalesce => { |
||||||
|
shortcircuit_operator_compilation(self, Opcode::Coalesce)?; |
||||||
|
return Ok(()); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
self.access_set(access, use_expr, |compiler, _| { |
||||||
|
compiler.access_get(access, true)?; |
||||||
|
compiler.compile_expr(assign.rhs(), true)?; |
||||||
|
compiler.emit(opcode, &[]); |
||||||
|
Ok(()) |
||||||
|
})?; |
||||||
|
} |
||||||
|
|
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,98 @@ |
|||||||
|
use boa_ast::expression::operator::{ |
||||||
|
binary::{ArithmeticOp, BinaryOp, BitwiseOp, LogicalOp, RelationalOp}, |
||||||
|
Binary, |
||||||
|
}; |
||||||
|
|
||||||
|
use crate::{bytecompiler::ByteCompiler, vm::Opcode, JsResult}; |
||||||
|
|
||||||
|
impl ByteCompiler<'_> { |
||||||
|
pub(crate) fn compile_binary(&mut self, binary: &Binary, use_expr: bool) -> JsResult<()> { |
||||||
|
self.compile_expr(binary.lhs(), true)?; |
||||||
|
match binary.op() { |
||||||
|
BinaryOp::Arithmetic(op) => { |
||||||
|
self.compile_expr(binary.rhs(), true)?; |
||||||
|
match op { |
||||||
|
ArithmeticOp::Add => self.emit_opcode(Opcode::Add), |
||||||
|
ArithmeticOp::Sub => self.emit_opcode(Opcode::Sub), |
||||||
|
ArithmeticOp::Div => self.emit_opcode(Opcode::Div), |
||||||
|
ArithmeticOp::Mul => self.emit_opcode(Opcode::Mul), |
||||||
|
ArithmeticOp::Exp => self.emit_opcode(Opcode::Pow), |
||||||
|
ArithmeticOp::Mod => self.emit_opcode(Opcode::Mod), |
||||||
|
} |
||||||
|
|
||||||
|
if !use_expr { |
||||||
|
self.emit(Opcode::Pop, &[]); |
||||||
|
} |
||||||
|
} |
||||||
|
BinaryOp::Bitwise(op) => { |
||||||
|
self.compile_expr(binary.rhs(), true)?; |
||||||
|
match op { |
||||||
|
BitwiseOp::And => self.emit_opcode(Opcode::BitAnd), |
||||||
|
BitwiseOp::Or => self.emit_opcode(Opcode::BitOr), |
||||||
|
BitwiseOp::Xor => self.emit_opcode(Opcode::BitXor), |
||||||
|
BitwiseOp::Shl => self.emit_opcode(Opcode::ShiftLeft), |
||||||
|
BitwiseOp::Shr => self.emit_opcode(Opcode::ShiftRight), |
||||||
|
BitwiseOp::UShr => self.emit_opcode(Opcode::UnsignedShiftRight), |
||||||
|
} |
||||||
|
|
||||||
|
if !use_expr { |
||||||
|
self.emit(Opcode::Pop, &[]); |
||||||
|
} |
||||||
|
} |
||||||
|
BinaryOp::Relational(op) => { |
||||||
|
self.compile_expr(binary.rhs(), true)?; |
||||||
|
match op { |
||||||
|
RelationalOp::Equal => self.emit_opcode(Opcode::Eq), |
||||||
|
RelationalOp::NotEqual => self.emit_opcode(Opcode::NotEq), |
||||||
|
RelationalOp::StrictEqual => self.emit_opcode(Opcode::StrictEq), |
||||||
|
RelationalOp::StrictNotEqual => self.emit_opcode(Opcode::StrictNotEq), |
||||||
|
RelationalOp::GreaterThan => self.emit_opcode(Opcode::GreaterThan), |
||||||
|
RelationalOp::GreaterThanOrEqual => { |
||||||
|
self.emit_opcode(Opcode::GreaterThanOrEq); |
||||||
|
} |
||||||
|
RelationalOp::LessThan => self.emit_opcode(Opcode::LessThan), |
||||||
|
RelationalOp::LessThanOrEqual => self.emit_opcode(Opcode::LessThanOrEq), |
||||||
|
RelationalOp::In => self.emit_opcode(Opcode::In), |
||||||
|
RelationalOp::InstanceOf => self.emit_opcode(Opcode::InstanceOf), |
||||||
|
} |
||||||
|
|
||||||
|
if !use_expr { |
||||||
|
self.emit(Opcode::Pop, &[]); |
||||||
|
} |
||||||
|
} |
||||||
|
BinaryOp::Logical(op) => { |
||||||
|
match op { |
||||||
|
LogicalOp::And => { |
||||||
|
let exit = self.emit_opcode_with_operand(Opcode::LogicalAnd); |
||||||
|
self.compile_expr(binary.rhs(), true)?; |
||||||
|
self.patch_jump(exit); |
||||||
|
} |
||||||
|
LogicalOp::Or => { |
||||||
|
let exit = self.emit_opcode_with_operand(Opcode::LogicalOr); |
||||||
|
self.compile_expr(binary.rhs(), true)?; |
||||||
|
self.patch_jump(exit); |
||||||
|
} |
||||||
|
LogicalOp::Coalesce => { |
||||||
|
let exit = self.emit_opcode_with_operand(Opcode::Coalesce); |
||||||
|
self.compile_expr(binary.rhs(), true)?; |
||||||
|
self.patch_jump(exit); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
if !use_expr { |
||||||
|
self.emit(Opcode::Pop, &[]); |
||||||
|
} |
||||||
|
} |
||||||
|
BinaryOp::Comma => { |
||||||
|
self.emit(Opcode::Pop, &[]); |
||||||
|
self.compile_expr(binary.rhs(), true)?; |
||||||
|
|
||||||
|
if !use_expr { |
||||||
|
self.emit(Opcode::Pop, &[]); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,317 @@ |
|||||||
|
use crate::{ |
||||||
|
bytecompiler::{ByteCompiler, Literal}, |
||||||
|
vm::Opcode, |
||||||
|
JsResult, |
||||||
|
}; |
||||||
|
|
||||||
|
use boa_ast::{ |
||||||
|
expression::{ |
||||||
|
access::{PropertyAccess, PropertyAccessField}, |
||||||
|
literal::{Literal as AstLiteral, TemplateElement, TemplateLiteral}, |
||||||
|
operator::Conditional, |
||||||
|
}, |
||||||
|
Expression, |
||||||
|
}; |
||||||
|
|
||||||
|
mod assign; |
||||||
|
mod binary; |
||||||
|
mod object_literal; |
||||||
|
mod unary; |
||||||
|
|
||||||
|
use boa_interner::Sym; |
||||||
|
|
||||||
|
use super::{Access, Callable, NodeKind}; |
||||||
|
impl ByteCompiler<'_> { |
||||||
|
fn compile_literal(&mut self, lit: &AstLiteral, use_expr: bool) { |
||||||
|
match lit { |
||||||
|
AstLiteral::String(v) => self.emit_push_literal(Literal::String( |
||||||
|
self.interner().resolve_expect(*v).into_common(false), |
||||||
|
)), |
||||||
|
AstLiteral::Int(v) => self.emit_push_integer(*v), |
||||||
|
AstLiteral::Num(v) => self.emit_push_rational(*v), |
||||||
|
AstLiteral::BigInt(v) => { |
||||||
|
self.emit_push_literal(Literal::BigInt(v.clone().into())); |
||||||
|
} |
||||||
|
AstLiteral::Bool(true) => self.emit(Opcode::PushTrue, &[]), |
||||||
|
AstLiteral::Bool(false) => self.emit(Opcode::PushFalse, &[]), |
||||||
|
AstLiteral::Null => self.emit(Opcode::PushNull, &[]), |
||||||
|
AstLiteral::Undefined => self.emit(Opcode::PushUndefined, &[]), |
||||||
|
} |
||||||
|
|
||||||
|
if !use_expr { |
||||||
|
self.emit(Opcode::Pop, &[]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn compile_conditional(&mut self, op: &Conditional, use_expr: bool) -> JsResult<()> { |
||||||
|
self.compile_expr(op.condition(), true)?; |
||||||
|
let jelse = self.jump_if_false(); |
||||||
|
self.compile_expr(op.if_true(), true)?; |
||||||
|
let exit = self.jump(); |
||||||
|
self.patch_jump(jelse); |
||||||
|
self.compile_expr(op.if_false(), true)?; |
||||||
|
self.patch_jump(exit); |
||||||
|
|
||||||
|
if !use_expr { |
||||||
|
self.emit(Opcode::Pop, &[]); |
||||||
|
}; |
||||||
|
|
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
fn compile_template_literal( |
||||||
|
&mut self, |
||||||
|
template_literal: &TemplateLiteral, |
||||||
|
use_expr: bool, |
||||||
|
) -> JsResult<()> { |
||||||
|
for element in template_literal.elements() { |
||||||
|
match element { |
||||||
|
TemplateElement::String(s) => self.emit_push_literal(Literal::String( |
||||||
|
self.interner().resolve_expect(*s).into_common(false), |
||||||
|
)), |
||||||
|
TemplateElement::Expr(expr) => { |
||||||
|
self.compile_expr(expr, true)?; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
self.emit( |
||||||
|
Opcode::ConcatToString, |
||||||
|
&[template_literal.elements().len() as u32], |
||||||
|
); |
||||||
|
|
||||||
|
if !use_expr { |
||||||
|
self.emit(Opcode::Pop, &[]); |
||||||
|
} |
||||||
|
|
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn compile_expr_impl(&mut self, expr: &Expression, use_expr: bool) -> JsResult<()> { |
||||||
|
match expr { |
||||||
|
Expression::Literal(lit) => self.compile_literal(lit, use_expr), |
||||||
|
Expression::Unary(unary) => self.compile_unary(unary, use_expr)?, |
||||||
|
Expression::Binary(binary) => self.compile_binary(binary, use_expr)?, |
||||||
|
Expression::Assign(assign) => self.compile_assign(assign, use_expr)?, |
||||||
|
Expression::ObjectLiteral(object) => { |
||||||
|
self.compile_object_literal(object, use_expr)?; |
||||||
|
} |
||||||
|
Expression::Identifier(name) => { |
||||||
|
self.access_get(Access::Variable { name: *name }, use_expr)?; |
||||||
|
} |
||||||
|
Expression::PropertyAccess(access) => { |
||||||
|
self.access_get(Access::Property { access }, use_expr)?; |
||||||
|
} |
||||||
|
Expression::Conditional(op) => self.compile_conditional(op, use_expr)?, |
||||||
|
Expression::ArrayLiteral(array) => { |
||||||
|
self.emit_opcode(Opcode::PushNewArray); |
||||||
|
self.emit_opcode(Opcode::PopOnReturnAdd); |
||||||
|
|
||||||
|
for element in array.as_ref() { |
||||||
|
if let Some(element) = element { |
||||||
|
self.compile_expr(element, true)?; |
||||||
|
if let Expression::Spread(_) = element { |
||||||
|
self.emit_opcode(Opcode::InitIterator); |
||||||
|
self.emit_opcode(Opcode::PushIteratorToArray); |
||||||
|
} else { |
||||||
|
self.emit_opcode(Opcode::PushValueToArray); |
||||||
|
} |
||||||
|
} else { |
||||||
|
self.emit_opcode(Opcode::PushElisionToArray); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
self.emit_opcode(Opcode::PopOnReturnSub); |
||||||
|
if !use_expr { |
||||||
|
self.emit(Opcode::Pop, &[]); |
||||||
|
} |
||||||
|
} |
||||||
|
Expression::This => { |
||||||
|
self.access_get(Access::This, use_expr)?; |
||||||
|
} |
||||||
|
Expression::Spread(spread) => self.compile_expr(spread.target(), true)?, |
||||||
|
Expression::Function(function) => { |
||||||
|
self.function(function.into(), NodeKind::Expression, use_expr)?; |
||||||
|
} |
||||||
|
Expression::ArrowFunction(function) => { |
||||||
|
self.function(function.into(), NodeKind::Expression, use_expr)?; |
||||||
|
} |
||||||
|
Expression::AsyncArrowFunction(function) => { |
||||||
|
self.function(function.into(), NodeKind::Expression, use_expr)?; |
||||||
|
} |
||||||
|
Expression::Generator(function) => { |
||||||
|
self.function(function.into(), NodeKind::Expression, use_expr)?; |
||||||
|
} |
||||||
|
Expression::AsyncFunction(function) => { |
||||||
|
self.function(function.into(), NodeKind::Expression, use_expr)?; |
||||||
|
} |
||||||
|
Expression::AsyncGenerator(function) => { |
||||||
|
self.function(function.into(), NodeKind::Expression, use_expr)?; |
||||||
|
} |
||||||
|
Expression::Call(call) => self.call(Callable::Call(call), use_expr)?, |
||||||
|
Expression::New(new) => self.call(Callable::New(new), use_expr)?, |
||||||
|
Expression::TemplateLiteral(template_literal) => { |
||||||
|
self.compile_template_literal(template_literal, use_expr)?; |
||||||
|
} |
||||||
|
Expression::Await(expr) => { |
||||||
|
self.compile_expr(expr.target(), true)?; |
||||||
|
self.emit_opcode(Opcode::Await); |
||||||
|
self.emit_opcode(Opcode::GeneratorNext); |
||||||
|
if !use_expr { |
||||||
|
self.emit_opcode(Opcode::Pop); |
||||||
|
} |
||||||
|
} |
||||||
|
Expression::Yield(r#yield) => { |
||||||
|
if let Some(expr) = r#yield.target() { |
||||||
|
self.compile_expr(expr, true)?; |
||||||
|
} else { |
||||||
|
self.emit_opcode(Opcode::PushUndefined); |
||||||
|
} |
||||||
|
|
||||||
|
if r#yield.delegate() { |
||||||
|
if self.in_async_generator { |
||||||
|
self.emit_opcode(Opcode::InitIteratorAsync); |
||||||
|
} else { |
||||||
|
self.emit_opcode(Opcode::InitIterator); |
||||||
|
} |
||||||
|
self.emit_opcode(Opcode::PushUndefined); |
||||||
|
let start_address = self.next_opcode_location(); |
||||||
|
let start = self.emit_opcode_with_operand(Opcode::GeneratorNextDelegate); |
||||||
|
self.emit(Opcode::Jump, &[start_address]); |
||||||
|
self.patch_jump(start); |
||||||
|
} else if self.in_async_generator { |
||||||
|
self.emit_opcode(Opcode::Await); |
||||||
|
self.emit_opcode(Opcode::AsyncGeneratorNext); |
||||||
|
let jump_return = self.emit_opcode_with_operand(Opcode::JumpIfFalse); |
||||||
|
let jump = self.emit_opcode_with_operand(Opcode::JumpIfFalse); |
||||||
|
self.emit_opcode(Opcode::Yield); |
||||||
|
self.emit_opcode(Opcode::GeneratorNext); |
||||||
|
self.patch_jump(jump); |
||||||
|
self.emit_opcode(Opcode::Await); |
||||||
|
self.emit_opcode(Opcode::GeneratorNext); |
||||||
|
self.patch_jump(jump_return); |
||||||
|
} else { |
||||||
|
self.emit_opcode(Opcode::Yield); |
||||||
|
self.emit_opcode(Opcode::GeneratorNext); |
||||||
|
} |
||||||
|
|
||||||
|
if !use_expr { |
||||||
|
self.emit_opcode(Opcode::Pop); |
||||||
|
} |
||||||
|
} |
||||||
|
Expression::TaggedTemplate(template) => { |
||||||
|
match template.tag() { |
||||||
|
Expression::PropertyAccess(PropertyAccess::Simple(access)) => { |
||||||
|
self.compile_expr(access.target(), true)?; |
||||||
|
self.emit(Opcode::Dup, &[]); |
||||||
|
match access.field() { |
||||||
|
PropertyAccessField::Const(field) => { |
||||||
|
let index = self.get_or_insert_name((*field).into()); |
||||||
|
self.emit(Opcode::GetPropertyByName, &[index]); |
||||||
|
} |
||||||
|
PropertyAccessField::Expr(field) => { |
||||||
|
self.compile_expr(field, true)?; |
||||||
|
self.emit(Opcode::GetPropertyByValue, &[]); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
Expression::PropertyAccess(PropertyAccess::Private(access)) => { |
||||||
|
self.compile_expr(access.target(), true)?; |
||||||
|
self.emit(Opcode::Dup, &[]); |
||||||
|
let index = self.get_or_insert_name(access.field().into()); |
||||||
|
self.emit(Opcode::GetPrivateField, &[index]); |
||||||
|
} |
||||||
|
expr => { |
||||||
|
self.compile_expr(expr, true)?; |
||||||
|
self.emit_opcode(Opcode::This); |
||||||
|
self.emit_opcode(Opcode::Swap); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
self.emit_opcode(Opcode::PushNewArray); |
||||||
|
for cooked in template.cookeds() { |
||||||
|
if let Some(cooked) = cooked { |
||||||
|
self.emit_push_literal(Literal::String( |
||||||
|
self.interner().resolve_expect(*cooked).into_common(false), |
||||||
|
)); |
||||||
|
} else { |
||||||
|
self.emit_opcode(Opcode::PushUndefined); |
||||||
|
} |
||||||
|
self.emit_opcode(Opcode::PushValueToArray); |
||||||
|
} |
||||||
|
self.emit_opcode(Opcode::Dup); |
||||||
|
|
||||||
|
self.emit_opcode(Opcode::PushNewArray); |
||||||
|
for raw in template.raws() { |
||||||
|
self.emit_push_literal(Literal::String( |
||||||
|
self.interner().resolve_expect(*raw).into_common(false), |
||||||
|
)); |
||||||
|
self.emit_opcode(Opcode::PushValueToArray); |
||||||
|
} |
||||||
|
|
||||||
|
let index = self.get_or_insert_name(Sym::RAW.into()); |
||||||
|
self.emit(Opcode::SetPropertyByName, &[index]); |
||||||
|
self.emit(Opcode::Pop, &[]); |
||||||
|
|
||||||
|
for expr in template.exprs() { |
||||||
|
self.compile_expr(expr, true)?; |
||||||
|
} |
||||||
|
|
||||||
|
self.emit(Opcode::Call, &[(template.exprs().len() + 1) as u32]); |
||||||
|
} |
||||||
|
Expression::Class(class) => self.class(class, true)?, |
||||||
|
Expression::SuperCall(super_call) => { |
||||||
|
let contains_spread = super_call |
||||||
|
.arguments() |
||||||
|
.iter() |
||||||
|
.any(|arg| matches!(arg, Expression::Spread(_))); |
||||||
|
|
||||||
|
if contains_spread { |
||||||
|
self.emit_opcode(Opcode::PushNewArray); |
||||||
|
for arg in super_call.arguments() { |
||||||
|
self.compile_expr(arg, true)?; |
||||||
|
if let Expression::Spread(_) = arg { |
||||||
|
self.emit_opcode(Opcode::InitIterator); |
||||||
|
self.emit_opcode(Opcode::PushIteratorToArray); |
||||||
|
} else { |
||||||
|
self.emit_opcode(Opcode::PushValueToArray); |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
for arg in super_call.arguments() { |
||||||
|
self.compile_expr(arg, true)?; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if contains_spread { |
||||||
|
self.emit_opcode(Opcode::SuperCallSpread); |
||||||
|
} else { |
||||||
|
self.emit(Opcode::SuperCall, &[super_call.arguments().len() as u32]); |
||||||
|
} |
||||||
|
|
||||||
|
if !use_expr { |
||||||
|
self.emit_opcode(Opcode::Pop); |
||||||
|
} |
||||||
|
} |
||||||
|
Expression::NewTarget => { |
||||||
|
if use_expr { |
||||||
|
self.emit_opcode(Opcode::PushNewTarget); |
||||||
|
} |
||||||
|
} |
||||||
|
Expression::Optional(opt) => { |
||||||
|
self.compile_optional_preserve_this(opt)?; |
||||||
|
|
||||||
|
self.emit_opcode(Opcode::Swap); |
||||||
|
self.emit_opcode(Opcode::Pop); |
||||||
|
|
||||||
|
if !use_expr { |
||||||
|
self.emit_opcode(Opcode::Pop); |
||||||
|
} |
||||||
|
} |
||||||
|
// TODO: try to remove this variant somehow
|
||||||
|
Expression::FormalParameterList(_) => unreachable!(), |
||||||
|
} |
||||||
|
|
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,170 @@ |
|||||||
|
use boa_ast::{ |
||||||
|
expression::literal::ObjectLiteral, |
||||||
|
property::{MethodDefinition, PropertyDefinition, PropertyName}, |
||||||
|
}; |
||||||
|
use boa_interner::Sym; |
||||||
|
|
||||||
|
use crate::{ |
||||||
|
bytecompiler::{Access, ByteCompiler, NodeKind}, |
||||||
|
vm::Opcode, |
||||||
|
JsNativeError, JsResult, |
||||||
|
}; |
||||||
|
impl ByteCompiler<'_> { |
||||||
|
pub(crate) fn compile_object_literal( |
||||||
|
&mut self, |
||||||
|
object: &ObjectLiteral, |
||||||
|
use_expr: bool, |
||||||
|
) -> JsResult<()> { |
||||||
|
self.emit_opcode(Opcode::PushEmptyObject); |
||||||
|
for property in object.properties() { |
||||||
|
self.emit_opcode(Opcode::Dup); |
||||||
|
match property { |
||||||
|
PropertyDefinition::IdentifierReference(ident) => { |
||||||
|
let index = self.get_or_insert_name(*ident); |
||||||
|
self.access_get(Access::Variable { name: *ident }, true)?; |
||||||
|
self.emit(Opcode::DefineOwnPropertyByName, &[index]); |
||||||
|
} |
||||||
|
PropertyDefinition::Property(name, expr) => match name { |
||||||
|
PropertyName::Literal(name) => { |
||||||
|
self.compile_expr(expr, true)?; |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
if *name == Sym::__PROTO__ && !self.json_parse { |
||||||
|
self.emit_opcode(Opcode::SetPrototype); |
||||||
|
} else { |
||||||
|
self.emit(Opcode::DefineOwnPropertyByName, &[index]); |
||||||
|
} |
||||||
|
} |
||||||
|
PropertyName::Computed(name_node) => { |
||||||
|
self.compile_expr(name_node, true)?; |
||||||
|
self.emit_opcode(Opcode::ToPropertyKey); |
||||||
|
if expr.is_function_definition() { |
||||||
|
self.emit_opcode(Opcode::Dup); |
||||||
|
self.compile_expr(expr, true)?; |
||||||
|
self.emit_opcode(Opcode::SetFunctionName); |
||||||
|
self.emit_u8(0); |
||||||
|
} else { |
||||||
|
self.compile_expr(expr, true)?; |
||||||
|
} |
||||||
|
self.emit_opcode(Opcode::DefineOwnPropertyByValue); |
||||||
|
} |
||||||
|
}, |
||||||
|
PropertyDefinition::MethodDefinition(name, kind) => match kind { |
||||||
|
MethodDefinition::Get(expr) => match name { |
||||||
|
PropertyName::Literal(name) => { |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::SetPropertyGetterByName, &[index]); |
||||||
|
} |
||||||
|
PropertyName::Computed(name_node) => { |
||||||
|
self.compile_expr(name_node, true)?; |
||||||
|
self.emit_opcode(Opcode::ToPropertyKey); |
||||||
|
self.emit_opcode(Opcode::Dup); |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
self.emit_opcode(Opcode::SetFunctionName); |
||||||
|
self.emit_u8(1); |
||||||
|
self.emit_opcode(Opcode::SetPropertyGetterByValue); |
||||||
|
} |
||||||
|
}, |
||||||
|
MethodDefinition::Set(expr) => match name { |
||||||
|
PropertyName::Literal(name) => { |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::SetPropertySetterByName, &[index]); |
||||||
|
} |
||||||
|
PropertyName::Computed(name_node) => { |
||||||
|
self.compile_expr(name_node, true)?; |
||||||
|
self.emit_opcode(Opcode::ToPropertyKey); |
||||||
|
self.emit_opcode(Opcode::Dup); |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
self.emit_opcode(Opcode::SetFunctionName); |
||||||
|
self.emit_u8(2); |
||||||
|
self.emit_opcode(Opcode::SetPropertySetterByValue); |
||||||
|
} |
||||||
|
}, |
||||||
|
MethodDefinition::Ordinary(expr) => match name { |
||||||
|
PropertyName::Literal(name) => { |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::DefineOwnPropertyByName, &[index]); |
||||||
|
} |
||||||
|
PropertyName::Computed(name_node) => { |
||||||
|
self.compile_expr(name_node, true)?; |
||||||
|
self.emit_opcode(Opcode::ToPropertyKey); |
||||||
|
self.emit_opcode(Opcode::Dup); |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
self.emit_opcode(Opcode::SetFunctionName); |
||||||
|
self.emit_u8(0); |
||||||
|
self.emit_opcode(Opcode::DefineOwnPropertyByValue); |
||||||
|
} |
||||||
|
}, |
||||||
|
MethodDefinition::Async(expr) => match name { |
||||||
|
PropertyName::Literal(name) => { |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::DefineOwnPropertyByName, &[index]); |
||||||
|
} |
||||||
|
PropertyName::Computed(name_node) => { |
||||||
|
self.compile_expr(name_node, true)?; |
||||||
|
self.emit_opcode(Opcode::ToPropertyKey); |
||||||
|
self.emit_opcode(Opcode::Dup); |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
self.emit_opcode(Opcode::SetFunctionName); |
||||||
|
self.emit_u8(0); |
||||||
|
self.emit_opcode(Opcode::DefineOwnPropertyByValue); |
||||||
|
} |
||||||
|
}, |
||||||
|
MethodDefinition::Generator(expr) => match name { |
||||||
|
PropertyName::Literal(name) => { |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::DefineOwnPropertyByName, &[index]); |
||||||
|
} |
||||||
|
PropertyName::Computed(name_node) => { |
||||||
|
self.compile_expr(name_node, true)?; |
||||||
|
self.emit_opcode(Opcode::ToPropertyKey); |
||||||
|
self.emit_opcode(Opcode::Dup); |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
self.emit_opcode(Opcode::SetFunctionName); |
||||||
|
self.emit_u8(0); |
||||||
|
self.emit_opcode(Opcode::DefineOwnPropertyByValue); |
||||||
|
} |
||||||
|
}, |
||||||
|
MethodDefinition::AsyncGenerator(expr) => match name { |
||||||
|
PropertyName::Literal(name) => { |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
let index = self.get_or_insert_name((*name).into()); |
||||||
|
self.emit(Opcode::DefineOwnPropertyByName, &[index]); |
||||||
|
} |
||||||
|
PropertyName::Computed(name_node) => { |
||||||
|
self.compile_expr(name_node, true)?; |
||||||
|
self.emit_opcode(Opcode::ToPropertyKey); |
||||||
|
self.emit_opcode(Opcode::Dup); |
||||||
|
self.function(expr.into(), NodeKind::Expression, true)?; |
||||||
|
self.emit_opcode(Opcode::SetFunctionName); |
||||||
|
self.emit_u8(0); |
||||||
|
self.emit_opcode(Opcode::DefineOwnPropertyByValue); |
||||||
|
} |
||||||
|
}, |
||||||
|
}, |
||||||
|
PropertyDefinition::SpreadObject(expr) => { |
||||||
|
self.compile_expr(expr, true)?; |
||||||
|
self.emit_opcode(Opcode::Swap); |
||||||
|
self.emit(Opcode::CopyDataProperties, &[0, 0]); |
||||||
|
self.emit_opcode(Opcode::Pop); |
||||||
|
} |
||||||
|
// TODO: Promote to early errors
|
||||||
|
PropertyDefinition::CoverInitializedName(_, _) => { |
||||||
|
return Err(JsNativeError::syntax() |
||||||
|
.with_message("invalid assignment pattern in object literal") |
||||||
|
.into()) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if !use_expr { |
||||||
|
self.emit(Opcode::Pop, &[]); |
||||||
|
} |
||||||
|
|
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,113 @@ |
|||||||
|
use boa_ast::{ |
||||||
|
expression::operator::{unary::UnaryOp, Unary}, |
||||||
|
Expression, |
||||||
|
}; |
||||||
|
|
||||||
|
use crate::{ |
||||||
|
bytecompiler::{Access, ByteCompiler}, |
||||||
|
vm::Opcode, |
||||||
|
JsNativeError, JsResult, |
||||||
|
}; |
||||||
|
|
||||||
|
impl ByteCompiler<'_> { |
||||||
|
pub(crate) fn compile_unary(&mut self, unary: &Unary, use_expr: bool) -> JsResult<()> { |
||||||
|
let opcode = match unary.op() { |
||||||
|
UnaryOp::IncrementPre => { |
||||||
|
// TODO: promote to an early error.
|
||||||
|
let access = Access::from_expression(unary.target()).ok_or_else(|| { |
||||||
|
JsNativeError::syntax().with_message("Invalid increment operand") |
||||||
|
})?; |
||||||
|
|
||||||
|
self.access_set(access, true, |compiler, _| { |
||||||
|
compiler.compile_expr(unary.target(), true)?; |
||||||
|
compiler.emit(Opcode::Inc, &[]); |
||||||
|
Ok(()) |
||||||
|
})?; |
||||||
|
|
||||||
|
None |
||||||
|
} |
||||||
|
UnaryOp::DecrementPre => { |
||||||
|
// TODO: promote to an early error.
|
||||||
|
let access = Access::from_expression(unary.target()).ok_or_else(|| { |
||||||
|
JsNativeError::syntax().with_message("Invalid decrement operand") |
||||||
|
})?; |
||||||
|
|
||||||
|
self.access_set(access, true, |compiler, _| { |
||||||
|
compiler.compile_expr(unary.target(), true)?; |
||||||
|
compiler.emit(Opcode::Dec, &[]); |
||||||
|
Ok(()) |
||||||
|
})?; |
||||||
|
None |
||||||
|
} |
||||||
|
UnaryOp::IncrementPost => { |
||||||
|
// TODO: promote to an early error.
|
||||||
|
let access = Access::from_expression(unary.target()).ok_or_else(|| { |
||||||
|
JsNativeError::syntax().with_message("Invalid increment operand") |
||||||
|
})?; |
||||||
|
|
||||||
|
self.access_set(access, false, |compiler, level| { |
||||||
|
compiler.compile_expr(unary.target(), true)?; |
||||||
|
compiler.emit(Opcode::IncPost, &[]); |
||||||
|
compiler.emit_opcode(Opcode::RotateRight); |
||||||
|
compiler.emit_u8(level + 2); |
||||||
|
Ok(()) |
||||||
|
})?; |
||||||
|
|
||||||
|
None |
||||||
|
} |
||||||
|
UnaryOp::DecrementPost => { |
||||||
|
// TODO: promote to an early error.
|
||||||
|
let access = Access::from_expression(unary.target()).ok_or_else(|| { |
||||||
|
JsNativeError::syntax().with_message("Invalid decrement operand") |
||||||
|
})?; |
||||||
|
|
||||||
|
self.access_set(access, false, |compiler, level| { |
||||||
|
compiler.compile_expr(unary.target(), true)?; |
||||||
|
compiler.emit(Opcode::DecPost, &[]); |
||||||
|
compiler.emit_opcode(Opcode::RotateRight); |
||||||
|
compiler.emit_u8(level + 2); |
||||||
|
Ok(()) |
||||||
|
})?; |
||||||
|
|
||||||
|
None |
||||||
|
} |
||||||
|
UnaryOp::Delete => { |
||||||
|
if let Some(access) = Access::from_expression(unary.target()) { |
||||||
|
self.access_delete(access)?; |
||||||
|
} else { |
||||||
|
self.compile_expr(unary.target(), false)?; |
||||||
|
self.emit(Opcode::PushTrue, &[]); |
||||||
|
} |
||||||
|
None |
||||||
|
} |
||||||
|
UnaryOp::Minus => Some(Opcode::Neg), |
||||||
|
UnaryOp::Plus => Some(Opcode::Pos), |
||||||
|
UnaryOp::Not => Some(Opcode::LogicalNot), |
||||||
|
UnaryOp::Tilde => Some(Opcode::BitNot), |
||||||
|
UnaryOp::TypeOf => { |
||||||
|
match &unary.target() { |
||||||
|
Expression::Identifier(identifier) => { |
||||||
|
let binding = self.context.get_binding_value(*identifier); |
||||||
|
let index = self.get_or_insert_binding(binding); |
||||||
|
self.emit(Opcode::GetNameOrUndefined, &[index]); |
||||||
|
} |
||||||
|
expr => self.compile_expr(expr, true)?, |
||||||
|
} |
||||||
|
self.emit_opcode(Opcode::TypeOf); |
||||||
|
None |
||||||
|
} |
||||||
|
UnaryOp::Void => Some(Opcode::Void), |
||||||
|
}; |
||||||
|
|
||||||
|
if let Some(opcode) = opcode { |
||||||
|
self.compile_expr(unary.target(), true)?; |
||||||
|
self.emit(opcode, &[]); |
||||||
|
} |
||||||
|
|
||||||
|
if !use_expr { |
||||||
|
self.emit(Opcode::Pop, &[]); |
||||||
|
} |
||||||
|
|
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,96 @@ |
|||||||
|
use boa_ast::statement::Continue; |
||||||
|
|
||||||
|
use crate::{ |
||||||
|
bytecompiler::{ByteCompiler, JumpControlInfoKind}, |
||||||
|
vm::Opcode, |
||||||
|
JsNativeError, JsResult, |
||||||
|
}; |
||||||
|
|
||||||
|
impl ByteCompiler<'_> { |
||||||
|
pub(crate) fn compile_continue(&mut self, node: Continue) -> JsResult<()> { |
||||||
|
let next = self.next_opcode_location(); |
||||||
|
if let Some(info) = self |
||||||
|
.jump_info |
||||||
|
.last() |
||||||
|
.filter(|info| info.kind == JumpControlInfoKind::Try) |
||||||
|
{ |
||||||
|
let start_address = info.start_address; |
||||||
|
let in_finally = if let Some(finally_start) = info.finally_start { |
||||||
|
next > finally_start.index |
||||||
|
} else { |
||||||
|
false |
||||||
|
}; |
||||||
|
let in_catch_no_finally = !info.has_finally && info.in_catch; |
||||||
|
|
||||||
|
if in_finally { |
||||||
|
self.emit_opcode(Opcode::PopIfThrown); |
||||||
|
} |
||||||
|
if in_finally || in_catch_no_finally { |
||||||
|
self.emit_opcode(Opcode::CatchEnd2); |
||||||
|
} else { |
||||||
|
self.emit_opcode(Opcode::TryEnd); |
||||||
|
} |
||||||
|
|
||||||
|
self.emit(Opcode::FinallySetJump, &[start_address]); |
||||||
|
|
||||||
|
let label = self.jump(); |
||||||
|
self.jump_info |
||||||
|
.last_mut() |
||||||
|
.expect("no jump information found") |
||||||
|
.try_continues |
||||||
|
.push(label); |
||||||
|
} else { |
||||||
|
let mut items = self |
||||||
|
.jump_info |
||||||
|
.iter() |
||||||
|
.rev() |
||||||
|
.filter(|info| info.kind == JumpControlInfoKind::Loop); |
||||||
|
let address = if let Some(label_name) = node.label() { |
||||||
|
let mut num_loops = 0; |
||||||
|
let mut emit_for_of_in_exit = 0; |
||||||
|
let mut address_info = None; |
||||||
|
for info in items { |
||||||
|
if info.label == node.label() { |
||||||
|
address_info = Some(info); |
||||||
|
break; |
||||||
|
} |
||||||
|
num_loops += 1; |
||||||
|
if info.for_of_in_loop { |
||||||
|
emit_for_of_in_exit += 1; |
||||||
|
} |
||||||
|
} |
||||||
|
// TODO: promote to an early error.
|
||||||
|
let address = address_info |
||||||
|
.ok_or_else(|| { |
||||||
|
JsNativeError::syntax().with_message(format!( |
||||||
|
"Cannot use the undeclared label '{}'", |
||||||
|
self.context.interner().resolve_expect(label_name) |
||||||
|
)) |
||||||
|
})? |
||||||
|
.start_address; |
||||||
|
for _ in 0..emit_for_of_in_exit { |
||||||
|
self.emit_opcode(Opcode::Pop); |
||||||
|
self.emit_opcode(Opcode::Pop); |
||||||
|
self.emit_opcode(Opcode::Pop); |
||||||
|
} |
||||||
|
for _ in 0..num_loops { |
||||||
|
self.emit_opcode(Opcode::LoopEnd); |
||||||
|
} |
||||||
|
address |
||||||
|
} else { |
||||||
|
items |
||||||
|
.next() |
||||||
|
// TODO: promote to an early error.
|
||||||
|
.ok_or_else(|| { |
||||||
|
JsNativeError::syntax().with_message("continue must be inside loop") |
||||||
|
})? |
||||||
|
.start_address |
||||||
|
}; |
||||||
|
self.emit_opcode(Opcode::LoopEnd); |
||||||
|
self.emit_opcode(Opcode::LoopStart); |
||||||
|
self.emit(Opcode::Jump, &[address]); |
||||||
|
} |
||||||
|
|
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,363 @@ |
|||||||
|
use boa_ast::{ |
||||||
|
declaration::Binding, |
||||||
|
operations::bound_names, |
||||||
|
statement::{ |
||||||
|
iteration::{ForLoopInitializer, IterableLoopInitializer}, |
||||||
|
DoWhileLoop, ForInLoop, ForLoop, ForOfLoop, WhileLoop, |
||||||
|
}, |
||||||
|
}; |
||||||
|
use boa_interner::Sym; |
||||||
|
|
||||||
|
use crate::{ |
||||||
|
bytecompiler::{Access, ByteCompiler}, |
||||||
|
vm::{BindingOpcode, Opcode}, |
||||||
|
JsResult, |
||||||
|
}; |
||||||
|
|
||||||
|
impl ByteCompiler<'_> { |
||||||
|
pub(crate) fn compile_for_loop( |
||||||
|
&mut self, |
||||||
|
for_loop: &ForLoop, |
||||||
|
label: Option<Sym>, |
||||||
|
configurable_globals: bool, |
||||||
|
) -> JsResult<()> { |
||||||
|
self.context.push_compile_time_environment(false); |
||||||
|
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); |
||||||
|
|
||||||
|
if let Some(init) = for_loop.init() { |
||||||
|
match init { |
||||||
|
ForLoopInitializer::Expression(expr) => self.compile_expr(expr, false)?, |
||||||
|
ForLoopInitializer::Var(decl) => { |
||||||
|
self.create_decls_from_var_decl(decl, configurable_globals); |
||||||
|
self.compile_var_decl(decl)?; |
||||||
|
} |
||||||
|
ForLoopInitializer::Lexical(decl) => { |
||||||
|
self.create_decls_from_lexical_decl(decl); |
||||||
|
self.compile_lexical_decl(decl)?; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
self.emit_opcode(Opcode::LoopStart); |
||||||
|
let initial_jump = self.jump(); |
||||||
|
|
||||||
|
let start_address = self.next_opcode_location(); |
||||||
|
self.push_loop_control_info(label, start_address); |
||||||
|
|
||||||
|
self.emit_opcode(Opcode::LoopContinue); |
||||||
|
if let Some(final_expr) = for_loop.final_expr() { |
||||||
|
self.compile_expr(final_expr, false)?; |
||||||
|
} |
||||||
|
|
||||||
|
self.patch_jump(initial_jump); |
||||||
|
|
||||||
|
if let Some(condition) = for_loop.condition() { |
||||||
|
self.compile_expr(condition, true)?; |
||||||
|
} else { |
||||||
|
self.emit_opcode(Opcode::PushTrue); |
||||||
|
} |
||||||
|
let exit = self.jump_if_false(); |
||||||
|
|
||||||
|
self.compile_stmt(for_loop.body(), false, configurable_globals)?; |
||||||
|
|
||||||
|
self.emit(Opcode::Jump, &[start_address]); |
||||||
|
|
||||||
|
self.patch_jump(exit); |
||||||
|
self.pop_loop_control_info(); |
||||||
|
self.emit_opcode(Opcode::LoopEnd); |
||||||
|
|
||||||
|
let (num_bindings, compile_environment) = self.context.pop_compile_time_environment(); |
||||||
|
let index_compile_environment = self.push_compile_environment(compile_environment); |
||||||
|
self.patch_jump_with_target(push_env.0, num_bindings as u32); |
||||||
|
self.patch_jump_with_target(push_env.1, index_compile_environment as u32); |
||||||
|
self.emit_opcode(Opcode::PopEnvironment); |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn compile_for_in_loop( |
||||||
|
&mut self, |
||||||
|
for_in_loop: &ForInLoop, |
||||||
|
label: Option<Sym>, |
||||||
|
configurable_globals: bool, |
||||||
|
) -> JsResult<()> { |
||||||
|
let init_bound_names = bound_names(for_in_loop.initializer()); |
||||||
|
if init_bound_names.is_empty() { |
||||||
|
self.compile_expr(for_in_loop.target(), true)?; |
||||||
|
} else { |
||||||
|
self.context.push_compile_time_environment(false); |
||||||
|
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); |
||||||
|
|
||||||
|
for name in init_bound_names { |
||||||
|
self.context.create_mutable_binding(name, false, false); |
||||||
|
} |
||||||
|
self.compile_expr(for_in_loop.target(), true)?; |
||||||
|
|
||||||
|
let (num_bindings, compile_environment) = self.context.pop_compile_time_environment(); |
||||||
|
let index_compile_environment = self.push_compile_environment(compile_environment); |
||||||
|
self.patch_jump_with_target(push_env.0, num_bindings as u32); |
||||||
|
self.patch_jump_with_target(push_env.1, index_compile_environment as u32); |
||||||
|
self.emit_opcode(Opcode::PopEnvironment); |
||||||
|
} |
||||||
|
|
||||||
|
let early_exit = self.emit_opcode_with_operand(Opcode::ForInLoopInitIterator); |
||||||
|
|
||||||
|
self.emit_opcode(Opcode::LoopStart); |
||||||
|
let start_address = self.next_opcode_location(); |
||||||
|
self.push_loop_control_info_for_of_in_loop(label, start_address); |
||||||
|
self.emit_opcode(Opcode::LoopContinue); |
||||||
|
|
||||||
|
self.context.push_compile_time_environment(false); |
||||||
|
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); |
||||||
|
let exit = self.emit_opcode_with_operand(Opcode::ForInLoopNext); |
||||||
|
|
||||||
|
match for_in_loop.initializer() { |
||||||
|
IterableLoopInitializer::Identifier(ident) => { |
||||||
|
self.context.create_mutable_binding(*ident, true, true); |
||||||
|
let binding = self.context.set_mutable_binding(*ident); |
||||||
|
let index = self.get_or_insert_binding(binding); |
||||||
|
self.emit(Opcode::DefInitVar, &[index]); |
||||||
|
} |
||||||
|
IterableLoopInitializer::Access(access) => { |
||||||
|
self.access_set( |
||||||
|
Access::Property { access }, |
||||||
|
false, |
||||||
|
ByteCompiler::access_set_top_of_stack_expr_fn, |
||||||
|
)?; |
||||||
|
} |
||||||
|
IterableLoopInitializer::Var(declaration) => match declaration { |
||||||
|
Binding::Identifier(ident) => { |
||||||
|
self.context |
||||||
|
.create_mutable_binding(*ident, true, configurable_globals); |
||||||
|
self.emit_binding(BindingOpcode::InitVar, *ident); |
||||||
|
} |
||||||
|
Binding::Pattern(pattern) => { |
||||||
|
for ident in bound_names(pattern) { |
||||||
|
self.context.create_mutable_binding(ident, true, false); |
||||||
|
} |
||||||
|
self.compile_declaration_pattern(pattern, BindingOpcode::InitVar)?; |
||||||
|
} |
||||||
|
}, |
||||||
|
IterableLoopInitializer::Let(declaration) => match declaration { |
||||||
|
Binding::Identifier(ident) => { |
||||||
|
self.context.create_mutable_binding(*ident, false, false); |
||||||
|
self.emit_binding(BindingOpcode::InitLet, *ident); |
||||||
|
} |
||||||
|
Binding::Pattern(pattern) => { |
||||||
|
for ident in bound_names(pattern) { |
||||||
|
self.context.create_mutable_binding(ident, false, false); |
||||||
|
} |
||||||
|
self.compile_declaration_pattern(pattern, BindingOpcode::InitLet)?; |
||||||
|
} |
||||||
|
}, |
||||||
|
IterableLoopInitializer::Const(declaration) => match declaration { |
||||||
|
Binding::Identifier(ident) => { |
||||||
|
self.context.create_immutable_binding(*ident, true); |
||||||
|
self.emit_binding(BindingOpcode::InitConst, *ident); |
||||||
|
} |
||||||
|
Binding::Pattern(pattern) => { |
||||||
|
for ident in bound_names(pattern) { |
||||||
|
self.context.create_immutable_binding(ident, true); |
||||||
|
} |
||||||
|
self.compile_declaration_pattern(pattern, BindingOpcode::InitConst)?; |
||||||
|
} |
||||||
|
}, |
||||||
|
IterableLoopInitializer::Pattern(pattern) => { |
||||||
|
for ident in bound_names(pattern) { |
||||||
|
self.context.create_mutable_binding(ident, true, true); |
||||||
|
} |
||||||
|
self.compile_declaration_pattern(pattern, BindingOpcode::InitVar)?; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
self.compile_stmt(for_in_loop.body(), false, configurable_globals)?; |
||||||
|
|
||||||
|
let (num_bindings, compile_environment) = self.context.pop_compile_time_environment(); |
||||||
|
let index_compile_environment = self.push_compile_environment(compile_environment); |
||||||
|
self.patch_jump_with_target(push_env.0, num_bindings as u32); |
||||||
|
self.patch_jump_with_target(push_env.1, index_compile_environment as u32); |
||||||
|
self.emit_opcode(Opcode::PopEnvironment); |
||||||
|
|
||||||
|
self.emit(Opcode::Jump, &[start_address]); |
||||||
|
|
||||||
|
self.patch_jump(exit); |
||||||
|
self.pop_loop_control_info(); |
||||||
|
self.emit_opcode(Opcode::LoopEnd); |
||||||
|
self.emit_opcode(Opcode::IteratorClose); |
||||||
|
|
||||||
|
self.patch_jump(early_exit); |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn compile_for_of_loop( |
||||||
|
&mut self, |
||||||
|
for_of_loop: &ForOfLoop, |
||||||
|
label: Option<Sym>, |
||||||
|
configurable_globals: bool, |
||||||
|
) -> JsResult<()> { |
||||||
|
let init_bound_names = bound_names(for_of_loop.initializer()); |
||||||
|
if init_bound_names.is_empty() { |
||||||
|
self.compile_expr(for_of_loop.iterable(), true)?; |
||||||
|
} else { |
||||||
|
self.context.push_compile_time_environment(false); |
||||||
|
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); |
||||||
|
|
||||||
|
for name in init_bound_names { |
||||||
|
self.context.create_mutable_binding(name, false, false); |
||||||
|
} |
||||||
|
self.compile_expr(for_of_loop.iterable(), true)?; |
||||||
|
|
||||||
|
let (num_bindings, compile_environment) = self.context.pop_compile_time_environment(); |
||||||
|
let index_compile_environment = self.push_compile_environment(compile_environment); |
||||||
|
self.patch_jump_with_target(push_env.0, num_bindings as u32); |
||||||
|
self.patch_jump_with_target(push_env.1, index_compile_environment as u32); |
||||||
|
self.emit_opcode(Opcode::PopEnvironment); |
||||||
|
} |
||||||
|
|
||||||
|
if for_of_loop.r#await() { |
||||||
|
self.emit_opcode(Opcode::InitIteratorAsync); |
||||||
|
} else { |
||||||
|
self.emit_opcode(Opcode::InitIterator); |
||||||
|
} |
||||||
|
|
||||||
|
self.emit_opcode(Opcode::LoopStart); |
||||||
|
let start_address = self.next_opcode_location(); |
||||||
|
self.push_loop_control_info_for_of_in_loop(label, start_address); |
||||||
|
self.emit_opcode(Opcode::LoopContinue); |
||||||
|
|
||||||
|
self.context.push_compile_time_environment(false); |
||||||
|
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); |
||||||
|
|
||||||
|
let exit = if for_of_loop.r#await() { |
||||||
|
self.emit_opcode(Opcode::ForAwaitOfLoopIterate); |
||||||
|
self.emit_opcode(Opcode::Await); |
||||||
|
self.emit_opcode(Opcode::GeneratorNext); |
||||||
|
self.emit_opcode_with_operand(Opcode::ForAwaitOfLoopNext) |
||||||
|
} else { |
||||||
|
self.emit_opcode_with_operand(Opcode::ForInLoopNext) |
||||||
|
}; |
||||||
|
|
||||||
|
match for_of_loop.initializer() { |
||||||
|
IterableLoopInitializer::Identifier(ref ident) => { |
||||||
|
self.context.create_mutable_binding(*ident, true, true); |
||||||
|
let binding = self.context.set_mutable_binding(*ident); |
||||||
|
let index = self.get_or_insert_binding(binding); |
||||||
|
self.emit(Opcode::DefInitVar, &[index]); |
||||||
|
} |
||||||
|
IterableLoopInitializer::Access(access) => { |
||||||
|
self.access_set( |
||||||
|
Access::Property { access }, |
||||||
|
false, |
||||||
|
ByteCompiler::access_set_top_of_stack_expr_fn, |
||||||
|
)?; |
||||||
|
} |
||||||
|
IterableLoopInitializer::Var(declaration) => match declaration { |
||||||
|
Binding::Identifier(ident) => { |
||||||
|
self.context.create_mutable_binding(*ident, true, false); |
||||||
|
self.emit_binding(BindingOpcode::InitVar, *ident); |
||||||
|
} |
||||||
|
Binding::Pattern(pattern) => { |
||||||
|
for ident in bound_names(pattern) { |
||||||
|
self.context.create_mutable_binding(ident, true, false); |
||||||
|
} |
||||||
|
self.compile_declaration_pattern(pattern, BindingOpcode::InitVar)?; |
||||||
|
} |
||||||
|
}, |
||||||
|
IterableLoopInitializer::Let(declaration) => match declaration { |
||||||
|
Binding::Identifier(ident) => { |
||||||
|
self.context.create_mutable_binding(*ident, false, false); |
||||||
|
self.emit_binding(BindingOpcode::InitLet, *ident); |
||||||
|
} |
||||||
|
Binding::Pattern(pattern) => { |
||||||
|
for ident in bound_names(pattern) { |
||||||
|
self.context.create_mutable_binding(ident, false, false); |
||||||
|
} |
||||||
|
self.compile_declaration_pattern(pattern, BindingOpcode::InitLet)?; |
||||||
|
} |
||||||
|
}, |
||||||
|
IterableLoopInitializer::Const(declaration) => match declaration { |
||||||
|
Binding::Identifier(ident) => { |
||||||
|
self.context.create_immutable_binding(*ident, true); |
||||||
|
self.emit_binding(BindingOpcode::InitConst, *ident); |
||||||
|
} |
||||||
|
Binding::Pattern(pattern) => { |
||||||
|
for ident in bound_names(pattern) { |
||||||
|
self.context.create_immutable_binding(ident, true); |
||||||
|
} |
||||||
|
self.compile_declaration_pattern(pattern, BindingOpcode::InitConst)?; |
||||||
|
} |
||||||
|
}, |
||||||
|
IterableLoopInitializer::Pattern(pattern) => { |
||||||
|
for ident in bound_names(pattern) { |
||||||
|
self.context.create_mutable_binding(ident, true, true); |
||||||
|
} |
||||||
|
self.compile_declaration_pattern(pattern, BindingOpcode::InitVar)?; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
self.compile_stmt(for_of_loop.body(), false, configurable_globals)?; |
||||||
|
|
||||||
|
let (num_bindings, compile_environment) = self.context.pop_compile_time_environment(); |
||||||
|
let index_compile_environment = self.push_compile_environment(compile_environment); |
||||||
|
self.patch_jump_with_target(push_env.0, num_bindings as u32); |
||||||
|
self.patch_jump_with_target(push_env.1, index_compile_environment as u32); |
||||||
|
self.emit_opcode(Opcode::PopEnvironment); |
||||||
|
|
||||||
|
self.emit(Opcode::Jump, &[start_address]); |
||||||
|
|
||||||
|
self.patch_jump(exit); |
||||||
|
self.pop_loop_control_info(); |
||||||
|
self.emit_opcode(Opcode::LoopEnd); |
||||||
|
self.emit_opcode(Opcode::IteratorClose); |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn compile_while_loop( |
||||||
|
&mut self, |
||||||
|
while_loop: &WhileLoop, |
||||||
|
label: Option<Sym>, |
||||||
|
configurable_globals: bool, |
||||||
|
) -> JsResult<()> { |
||||||
|
self.emit_opcode(Opcode::LoopStart); |
||||||
|
let start_address = self.next_opcode_location(); |
||||||
|
self.push_loop_control_info(label, start_address); |
||||||
|
self.emit_opcode(Opcode::LoopContinue); |
||||||
|
|
||||||
|
self.compile_expr(while_loop.condition(), true)?; |
||||||
|
let exit = self.jump_if_false(); |
||||||
|
self.compile_stmt(while_loop.body(), false, configurable_globals)?; |
||||||
|
self.emit(Opcode::Jump, &[start_address]); |
||||||
|
self.patch_jump(exit); |
||||||
|
|
||||||
|
self.pop_loop_control_info(); |
||||||
|
self.emit_opcode(Opcode::LoopEnd); |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn compile_do_while_loop( |
||||||
|
&mut self, |
||||||
|
do_while_loop: &DoWhileLoop, |
||||||
|
label: Option<Sym>, |
||||||
|
configurable_globals: bool, |
||||||
|
) -> JsResult<()> { |
||||||
|
self.emit_opcode(Opcode::LoopStart); |
||||||
|
let initial_label = self.jump(); |
||||||
|
|
||||||
|
let start_address = self.next_opcode_location(); |
||||||
|
self.push_loop_control_info(label, start_address); |
||||||
|
self.emit_opcode(Opcode::LoopContinue); |
||||||
|
|
||||||
|
let condition_label_address = self.next_opcode_location(); |
||||||
|
self.compile_expr(do_while_loop.cond(), true)?; |
||||||
|
let exit = self.jump_if_false(); |
||||||
|
|
||||||
|
self.patch_jump(initial_label); |
||||||
|
|
||||||
|
self.compile_stmt(do_while_loop.body(), false, configurable_globals)?; |
||||||
|
self.emit(Opcode::Jump, &[condition_label_address]); |
||||||
|
self.patch_jump(exit); |
||||||
|
|
||||||
|
self.pop_loop_control_info(); |
||||||
|
self.emit_opcode(Opcode::LoopEnd); |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,228 @@ |
|||||||
|
use boa_ast::{ |
||||||
|
statement::{Block, Break, If, Labelled, LabelledItem, Switch}, |
||||||
|
Statement, |
||||||
|
}; |
||||||
|
use boa_interner::Sym; |
||||||
|
|
||||||
|
use crate::{vm::Opcode, JsNativeError, JsResult}; |
||||||
|
|
||||||
|
use super::{ByteCompiler, JumpControlInfoKind, NodeKind}; |
||||||
|
|
||||||
|
mod r#continue; |
||||||
|
mod r#loop; |
||||||
|
mod r#try; |
||||||
|
|
||||||
|
impl ByteCompiler<'_> { |
||||||
|
pub(crate) fn compile_if(&mut self, node: &If, configurable_globals: bool) -> JsResult<()> { |
||||||
|
self.compile_expr(node.cond(), true)?; |
||||||
|
let jelse = self.jump_if_false(); |
||||||
|
|
||||||
|
self.compile_stmt(node.body(), false, configurable_globals)?; |
||||||
|
|
||||||
|
match node.else_node() { |
||||||
|
None => { |
||||||
|
self.patch_jump(jelse); |
||||||
|
} |
||||||
|
Some(else_body) => { |
||||||
|
let exit = self.jump(); |
||||||
|
self.patch_jump(jelse); |
||||||
|
self.compile_stmt(else_body, false, configurable_globals)?; |
||||||
|
self.patch_jump(exit); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn compile_labelled( |
||||||
|
&mut self, |
||||||
|
labelled: &Labelled, |
||||||
|
use_expr: bool, |
||||||
|
configurable_globals: bool, |
||||||
|
) -> JsResult<()> { |
||||||
|
match labelled.item() { |
||||||
|
LabelledItem::Statement(stmt) => match stmt { |
||||||
|
Statement::ForLoop(for_loop) => { |
||||||
|
self.compile_for_loop(for_loop, Some(labelled.label()), configurable_globals)?; |
||||||
|
} |
||||||
|
Statement::ForInLoop(for_in_loop) => { |
||||||
|
self.compile_for_in_loop( |
||||||
|
for_in_loop, |
||||||
|
Some(labelled.label()), |
||||||
|
configurable_globals, |
||||||
|
)?; |
||||||
|
} |
||||||
|
Statement::ForOfLoop(for_of_loop) => { |
||||||
|
self.compile_for_of_loop( |
||||||
|
for_of_loop, |
||||||
|
Some(labelled.label()), |
||||||
|
configurable_globals, |
||||||
|
)?; |
||||||
|
} |
||||||
|
Statement::WhileLoop(while_loop) => { |
||||||
|
self.compile_while_loop( |
||||||
|
while_loop, |
||||||
|
Some(labelled.label()), |
||||||
|
configurable_globals, |
||||||
|
)?; |
||||||
|
} |
||||||
|
Statement::DoWhileLoop(do_while_loop) => { |
||||||
|
self.compile_do_while_loop( |
||||||
|
do_while_loop, |
||||||
|
Some(labelled.label()), |
||||||
|
configurable_globals, |
||||||
|
)?; |
||||||
|
} |
||||||
|
Statement::Block(block) => { |
||||||
|
self.compile_block( |
||||||
|
block, |
||||||
|
Some(labelled.label()), |
||||||
|
use_expr, |
||||||
|
configurable_globals, |
||||||
|
)?; |
||||||
|
} |
||||||
|
stmt => self.compile_stmt(stmt, use_expr, configurable_globals)?, |
||||||
|
}, |
||||||
|
LabelledItem::Function(f) => { |
||||||
|
self.function(f.into(), NodeKind::Declaration, false)?; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn compile_break(&mut self, node: Break) -> JsResult<()> { |
||||||
|
let next = self.next_opcode_location(); |
||||||
|
if let Some(info) = self |
||||||
|
.jump_info |
||||||
|
.last() |
||||||
|
.filter(|info| info.kind == JumpControlInfoKind::Try) |
||||||
|
{ |
||||||
|
let in_finally = if let Some(finally_start) = info.finally_start { |
||||||
|
next >= finally_start.index |
||||||
|
} else { |
||||||
|
false |
||||||
|
}; |
||||||
|
let in_catch_no_finally = !info.has_finally && info.in_catch; |
||||||
|
|
||||||
|
if in_finally { |
||||||
|
self.emit_opcode(Opcode::PopIfThrown); |
||||||
|
} |
||||||
|
if in_finally || in_catch_no_finally { |
||||||
|
self.emit_opcode(Opcode::CatchEnd2); |
||||||
|
} else { |
||||||
|
self.emit_opcode(Opcode::TryEnd); |
||||||
|
} |
||||||
|
self.emit(Opcode::FinallySetJump, &[u32::MAX]); |
||||||
|
} |
||||||
|
let label = self.jump(); |
||||||
|
if let Some(label_name) = node.label() { |
||||||
|
let mut found = false; |
||||||
|
for info in self.jump_info.iter_mut().rev() { |
||||||
|
if info.label == Some(label_name) { |
||||||
|
info.breaks.push(label); |
||||||
|
found = true; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
// TODO: promote to an early error.
|
||||||
|
if !found { |
||||||
|
return Err(JsNativeError::syntax() |
||||||
|
.with_message(format!( |
||||||
|
"Cannot use the undeclared label '{}'", |
||||||
|
self.interner().resolve_expect(label_name) |
||||||
|
)) |
||||||
|
.into()); |
||||||
|
} |
||||||
|
} else { |
||||||
|
self.jump_info |
||||||
|
.last_mut() |
||||||
|
// TODO: promote to an early error.
|
||||||
|
.ok_or_else(|| { |
||||||
|
JsNativeError::syntax() |
||||||
|
.with_message("unlabeled break must be inside loop or switch") |
||||||
|
})? |
||||||
|
.breaks |
||||||
|
.push(label); |
||||||
|
} |
||||||
|
|
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn compile_switch( |
||||||
|
&mut self, |
||||||
|
switch: &Switch, |
||||||
|
configurable_globals: bool, |
||||||
|
) -> JsResult<()> { |
||||||
|
self.context.push_compile_time_environment(false); |
||||||
|
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); |
||||||
|
for case in switch.cases() { |
||||||
|
self.create_decls(case.body(), configurable_globals); |
||||||
|
} |
||||||
|
self.emit_opcode(Opcode::LoopStart); |
||||||
|
|
||||||
|
let start_address = self.next_opcode_location(); |
||||||
|
self.push_switch_control_info(None, start_address); |
||||||
|
|
||||||
|
self.compile_expr(switch.val(), true)?; |
||||||
|
let mut labels = Vec::with_capacity(switch.cases().len()); |
||||||
|
for case in switch.cases() { |
||||||
|
self.compile_expr(case.condition(), true)?; |
||||||
|
labels.push(self.emit_opcode_with_operand(Opcode::Case)); |
||||||
|
} |
||||||
|
|
||||||
|
let exit = self.emit_opcode_with_operand(Opcode::Default); |
||||||
|
|
||||||
|
for (label, case) in labels.into_iter().zip(switch.cases()) { |
||||||
|
self.patch_jump(label); |
||||||
|
self.compile_statement_list(case.body(), false, configurable_globals)?; |
||||||
|
} |
||||||
|
|
||||||
|
self.patch_jump(exit); |
||||||
|
if let Some(body) = switch.default() { |
||||||
|
self.create_decls(body, configurable_globals); |
||||||
|
self.compile_statement_list(body, false, configurable_globals)?; |
||||||
|
} |
||||||
|
|
||||||
|
self.pop_switch_control_info(); |
||||||
|
|
||||||
|
self.emit_opcode(Opcode::LoopEnd); |
||||||
|
|
||||||
|
let (num_bindings, compile_environment) = self.context.pop_compile_time_environment(); |
||||||
|
let index_compile_environment = self.push_compile_environment(compile_environment); |
||||||
|
self.patch_jump_with_target(push_env.0, num_bindings as u32); |
||||||
|
self.patch_jump_with_target(push_env.1, index_compile_environment as u32); |
||||||
|
self.emit_opcode(Opcode::PopEnvironment); |
||||||
|
|
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn compile_block( |
||||||
|
&mut self, |
||||||
|
block: &Block, |
||||||
|
label: Option<Sym>, |
||||||
|
use_expr: bool, |
||||||
|
configurable_globals: bool, |
||||||
|
) -> JsResult<()> { |
||||||
|
if let Some(label) = label { |
||||||
|
let next = self.next_opcode_location(); |
||||||
|
self.push_labelled_block_control_info(label, next); |
||||||
|
} |
||||||
|
|
||||||
|
self.context.push_compile_time_environment(false); |
||||||
|
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); |
||||||
|
self.create_decls(block.statement_list(), configurable_globals); |
||||||
|
self.compile_statement_list(block.statement_list(), use_expr, configurable_globals)?; |
||||||
|
let (num_bindings, compile_environment) = self.context.pop_compile_time_environment(); |
||||||
|
let index_compile_environment = self.push_compile_environment(compile_environment); |
||||||
|
self.patch_jump_with_target(push_env.0, num_bindings as u32); |
||||||
|
self.patch_jump_with_target(push_env.1, index_compile_environment as u32); |
||||||
|
|
||||||
|
if label.is_some() { |
||||||
|
self.pop_labelled_block_control_info(); |
||||||
|
} |
||||||
|
|
||||||
|
self.emit_opcode(Opcode::PopEnvironment); |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,120 @@ |
|||||||
|
use boa_ast::{declaration::Binding, operations::bound_names, statement::Try}; |
||||||
|
|
||||||
|
use crate::{ |
||||||
|
bytecompiler::{ByteCompiler, Label}, |
||||||
|
vm::{BindingOpcode, Opcode}, |
||||||
|
JsResult, |
||||||
|
}; |
||||||
|
|
||||||
|
impl ByteCompiler<'_> { |
||||||
|
pub(crate) fn compile_try( |
||||||
|
&mut self, |
||||||
|
t: &Try, |
||||||
|
use_expr: bool, |
||||||
|
configurable_globals: bool, |
||||||
|
) -> JsResult<()> { |
||||||
|
self.push_try_control_info(t.finally().is_some()); |
||||||
|
let try_start = self.next_opcode_location(); |
||||||
|
self.emit(Opcode::TryStart, &[ByteCompiler::DUMMY_ADDRESS, 0]); |
||||||
|
self.context.push_compile_time_environment(false); |
||||||
|
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); |
||||||
|
|
||||||
|
self.create_decls(t.block().statement_list(), configurable_globals); |
||||||
|
self.compile_statement_list(t.block().statement_list(), use_expr, configurable_globals)?; |
||||||
|
|
||||||
|
let (num_bindings, compile_environment) = self.context.pop_compile_time_environment(); |
||||||
|
let index_compile_environment = self.push_compile_environment(compile_environment); |
||||||
|
self.patch_jump_with_target(push_env.0, num_bindings as u32); |
||||||
|
self.patch_jump_with_target(push_env.1, index_compile_environment as u32); |
||||||
|
self.emit_opcode(Opcode::PopEnvironment); |
||||||
|
self.emit_opcode(Opcode::TryEnd); |
||||||
|
|
||||||
|
let finally = self.jump(); |
||||||
|
self.patch_jump(Label { index: try_start }); |
||||||
|
|
||||||
|
if let Some(catch) = t.catch() { |
||||||
|
self.push_try_control_info_catch_start(); |
||||||
|
let catch_start = if t.finally().is_some() { |
||||||
|
Some(self.emit_opcode_with_operand(Opcode::CatchStart)) |
||||||
|
} else { |
||||||
|
None |
||||||
|
}; |
||||||
|
self.context.push_compile_time_environment(false); |
||||||
|
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); |
||||||
|
if let Some(binding) = catch.parameter() { |
||||||
|
match binding { |
||||||
|
Binding::Identifier(ident) => { |
||||||
|
self.context.create_mutable_binding(*ident, false, false); |
||||||
|
self.emit_binding(BindingOpcode::InitLet, *ident); |
||||||
|
} |
||||||
|
Binding::Pattern(pattern) => { |
||||||
|
for ident in bound_names(pattern) { |
||||||
|
self.context.create_mutable_binding(ident, false, false); |
||||||
|
} |
||||||
|
self.compile_declaration_pattern(pattern, BindingOpcode::InitLet)?; |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
self.emit_opcode(Opcode::Pop); |
||||||
|
} |
||||||
|
|
||||||
|
self.create_decls(catch.block().statement_list(), configurable_globals); |
||||||
|
self.compile_statement_list( |
||||||
|
catch.block().statement_list(), |
||||||
|
use_expr, |
||||||
|
configurable_globals, |
||||||
|
)?; |
||||||
|
|
||||||
|
let (num_bindings, compile_environment) = self.context.pop_compile_time_environment(); |
||||||
|
let index_compile_environment = self.push_compile_environment(compile_environment); |
||||||
|
self.patch_jump_with_target(push_env.0, num_bindings as u32); |
||||||
|
self.patch_jump_with_target(push_env.1, index_compile_environment as u32); |
||||||
|
self.emit_opcode(Opcode::PopEnvironment); |
||||||
|
if let Some(catch_start) = catch_start { |
||||||
|
self.emit_opcode(Opcode::CatchEnd); |
||||||
|
self.patch_jump(catch_start); |
||||||
|
} else { |
||||||
|
self.emit_opcode(Opcode::CatchEnd2); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
self.patch_jump(finally); |
||||||
|
|
||||||
|
if let Some(finally) = t.finally() { |
||||||
|
self.emit_opcode(Opcode::FinallyStart); |
||||||
|
let finally_start_address = self.next_opcode_location(); |
||||||
|
self.push_try_control_info_finally_start(Label { |
||||||
|
index: finally_start_address, |
||||||
|
}); |
||||||
|
self.patch_jump_with_target( |
||||||
|
Label { |
||||||
|
index: try_start + 4, |
||||||
|
}, |
||||||
|
finally_start_address, |
||||||
|
); |
||||||
|
|
||||||
|
self.context.push_compile_time_environment(false); |
||||||
|
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); |
||||||
|
|
||||||
|
self.create_decls(finally.block().statement_list(), configurable_globals); |
||||||
|
self.compile_statement_list( |
||||||
|
finally.block().statement_list(), |
||||||
|
false, |
||||||
|
configurable_globals, |
||||||
|
)?; |
||||||
|
|
||||||
|
let (num_bindings, compile_environment) = self.context.pop_compile_time_environment(); |
||||||
|
let index_compile_environment = self.push_compile_environment(compile_environment); |
||||||
|
self.patch_jump_with_target(push_env.0, num_bindings as u32); |
||||||
|
self.patch_jump_with_target(push_env.1, index_compile_environment as u32); |
||||||
|
self.emit_opcode(Opcode::PopEnvironment); |
||||||
|
|
||||||
|
self.emit_opcode(Opcode::FinallyEnd); |
||||||
|
self.pop_try_control_info(Some(finally_start_address)); |
||||||
|
} else { |
||||||
|
self.pop_try_control_info(None); |
||||||
|
} |
||||||
|
|
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue