diff --git a/boa/src/exec/array/mod.rs b/boa/src/exec/array/mod.rs deleted file mode 100644 index 3aa5831dd4..0000000000 --- a/boa/src/exec/array/mod.rs +++ /dev/null @@ -1,28 +0,0 @@ -//! Array declaration execution. - -use super::{Context, Executable}; -use crate::{ - builtins::Array, - syntax::ast::node::{ArrayDecl, Node}, - BoaProfiler, Result, Value, -}; - -impl Executable for ArrayDecl { - fn run(&self, interpreter: &mut Context) -> Result { - let _timer = BoaProfiler::global().start_event("ArrayDecl", "exec"); - let array = Array::new_array(interpreter)?; - let mut elements = Vec::new(); - for elem in self.as_ref() { - if let Node::Spread(ref x) = elem { - let val = x.run(interpreter)?; - let mut vals = interpreter.extract_array_properties(&val).unwrap(); - elements.append(&mut vals); - continue; // Don't push array after spread - } - elements.push(elem.run(interpreter)?); - } - Array::add_to_array_object(&array, &elements)?; - - Ok(array) - } -} diff --git a/boa/src/exec/block/mod.rs b/boa/src/exec/block/mod.rs deleted file mode 100644 index 7564d3fef1..0000000000 --- a/boa/src/exec/block/mod.rs +++ /dev/null @@ -1,51 +0,0 @@ -//! Block statement execution. - -use super::{Context, Executable, InterpreterState}; -use crate::{ - environment::lexical_environment::new_declarative_environment, syntax::ast::node::Block, - BoaProfiler, Result, Value, -}; - -impl Executable for Block { - fn run(&self, interpreter: &mut Context) -> Result { - let _timer = BoaProfiler::global().start_event("Block", "exec"); - { - let env = &mut interpreter.realm_mut().environment; - env.push(new_declarative_environment(Some( - env.get_current_environment_ref().clone(), - ))); - } - - // https://tc39.es/ecma262/#sec-block-runtime-semantics-evaluation - // The return value is uninitialized, which means it defaults to Value::Undefined - let mut obj = Value::default(); - for statement in self.statements() { - obj = statement.run(interpreter)?; - - match interpreter.executor().get_current_state() { - InterpreterState::Return => { - // Early return. - break; - } - InterpreterState::Break(_label) => { - // TODO, break to a label. - - // Early break. - break; - } - InterpreterState::Continue(_label) => { - // TODO, continue to a label - break; - } - InterpreterState::Executing => { - // Continue execution - } - } - } - - // pop the block env - let _ = interpreter.realm_mut().environment.pop(); - - Ok(obj) - } -} diff --git a/boa/src/exec/break_node/mod.rs b/boa/src/exec/break_node/mod.rs deleted file mode 100644 index 6d8071c316..0000000000 --- a/boa/src/exec/break_node/mod.rs +++ /dev/null @@ -1,28 +0,0 @@ -use super::{Context, Executable, InterpreterState}; -use crate::{ - syntax::ast::node::{Break, Continue}, - Result, Value, -}; - -#[cfg(test)] -mod tests; - -impl Executable for Break { - fn run(&self, interpreter: &mut Context) -> Result { - interpreter - .executor() - .set_current_state(InterpreterState::Break(self.label().map(Box::from))); - - Ok(Value::undefined()) - } -} - -impl Executable for Continue { - fn run(&self, interpreter: &mut Context) -> Result { - interpreter - .executor() - .set_current_state(InterpreterState::Continue(self.label().map(Box::from))); - - Ok(Value::undefined()) - } -} diff --git a/boa/src/exec/call/mod.rs b/boa/src/exec/call/mod.rs deleted file mode 100644 index dcf86bee73..0000000000 --- a/boa/src/exec/call/mod.rs +++ /dev/null @@ -1,53 +0,0 @@ -use super::{Context, Executable, InterpreterState}; -use crate::{ - syntax::ast::node::{Call, Node}, - value::{Type, Value}, - BoaProfiler, Result, -}; - -impl Executable for Call { - fn run(&self, interpreter: &mut Context) -> Result { - let _timer = BoaProfiler::global().start_event("Call", "exec"); - let (this, func) = match self.expr() { - Node::GetConstField(ref get_const_field) => { - let mut obj = get_const_field.obj().run(interpreter)?; - if obj.get_type() != Type::Object { - obj = Value::Object(obj.to_object(interpreter)?); - } - (obj.clone(), obj.get_field(get_const_field.field())) - } - Node::GetField(ref get_field) => { - let obj = get_field.obj().run(interpreter)?; - let field = get_field.field().run(interpreter)?; - ( - obj.clone(), - obj.get_field(field.to_property_key(interpreter)?), - ) - } - _ => ( - interpreter.realm().global_obj.clone(), - self.expr().run(interpreter)?, - ), // 'this' binding should come from the function's self-contained environment - }; - let mut v_args = Vec::with_capacity(self.args().len()); - for arg in self.args() { - if let Node::Spread(ref x) = arg { - let val = x.run(interpreter)?; - let mut vals = interpreter.extract_array_properties(&val).unwrap(); - v_args.append(&mut vals); - break; // after spread we don't accept any new arguments - } - v_args.push(arg.run(interpreter)?); - } - - // execute the function call itself - let fnct_result = interpreter.call(&func, &this, &v_args); - - // unset the early return flag - interpreter - .executor() - .set_current_state(InterpreterState::Executing); - - fnct_result - } -} diff --git a/boa/src/exec/conditional/mod.rs b/boa/src/exec/conditional/mod.rs deleted file mode 100644 index 4b56464649..0000000000 --- a/boa/src/exec/conditional/mod.rs +++ /dev/null @@ -1,27 +0,0 @@ -use super::{Context, Executable}; -use crate::{ - syntax::ast::node::{ConditionalOp, If}, - Result, Value, -}; - -impl Executable for If { - fn run(&self, interpreter: &mut Context) -> Result { - Ok(if self.cond().run(interpreter)?.to_boolean() { - self.body().run(interpreter)? - } else if let Some(ref else_e) = self.else_node() { - else_e.run(interpreter)? - } else { - Value::undefined() - }) - } -} - -impl Executable for ConditionalOp { - fn run(&self, interpreter: &mut Context) -> Result { - Ok(if self.cond().run(interpreter)?.to_boolean() { - self.if_true().run(interpreter)? - } else { - self.if_false().run(interpreter)? - }) - } -} diff --git a/boa/src/exec/declaration/mod.rs b/boa/src/exec/declaration/mod.rs deleted file mode 100644 index a882ea78aa..0000000000 --- a/boa/src/exec/declaration/mod.rs +++ /dev/null @@ -1,131 +0,0 @@ -//! Declaration execution. - -use super::{Context, Executable}; -use crate::{ - builtins::function::FunctionFlags, - environment::lexical_environment::VariableScope, - syntax::ast::node::{ - ArrowFunctionDecl, ConstDeclList, FunctionDecl, FunctionExpr, LetDeclList, VarDeclList, - }, - BoaProfiler, Result, Value, -}; - -impl Executable for FunctionDecl { - fn run(&self, interpreter: &mut Context) -> Result { - let _timer = BoaProfiler::global().start_event("FunctionDecl", "exec"); - let val = interpreter.create_function( - self.parameters().to_vec(), - self.body().to_vec(), - FunctionFlags::CALLABLE | FunctionFlags::CONSTRUCTABLE, - ); - - // Set the name and assign it in the current environment - val.set_field("name", self.name()); - interpreter.realm_mut().environment.create_mutable_binding( - self.name().to_owned(), - false, - VariableScope::Function, - ); - - interpreter - .realm_mut() - .environment - .initialize_binding(self.name(), val); - - Ok(Value::undefined()) - } -} - -impl Executable for FunctionExpr { - fn run(&self, interpreter: &mut Context) -> Result { - let val = interpreter.create_function( - self.parameters().to_vec(), - self.body().to_vec(), - FunctionFlags::CALLABLE | FunctionFlags::CONSTRUCTABLE, - ); - - if let Some(name) = self.name() { - val.set_field("name", Value::from(name)); - } - - Ok(val) - } -} - -impl Executable for VarDeclList { - fn run(&self, interpreter: &mut Context) -> Result { - for var in self.as_ref() { - let val = match var.init() { - Some(v) => v.run(interpreter)?, - None => Value::undefined(), - }; - let environment = &mut interpreter.realm_mut().environment; - - if environment.has_binding(var.name()) { - if var.init().is_some() { - environment.set_mutable_binding(var.name(), val, true); - } - } else { - environment.create_mutable_binding( - var.name().to_owned(), - false, - VariableScope::Function, - ); - environment.initialize_binding(var.name(), val); - } - } - Ok(Value::undefined()) - } -} - -impl Executable for ConstDeclList { - fn run(&self, interpreter: &mut Context) -> Result { - for decl in self.as_ref() { - let val = decl.init().run(interpreter)?; - - interpreter - .realm_mut() - .environment - .create_immutable_binding(decl.name().to_owned(), false, VariableScope::Block); - - interpreter - .realm_mut() - .environment - .initialize_binding(decl.name(), val); - } - Ok(Value::undefined()) - } -} - -impl Executable for LetDeclList { - fn run(&self, interpreter: &mut Context) -> Result { - for var in self.as_ref() { - let val = match var.init() { - Some(v) => v.run(interpreter)?, - None => Value::undefined(), - }; - interpreter.realm_mut().environment.create_mutable_binding( - var.name().to_owned(), - false, - VariableScope::Block, - ); - interpreter - .realm_mut() - .environment - .initialize_binding(var.name(), val); - } - Ok(Value::undefined()) - } -} - -impl Executable for ArrowFunctionDecl { - fn run(&self, interpreter: &mut Context) -> Result { - Ok(interpreter.create_function( - self.params().to_vec(), - self.body().to_vec(), - FunctionFlags::CALLABLE - | FunctionFlags::CONSTRUCTABLE - | FunctionFlags::LEXICAL_THIS_MODE, - )) - } -} diff --git a/boa/src/exec/field/mod.rs b/boa/src/exec/field/mod.rs deleted file mode 100644 index dfa9f4d840..0000000000 --- a/boa/src/exec/field/mod.rs +++ /dev/null @@ -1,29 +0,0 @@ -use super::{Context, Executable}; -use crate::{ - syntax::ast::node::{GetConstField, GetField}, - value::{Type, Value}, - Result, -}; - -impl Executable for GetConstField { - fn run(&self, interpreter: &mut Context) -> Result { - let mut obj = self.obj().run(interpreter)?; - if obj.get_type() != Type::Object { - obj = Value::Object(obj.to_object(interpreter)?); - } - - Ok(obj.get_field(self.field())) - } -} - -impl Executable for GetField { - fn run(&self, interpreter: &mut Context) -> Result { - let mut obj = self.obj().run(interpreter)?; - if obj.get_type() != Type::Object { - obj = Value::Object(obj.to_object(interpreter)?); - } - let field = self.field().run(interpreter)?; - - Ok(obj.get_field(field.to_property_key(interpreter)?)) - } -} diff --git a/boa/src/exec/identifier/mod.rs b/boa/src/exec/identifier/mod.rs deleted file mode 100644 index 6b113acc57..0000000000 --- a/boa/src/exec/identifier/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -use super::{Context, Executable}; -use crate::{syntax::ast::node::identifier::Identifier, Result, Value}; - -impl Executable for Identifier { - fn run(&self, interpreter: &mut Context) -> Result { - interpreter - .realm() - .environment - .get_binding_value(self.as_ref()) - .ok_or_else(|| interpreter.construct_reference_error(self.as_ref())) - } -} diff --git a/boa/src/exec/iteration/mod.rs b/boa/src/exec/iteration/mod.rs deleted file mode 100644 index a672078859..0000000000 --- a/boa/src/exec/iteration/mod.rs +++ /dev/null @@ -1,168 +0,0 @@ -//! Iteration node execution. - -use super::{Context, Executable, InterpreterState}; -use crate::{ - environment::lexical_environment::new_declarative_environment, - syntax::ast::node::{DoWhileLoop, ForLoop, WhileLoop}, - BoaProfiler, Result, Value, -}; - -#[cfg(test)] -mod tests; - -// Checking labels for break and continue is the same operation for `ForLoop`, `While` and `DoWhile` -macro_rules! handle_state_with_labels { - ($self:ident, $label:ident, $interpreter:ident, $state:tt) => {{ - if let Some(brk_label) = $label { - if let Some(stmt_label) = $self.label() { - // Break from where we are, keeping "continue" set as the state - if stmt_label != brk_label.as_ref() { - break; - } - } else { - // if a label is set but the current block has no label, break - break; - } - } - - $interpreter - .executor() - .set_current_state(InterpreterState::Executing); - }}; -} - -impl Executable for ForLoop { - fn run(&self, interpreter: &mut Context) -> Result { - // Create the block environment. - let _timer = BoaProfiler::global().start_event("ForLoop", "exec"); - { - let env = &mut interpreter.realm_mut().environment; - env.push(new_declarative_environment(Some( - env.get_current_environment_ref().clone(), - ))); - } - - if let Some(init) = self.init() { - init.run(interpreter)?; - } - - while self - .condition() - .map(|cond| cond.run(interpreter).map(|v| v.to_boolean())) - .transpose()? - .unwrap_or(true) - { - let result = self.body().run(interpreter)?; - - match interpreter.executor().get_current_state() { - InterpreterState::Break(label) => { - handle_state_with_labels!(self, label, interpreter, break); - break; - } - InterpreterState::Continue(label) => { - handle_state_with_labels!(self, label, interpreter, continue); - } - - InterpreterState::Return => { - return Ok(result); - } - InterpreterState::Executing => { - // Continue execution. - } - } - - if let Some(final_expr) = self.final_expr() { - final_expr.run(interpreter)?; - } - } - - // pop the block env - let _ = interpreter.realm_mut().environment.pop(); - - Ok(Value::undefined()) - } -} - -impl Executable for WhileLoop { - fn run(&self, interpreter: &mut Context) -> Result { - let mut result = Value::undefined(); - while self.cond().run(interpreter)?.to_boolean() { - result = self.expr().run(interpreter)?; - match interpreter.executor().get_current_state() { - InterpreterState::Break(label) => { - handle_state_with_labels!(self, label, interpreter, break); - break; - } - InterpreterState::Continue(label) => { - handle_state_with_labels!(self, label, interpreter, continue) - } - InterpreterState::Return => { - return Ok(result); - } - InterpreterState::Executing => { - // Continue execution. - } - } - } - Ok(result) - } -} - -impl Executable for DoWhileLoop { - fn run(&self, interpreter: &mut Context) -> Result { - let mut result = self.body().run(interpreter)?; - match interpreter.executor().get_current_state() { - InterpreterState::Break(_label) => { - // TODO break to label. - - // Loops 'consume' breaks. - interpreter - .executor() - .set_current_state(InterpreterState::Executing); - return Ok(result); - } - InterpreterState::Continue(_label) => { - // TODO continue to label; - interpreter - .executor() - .set_current_state(InterpreterState::Executing); - // after breaking out of the block, continue execution of the loop - } - InterpreterState::Return => { - return Ok(result); - } - InterpreterState::Executing => { - // Continue execution. - } - } - - while self.cond().run(interpreter)?.to_boolean() { - result = self.body().run(interpreter)?; - match interpreter.executor().get_current_state() { - InterpreterState::Break(_label) => { - // TODO break to label. - - // Loops 'consume' breaks. - interpreter - .executor() - .set_current_state(InterpreterState::Executing); - break; - } - InterpreterState::Continue(_label) => { - // TODO continue to label. - interpreter - .executor() - .set_current_state(InterpreterState::Executing); - // after breaking out of the block, continue execution of the loop - } - InterpreterState::Return => { - return Ok(result); - } - InterpreterState::Executing => { - // Continue execution. - } - } - } - Ok(result) - } -} diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 5d12878414..6bf5e0cc17 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -1,31 +1,9 @@ //! Execution of the AST, this is where the interpreter actually runs -mod array; -mod block; -mod break_node; -mod call; -mod conditional; -mod declaration; -mod field; -mod identifier; -mod iteration; -mod new; -mod object; -mod operator; -mod return_smt; -mod spread; -mod statement_list; -mod switch; -mod throw; -mod try_node; - #[cfg(test)] mod tests; -use crate::{ - syntax::ast::{constant::Const, node::Node}, - BoaProfiler, Context, Result, Value, -}; +use crate::{Context, Result, Value}; pub trait Executable { /// Runs this executable in the given context. @@ -71,56 +49,3 @@ impl Interpreter { &self.state } } - -impl Executable for Node { - fn run(&self, interpreter: &mut Context) -> Result { - let _timer = BoaProfiler::global().start_event("Executable", "exec"); - match *self { - Node::Const(Const::Null) => Ok(Value::null()), - Node::Const(Const::Num(num)) => Ok(Value::rational(num)), - Node::Const(Const::Int(num)) => Ok(Value::integer(num)), - Node::Const(Const::BigInt(ref num)) => Ok(Value::from(num.clone())), - Node::Const(Const::Undefined) => Ok(Value::Undefined), - // we can't move String from Const into value, because const is a garbage collected value - // Which means Drop() get's called on Const, but str will be gone at that point. - // Do Const values need to be garbage collected? We no longer need them once we've generated Values - Node::Const(Const::String(ref value)) => Ok(Value::string(value.to_string())), - Node::Const(Const::Bool(value)) => Ok(Value::boolean(value)), - Node::Block(ref block) => block.run(interpreter), - Node::Identifier(ref identifier) => identifier.run(interpreter), - Node::GetConstField(ref get_const_field_node) => get_const_field_node.run(interpreter), - Node::GetField(ref get_field) => get_field.run(interpreter), - Node::Call(ref call) => call.run(interpreter), - Node::WhileLoop(ref while_loop) => while_loop.run(interpreter), - Node::DoWhileLoop(ref do_while) => do_while.run(interpreter), - Node::ForLoop(ref for_loop) => for_loop.run(interpreter), - Node::If(ref if_smt) => if_smt.run(interpreter), - Node::ConditionalOp(ref op) => op.run(interpreter), - Node::Switch(ref switch) => switch.run(interpreter), - Node::Object(ref obj) => obj.run(interpreter), - Node::ArrayDecl(ref arr) => arr.run(interpreter), - // - Node::FunctionDecl(ref decl) => decl.run(interpreter), - // - Node::FunctionExpr(ref function_expr) => function_expr.run(interpreter), - Node::ArrowFunctionDecl(ref decl) => decl.run(interpreter), - Node::BinOp(ref op) => op.run(interpreter), - Node::UnaryOp(ref op) => op.run(interpreter), - Node::New(ref call) => call.run(interpreter), - Node::Return(ref ret) => ret.run(interpreter), - Node::Throw(ref throw) => throw.run(interpreter), - Node::Assign(ref op) => op.run(interpreter), - Node::VarDeclList(ref decl) => decl.run(interpreter), - Node::LetDeclList(ref decl) => decl.run(interpreter), - Node::ConstDeclList(ref decl) => decl.run(interpreter), - Node::Spread(ref spread) => spread.run(interpreter), - Node::This => { - // Will either return `this` binding or undefined - Ok(interpreter.realm().environment.get_this_binding()) - } - Node::Try(ref try_node) => try_node.run(interpreter), - Node::Break(ref break_node) => break_node.run(interpreter), - Node::Continue(ref continue_node) => continue_node.run(interpreter), - } - } -} diff --git a/boa/src/exec/new/mod.rs b/boa/src/exec/new/mod.rs deleted file mode 100644 index 5266cc090a..0000000000 --- a/boa/src/exec/new/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -use super::{Context, Executable}; -use crate::{syntax::ast::node::New, BoaProfiler, Result, Value}; - -impl Executable for New { - fn run(&self, interpreter: &mut Context) -> Result { - let _timer = BoaProfiler::global().start_event("New", "exec"); - - let func_object = self.expr().run(interpreter)?; - let mut v_args = Vec::with_capacity(self.args().len()); - for arg in self.args() { - v_args.push(arg.run(interpreter)?); - } - - match func_object { - Value::Object(ref object) => object.construct(&v_args, interpreter), - _ => Ok(Value::undefined()), - } - } -} diff --git a/boa/src/exec/object/mod.rs b/boa/src/exec/object/mod.rs deleted file mode 100644 index 6485f64695..0000000000 --- a/boa/src/exec/object/mod.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! Object execution. - -use super::{Context, Executable}; -use crate::{ - syntax::ast::node::MethodDefinitionKind, - syntax::ast::node::{Object, PropertyDefinition}, - Result, Value, -}; - -impl Executable for Object { - fn run(&self, interpreter: &mut Context) -> Result { - let global_val = &interpreter - .realm() - .environment - .get_global_object() - .expect("Could not get the global object"); - let obj = Value::new_object(Some(global_val)); - - // TODO: Implement the rest of the property types. - for property in self.properties().iter() { - match property { - PropertyDefinition::Property(key, value) => { - obj.set_field(key.clone(), value.run(interpreter)?); - } - PropertyDefinition::MethodDefinition(kind, name, func) => { - if let MethodDefinitionKind::Ordinary = kind { - obj.set_field(name.clone(), func.run(interpreter)?); - } else { - // TODO: Implement other types of MethodDefinitionKinds. - unimplemented!("other types of property method definitions."); - } - } - i => unimplemented!("{:?} type of property", i), - } - } - - Ok(obj) - } -} diff --git a/boa/src/exec/return_smt/mod.rs b/boa/src/exec/return_smt/mod.rs deleted file mode 100644 index 112c0ec6f7..0000000000 --- a/boa/src/exec/return_smt/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -use super::{Context, Executable, InterpreterState}; -use crate::{syntax::ast::node::Return, Result, Value}; - -impl Executable for Return { - fn run(&self, interpreter: &mut Context) -> Result { - let result = match self.expr() { - Some(ref v) => v.run(interpreter), - None => Ok(Value::undefined()), - }; - // Set flag for return - interpreter - .executor() - .set_current_state(InterpreterState::Return); - result - } -} diff --git a/boa/src/exec/spread/mod.rs b/boa/src/exec/spread/mod.rs deleted file mode 100644 index d5fa2805b7..0000000000 --- a/boa/src/exec/spread/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -use super::{Context, Executable}; -use crate::{syntax::ast::node::Spread, Result, Value}; - -impl Executable for Spread { - fn run(&self, interpreter: &mut Context) -> Result { - // TODO: for now we can do nothing but return the value as-is - self.val().run(interpreter) - } -} diff --git a/boa/src/exec/statement_list.rs b/boa/src/exec/statement_list.rs deleted file mode 100644 index 2f63d5eea6..0000000000 --- a/boa/src/exec/statement_list.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! Statement list execution. - -use super::{Context, Executable, InterpreterState}; -use crate::{syntax::ast::node::StatementList, BoaProfiler, Result, Value}; - -impl Executable for StatementList { - fn run(&self, interpreter: &mut Context) -> Result { - let _timer = BoaProfiler::global().start_event("StatementList", "exec"); - - // https://tc39.es/ecma262/#sec-block-runtime-semantics-evaluation - // The return value is uninitialized, which means it defaults to Value::Undefined - let mut obj = Value::default(); - interpreter - .executor() - .set_current_state(InterpreterState::Executing); - for (i, item) in self.statements().iter().enumerate() { - let val = item.run(interpreter)?; - match interpreter.executor().get_current_state() { - InterpreterState::Return => { - // Early return. - obj = val; - break; - } - InterpreterState::Break(_label) => { - // Early break. - break; - } - InterpreterState::Continue(_label) => { - break; - } - InterpreterState::Executing => { - // Continue execution - } - } - if i + 1 == self.statements().len() { - obj = val; - } - } - - Ok(obj) - } -} diff --git a/boa/src/exec/switch/mod.rs b/boa/src/exec/switch/mod.rs deleted file mode 100644 index 316e2bc720..0000000000 --- a/boa/src/exec/switch/mod.rs +++ /dev/null @@ -1,83 +0,0 @@ -use super::{Context, Executable, InterpreterState}; -use crate::{syntax::ast::node::Switch, Result, Value}; - -#[cfg(test)] -mod tests; - -impl Executable for Switch { - fn run(&self, interpreter: &mut Context) -> Result { - let val = self.val().run(interpreter)?; - let mut result = Value::null(); - let mut matched = false; - interpreter - .executor() - .set_current_state(InterpreterState::Executing); - - // If a case block does not end with a break statement then subsequent cases will be run without - // checking their conditions until a break is encountered. - let mut fall_through: bool = false; - - for case in self.cases().iter() { - let cond = case.condition(); - let block = case.body(); - if fall_through || val.strict_equals(&cond.run(interpreter)?) { - matched = true; - let result = block.run(interpreter)?; - match interpreter.executor().get_current_state() { - InterpreterState::Return => { - // Early return. - return Ok(result); - } - InterpreterState::Break(_label) => { - // TODO, break to a label. - // Break statement encountered so therefore end switch statement. - interpreter - .executor() - .set_current_state(InterpreterState::Executing); - break; - } - InterpreterState::Continue(_label) => { - // TODO, continue to a label. - break; - } - InterpreterState::Executing => { - // Continuing execution / falling through to next case statement(s). - fall_through = true; - } - } - } - } - - if !matched { - if let Some(default) = self.default() { - interpreter - .executor() - .set_current_state(InterpreterState::Executing); - for (i, item) in default.iter().enumerate() { - let val = item.run(interpreter)?; - match interpreter.executor().get_current_state() { - InterpreterState::Return => { - // Early return. - result = val; - break; - } - InterpreterState::Break(_label) => { - // TODO, break to a label. - - // Early break. - break; - } - _ => { - // Continue execution - } - } - if i == default.len() - 1 { - result = val; - } - } - } - } - - Ok(result) - } -} diff --git a/boa/src/exec/throw/mod.rs b/boa/src/exec/throw/mod.rs deleted file mode 100644 index cd277ee7d1..0000000000 --- a/boa/src/exec/throw/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -use super::{Context, Executable}; -use crate::{syntax::ast::node::Throw, Result, Value}; - -impl Executable for Throw { - #[inline] - fn run(&self, interpreter: &mut Context) -> Result { - Err(self.expr().run(interpreter)?) - } -} diff --git a/boa/src/exec/try_node/mod.rs b/boa/src/exec/try_node/mod.rs deleted file mode 100644 index cd3d0cc4ef..0000000000 --- a/boa/src/exec/try_node/mod.rs +++ /dev/null @@ -1,55 +0,0 @@ -//! Try..catch node execution. - -use super::{Context, Executable}; -use crate::{ - environment::lexical_environment::{new_declarative_environment, VariableScope}, - syntax::ast::node::Try, - BoaProfiler, Result, Value, -}; - -#[cfg(test)] -mod tests; - -impl Executable for Try { - fn run(&self, interpreter: &mut Context) -> Result { - let _timer = BoaProfiler::global().start_event("Try", "exec"); - let res = self.block().run(interpreter).map_or_else( - |err| { - if let Some(catch) = self.catch() { - { - let env = &mut interpreter.realm_mut().environment; - env.push(new_declarative_environment(Some( - env.get_current_environment_ref().clone(), - ))); - - if let Some(param) = catch.parameter() { - env.create_mutable_binding( - param.to_owned(), - false, - VariableScope::Block, - ); - - env.initialize_binding(param, err); - } - } - - let res = catch.block().run(interpreter); - - // pop the block env - let _ = interpreter.realm_mut().environment.pop(); - - res - } else { - Err(err) - } - }, - Ok, - ); - - if let Some(finally) = self.finally() { - finally.run(interpreter)?; - } - - res - } -} diff --git a/boa/src/syntax/ast/node/array.rs b/boa/src/syntax/ast/node/array/mod.rs similarity index 66% rename from boa/src/syntax/ast/node/array.rs rename to boa/src/syntax/ast/node/array/mod.rs index c1f2479e59..b98f951494 100644 --- a/boa/src/syntax/ast/node/array.rs +++ b/boa/src/syntax/ast/node/array/mod.rs @@ -1,6 +1,7 @@ //! Array declaration node. use super::{join_nodes, Node}; +use crate::{builtins::Array, exec::Executable, BoaProfiler, Context, Result, Value}; use gc::{Finalize, Trace}; use std::fmt; @@ -30,6 +31,26 @@ pub struct ArrayDecl { arr: Box<[Node]>, } +impl Executable for ArrayDecl { + fn run(&self, interpreter: &mut Context) -> Result { + let _timer = BoaProfiler::global().start_event("ArrayDecl", "exec"); + let array = Array::new_array(interpreter)?; + let mut elements = Vec::new(); + for elem in self.as_ref() { + if let Node::Spread(ref x) = elem { + let val = x.run(interpreter)?; + let mut vals = interpreter.extract_array_properties(&val).unwrap(); + elements.append(&mut vals); + continue; // Don't push array after spread + } + elements.push(elem.run(interpreter)?); + } + Array::add_to_array_object(&array, &elements)?; + + Ok(array) + } +} + impl AsRef<[Node]> for ArrayDecl { fn as_ref(&self) -> &[Node] { &self.arr diff --git a/boa/src/syntax/ast/node/block.rs b/boa/src/syntax/ast/node/block/mod.rs similarity index 55% rename from boa/src/syntax/ast/node/block.rs rename to boa/src/syntax/ast/node/block/mod.rs index 68a152b14f..2520865312 100644 --- a/boa/src/syntax/ast/node/block.rs +++ b/boa/src/syntax/ast/node/block/mod.rs @@ -1,6 +1,10 @@ //! Block AST node. use super::{Node, StatementList}; +use crate::{ + environment::lexical_environment::new_declarative_environment, exec::Executable, + exec::InterpreterState, BoaProfiler, Context, Result, Value, +}; use gc::{Finalize, Trace}; use std::fmt; @@ -44,6 +48,50 @@ impl Block { } } +impl Executable for Block { + fn run(&self, interpreter: &mut Context) -> Result { + let _timer = BoaProfiler::global().start_event("Block", "exec"); + { + let env = &mut interpreter.realm_mut().environment; + env.push(new_declarative_environment(Some( + env.get_current_environment_ref().clone(), + ))); + } + + // https://tc39.es/ecma262/#sec-block-runtime-semantics-evaluation + // The return value is uninitialized, which means it defaults to Value::Undefined + let mut obj = Value::default(); + for statement in self.statements() { + obj = statement.run(interpreter)?; + + match interpreter.executor().get_current_state() { + InterpreterState::Return => { + // Early return. + break; + } + InterpreterState::Break(_label) => { + // TODO, break to a label. + + // Early break. + break; + } + InterpreterState::Continue(_label) => { + // TODO, continue to a label + break; + } + InterpreterState::Executing => { + // Continue execution + } + } + } + + // pop the block env + let _ = interpreter.realm_mut().environment.pop(); + + Ok(obj) + } +} + impl From for Block where T: Into, diff --git a/boa/src/syntax/ast/node/break_node.rs b/boa/src/syntax/ast/node/break_node/mod.rs similarity index 83% rename from boa/src/syntax/ast/node/break_node.rs rename to boa/src/syntax/ast/node/break_node/mod.rs index d12aa07cc0..8b7f723aae 100644 --- a/boa/src/syntax/ast/node/break_node.rs +++ b/boa/src/syntax/ast/node/break_node/mod.rs @@ -1,10 +1,14 @@ use super::Node; +use crate::{exec::Executable, exec::InterpreterState, Context, Result, Value}; use gc::{Finalize, Trace}; use std::fmt; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +#[cfg(test)] +mod tests; + /// The `break` statement terminates the current loop, switch, or label statement and transfers /// program control to the statement following the terminated statement. /// @@ -43,6 +47,16 @@ impl Break { } } +impl Executable for Break { + fn run(&self, interpreter: &mut Context) -> Result { + interpreter + .executor() + .set_current_state(InterpreterState::Break(self.label().map(Box::from))); + + Ok(Value::undefined()) + } +} + impl fmt::Display for Break { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( diff --git a/boa/src/exec/break_node/tests.rs b/boa/src/syntax/ast/node/break_node/tests.rs similarity index 73% rename from boa/src/exec/break_node/tests.rs rename to boa/src/syntax/ast/node/break_node/tests.rs index 8444a40ad2..b1ae0b4dd8 100644 --- a/boa/src/exec/break_node/tests.rs +++ b/boa/src/syntax/ast/node/break_node/tests.rs @@ -1,5 +1,8 @@ -use super::{Context, InterpreterState}; -use crate::{exec::Executable, syntax::ast::node::Break}; +use crate::{ + exec::{Executable, InterpreterState}, + syntax::ast::node::Break, + Context, +}; #[test] fn check_post_state() { diff --git a/boa/src/syntax/ast/node/conditional/conditional_op/mod.rs b/boa/src/syntax/ast/node/conditional/conditional_op/mod.rs new file mode 100644 index 0000000000..ee7a6a7f85 --- /dev/null +++ b/boa/src/syntax/ast/node/conditional/conditional_op/mod.rs @@ -0,0 +1,84 @@ +use crate::{exec::Executable, syntax::ast::node::Node, Context, Result, Value}; +use gc::{Finalize, Trace}; +use std::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// The `conditional` (ternary) operator is the only JavaScript operator that takes three +/// operands. +/// +/// This operator is the only JavaScript operator that takes three operands: a condition +/// followed by a question mark (`?`), then an expression to execute `if` the condition is +/// truthy followed by a colon (`:`), and finally the expression to execute if the condition +/// is `false`. This operator is frequently used as a shortcut for the `if` statement. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#prod-ConditionalExpression +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#Literals +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct ConditionalOp { + condition: Box, + if_true: Box, + if_false: Box, +} + +impl ConditionalOp { + pub fn cond(&self) -> &Node { + &self.condition + } + + pub fn if_true(&self) -> &Node { + &self.if_true + } + + pub fn if_false(&self) -> &Node { + &self.if_false + } + + /// Creates a `ConditionalOp` AST node. + pub fn new(condition: C, if_true: T, if_false: F) -> Self + where + C: Into, + T: Into, + F: Into, + { + Self { + condition: Box::new(condition.into()), + if_true: Box::new(if_true.into()), + if_false: Box::new(if_false.into()), + } + } +} + +impl Executable for ConditionalOp { + fn run(&self, interpreter: &mut Context) -> Result { + Ok(if self.cond().run(interpreter)?.to_boolean() { + self.if_true().run(interpreter)? + } else { + self.if_false().run(interpreter)? + }) + } +} + +impl fmt::Display for ConditionalOp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{} ? {} : {}", + self.cond(), + self.if_true(), + self.if_false() + ) + } +} + +impl From for Node { + fn from(cond_op: ConditionalOp) -> Node { + Self::ConditionalOp(cond_op) + } +} diff --git a/boa/src/syntax/ast/node/conditional.rs b/boa/src/syntax/ast/node/conditional/if_node/mod.rs similarity index 53% rename from boa/src/syntax/ast/node/conditional.rs rename to boa/src/syntax/ast/node/conditional/if_node/mod.rs index 973a3bef00..6257d7d841 100644 --- a/boa/src/syntax/ast/node/conditional.rs +++ b/boa/src/syntax/ast/node/conditional/if_node/mod.rs @@ -1,4 +1,4 @@ -use super::Node; +use crate::{exec::Executable, syntax::ast::node::Node, Context, Result, Value}; use gc::{Finalize, Trace}; use std::fmt; @@ -57,7 +57,11 @@ impl If { } } - pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indent: usize) -> fmt::Result { + pub(in crate::syntax::ast::node) fn display( + &self, + f: &mut fmt::Formatter<'_>, + indent: usize, + ) -> fmt::Result { write!(f, "if ({}) ", self.cond())?; match self.else_node() { Some(else_e) => { @@ -70,6 +74,18 @@ impl If { } } +impl Executable for If { + fn run(&self, interpreter: &mut Context) -> Result { + Ok(if self.cond().run(interpreter)?.to_boolean() { + self.body().run(interpreter)? + } else if let Some(ref else_e) = self.else_node() { + else_e.run(interpreter)? + } else { + Value::undefined() + }) + } +} + impl fmt::Display for If { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.display(f, 0) @@ -81,71 +97,3 @@ impl From for Node { Self::If(if_stm) } } - -/// The `conditional` (ternary) operator is the only JavaScript operator that takes three -/// operands. -/// -/// This operator is the only JavaScript operator that takes three operands: a condition -/// followed by a question mark (`?`), then an expression to execute `if` the condition is -/// truthy followed by a colon (`:`), and finally the expression to execute if the condition -/// is `false`. This operator is frequently used as a shortcut for the `if` statement. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#prod-ConditionalExpression -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types#Literals -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] -pub struct ConditionalOp { - condition: Box, - if_true: Box, - if_false: Box, -} - -impl ConditionalOp { - pub fn cond(&self) -> &Node { - &self.condition - } - - pub fn if_true(&self) -> &Node { - &self.if_true - } - - pub fn if_false(&self) -> &Node { - &self.if_false - } - - /// Creates a `ConditionalOp` AST node. - pub fn new(condition: C, if_true: T, if_false: F) -> Self - where - C: Into, - T: Into, - F: Into, - { - Self { - condition: Box::new(condition.into()), - if_true: Box::new(if_true.into()), - if_false: Box::new(if_false.into()), - } - } -} - -impl fmt::Display for ConditionalOp { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{} ? {} : {}", - self.cond(), - self.if_true(), - self.if_false() - ) - } -} - -impl From for Node { - fn from(cond_op: ConditionalOp) -> Node { - Self::ConditionalOp(cond_op) - } -} diff --git a/boa/src/syntax/ast/node/conditional/mod.rs b/boa/src/syntax/ast/node/conditional/mod.rs new file mode 100644 index 0000000000..fdfe8ea81f --- /dev/null +++ b/boa/src/syntax/ast/node/conditional/mod.rs @@ -0,0 +1,6 @@ +//! Conditional nodes + +pub mod conditional_op; +pub mod if_node; + +pub use self::{conditional_op::ConditionalOp, if_node::If}; diff --git a/boa/src/syntax/ast/node/declaration.rs b/boa/src/syntax/ast/node/declaration.rs deleted file mode 100644 index ea73685bd0..0000000000 --- a/boa/src/syntax/ast/node/declaration.rs +++ /dev/null @@ -1,550 +0,0 @@ -//! Declaration nodes. - -use super::{join_nodes, FormalParameter, Identifier, Node, StatementList}; -use gc::{Finalize, Trace}; -use std::fmt; - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -/// The `var` statement declares a variable, optionally initializing it to a value. -/// -/// var declarations, wherever they occur, are processed before any code is executed. This is -/// called hoisting, and is discussed further below. -/// -/// The scope of a variable declared with var is its current execution context, which is either -/// the enclosing function or, for variables declared outside any function, global. If you -/// re-declare a JavaScript variable, it will not lose its value. -/// -/// Assigning a value to an undeclared variable implicitly creates it as a global variable (it -/// becomes a property of the global object) when the assignment is executed. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#prod-VariableStatement -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] -pub struct VarDeclList { - #[cfg_attr(feature = "serde", serde(flatten))] - vars: Box<[VarDecl]>, -} - -impl From for VarDeclList -where - T: Into>, -{ - fn from(list: T) -> Self { - Self { vars: list.into() } - } -} - -impl From for VarDeclList { - fn from(decl: VarDecl) -> Self { - Self { - vars: Box::new([decl]), - } - } -} - -impl AsRef<[VarDecl]> for VarDeclList { - fn as_ref(&self) -> &[VarDecl] { - &self.vars - } -} - -impl fmt::Display for VarDeclList { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if !self.vars.is_empty() { - write!(f, "var ")?; - join_nodes(f, &self.vars) - } else { - Ok(()) - } - } -} - -impl From for Node { - fn from(list: VarDeclList) -> Self { - Self::VarDeclList(list) - } -} - -/// Individual variable declaration. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] -pub struct VarDecl { - name: Identifier, - init: Option, -} - -impl fmt::Display for VarDecl { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&self.name, f)?; - if let Some(ref init) = self.init { - write!(f, " = {}", init)?; - } - Ok(()) - } -} - -impl VarDecl { - /// Creates a new variable declaration. - pub(in crate::syntax) fn new(name: N, init: I) -> Self - where - N: Into, - I: Into>, - { - Self { - name: name.into(), - init: init.into(), - } - } - - /// Gets the name of the variable. - pub fn name(&self) -> &str { - self.name.as_ref() - } - - /// Gets the initialization node for the variable, if any. - pub fn init(&self) -> Option<&Node> { - self.init.as_ref() - } -} - -/// The `function` expression defines a function with the specified parameters. -/// -/// A function created with a function expression is a `Function` object and has all the -/// properties, methods and behavior of `Function`. -/// -/// A function can also be created using a declaration (see function expression). -/// -/// By default, functions return `undefined`. To return any other value, the function must have -/// a return statement that specifies the value to return. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-terms-and-definitions-function -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] -pub struct FunctionExpr { - name: Option>, - parameters: Box<[FormalParameter]>, - body: StatementList, -} - -impl FunctionExpr { - /// Creates a new function expression - pub(in crate::syntax) fn new(name: N, parameters: P, body: B) -> Self - where - N: Into>>, - P: Into>, - B: Into, - { - Self { - name: name.into(), - parameters: parameters.into(), - body: body.into(), - } - } - - /// Gets the name of the function declaration. - pub fn name(&self) -> Option<&str> { - self.name.as_ref().map(Box::as_ref) - } - - /// Gets the list of parameters of the function declaration. - pub fn parameters(&self) -> &[FormalParameter] { - &self.parameters - } - - /// Gets the body of the function declaration. - pub fn body(&self) -> &[Node] { - self.body.statements() - } - - /// Implements the display formatting with indentation. - pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { - f.write_str("function")?; - if let Some(ref name) = self.name { - write!(f, " {}", name)?; - } - f.write_str("(")?; - join_nodes(f, &self.parameters)?; - f.write_str(") {{")?; - - self.body.display(f, indentation + 1)?; - - writeln!(f, "}}") - } -} - -impl fmt::Display for FunctionExpr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.display(f, 0) - } -} - -impl From for Node { - fn from(expr: FunctionExpr) -> Self { - Self::FunctionExpr(expr) - } -} - -/// The `function` declaration (function statement) defines a function with the specified -/// parameters. -/// -/// A function created with a function declaration is a `Function` object and has all the -/// properties, methods and behavior of `Function`. -/// -/// A function can also be created using an expression (see [function expression][func_expr]). -/// -/// By default, functions return `undefined`. To return any other value, the function must have -/// a return statement that specifies the value to return. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-terms-and-definitions-function -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function -/// [func_expr]: ../enum.Node.html#variant.FunctionExpr -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] -pub struct FunctionDecl { - name: Box, - parameters: Box<[FormalParameter]>, - body: StatementList, -} - -impl FunctionDecl { - /// Creates a new function declaration. - pub(in crate::syntax) fn new(name: N, parameters: P, body: B) -> Self - where - N: Into>, - P: Into>, - B: Into, - { - Self { - name: name.into(), - parameters: parameters.into(), - body: body.into(), - } - } - - /// Gets the name of the function declaration. - pub fn name(&self) -> &str { - &self.name - } - - /// Gets the list of parameters of the function declaration. - pub fn parameters(&self) -> &[FormalParameter] { - &self.parameters - } - - /// Gets the body of the function declaration. - pub fn body(&self) -> &[Node] { - self.body.statements() - } - - /// Implements the display formatting with indentation. - pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { - write!(f, "function {}(", self.name)?; - join_nodes(f, &self.parameters)?; - f.write_str(") {{")?; - - self.body.display(f, indentation + 1)?; - - writeln!(f, "}}") - } -} - -impl From for Node { - fn from(decl: FunctionDecl) -> Self { - Self::FunctionDecl(decl) - } -} - -impl fmt::Display for FunctionDecl { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.display(f, 0) - } -} - -/// An arrow function expression is a syntactically compact alternative to a regular function -/// expression. -/// -/// Arrow function expressions are ill suited as methods, and they cannot be used as -/// constructors. Arrow functions cannot be used as constructors and will throw an error when -/// used with new. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#prod-ArrowFunction -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] -pub struct ArrowFunctionDecl { - params: Box<[FormalParameter]>, - body: StatementList, -} - -impl ArrowFunctionDecl { - /// Creates a new `ArrowFunctionDecl` AST node. - pub(in crate::syntax) fn new(params: P, body: B) -> Self - where - P: Into>, - B: Into, - { - Self { - params: params.into(), - body: body.into(), - } - } - - /// Gets the list of parameters of the arrow function. - pub(crate) fn params(&self) -> &[FormalParameter] { - &self.params - } - - /// Gets the body of the arrow function. - pub(crate) fn body(&self) -> &[Node] { - &self.body.statements() - } - - /// Implements the display formatting with indentation. - pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { - write!(f, "(")?; - join_nodes(f, &self.params)?; - f.write_str(") => ")?; - self.body.display(f, indentation) - } -} - -impl fmt::Display for ArrowFunctionDecl { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.display(f, 0) - } -} - -impl From for Node { - fn from(decl: ArrowFunctionDecl) -> Self { - Self::ArrowFunctionDecl(decl) - } -} - -/// The `const` statements are block-scoped, much like variables defined using the `let` -/// keyword. -/// -/// This declaration creates a constant whose scope can be either global or local to the block -/// in which it is declared. Global constants do not become properties of the window object, -/// unlike var variables. -/// -/// An initializer for a constant is required. You must specify its value in the same statement -/// in which it's declared. (This makes sense, given that it can't be changed later.) -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-let-and-const-declarations -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const -/// [identifier]: https://developer.mozilla.org/en-US/docs/Glossary/identifier -/// [expression]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Expressions -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] -pub struct ConstDeclList { - #[cfg_attr(feature = "serde", serde(flatten))] - list: Box<[ConstDecl]>, -} - -impl From for ConstDeclList -where - T: Into>, -{ - fn from(list: T) -> Self { - Self { list: list.into() } - } -} - -impl From for ConstDeclList { - fn from(decl: ConstDecl) -> Self { - Self { - list: Box::new([decl]), - } - } -} - -impl AsRef<[ConstDecl]> for ConstDeclList { - fn as_ref(&self) -> &[ConstDecl] { - &self.list - } -} - -impl fmt::Display for ConstDeclList { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if !self.list.is_empty() { - write!(f, "const ")?; - join_nodes(f, &self.list) - } else { - Ok(()) - } - } -} - -impl From for Node { - fn from(list: ConstDeclList) -> Self { - Self::ConstDeclList(list) - } -} - -/// Individual constant declaration. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] -pub struct ConstDecl { - name: Identifier, - init: Node, -} - -impl fmt::Display for ConstDecl { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{} = {}", self.name, self.init) - } -} - -impl ConstDecl { - /// Creates a new variable declaration. - pub(in crate::syntax) fn new(name: N, init: I) -> Self - where - N: Into, - I: Into, - { - Self { - name: name.into(), - init: init.into(), - } - } - - /// Gets the name of the variable. - pub fn name(&self) -> &str { - self.name.as_ref() - } - - /// Gets the initialization node for the variable, if any. - pub fn init(&self) -> &Node { - &self.init - } -} - -/// The `let` statement declares a block scope local variable, optionally initializing it to a -/// value. -/// -/// -/// `let` allows you to declare variables that are limited to a scope of a block statement, or -/// expression on which it is used, unlike the `var` keyword, which defines a variable -/// globally, or locally to an entire function regardless of block scope. -/// -/// Just like const the `let` does not create properties of the window object when declared -/// globally (in the top-most scope). -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-let-and-const-declarations -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] -pub struct LetDeclList { - #[cfg_attr(feature = "serde", serde(flatten))] - list: Box<[LetDecl]>, -} - -impl From for LetDeclList -where - T: Into>, -{ - fn from(list: T) -> Self { - Self { list: list.into() } - } -} - -impl From for LetDeclList { - fn from(decl: LetDecl) -> Self { - Self { - list: Box::new([decl]), - } - } -} - -impl AsRef<[LetDecl]> for LetDeclList { - fn as_ref(&self) -> &[LetDecl] { - &self.list - } -} - -impl fmt::Display for LetDeclList { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if !self.list.is_empty() { - write!(f, "let ")?; - join_nodes(f, &self.list) - } else { - Ok(()) - } - } -} - -impl From for Node { - fn from(list: LetDeclList) -> Self { - Self::LetDeclList(list) - } -} - -/// Individual constant declaration. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] -pub struct LetDecl { - name: Identifier, - init: Option, -} - -impl fmt::Display for LetDecl { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&self.name, f)?; - if let Some(ref init) = self.init { - write!(f, " = {}", init)?; - } - Ok(()) - } -} - -impl LetDecl { - /// Creates a new variable declaration. - pub(in crate::syntax) fn new(name: N, init: I) -> Self - where - N: Into, - I: Into>, - { - Self { - name: name.into(), - init: init.into(), - } - } - - /// Gets the name of the variable. - pub fn name(&self) -> &str { - self.name.as_ref() - } - - /// Gets the initialization node for the variable, if any. - pub fn init(&self) -> Option<&Node> { - self.init.as_ref() - } -} diff --git a/boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs b/boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs new file mode 100644 index 0000000000..b7db3d6e06 --- /dev/null +++ b/boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs @@ -0,0 +1,91 @@ +use crate::{ + builtins::function::FunctionFlags, + exec::Executable, + syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList}, + Context, Result, Value, +}; +use gc::{Finalize, Trace}; +use std::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// An arrow function expression is a syntactically compact alternative to a regular function +/// expression. +/// +/// Arrow function expressions are ill suited as methods, and they cannot be used as +/// constructors. Arrow functions cannot be used as constructors and will throw an error when +/// used with new. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#prod-ArrowFunction +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct ArrowFunctionDecl { + params: Box<[FormalParameter]>, + body: StatementList, +} + +impl ArrowFunctionDecl { + /// Creates a new `ArrowFunctionDecl` AST node. + pub(in crate::syntax) fn new(params: P, body: B) -> Self + where + P: Into>, + B: Into, + { + Self { + params: params.into(), + body: body.into(), + } + } + + /// Gets the list of parameters of the arrow function. + pub(crate) fn params(&self) -> &[FormalParameter] { + &self.params + } + + /// Gets the body of the arrow function. + pub(crate) fn body(&self) -> &[Node] { + &self.body.statements() + } + + /// Implements the display formatting with indentation. + pub(in crate::syntax::ast::node) fn display( + &self, + f: &mut fmt::Formatter<'_>, + indentation: usize, + ) -> fmt::Result { + write!(f, "(")?; + join_nodes(f, &self.params)?; + f.write_str(") => ")?; + self.body.display(f, indentation) + } +} + +impl Executable for ArrowFunctionDecl { + fn run(&self, interpreter: &mut Context) -> Result { + Ok(interpreter.create_function( + self.params().to_vec(), + self.body().to_vec(), + FunctionFlags::CALLABLE + | FunctionFlags::CONSTRUCTABLE + | FunctionFlags::LEXICAL_THIS_MODE, + )) + } +} + +impl fmt::Display for ArrowFunctionDecl { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.display(f, 0) + } +} + +impl From for Node { + fn from(decl: ArrowFunctionDecl) -> Self { + Self::ArrowFunctionDecl(decl) + } +} diff --git a/boa/src/syntax/ast/node/declaration/const_decl_list/mod.rs b/boa/src/syntax/ast/node/declaration/const_decl_list/mod.rs new file mode 100644 index 0000000000..40d95ef230 --- /dev/null +++ b/boa/src/syntax/ast/node/declaration/const_decl_list/mod.rs @@ -0,0 +1,133 @@ +use crate::{ + environment::lexical_environment::VariableScope, + exec::Executable, + syntax::ast::node::{join_nodes, Identifier, Node}, + Context, Result, Value, +}; +use gc::{Finalize, Trace}; +use std::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// The `const` statements are block-scoped, much like variables defined using the `let` +/// keyword. +/// +/// This declaration creates a constant whose scope can be either global or local to the block +/// in which it is declared. Global constants do not become properties of the window object, +/// unlike var variables. +/// +/// An initializer for a constant is required. You must specify its value in the same statement +/// in which it's declared. (This makes sense, given that it can't be changed later.) +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#sec-let-and-const-declarations +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const +/// [identifier]: https://developer.mozilla.org/en-US/docs/Glossary/identifier +/// [expression]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Expressions +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct ConstDeclList { + #[cfg_attr(feature = "serde", serde(flatten))] + list: Box<[ConstDecl]>, +} + +impl Executable for ConstDeclList { + fn run(&self, interpreter: &mut Context) -> Result { + for decl in self.as_ref() { + let val = decl.init().run(interpreter)?; + + interpreter + .realm_mut() + .environment + .create_immutable_binding(decl.name().to_owned(), false, VariableScope::Block); + + interpreter + .realm_mut() + .environment + .initialize_binding(decl.name(), val); + } + Ok(Value::undefined()) + } +} + +impl From for ConstDeclList +where + T: Into>, +{ + fn from(list: T) -> Self { + Self { list: list.into() } + } +} + +impl From for ConstDeclList { + fn from(decl: ConstDecl) -> Self { + Self { + list: Box::new([decl]), + } + } +} + +impl AsRef<[ConstDecl]> for ConstDeclList { + fn as_ref(&self) -> &[ConstDecl] { + &self.list + } +} + +impl fmt::Display for ConstDeclList { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if !self.list.is_empty() { + write!(f, "const ")?; + join_nodes(f, &self.list) + } else { + Ok(()) + } + } +} + +impl From for Node { + fn from(list: ConstDeclList) -> Self { + Self::ConstDeclList(list) + } +} + +/// Individual constant declaration. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct ConstDecl { + name: Identifier, + init: Node, +} + +impl fmt::Display for ConstDecl { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} = {}", self.name, self.init) + } +} + +impl ConstDecl { + /// Creates a new variable declaration. + pub(in crate::syntax) fn new(name: N, init: I) -> Self + where + N: Into, + I: Into, + { + Self { + name: name.into(), + init: init.into(), + } + } + + /// Gets the name of the variable. + pub fn name(&self) -> &str { + self.name.as_ref() + } + + /// Gets the initialization node for the variable, if any. + pub fn init(&self) -> &Node { + &self.init + } +} diff --git a/boa/src/syntax/ast/node/declaration/function_decl/mod.rs b/boa/src/syntax/ast/node/declaration/function_decl/mod.rs new file mode 100644 index 0000000000..14c55adaf8 --- /dev/null +++ b/boa/src/syntax/ast/node/declaration/function_decl/mod.rs @@ -0,0 +1,122 @@ +use crate::{ + builtins::function::FunctionFlags, + environment::lexical_environment::VariableScope, + exec::Executable, + syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList}, + BoaProfiler, Context, Result, Value, +}; +use gc::{Finalize, Trace}; +use std::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// The `function` declaration (function statement) defines a function with the specified +/// parameters. +/// +/// A function created with a function declaration is a `Function` object and has all the +/// properties, methods and behavior of `Function`. +/// +/// A function can also be created using an expression (see [function expression][func_expr]). +/// +/// By default, functions return `undefined`. To return any other value, the function must have +/// a return statement that specifies the value to return. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#sec-terms-and-definitions-function +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function +/// [func_expr]: ../enum.Node.html#variant.FunctionExpr +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct FunctionDecl { + name: Box, + parameters: Box<[FormalParameter]>, + body: StatementList, +} + +impl FunctionDecl { + /// Creates a new function declaration. + pub(in crate::syntax) fn new(name: N, parameters: P, body: B) -> Self + where + N: Into>, + P: Into>, + B: Into, + { + Self { + name: name.into(), + parameters: parameters.into(), + body: body.into(), + } + } + + /// Gets the name of the function declaration. + pub fn name(&self) -> &str { + &self.name + } + + /// Gets the list of parameters of the function declaration. + pub fn parameters(&self) -> &[FormalParameter] { + &self.parameters + } + + /// Gets the body of the function declaration. + pub fn body(&self) -> &[Node] { + self.body.statements() + } + + /// Implements the display formatting with indentation. + pub(in crate::syntax::ast::node) fn display( + &self, + f: &mut fmt::Formatter<'_>, + indentation: usize, + ) -> fmt::Result { + write!(f, "function {}(", self.name)?; + join_nodes(f, &self.parameters)?; + f.write_str(") {{")?; + + self.body.display(f, indentation + 1)?; + + writeln!(f, "}}") + } +} + +impl Executable for FunctionDecl { + fn run(&self, interpreter: &mut Context) -> Result { + let _timer = BoaProfiler::global().start_event("FunctionDecl", "exec"); + let val = interpreter.create_function( + self.parameters().to_vec(), + self.body().to_vec(), + FunctionFlags::CALLABLE | FunctionFlags::CONSTRUCTABLE, + ); + + // Set the name and assign it in the current environment + val.set_field("name", self.name()); + interpreter.realm_mut().environment.create_mutable_binding( + self.name().to_owned(), + false, + VariableScope::Function, + ); + + interpreter + .realm_mut() + .environment + .initialize_binding(self.name(), val); + + Ok(Value::undefined()) + } +} + +impl From for Node { + fn from(decl: FunctionDecl) -> Self { + Self::FunctionDecl(decl) + } +} + +impl fmt::Display for FunctionDecl { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.display(f, 0) + } +} diff --git a/boa/src/syntax/ast/node/declaration/function_expr/mod.rs b/boa/src/syntax/ast/node/declaration/function_expr/mod.rs new file mode 100644 index 0000000000..d2749b9a95 --- /dev/null +++ b/boa/src/syntax/ast/node/declaration/function_expr/mod.rs @@ -0,0 +1,113 @@ +use crate::{ + builtins::function::FunctionFlags, + exec::Executable, + syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList}, + Context, Result, Value, +}; +use gc::{Finalize, Trace}; +use std::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// The `function` expression defines a function with the specified parameters. +/// +/// A function created with a function expression is a `Function` object and has all the +/// properties, methods and behavior of `Function`. +/// +/// A function can also be created using a declaration (see function expression). +/// +/// By default, functions return `undefined`. To return any other value, the function must have +/// a return statement that specifies the value to return. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#sec-terms-and-definitions-function +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct FunctionExpr { + name: Option>, + parameters: Box<[FormalParameter]>, + body: StatementList, +} + +impl FunctionExpr { + /// Creates a new function expression + pub(in crate::syntax) fn new(name: N, parameters: P, body: B) -> Self + where + N: Into>>, + P: Into>, + B: Into, + { + Self { + name: name.into(), + parameters: parameters.into(), + body: body.into(), + } + } + + /// Gets the name of the function declaration. + pub fn name(&self) -> Option<&str> { + self.name.as_ref().map(Box::as_ref) + } + + /// Gets the list of parameters of the function declaration. + pub fn parameters(&self) -> &[FormalParameter] { + &self.parameters + } + + /// Gets the body of the function declaration. + pub fn body(&self) -> &[Node] { + self.body.statements() + } + + /// Implements the display formatting with indentation. + pub(in crate::syntax::ast::node) fn display( + &self, + f: &mut fmt::Formatter<'_>, + indentation: usize, + ) -> fmt::Result { + f.write_str("function")?; + if let Some(ref name) = self.name { + write!(f, " {}", name)?; + } + f.write_str("(")?; + join_nodes(f, &self.parameters)?; + f.write_str(") {{")?; + + self.body.display(f, indentation + 1)?; + + writeln!(f, "}}") + } +} + +impl Executable for FunctionExpr { + fn run(&self, interpreter: &mut Context) -> Result { + let val = interpreter.create_function( + self.parameters().to_vec(), + self.body().to_vec(), + FunctionFlags::CALLABLE | FunctionFlags::CONSTRUCTABLE, + ); + + if let Some(name) = self.name() { + val.set_field("name", Value::from(name)); + } + + Ok(val) + } +} + +impl fmt::Display for FunctionExpr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.display(f, 0) + } +} + +impl From for Node { + fn from(expr: FunctionExpr) -> Self { + Self::FunctionExpr(expr) + } +} diff --git a/boa/src/syntax/ast/node/declaration/let_decl_list/mod.rs b/boa/src/syntax/ast/node/declaration/let_decl_list/mod.rs new file mode 100644 index 0000000000..064492611a --- /dev/null +++ b/boa/src/syntax/ast/node/declaration/let_decl_list/mod.rs @@ -0,0 +1,138 @@ +use crate::{ + environment::lexical_environment::VariableScope, + exec::Executable, + syntax::ast::node::{join_nodes, Identifier, Node}, + Context, Result, Value, +}; +use gc::{Finalize, Trace}; +use std::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// The `let` statement declares a block scope local variable, optionally initializing it to a +/// value. +/// +/// +/// `let` allows you to declare variables that are limited to a scope of a block statement, or +/// expression on which it is used, unlike the `var` keyword, which defines a variable +/// globally, or locally to an entire function regardless of block scope. +/// +/// Just like const the `let` does not create properties of the window object when declared +/// globally (in the top-most scope). +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#sec-let-and-const-declarations +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct LetDeclList { + #[cfg_attr(feature = "serde", serde(flatten))] + list: Box<[LetDecl]>, +} + +impl Executable for LetDeclList { + fn run(&self, interpreter: &mut Context) -> Result { + for var in self.as_ref() { + let val = match var.init() { + Some(v) => v.run(interpreter)?, + None => Value::undefined(), + }; + interpreter.realm_mut().environment.create_mutable_binding( + var.name().to_owned(), + false, + VariableScope::Block, + ); + interpreter + .realm_mut() + .environment + .initialize_binding(var.name(), val); + } + Ok(Value::undefined()) + } +} + +impl From for LetDeclList +where + T: Into>, +{ + fn from(list: T) -> Self { + Self { list: list.into() } + } +} + +impl From for LetDeclList { + fn from(decl: LetDecl) -> Self { + Self { + list: Box::new([decl]), + } + } +} + +impl AsRef<[LetDecl]> for LetDeclList { + fn as_ref(&self) -> &[LetDecl] { + &self.list + } +} + +impl fmt::Display for LetDeclList { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if !self.list.is_empty() { + write!(f, "let ")?; + join_nodes(f, &self.list) + } else { + Ok(()) + } + } +} + +impl From for Node { + fn from(list: LetDeclList) -> Self { + Self::LetDeclList(list) + } +} + +/// Individual constant declaration. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct LetDecl { + name: Identifier, + init: Option, +} + +impl fmt::Display for LetDecl { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.name, f)?; + if let Some(ref init) = self.init { + write!(f, " = {}", init)?; + } + Ok(()) + } +} + +impl LetDecl { + /// Creates a new variable declaration. + pub(in crate::syntax) fn new(name: N, init: I) -> Self + where + N: Into, + I: Into>, + { + Self { + name: name.into(), + init: init.into(), + } + } + + /// Gets the name of the variable. + pub fn name(&self) -> &str { + self.name.as_ref() + } + + /// Gets the initialization node for the variable, if any. + pub fn init(&self) -> Option<&Node> { + self.init.as_ref() + } +} diff --git a/boa/src/syntax/ast/node/declaration/mod.rs b/boa/src/syntax/ast/node/declaration/mod.rs new file mode 100644 index 0000000000..9e5d181a81 --- /dev/null +++ b/boa/src/syntax/ast/node/declaration/mod.rs @@ -0,0 +1,17 @@ +//! Declaration nodes + +pub mod arrow_function_decl; +pub mod const_decl_list; +pub mod function_decl; +pub mod function_expr; +pub mod let_decl_list; +pub mod var_decl_list; + +pub use self::{ + arrow_function_decl::ArrowFunctionDecl, + const_decl_list::{ConstDecl, ConstDeclList}, + function_decl::FunctionDecl, + function_expr::FunctionExpr, + let_decl_list::{LetDecl, LetDeclList}, + var_decl_list::{VarDecl, VarDeclList}, +}; diff --git a/boa/src/syntax/ast/node/declaration/var_decl_list/mod.rs b/boa/src/syntax/ast/node/declaration/var_decl_list/mod.rs new file mode 100644 index 0000000000..07023293d8 --- /dev/null +++ b/boa/src/syntax/ast/node/declaration/var_decl_list/mod.rs @@ -0,0 +1,144 @@ +use crate::{ + environment::lexical_environment::VariableScope, + exec::Executable, + syntax::ast::node::{join_nodes, Identifier, Node}, + Context, Result, Value, +}; +use gc::{Finalize, Trace}; +use std::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// The `var` statement declares a variable, optionally initializing it to a value. +/// +/// var declarations, wherever they occur, are processed before any code is executed. This is +/// called hoisting, and is discussed further below. +/// +/// The scope of a variable declared with var is its current execution context, which is either +/// the enclosing function or, for variables declared outside any function, global. If you +/// re-declare a JavaScript variable, it will not lose its value. +/// +/// Assigning a value to an undeclared variable implicitly creates it as a global variable (it +/// becomes a property of the global object) when the assignment is executed. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#prod-VariableStatement +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct VarDeclList { + #[cfg_attr(feature = "serde", serde(flatten))] + vars: Box<[VarDecl]>, +} + +impl Executable for VarDeclList { + fn run(&self, interpreter: &mut Context) -> Result { + for var in self.as_ref() { + let val = match var.init() { + Some(v) => v.run(interpreter)?, + None => Value::undefined(), + }; + let environment = &mut interpreter.realm_mut().environment; + + if environment.has_binding(var.name()) { + if var.init().is_some() { + environment.set_mutable_binding(var.name(), val, true); + } + } else { + environment.create_mutable_binding( + var.name().to_owned(), + false, + VariableScope::Function, + ); + environment.initialize_binding(var.name(), val); + } + } + Ok(Value::undefined()) + } +} + +impl From for VarDeclList +where + T: Into>, +{ + fn from(list: T) -> Self { + Self { vars: list.into() } + } +} + +impl From for VarDeclList { + fn from(decl: VarDecl) -> Self { + Self { + vars: Box::new([decl]), + } + } +} + +impl AsRef<[VarDecl]> for VarDeclList { + fn as_ref(&self) -> &[VarDecl] { + &self.vars + } +} + +impl fmt::Display for VarDeclList { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if !self.vars.is_empty() { + write!(f, "var ")?; + join_nodes(f, &self.vars) + } else { + Ok(()) + } + } +} + +impl From for Node { + fn from(list: VarDeclList) -> Self { + Self::VarDeclList(list) + } +} + +/// Individual variable declaration. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct VarDecl { + name: Identifier, + init: Option, +} + +impl fmt::Display for VarDecl { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.name, f)?; + if let Some(ref init) = self.init { + write!(f, " = {}", init)?; + } + Ok(()) + } +} + +impl VarDecl { + /// Creates a new variable declaration. + pub(in crate::syntax) fn new(name: N, init: I) -> Self + where + N: Into, + I: Into>, + { + Self { + name: name.into(), + init: init.into(), + } + } + + /// Gets the name of the variable. + pub fn name(&self) -> &str { + self.name.as_ref() + } + + /// Gets the initialization node for the variable, if any. + pub fn init(&self) -> Option<&Node> { + self.init.as_ref() + } +} diff --git a/boa/src/syntax/ast/node/expression.rs b/boa/src/syntax/ast/node/expression.rs deleted file mode 100644 index 5c2b8ae12f..0000000000 --- a/boa/src/syntax/ast/node/expression.rs +++ /dev/null @@ -1,118 +0,0 @@ -//! Expression nodes. - -use super::{join_nodes, Node}; -use gc::{Finalize, Trace}; -use std::fmt; - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -/// Calling the function actually performs the specified actions with the indicated parameters. -/// -/// Defining a function does not execute it. Defining it simply names the function and -/// specifies what to do when the function is called. Functions must be in scope when they are -/// called, but the function declaration can be hoisted. The scope of a function is the -/// function in which it is declared (or the entire program, if it is declared at the top -/// level). -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#prod-CallExpression -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions#Calling_functions -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] -pub struct Call { - expr: Box, - args: Box<[Node]>, -} - -impl Call { - /// Creates a new `Call` AST node. - pub fn new(expr: E, args: A) -> Self - where - E: Into, - A: Into>, - { - Self { - expr: Box::new(expr.into()), - args: args.into(), - } - } - - /// Gets the name of the function call. - pub fn expr(&self) -> &Node { - &self.expr - } - - /// Retrieves the arguments passed to the function. - pub fn args(&self) -> &[Node] { - &self.args - } -} - -impl fmt::Display for Call { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}(", self.expr)?; - join_nodes(f, &self.args)?; - f.write_str(")") - } -} - -impl From for Node { - fn from(call: Call) -> Self { - Self::Call(call) - } -} - -/// The `new` operator lets developers create an instance of a user-defined object type or of -/// one of the built-in object types that has a constructor function. -/// -/// The new keyword does the following things: -/// - Creates a blank, plain JavaScript object; -/// - Links (sets the constructor of) this object to another object; -/// - Passes the newly created object from Step 1 as the this context; -/// - Returns this if the function doesn't return its own object. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#prod-NewExpression -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] -pub struct New { - call: Call, -} - -impl New { - /// Gets the name of the function call. - pub fn expr(&self) -> &Node { - &self.call.expr() - } - - /// Retrieves the arguments passed to the function. - pub fn args(&self) -> &[Node] { - &self.call.args() - } -} - -impl From for New { - fn from(call: Call) -> Self { - Self { call } - } -} - -impl fmt::Display for New { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "new {}", self.call) - } -} - -impl From for Node { - fn from(new: New) -> Self { - Self::New(new) - } -} diff --git a/boa/src/syntax/ast/node/expression/call/mod.rs b/boa/src/syntax/ast/node/expression/call/mod.rs new file mode 100644 index 0000000000..60321ed8e0 --- /dev/null +++ b/boa/src/syntax/ast/node/expression/call/mod.rs @@ -0,0 +1,118 @@ +use crate::{ + exec::Executable, + exec::InterpreterState, + syntax::ast::node::{join_nodes, Node}, + value::{Type, Value}, + BoaProfiler, Context, Result, +}; +use gc::{Finalize, Trace}; +use std::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Calling the function actually performs the specified actions with the indicated parameters. +/// +/// Defining a function does not execute it. Defining it simply names the function and +/// specifies what to do when the function is called. Functions must be in scope when they are +/// called, but the function declaration can be hoisted. The scope of a function is the +/// function in which it is declared (or the entire program, if it is declared at the top +/// level). +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#prod-CallExpression +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions#Calling_functions +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct Call { + expr: Box, + args: Box<[Node]>, +} + +impl Call { + /// Creates a new `Call` AST node. + pub fn new(expr: E, args: A) -> Self + where + E: Into, + A: Into>, + { + Self { + expr: Box::new(expr.into()), + args: args.into(), + } + } + + /// Gets the name of the function call. + pub fn expr(&self) -> &Node { + &self.expr + } + + /// Retrieves the arguments passed to the function. + pub fn args(&self) -> &[Node] { + &self.args + } +} + +impl Executable for Call { + fn run(&self, interpreter: &mut Context) -> Result { + let _timer = BoaProfiler::global().start_event("Call", "exec"); + let (this, func) = match self.expr() { + Node::GetConstField(ref get_const_field) => { + let mut obj = get_const_field.obj().run(interpreter)?; + if obj.get_type() != Type::Object { + obj = Value::Object(obj.to_object(interpreter)?); + } + (obj.clone(), obj.get_field(get_const_field.field())) + } + Node::GetField(ref get_field) => { + let obj = get_field.obj().run(interpreter)?; + let field = get_field.field().run(interpreter)?; + ( + obj.clone(), + obj.get_field(field.to_property_key(interpreter)?), + ) + } + _ => ( + interpreter.realm().global_obj.clone(), + self.expr().run(interpreter)?, + ), // 'this' binding should come from the function's self-contained environment + }; + let mut v_args = Vec::with_capacity(self.args().len()); + for arg in self.args() { + if let Node::Spread(ref x) = arg { + let val = x.run(interpreter)?; + let mut vals = interpreter.extract_array_properties(&val).unwrap(); + v_args.append(&mut vals); + break; // after spread we don't accept any new arguments + } + v_args.push(arg.run(interpreter)?); + } + + // execute the function call itself + let fnct_result = interpreter.call(&func, &this, &v_args); + + // unset the early return flag + interpreter + .executor() + .set_current_state(InterpreterState::Executing); + + fnct_result + } +} + +impl fmt::Display for Call { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}(", self.expr)?; + join_nodes(f, &self.args)?; + f.write_str(")") + } +} + +impl From for Node { + fn from(call: Call) -> Self { + Self::Call(call) + } +} diff --git a/boa/src/syntax/ast/node/expression/mod.rs b/boa/src/syntax/ast/node/expression/mod.rs new file mode 100644 index 0000000000..aaa67d8b6e --- /dev/null +++ b/boa/src/syntax/ast/node/expression/mod.rs @@ -0,0 +1,6 @@ +//! Expression nodes + +pub mod call; +pub mod new; + +pub use self::{call::Call, new::New}; diff --git a/boa/src/syntax/ast/node/expression/new/mod.rs b/boa/src/syntax/ast/node/expression/new/mod.rs new file mode 100644 index 0000000000..eeb47dbed2 --- /dev/null +++ b/boa/src/syntax/ast/node/expression/new/mod.rs @@ -0,0 +1,79 @@ +use crate::{ + exec::Executable, + syntax::ast::node::{Call, Node}, + value::Value, + BoaProfiler, Context, Result, +}; +use gc::{Finalize, Trace}; +use std::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// The `new` operator lets developers create an instance of a user-defined object type or of +/// one of the built-in object types that has a constructor function. +/// +/// The new keyword does the following things: +/// - Creates a blank, plain JavaScript object; +/// - Links (sets the constructor of) this object to another object; +/// - Passes the newly created object from Step 1 as the this context; +/// - Returns this if the function doesn't return its own object. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#prod-NewExpression +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct New { + call: Call, +} + +impl New { + /// Gets the name of the function call. + pub fn expr(&self) -> &Node { + &self.call.expr() + } + + /// Retrieves the arguments passed to the function. + pub fn args(&self) -> &[Node] { + &self.call.args() + } +} + +impl Executable for New { + fn run(&self, interpreter: &mut Context) -> Result { + let _timer = BoaProfiler::global().start_event("New", "exec"); + + let func_object = self.expr().run(interpreter)?; + let mut v_args = Vec::with_capacity(self.args().len()); + for arg in self.args() { + v_args.push(arg.run(interpreter)?); + } + + match func_object { + Value::Object(ref object) => object.construct(&v_args, interpreter), + _ => Ok(Value::undefined()), + } + } +} + +impl From for New { + fn from(call: Call) -> Self { + Self { call } + } +} + +impl fmt::Display for New { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "new {}", self.call) + } +} + +impl From for Node { + fn from(new: New) -> Self { + Self::New(new) + } +} diff --git a/boa/src/syntax/ast/node/field.rs b/boa/src/syntax/ast/node/field/get_const_field/mod.rs similarity index 52% rename from boa/src/syntax/ast/node/field.rs rename to boa/src/syntax/ast/node/field/get_const_field/mod.rs index 42272c811c..daa8c5e540 100644 --- a/boa/src/syntax/ast/node/field.rs +++ b/boa/src/syntax/ast/node/field/get_const_field/mod.rs @@ -1,6 +1,9 @@ -//! Field AST node. -//! -use super::Node; +use crate::{ + exec::Executable, + syntax::ast::node::Node, + value::{Type, Value}, + Context, Result, +}; use gc::{Finalize, Trace}; use std::fmt; @@ -59,6 +62,17 @@ impl GetConstField { } } +impl Executable for GetConstField { + fn run(&self, interpreter: &mut Context) -> Result { + let mut obj = self.obj().run(interpreter)?; + if obj.get_type() != Type::Object { + obj = Value::Object(obj.to_object(interpreter)?); + } + + Ok(obj.get_field(self.field())) + } +} + impl fmt::Display for GetConstField { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}.{}", self.obj(), self.field()) @@ -70,66 +84,3 @@ impl From for Node { Self::GetConstField(get_const_field) } } - -/// This property accessor provides access to an object's properties by using the -/// [bracket notation][mdn]. -/// -/// In the object[property_name] syntax, the property_name is just a string or -/// [Symbol][symbol]. So, it can be any string, including '1foo', '!bar!', or even ' ' (a -/// space). -/// -/// One can think of an object as an associative array (a.k.a. map, dictionary, hash, lookup -/// table). The keys in this array are the names of the object's properties. -/// -/// It's typical when speaking of an object's properties to make a distinction between -/// properties and methods. However, the property/method distinction is little more than a -/// convention. A method is simply a property that can be called (for example, if it has a -/// reference to a Function instance as its value). -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-property-accessors -/// [symbol]: https://developer.mozilla.org/en-US/docs/Glossary/Symbol -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors#Bracket_notation -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] -pub struct GetField { - obj: Box, - field: Box, -} - -impl GetField { - pub fn obj(&self) -> &Node { - &self.obj - } - - pub fn field(&self) -> &Node { - &self.field - } - - /// Creates a `GetField` AST node. - pub fn new(value: V, field: F) -> Self - where - V: Into, - F: Into, - { - Self { - obj: Box::new(value.into()), - field: Box::new(field.into()), - } - } -} - -impl fmt::Display for GetField { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}[{}]", self.obj(), self.field()) - } -} - -impl From for Node { - fn from(get_field: GetField) -> Self { - Self::GetField(get_field) - } -} diff --git a/boa/src/syntax/ast/node/field/get_field/mod.rs b/boa/src/syntax/ast/node/field/get_field/mod.rs new file mode 100644 index 0000000000..b418e46efc --- /dev/null +++ b/boa/src/syntax/ast/node/field/get_field/mod.rs @@ -0,0 +1,86 @@ +use crate::{ + exec::Executable, + syntax::ast::node::Node, + value::{Type, Value}, + Context, Result, +}; +use gc::{Finalize, Trace}; +use std::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// This property accessor provides access to an object's properties by using the +/// [bracket notation][mdn]. +/// +/// In the object[property_name] syntax, the property_name is just a string or +/// [Symbol][symbol]. So, it can be any string, including '1foo', '!bar!', or even ' ' (a +/// space). +/// +/// One can think of an object as an associative array (a.k.a. map, dictionary, hash, lookup +/// table). The keys in this array are the names of the object's properties. +/// +/// It's typical when speaking of an object's properties to make a distinction between +/// properties and methods. However, the property/method distinction is little more than a +/// convention. A method is simply a property that can be called (for example, if it has a +/// reference to a Function instance as its value). +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#sec-property-accessors +/// [symbol]: https://developer.mozilla.org/en-US/docs/Glossary/Symbol +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors#Bracket_notation +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct GetField { + obj: Box, + field: Box, +} + +impl GetField { + pub fn obj(&self) -> &Node { + &self.obj + } + + pub fn field(&self) -> &Node { + &self.field + } + + /// Creates a `GetField` AST node. + pub fn new(value: V, field: F) -> Self + where + V: Into, + F: Into, + { + Self { + obj: Box::new(value.into()), + field: Box::new(field.into()), + } + } +} + +impl Executable for GetField { + fn run(&self, interpreter: &mut Context) -> Result { + let mut obj = self.obj().run(interpreter)?; + if obj.get_type() != Type::Object { + obj = Value::Object(obj.to_object(interpreter)?); + } + let field = self.field().run(interpreter)?; + + Ok(obj.get_field(field.to_property_key(interpreter)?)) + } +} + +impl fmt::Display for GetField { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}[{}]", self.obj(), self.field()) + } +} + +impl From for Node { + fn from(get_field: GetField) -> Self { + Self::GetField(get_field) + } +} diff --git a/boa/src/syntax/ast/node/field/mod.rs b/boa/src/syntax/ast/node/field/mod.rs new file mode 100644 index 0000000000..8c52719e8f --- /dev/null +++ b/boa/src/syntax/ast/node/field/mod.rs @@ -0,0 +1,6 @@ +//! Field nodes + +pub mod get_const_field; +pub mod get_field; + +pub use self::{get_const_field::GetConstField, get_field::GetField}; diff --git a/boa/src/syntax/ast/node/identifier.rs b/boa/src/syntax/ast/node/identifier/mod.rs similarity index 80% rename from boa/src/syntax/ast/node/identifier.rs rename to boa/src/syntax/ast/node/identifier/mod.rs index 80c8cfeb7d..fe0378d1b6 100644 --- a/boa/src/syntax/ast/node/identifier.rs +++ b/boa/src/syntax/ast/node/identifier/mod.rs @@ -1,6 +1,6 @@ //! Local identifier node. -use super::Node; +use crate::{exec::Executable, syntax::ast::node::Node, Context, Result, Value}; use gc::{Finalize, Trace}; use std::fmt; @@ -30,6 +30,16 @@ pub struct Identifier { ident: Box, } +impl Executable for Identifier { + fn run(&self, interpreter: &mut Context) -> Result { + interpreter + .realm() + .environment + .get_binding_value(self.as_ref()) + .ok_or_else(|| interpreter.construct_reference_error(self.as_ref())) + } +} + impl fmt::Display for Identifier { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.ident, f) diff --git a/boa/src/syntax/ast/node/iteration.rs b/boa/src/syntax/ast/node/iteration.rs deleted file mode 100644 index 8b2dcdfc8d..0000000000 --- a/boa/src/syntax/ast/node/iteration.rs +++ /dev/null @@ -1,333 +0,0 @@ -use super::Node; -use gc::{Finalize, Trace}; -use std::fmt; - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -/// The `for` statement creates a loop that consists of three optional expressions. -/// -/// A `for` loop repeats until a specified condition evaluates to `false`. -/// The JavaScript for loop is similar to the Java and C for loop. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#prod-ForDeclaration -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] -pub struct ForLoop { - #[cfg_attr(feature = "serde", serde(flatten))] - inner: Box, - label: Option>, -} - -impl ForLoop { - /// Creates a new for loop AST node. - pub(in crate::syntax) fn new(init: I, condition: C, final_expr: E, body: B) -> Self - where - I: Into>, - C: Into>, - E: Into>, - B: Into, - { - Self { - inner: Box::new(InnerForLoop::new(init, condition, final_expr, body)), - label: None, - } - } - - /// Gets the initialization node. - pub fn init(&self) -> Option<&Node> { - self.inner.init() - } - - /// Gets the loop condition node. - pub fn condition(&self) -> Option<&Node> { - self.inner.condition() - } - - /// Gets the final expression node. - pub fn final_expr(&self) -> Option<&Node> { - self.inner.final_expr() - } - - /// Gets the body of the for loop. - pub fn body(&self) -> &Node { - self.inner.body() - } - - pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { - f.write_str("for (")?; - if let Some(init) = self.init() { - fmt::Display::fmt(init, f)?; - } - f.write_str(";")?; - if let Some(condition) = self.condition() { - fmt::Display::fmt(condition, f)?; - } - f.write_str(";")?; - if let Some(final_expr) = self.final_expr() { - fmt::Display::fmt(final_expr, f)?; - } - writeln!(f, ") {{")?; - - self.inner.body().display(f, indentation + 1)?; - - write!(f, "}}") - } - - pub fn label(&self) -> Option<&str> { - self.label.as_ref().map(Box::as_ref) - } - - pub fn set_label(&mut self, label: Box) { - self.label = Some(label); - } -} - -impl fmt::Display for ForLoop { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.display(f, 0) - } -} - -impl From for Node { - fn from(for_loop: ForLoop) -> Self { - Self::ForLoop(for_loop) - } -} - -/// Inner structure to avoid multiple indirections in the heap. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] -struct InnerForLoop { - init: Option, - condition: Option, - final_expr: Option, - body: Node, -} - -impl InnerForLoop { - /// Creates a new inner for loop. - fn new(init: I, condition: C, final_expr: E, body: B) -> Self - where - I: Into>, - C: Into>, - E: Into>, - B: Into, - { - Self { - init: init.into(), - condition: condition.into(), - final_expr: final_expr.into(), - body: body.into(), - } - } - - /// Gets the initialization node. - fn init(&self) -> Option<&Node> { - self.init.as_ref() - } - - /// Gets the loop condition node. - fn condition(&self) -> Option<&Node> { - self.condition.as_ref() - } - - /// Gets the final expression node. - fn final_expr(&self) -> Option<&Node> { - self.final_expr.as_ref() - } - - /// Gets the body of the for loop. - fn body(&self) -> &Node { - &self.body - } -} - -/// The `while` statement creates a loop that executes a specified statement as long as the -/// test condition evaluates to `true`. -/// -/// The condition is evaluated before executing the statement. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#prod-grammar-notation-WhileStatement -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/while -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] -pub struct WhileLoop { - cond: Box, - expr: Box, - label: Option>, -} - -impl WhileLoop { - pub fn cond(&self) -> &Node { - &self.cond - } - - pub fn expr(&self) -> &Node { - &self.expr - } - - pub fn label(&self) -> Option<&str> { - self.label.as_ref().map(Box::as_ref) - } - - /// Creates a `WhileLoop` AST node. - pub fn new(condition: C, body: B) -> Self - where - C: Into, - B: Into, - { - Self { - cond: Box::new(condition.into()), - expr: Box::new(body.into()), - label: None, - } - } - - pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { - write!(f, "while ({}) ", self.cond())?; - self.expr().display(f, indentation) - } -} - -impl fmt::Display for WhileLoop { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.display(f, 0) - } -} - -impl From for Node { - fn from(while_loop: WhileLoop) -> Self { - Self::WhileLoop(while_loop) - } -} - -/// The `do...while` statement creates a loop that executes a specified statement until the -/// test condition evaluates to false. -/// -/// The condition is evaluated after executing the statement, resulting in the specified -/// statement executing at least once. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-do-while-statement -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/do...while -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] -pub struct DoWhileLoop { - body: Box, - cond: Box, - label: Option>, -} - -impl DoWhileLoop { - pub fn body(&self) -> &Node { - &self.body - } - - pub fn cond(&self) -> &Node { - &self.cond - } - - pub fn label(&self) -> Option<&str> { - self.label.as_ref().map(Box::as_ref) - } - - /// Creates a `DoWhileLoop` AST node. - pub fn new(body: B, condition: C) -> Self - where - B: Into, - C: Into, - { - Self { - body: Box::new(body.into()), - cond: Box::new(condition.into()), - label: None, - } - } - - pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { - write!(f, "do")?; - self.body().display(f, indentation)?; - write!(f, "while ({})", self.cond()) - } -} - -impl fmt::Display for DoWhileLoop { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.display(f, 0) - } -} - -impl From for Node { - fn from(do_while: DoWhileLoop) -> Self { - Self::DoWhileLoop(do_while) - } -} - -/// The `continue` statement terminates execution of the statements in the current iteration of -/// the current or labeled loop, and continues execution of the loop with the next iteration. -/// -/// The continue statement can include an optional label that allows the program to jump to the -/// next iteration of a labeled loop statement instead of the current loop. In this case, the -/// continue statement needs to be nested within this labeled statement. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#prod-ContinueStatement -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/continue -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] -pub struct Continue { - label: Option>, -} - -impl Continue { - pub fn label(&self) -> Option<&str> { - self.label.as_ref().map(Box::as_ref) - } - - /// Creates a `Continue` AST node. - pub fn new(label: OL) -> Self - where - L: Into>, - OL: Into>, - { - Self { - label: label.into().map(L::into), - } - } -} - -impl fmt::Display for Continue { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "continue{}", - if let Some(label) = self.label() { - format!(" {}", label) - } else { - String::new() - } - ) - } -} - -impl From for Node { - fn from(cont: Continue) -> Node { - Self::Continue(cont) - } -} diff --git a/boa/src/syntax/ast/node/iteration/continue_node/mod.rs b/boa/src/syntax/ast/node/iteration/continue_node/mod.rs new file mode 100644 index 0000000000..a551ccdb16 --- /dev/null +++ b/boa/src/syntax/ast/node/iteration/continue_node/mod.rs @@ -0,0 +1,76 @@ +use crate::{ + exec::{Executable, InterpreterState}, + syntax::ast::node::Node, + Context, Result, Value, +}; +use gc::{Finalize, Trace}; +use std::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// The `continue` statement terminates execution of the statements in the current iteration of +/// the current or labeled loop, and continues execution of the loop with the next iteration. +/// +/// The continue statement can include an optional label that allows the program to jump to the +/// next iteration of a labeled loop statement instead of the current loop. In this case, the +/// continue statement needs to be nested within this labeled statement. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#prod-ContinueStatement +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/continue +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct Continue { + label: Option>, +} + +impl Continue { + pub fn label(&self) -> Option<&str> { + self.label.as_ref().map(Box::as_ref) + } + + /// Creates a `Continue` AST node. + pub fn new(label: OL) -> Self + where + L: Into>, + OL: Into>, + { + Self { + label: label.into().map(L::into), + } + } +} + +impl Executable for Continue { + fn run(&self, interpreter: &mut Context) -> Result { + interpreter + .executor() + .set_current_state(InterpreterState::Continue(self.label().map(Box::from))); + + Ok(Value::undefined()) + } +} + +impl fmt::Display for Continue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "continue{}", + if let Some(label) = self.label() { + format!(" {}", label) + } else { + String::new() + } + ) + } +} + +impl From for Node { + fn from(cont: Continue) -> Node { + Self::Continue(cont) + } +} diff --git a/boa/src/syntax/ast/node/iteration/do_while_loop/mod.rs b/boa/src/syntax/ast/node/iteration/do_while_loop/mod.rs new file mode 100644 index 0000000000..9ebf49f702 --- /dev/null +++ b/boa/src/syntax/ast/node/iteration/do_while_loop/mod.rs @@ -0,0 +1,138 @@ +use crate::{ + exec::{Executable, InterpreterState}, + syntax::ast::node::Node, + Context, Result, Value, +}; +use gc::{Finalize, Trace}; +use std::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// The `do...while` statement creates a loop that executes a specified statement until the +/// test condition evaluates to false. +/// +/// The condition is evaluated after executing the statement, resulting in the specified +/// statement executing at least once. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#sec-do-while-statement +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/do...while +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct DoWhileLoop { + body: Box, + cond: Box, + label: Option>, +} + +impl DoWhileLoop { + pub fn body(&self) -> &Node { + &self.body + } + + pub fn cond(&self) -> &Node { + &self.cond + } + + pub fn label(&self) -> Option<&str> { + self.label.as_ref().map(Box::as_ref) + } + + /// Creates a `DoWhileLoop` AST node. + pub fn new(body: B, condition: C) -> Self + where + B: Into, + C: Into, + { + Self { + body: Box::new(body.into()), + cond: Box::new(condition.into()), + label: None, + } + } + + pub(in crate::syntax::ast::node) fn display( + &self, + f: &mut fmt::Formatter<'_>, + indentation: usize, + ) -> fmt::Result { + write!(f, "do")?; + self.body().display(f, indentation)?; + write!(f, "while ({})", self.cond()) + } +} + +impl Executable for DoWhileLoop { + fn run(&self, interpreter: &mut Context) -> Result { + let mut result = self.body().run(interpreter)?; + match interpreter.executor().get_current_state() { + InterpreterState::Break(_label) => { + // TODO break to label. + + // Loops 'consume' breaks. + interpreter + .executor() + .set_current_state(InterpreterState::Executing); + return Ok(result); + } + InterpreterState::Continue(_label) => { + // TODO continue to label; + interpreter + .executor() + .set_current_state(InterpreterState::Executing); + // after breaking out of the block, continue execution of the loop + } + InterpreterState::Return => { + return Ok(result); + } + InterpreterState::Executing => { + // Continue execution. + } + } + + while self.cond().run(interpreter)?.to_boolean() { + result = self.body().run(interpreter)?; + match interpreter.executor().get_current_state() { + InterpreterState::Break(_label) => { + // TODO break to label. + + // Loops 'consume' breaks. + interpreter + .executor() + .set_current_state(InterpreterState::Executing); + break; + } + InterpreterState::Continue(_label) => { + // TODO continue to label. + interpreter + .executor() + .set_current_state(InterpreterState::Executing); + // after breaking out of the block, continue execution of the loop + } + InterpreterState::Return => { + return Ok(result); + } + InterpreterState::Executing => { + // Continue execution. + } + } + } + Ok(result) + } +} + +impl fmt::Display for DoWhileLoop { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.display(f, 0) + } +} + +impl From for Node { + fn from(do_while: DoWhileLoop) -> Self { + Self::DoWhileLoop(do_while) + } +} diff --git a/boa/src/syntax/ast/node/iteration/for_loop/mod.rs b/boa/src/syntax/ast/node/iteration/for_loop/mod.rs new file mode 100644 index 0000000000..8a8da1abed --- /dev/null +++ b/boa/src/syntax/ast/node/iteration/for_loop/mod.rs @@ -0,0 +1,210 @@ +use crate::{ + environment::lexical_environment::new_declarative_environment, + exec::{Executable, InterpreterState}, + syntax::ast::node::Node, + BoaProfiler, Context, Result, Value, +}; +use gc::{Finalize, Trace}; +use std::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// The `for` statement creates a loop that consists of three optional expressions. +/// +/// A `for` loop repeats until a specified condition evaluates to `false`. +/// The JavaScript for loop is similar to the Java and C for loop. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#prod-ForDeclaration +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct ForLoop { + #[cfg_attr(feature = "serde", serde(flatten))] + inner: Box, + label: Option>, +} + +impl ForLoop { + /// Creates a new for loop AST node. + pub(in crate::syntax) fn new(init: I, condition: C, final_expr: E, body: B) -> Self + where + I: Into>, + C: Into>, + E: Into>, + B: Into, + { + Self { + inner: Box::new(InnerForLoop::new(init, condition, final_expr, body)), + label: None, + } + } + + /// Gets the initialization node. + pub fn init(&self) -> Option<&Node> { + self.inner.init() + } + + /// Gets the loop condition node. + pub fn condition(&self) -> Option<&Node> { + self.inner.condition() + } + + /// Gets the final expression node. + pub fn final_expr(&self) -> Option<&Node> { + self.inner.final_expr() + } + + /// Gets the body of the for loop. + pub fn body(&self) -> &Node { + self.inner.body() + } + + pub(in crate::syntax::ast::node) fn display( + &self, + f: &mut fmt::Formatter<'_>, + indentation: usize, + ) -> fmt::Result { + f.write_str("for (")?; + if let Some(init) = self.init() { + fmt::Display::fmt(init, f)?; + } + f.write_str(";")?; + if let Some(condition) = self.condition() { + fmt::Display::fmt(condition, f)?; + } + f.write_str(";")?; + if let Some(final_expr) = self.final_expr() { + fmt::Display::fmt(final_expr, f)?; + } + writeln!(f, ") {{")?; + + self.inner.body().display(f, indentation + 1)?; + + write!(f, "}}") + } + + pub fn label(&self) -> Option<&str> { + self.label.as_ref().map(Box::as_ref) + } + + pub fn set_label(&mut self, label: Box) { + self.label = Some(label); + } +} + +impl Executable for ForLoop { + fn run(&self, interpreter: &mut Context) -> Result { + // Create the block environment. + let _timer = BoaProfiler::global().start_event("ForLoop", "exec"); + { + let env = &mut interpreter.realm_mut().environment; + env.push(new_declarative_environment(Some( + env.get_current_environment_ref().clone(), + ))); + } + + if let Some(init) = self.init() { + init.run(interpreter)?; + } + + while self + .condition() + .map(|cond| cond.run(interpreter).map(|v| v.to_boolean())) + .transpose()? + .unwrap_or(true) + { + let result = self.body().run(interpreter)?; + + match interpreter.executor().get_current_state() { + InterpreterState::Break(label) => { + handle_state_with_labels!(self, label, interpreter, break); + break; + } + InterpreterState::Continue(label) => { + handle_state_with_labels!(self, label, interpreter, continue); + } + + InterpreterState::Return => { + return Ok(result); + } + InterpreterState::Executing => { + // Continue execution. + } + } + + if let Some(final_expr) = self.final_expr() { + final_expr.run(interpreter)?; + } + } + + // pop the block env + let _ = interpreter.realm_mut().environment.pop(); + + Ok(Value::undefined()) + } +} + +impl fmt::Display for ForLoop { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.display(f, 0) + } +} + +impl From for Node { + fn from(for_loop: ForLoop) -> Self { + Self::ForLoop(for_loop) + } +} + +/// Inner structure to avoid multiple indirections in the heap. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +struct InnerForLoop { + init: Option, + condition: Option, + final_expr: Option, + body: Node, +} + +impl InnerForLoop { + /// Creates a new inner for loop. + fn new(init: I, condition: C, final_expr: E, body: B) -> Self + where + I: Into>, + C: Into>, + E: Into>, + B: Into, + { + Self { + init: init.into(), + condition: condition.into(), + final_expr: final_expr.into(), + body: body.into(), + } + } + + /// Gets the initialization node. + fn init(&self) -> Option<&Node> { + self.init.as_ref() + } + + /// Gets the loop condition node. + fn condition(&self) -> Option<&Node> { + self.condition.as_ref() + } + + /// Gets the final expression node. + fn final_expr(&self) -> Option<&Node> { + self.final_expr.as_ref() + } + + /// Gets the body of the for loop. + fn body(&self) -> &Node { + &self.body + } +} diff --git a/boa/src/syntax/ast/node/iteration/mod.rs b/boa/src/syntax/ast/node/iteration/mod.rs new file mode 100644 index 0000000000..73a227bb8f --- /dev/null +++ b/boa/src/syntax/ast/node/iteration/mod.rs @@ -0,0 +1,35 @@ +//! Iteration nodes + +pub use self::{ + continue_node::Continue, do_while_loop::DoWhileLoop, for_loop::ForLoop, while_loop::WhileLoop, +}; + +#[cfg(test)] +mod tests; + +// Checking labels for break and continue is the same operation for `ForLoop`, `While` and `DoWhile` +#[macro_use] +macro_rules! handle_state_with_labels { + ($self:ident, $label:ident, $interpreter:ident, $state:tt) => {{ + if let Some(brk_label) = $label { + if let Some(stmt_label) = $self.label() { + // Break from where we are, keeping "continue" set as the state + if stmt_label != brk_label.as_ref() { + break; + } + } else { + // if a label is set but the current block has no label, break + break; + } + } + + $interpreter + .executor() + .set_current_state(InterpreterState::Executing); + }}; +} + +pub mod continue_node; +pub mod do_while_loop; +pub mod for_loop; +pub mod while_loop; diff --git a/boa/src/exec/iteration/tests.rs b/boa/src/syntax/ast/node/iteration/tests.rs similarity index 100% rename from boa/src/exec/iteration/tests.rs rename to boa/src/syntax/ast/node/iteration/tests.rs diff --git a/boa/src/syntax/ast/node/iteration/while_loop/mod.rs b/boa/src/syntax/ast/node/iteration/while_loop/mod.rs new file mode 100644 index 0000000000..eb266f3742 --- /dev/null +++ b/boa/src/syntax/ast/node/iteration/while_loop/mod.rs @@ -0,0 +1,102 @@ +use crate::{ + exec::{Executable, InterpreterState}, + syntax::ast::node::Node, + Context, Result, Value, +}; +use gc::{Finalize, Trace}; +use std::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// The `while` statement creates a loop that executes a specified statement as long as the +/// test condition evaluates to `true`. +/// +/// The condition is evaluated before executing the statement. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#prod-grammar-notation-WhileStatement +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/while +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct WhileLoop { + cond: Box, + expr: Box, + label: Option>, +} + +impl WhileLoop { + pub fn cond(&self) -> &Node { + &self.cond + } + + pub fn expr(&self) -> &Node { + &self.expr + } + + pub fn label(&self) -> Option<&str> { + self.label.as_ref().map(Box::as_ref) + } + + /// Creates a `WhileLoop` AST node. + pub fn new(condition: C, body: B) -> Self + where + C: Into, + B: Into, + { + Self { + cond: Box::new(condition.into()), + expr: Box::new(body.into()), + label: None, + } + } + + pub(in crate::syntax::ast::node) fn display( + &self, + f: &mut fmt::Formatter<'_>, + indentation: usize, + ) -> fmt::Result { + write!(f, "while ({}) ", self.cond())?; + self.expr().display(f, indentation) + } +} + +impl Executable for WhileLoop { + fn run(&self, interpreter: &mut Context) -> Result { + let mut result = Value::undefined(); + while self.cond().run(interpreter)?.to_boolean() { + result = self.expr().run(interpreter)?; + match interpreter.executor().get_current_state() { + InterpreterState::Break(label) => { + handle_state_with_labels!(self, label, interpreter, break); + break; + } + InterpreterState::Continue(label) => { + handle_state_with_labels!(self, label, interpreter, continue) + } + InterpreterState::Return => { + return Ok(result); + } + InterpreterState::Executing => { + // Continue execution. + } + } + } + Ok(result) + } +} + +impl fmt::Display for WhileLoop { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.display(f, 0) + } +} + +impl From for Node { + fn from(while_loop: WhileLoop) -> Self { + Self::WhileLoop(while_loop) + } +} diff --git a/boa/src/syntax/ast/node/mod.rs b/boa/src/syntax/ast/node/mod.rs index 7042451acb..e039eb78c4 100644 --- a/boa/src/syntax/ast/node/mod.rs +++ b/boa/src/syntax/ast/node/mod.rs @@ -41,6 +41,7 @@ pub use self::{ try_node::{Catch, Finally, Try}, }; use super::Const; +use crate::{exec::Executable, BoaProfiler, Context, Result, Value}; use gc::{unsafe_empty_trace, Finalize, Trace}; use std::{ cmp::Ordering, @@ -240,6 +241,59 @@ impl Node { } } +impl Executable for Node { + fn run(&self, interpreter: &mut Context) -> Result { + let _timer = BoaProfiler::global().start_event("Executable", "exec"); + match *self { + Node::Const(Const::Null) => Ok(Value::null()), + Node::Const(Const::Num(num)) => Ok(Value::rational(num)), + Node::Const(Const::Int(num)) => Ok(Value::integer(num)), + Node::Const(Const::BigInt(ref num)) => Ok(Value::from(num.clone())), + Node::Const(Const::Undefined) => Ok(Value::Undefined), + // we can't move String from Const into value, because const is a garbage collected value + // Which means Drop() get's called on Const, but str will be gone at that point. + // Do Const values need to be garbage collected? We no longer need them once we've generated Values + Node::Const(Const::String(ref value)) => Ok(Value::string(value.to_string())), + Node::Const(Const::Bool(value)) => Ok(Value::boolean(value)), + Node::Block(ref block) => block.run(interpreter), + Node::Identifier(ref identifier) => identifier.run(interpreter), + Node::GetConstField(ref get_const_field_node) => get_const_field_node.run(interpreter), + Node::GetField(ref get_field) => get_field.run(interpreter), + Node::Call(ref call) => call.run(interpreter), + Node::WhileLoop(ref while_loop) => while_loop.run(interpreter), + Node::DoWhileLoop(ref do_while) => do_while.run(interpreter), + Node::ForLoop(ref for_loop) => for_loop.run(interpreter), + Node::If(ref if_smt) => if_smt.run(interpreter), + Node::ConditionalOp(ref op) => op.run(interpreter), + Node::Switch(ref switch) => switch.run(interpreter), + Node::Object(ref obj) => obj.run(interpreter), + Node::ArrayDecl(ref arr) => arr.run(interpreter), + // + Node::FunctionDecl(ref decl) => decl.run(interpreter), + // + Node::FunctionExpr(ref function_expr) => function_expr.run(interpreter), + Node::ArrowFunctionDecl(ref decl) => decl.run(interpreter), + Node::BinOp(ref op) => op.run(interpreter), + Node::UnaryOp(ref op) => op.run(interpreter), + Node::New(ref call) => call.run(interpreter), + Node::Return(ref ret) => ret.run(interpreter), + Node::Throw(ref throw) => throw.run(interpreter), + Node::Assign(ref op) => op.run(interpreter), + Node::VarDeclList(ref decl) => decl.run(interpreter), + Node::LetDeclList(ref decl) => decl.run(interpreter), + Node::ConstDeclList(ref decl) => decl.run(interpreter), + Node::Spread(ref spread) => spread.run(interpreter), + Node::This => { + // Will either return `this` binding or undefined + Ok(interpreter.realm().environment.get_this_binding()) + } + Node::Try(ref try_node) => try_node.run(interpreter), + Node::Break(ref break_node) => break_node.run(interpreter), + Node::Continue(ref continue_node) => continue_node.run(interpreter), + } + } +} + /// Utility to join multiple Nodes into a single string. fn join_nodes(f: &mut fmt::Formatter<'_>, nodes: &[N]) -> fmt::Result where diff --git a/boa/src/syntax/ast/node/object.rs b/boa/src/syntax/ast/node/object/mod.rs similarity index 65% rename from boa/src/syntax/ast/node/object.rs rename to boa/src/syntax/ast/node/object/mod.rs index b2fcbd6607..486b25b02d 100644 --- a/boa/src/syntax/ast/node/object.rs +++ b/boa/src/syntax/ast/node/object/mod.rs @@ -1,11 +1,13 @@ //! Object node. -use super::Node; +use crate::{ + exec::Executable, + syntax::ast::node::{MethodDefinitionKind, Node, PropertyDefinition}, + Context, Result, Value, +}; use gc::{Finalize, Trace}; use std::fmt; -use crate::syntax::ast::node::PropertyDefinition; - #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -41,7 +43,11 @@ impl Object { } /// Implements the display formatting with indentation. - pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indent: usize) -> fmt::Result { + pub(in crate::syntax::ast::node) fn display( + &self, + f: &mut fmt::Formatter<'_>, + indent: usize, + ) -> fmt::Result { f.write_str("{\n")?; for property in self.properties().iter() { match property { @@ -64,6 +70,37 @@ impl Object { } } +impl Executable for Object { + fn run(&self, interpreter: &mut Context) -> Result { + let global_val = &interpreter + .realm() + .environment + .get_global_object() + .expect("Could not get the global object"); + let obj = Value::new_object(Some(global_val)); + + // TODO: Implement the rest of the property types. + for property in self.properties().iter() { + match property { + PropertyDefinition::Property(key, value) => { + obj.set_field(key.clone(), value.run(interpreter)?); + } + PropertyDefinition::MethodDefinition(kind, name, func) => { + if let MethodDefinitionKind::Ordinary = kind { + obj.set_field(name.clone(), func.run(interpreter)?); + } else { + // TODO: Implement other types of MethodDefinitionKinds. + unimplemented!("other types of property method definitions."); + } + } + i => unimplemented!("{:?} type of property", i), + } + } + + Ok(obj) + } +} + impl fmt::Display for Object { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.display(f, 0) diff --git a/boa/src/syntax/ast/node/operator.rs b/boa/src/syntax/ast/node/operator.rs deleted file mode 100644 index 8725b82ba2..0000000000 --- a/boa/src/syntax/ast/node/operator.rs +++ /dev/null @@ -1,168 +0,0 @@ -use super::Node; -use crate::syntax::ast::op; -use gc::{Finalize, Trace}; -use std::fmt; - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -/// An assignment operator assigns a value to its left operand based on the value of its right -/// operand. -/// -/// Assignment operator (`=`), assigns the value of its right operand to its left operand. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#prod-AssignmentExpression -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] -pub struct Assign { - lhs: Box, - rhs: Box, -} - -impl Assign { - /// Creates an `Assign` AST node. - pub(in crate::syntax) fn new(lhs: L, rhs: R) -> Self - where - L: Into, - R: Into, - { - Self { - lhs: Box::new(lhs.into()), - rhs: Box::new(rhs.into()), - } - } - - /// Gets the left hand side of the assignment operation. - pub fn lhs(&self) -> &Node { - &self.lhs - } - - /// Gets the right hand side of the assignment operation. - pub fn rhs(&self) -> &Node { - &self.rhs - } -} - -impl fmt::Display for Assign { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{} = {}", self.lhs, self.rhs) - } -} - -impl From for Node { - fn from(op: Assign) -> Self { - Self::Assign(op) - } -} - -/// Binary operators requires two operands, one before the operator and one after the operator. -/// -/// More information: -/// - [MDN documentation][mdn] -/// -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Operators -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] -pub struct BinOp { - op: op::BinOp, - lhs: Box, - rhs: Box, -} - -impl BinOp { - /// Creates a `BinOp` AST node. - pub(in crate::syntax) fn new(op: O, lhs: L, rhs: R) -> Self - where - O: Into, - L: Into, - R: Into, - { - Self { - op: op.into(), - lhs: Box::new(lhs.into()), - rhs: Box::new(rhs.into()), - } - } - - /// Gets the binary operation of the node. - pub fn op(&self) -> op::BinOp { - self.op - } - - /// Gets the left hand side of the binary operation. - pub fn lhs(&self) -> &Node { - &self.lhs - } - - /// Gets the right hand side of the binary operation. - pub fn rhs(&self) -> &Node { - &self.rhs - } -} - -impl fmt::Display for BinOp { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{} {} {}", self.lhs, self.op, self.rhs) - } -} - -impl From for Node { - fn from(op: BinOp) -> Self { - Self::BinOp(op) - } -} - -/// A unary operation is an operation with only one operand. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#prod-UnaryExpression -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Unary_operators -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] -pub struct UnaryOp { - op: op::UnaryOp, - target: Box, -} - -impl UnaryOp { - /// Creates a new `UnaryOp` AST node. - pub(in crate::syntax) fn new(op: op::UnaryOp, target: V) -> Self - where - V: Into, - { - Self { - op, - target: Box::new(target.into()), - } - } - - /// Gets the unary operation of the node. - pub fn op(&self) -> op::UnaryOp { - self.op - } - - /// Gets the target of this unary operator. - pub fn target(&self) -> &Node { - self.target.as_ref() - } -} - -impl fmt::Display for UnaryOp { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}{}", self.op, self.target) - } -} - -impl From for Node { - fn from(op: UnaryOp) -> Self { - Self::UnaryOp(op) - } -} diff --git a/boa/src/syntax/ast/node/operator/assign/mod.rs b/boa/src/syntax/ast/node/operator/assign/mod.rs new file mode 100644 index 0000000000..6676f4829a --- /dev/null +++ b/boa/src/syntax/ast/node/operator/assign/mod.rs @@ -0,0 +1,99 @@ +use crate::{ + environment::lexical_environment::VariableScope, exec::Executable, syntax::ast::node::Node, + BoaProfiler, Context, Result, Value, +}; +use gc::{Finalize, Trace}; +use std::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// An assignment operator assigns a value to its left operand based on the value of its right +/// operand. +/// +/// Assignment operator (`=`), assigns the value of its right operand to its left operand. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#prod-AssignmentExpression +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct Assign { + lhs: Box, + rhs: Box, +} + +impl Assign { + /// Creates an `Assign` AST node. + pub(in crate::syntax) fn new(lhs: L, rhs: R) -> Self + where + L: Into, + R: Into, + { + Self { + lhs: Box::new(lhs.into()), + rhs: Box::new(rhs.into()), + } + } + + /// Gets the left hand side of the assignment operation. + pub fn lhs(&self) -> &Node { + &self.lhs + } + + /// Gets the right hand side of the assignment operation. + pub fn rhs(&self) -> &Node { + &self.rhs + } +} + +impl Executable for Assign { + fn run(&self, interpreter: &mut Context) -> Result { + let _timer = BoaProfiler::global().start_event("Assign", "exec"); + let val = self.rhs().run(interpreter)?; + match self.lhs() { + Node::Identifier(ref name) => { + let environment = &mut interpreter.realm_mut().environment; + + if environment.has_binding(name.as_ref()) { + // Binding already exists + environment.set_mutable_binding(name.as_ref(), val.clone(), true); + } else { + environment.create_mutable_binding( + name.as_ref().to_owned(), + true, + VariableScope::Function, + ); + environment.initialize_binding(name.as_ref(), val.clone()); + } + } + Node::GetConstField(ref get_const_field) => { + let val_obj = get_const_field.obj().run(interpreter)?; + val_obj.set_field(get_const_field.field(), val.clone()); + } + Node::GetField(ref get_field) => { + let object = get_field.obj().run(interpreter)?; + let field = get_field.field().run(interpreter)?; + let key = field.to_property_key(interpreter)?; + object.set_field(key, val.clone()); + } + _ => (), + } + Ok(val) + } +} + +impl fmt::Display for Assign { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} = {}", self.lhs, self.rhs) + } +} + +impl From for Node { + fn from(op: Assign) -> Self { + Self::Assign(op) + } +} diff --git a/boa/src/exec/operator/mod.rs b/boa/src/syntax/ast/node/operator/bin_op/mod.rs similarity index 57% rename from boa/src/exec/operator/mod.rs rename to boa/src/syntax/ast/node/operator/bin_op/mod.rs index abb07ae9d2..db8985eec2 100644 --- a/boa/src/exec/operator/mod.rs +++ b/boa/src/syntax/ast/node/operator/bin_op/mod.rs @@ -1,50 +1,77 @@ -//! Operator execution. -#[cfg(test)] -mod tests; - -use super::{Context, Executable}; use crate::{ - environment::lexical_environment::VariableScope, + exec::Executable, syntax::ast::{ - node::{Assign, BinOp, Node, UnaryOp}, + node::Node, op::{self, AssignOp, BitOp, CompOp, LogOp, NumOp}, }, - BoaProfiler, Result, Value, + Context, Result, Value, }; +use gc::{Finalize, Trace}; +use std::fmt; -impl Executable for Assign { - fn run(&self, interpreter: &mut Context) -> Result { - let _timer = BoaProfiler::global().start_event("Assign", "exec"); - let val = self.rhs().run(interpreter)?; - match self.lhs() { - Node::Identifier(ref name) => { - let environment = &mut interpreter.realm_mut().environment; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; - if environment.has_binding(name.as_ref()) { - // Binding already exists - environment.set_mutable_binding(name.as_ref(), val.clone(), true); - } else { - environment.create_mutable_binding( - name.as_ref().to_owned(), - true, - VariableScope::Function, - ); - environment.initialize_binding(name.as_ref(), val.clone()); - } - } - Node::GetConstField(ref get_const_field) => { - let val_obj = get_const_field.obj().run(interpreter)?; - val_obj.set_field(get_const_field.field(), val.clone()); - } - Node::GetField(ref get_field) => { - let object = get_field.obj().run(interpreter)?; - let field = get_field.field().run(interpreter)?; - let key = field.to_property_key(interpreter)?; - object.set_field(key, val.clone()); - } - _ => (), +/// Binary operators requires two operands, one before the operator and one after the operator. +/// +/// More information: +/// - [MDN documentation][mdn] +/// +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Operators +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct BinOp { + op: op::BinOp, + lhs: Box, + rhs: Box, +} + +impl BinOp { + /// Creates a `BinOp` AST node. + pub(in crate::syntax) fn new(op: O, lhs: L, rhs: R) -> Self + where + O: Into, + L: Into, + R: Into, + { + Self { + op: op.into(), + lhs: Box::new(lhs.into()), + rhs: Box::new(rhs.into()), + } + } + + /// Gets the binary operation of the node. + pub fn op(&self) -> op::BinOp { + self.op + } + + /// Gets the left hand side of the binary operation. + pub fn lhs(&self) -> &Node { + &self.lhs + } + + /// Gets the right hand side of the binary operation. + pub fn rhs(&self) -> &Node { + &self.rhs + } + + /// Runs the assignment operators. + fn run_assign(op: AssignOp, x: Value, y: Value, interpreter: &mut Context) -> Result { + match op { + AssignOp::Add => x.add(&y, interpreter), + AssignOp::Sub => x.sub(&y, interpreter), + AssignOp::Mul => x.mul(&y, interpreter), + AssignOp::Exp => x.pow(&y, interpreter), + AssignOp::Div => x.div(&y, interpreter), + AssignOp::Mod => x.rem(&y, interpreter), + AssignOp::And => x.bitand(&y, interpreter), + AssignOp::Or => x.bitor(&y, interpreter), + AssignOp::Xor => x.bitxor(&y, interpreter), + AssignOp::Shl => x.shl(&y, interpreter), + AssignOp::Shr => x.shr(&y, interpreter), + AssignOp::Ushr => x.ushr(&y, interpreter), } - Ok(val) } } @@ -158,89 +185,14 @@ impl Executable for BinOp { } } -impl BinOp { - /// Runs the assignment operators. - fn run_assign(op: AssignOp, x: Value, y: Value, interpreter: &mut Context) -> Result { - match op { - AssignOp::Add => x.add(&y, interpreter), - AssignOp::Sub => x.sub(&y, interpreter), - AssignOp::Mul => x.mul(&y, interpreter), - AssignOp::Exp => x.pow(&y, interpreter), - AssignOp::Div => x.div(&y, interpreter), - AssignOp::Mod => x.rem(&y, interpreter), - AssignOp::And => x.bitand(&y, interpreter), - AssignOp::Or => x.bitor(&y, interpreter), - AssignOp::Xor => x.bitxor(&y, interpreter), - AssignOp::Shl => x.shl(&y, interpreter), - AssignOp::Shr => x.shr(&y, interpreter), - AssignOp::Ushr => x.ushr(&y, interpreter), - } +impl fmt::Display for BinOp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} {} {}", self.lhs, self.op, self.rhs) } } -impl Executable for UnaryOp { - fn run(&self, interpreter: &mut Context) -> Result { - let x = self.target().run(interpreter)?; - - Ok(match self.op() { - op::UnaryOp::Minus => x.neg(interpreter)?, - op::UnaryOp::Plus => Value::from(x.to_number(interpreter)?), - op::UnaryOp::IncrementPost => { - let ret = x.clone(); - let result = x.to_number(interpreter)? + 1.0; - interpreter.set_value(self.target(), result.into())?; - ret - } - op::UnaryOp::IncrementPre => { - let result = x.to_number(interpreter)? + 1.0; - interpreter.set_value(self.target(), result.into())? - } - op::UnaryOp::DecrementPost => { - let ret = x.clone(); - let result = x.to_number(interpreter)? - 1.0; - interpreter.set_value(self.target(), result.into())?; - ret - } - op::UnaryOp::DecrementPre => { - let result = x.to_number(interpreter)? - 1.0; - interpreter.set_value(self.target(), result.into())? - } - op::UnaryOp::Not => x.not(interpreter)?.into(), - op::UnaryOp::Tilde => { - let num_v_a = x.to_number(interpreter)?; - Value::from(if num_v_a.is_nan() { - -1 - } else { - // TODO: this is not spec compliant. - !(num_v_a as i32) - }) - } - op::UnaryOp::Void => Value::undefined(), - op::UnaryOp::Delete => match *self.target() { - Node::GetConstField(ref get_const_field) => Value::boolean( - get_const_field - .obj() - .run(interpreter)? - .remove_property(get_const_field.field()), - ), - Node::GetField(ref get_field) => { - let obj = get_field.obj().run(interpreter)?; - let field = &get_field.field().run(interpreter)?; - let res = obj.remove_property(field.to_string(interpreter)?.as_str()); - return Ok(Value::boolean(res)); - } - Node::Identifier(_) => Value::boolean(false), - Node::ArrayDecl(_) - | Node::Block(_) - | Node::Const(_) - | Node::FunctionDecl(_) - | Node::FunctionExpr(_) - | Node::New(_) - | Node::Object(_) - | Node::UnaryOp(_) => Value::boolean(true), - _ => panic!("SyntaxError: wrong delete argument {}", self), - }, - op::UnaryOp::TypeOf => Value::from(x.get_type().as_str()), - }) +impl From for Node { + fn from(op: BinOp) -> Self { + Self::BinOp(op) } } diff --git a/boa/src/syntax/ast/node/operator/mod.rs b/boa/src/syntax/ast/node/operator/mod.rs new file mode 100644 index 0000000000..2efae8c73d --- /dev/null +++ b/boa/src/syntax/ast/node/operator/mod.rs @@ -0,0 +1,10 @@ +//! Operator nodes + +pub mod assign; +pub mod bin_op; +pub mod unary_op; + +pub use self::{assign::Assign, bin_op::BinOp, unary_op::UnaryOp}; + +#[cfg(test)] +mod tests; diff --git a/boa/src/exec/operator/tests.rs b/boa/src/syntax/ast/node/operator/tests.rs similarity index 100% rename from boa/src/exec/operator/tests.rs rename to boa/src/syntax/ast/node/operator/tests.rs diff --git a/boa/src/syntax/ast/node/operator/unary_op/mod.rs b/boa/src/syntax/ast/node/operator/unary_op/mod.rs new file mode 100644 index 0000000000..ec4931eb1c --- /dev/null +++ b/boa/src/syntax/ast/node/operator/unary_op/mod.rs @@ -0,0 +1,127 @@ +use crate::{ + exec::Executable, + syntax::ast::{node::Node, op}, + Context, Result, Value, +}; +use gc::{Finalize, Trace}; +use std::fmt; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// A unary operation is an operation with only one operand. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#prod-UnaryExpression +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Unary_operators +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct UnaryOp { + op: op::UnaryOp, + target: Box, +} + +impl UnaryOp { + /// Creates a new `UnaryOp` AST node. + pub(in crate::syntax) fn new(op: op::UnaryOp, target: V) -> Self + where + V: Into, + { + Self { + op, + target: Box::new(target.into()), + } + } + + /// Gets the unary operation of the node. + pub fn op(&self) -> op::UnaryOp { + self.op + } + + /// Gets the target of this unary operator. + pub fn target(&self) -> &Node { + self.target.as_ref() + } +} + +impl Executable for UnaryOp { + fn run(&self, interpreter: &mut Context) -> Result { + let x = self.target().run(interpreter)?; + + Ok(match self.op() { + op::UnaryOp::Minus => x.neg(interpreter)?, + op::UnaryOp::Plus => Value::from(x.to_number(interpreter)?), + op::UnaryOp::IncrementPost => { + let ret = x.clone(); + let result = x.to_number(interpreter)? + 1.0; + interpreter.set_value(self.target(), result.into())?; + ret + } + op::UnaryOp::IncrementPre => { + let result = x.to_number(interpreter)? + 1.0; + interpreter.set_value(self.target(), result.into())? + } + op::UnaryOp::DecrementPost => { + let ret = x.clone(); + let result = x.to_number(interpreter)? - 1.0; + interpreter.set_value(self.target(), result.into())?; + ret + } + op::UnaryOp::DecrementPre => { + let result = x.to_number(interpreter)? - 1.0; + interpreter.set_value(self.target(), result.into())? + } + op::UnaryOp::Not => x.not(interpreter)?.into(), + op::UnaryOp::Tilde => { + let num_v_a = x.to_number(interpreter)?; + Value::from(if num_v_a.is_nan() { + -1 + } else { + // TODO: this is not spec compliant. + !(num_v_a as i32) + }) + } + op::UnaryOp::Void => Value::undefined(), + op::UnaryOp::Delete => match *self.target() { + Node::GetConstField(ref get_const_field) => Value::boolean( + get_const_field + .obj() + .run(interpreter)? + .remove_property(get_const_field.field()), + ), + Node::GetField(ref get_field) => { + let obj = get_field.obj().run(interpreter)?; + let field = &get_field.field().run(interpreter)?; + let res = obj.remove_property(field.to_string(interpreter)?.as_str()); + return Ok(Value::boolean(res)); + } + Node::Identifier(_) => Value::boolean(false), + Node::ArrayDecl(_) + | Node::Block(_) + | Node::Const(_) + | Node::FunctionDecl(_) + | Node::FunctionExpr(_) + | Node::New(_) + | Node::Object(_) + | Node::UnaryOp(_) => Value::boolean(true), + _ => panic!("SyntaxError: wrong delete argument {}", self), + }, + op::UnaryOp::TypeOf => Value::from(x.get_type().as_str()), + }) + } +} + +impl fmt::Display for UnaryOp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}{}", self.op, self.target) + } +} + +impl From for Node { + fn from(op: UnaryOp) -> Self { + Self::UnaryOp(op) + } +} diff --git a/boa/src/syntax/ast/node/return_smt.rs b/boa/src/syntax/ast/node/return_smt/mod.rs similarity index 78% rename from boa/src/syntax/ast/node/return_smt.rs rename to boa/src/syntax/ast/node/return_smt/mod.rs index 468c555994..d77f53a5ae 100644 --- a/boa/src/syntax/ast/node/return_smt.rs +++ b/boa/src/syntax/ast/node/return_smt/mod.rs @@ -1,4 +1,8 @@ -use super::Node; +use crate::{ + exec::{Executable, InterpreterState}, + syntax::ast::node::Node, + Context, Result, Value, +}; use gc::{Finalize, Trace}; use std::fmt; @@ -53,6 +57,20 @@ impl Return { } } +impl Executable for Return { + fn run(&self, interpreter: &mut Context) -> Result { + let result = match self.expr() { + Some(ref v) => v.run(interpreter), + None => Ok(Value::undefined()), + }; + // Set flag for return + interpreter + .executor() + .set_current_state(InterpreterState::Return); + result + } +} + impl From for Node { fn from(return_smt: Return) -> Node { Node::Return(return_smt) diff --git a/boa/src/syntax/ast/node/spread.rs b/boa/src/syntax/ast/node/spread/mod.rs similarity index 83% rename from boa/src/syntax/ast/node/spread.rs rename to boa/src/syntax/ast/node/spread/mod.rs index 3afe90775f..b07aef0dbe 100644 --- a/boa/src/syntax/ast/node/spread.rs +++ b/boa/src/syntax/ast/node/spread/mod.rs @@ -1,4 +1,4 @@ -use super::Node; +use crate::{exec::Executable, syntax::ast::node::Node, Context, Result, Value}; use gc::{Finalize, Trace}; use std::fmt; @@ -44,6 +44,13 @@ impl Spread { } } +impl Executable for Spread { + fn run(&self, interpreter: &mut Context) -> Result { + // TODO: for now we can do nothing but return the value as-is + self.val().run(interpreter) + } +} + impl fmt::Display for Spread { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "...{}", self.val()) diff --git a/boa/src/syntax/ast/node/statement_list.rs b/boa/src/syntax/ast/node/statement_list/mod.rs similarity index 59% rename from boa/src/syntax/ast/node/statement_list.rs rename to boa/src/syntax/ast/node/statement_list/mod.rs index 4b3c3da008..4c8ab7cd27 100644 --- a/boa/src/syntax/ast/node/statement_list.rs +++ b/boa/src/syntax/ast/node/statement_list/mod.rs @@ -1,6 +1,10 @@ //! Statement list node. -use super::Node; +use crate::{ + exec::{Executable, InterpreterState}, + syntax::ast::node::Node, + BoaProfiler, Context, Result, Value, +}; use gc::{unsafe_empty_trace, Finalize, Trace}; use std::fmt; use std::ops::Deref; @@ -31,7 +35,11 @@ impl StatementList { } /// Implements the display formatting with indentation. - pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { + pub(in crate::syntax::ast::node) fn display( + &self, + f: &mut fmt::Formatter<'_>, + indentation: usize, + ) -> fmt::Result { let indent = " ".repeat(indentation); // Print statements for node in self.statements.iter() { @@ -48,6 +56,44 @@ impl StatementList { } } +impl Executable for StatementList { + fn run(&self, interpreter: &mut Context) -> Result { + let _timer = BoaProfiler::global().start_event("StatementList", "exec"); + + // https://tc39.es/ecma262/#sec-block-runtime-semantics-evaluation + // The return value is uninitialized, which means it defaults to Value::Undefined + let mut obj = Value::default(); + interpreter + .executor() + .set_current_state(InterpreterState::Executing); + for (i, item) in self.statements().iter().enumerate() { + let val = item.run(interpreter)?; + match interpreter.executor().get_current_state() { + InterpreterState::Return => { + // Early return. + obj = val; + break; + } + InterpreterState::Break(_label) => { + // Early break. + break; + } + InterpreterState::Continue(_label) => { + break; + } + InterpreterState::Executing => { + // Continue execution + } + } + if i + 1 == self.statements().len() { + obj = val; + } + } + + Ok(obj) + } +} + impl From for StatementList where T: Into>, diff --git a/boa/src/syntax/ast/node/switch.rs b/boa/src/syntax/ast/node/switch/mod.rs similarity index 51% rename from boa/src/syntax/ast/node/switch.rs rename to boa/src/syntax/ast/node/switch/mod.rs index 74c2d2ee40..42d1f9df7a 100644 --- a/boa/src/syntax/ast/node/switch.rs +++ b/boa/src/syntax/ast/node/switch/mod.rs @@ -1,6 +1,10 @@ //! Switch node. //! -use super::Node; +use crate::{ + exec::{Executable, InterpreterState}, + syntax::ast::node::Node, + Context, Result, Value, +}; use gc::{Finalize, Trace}; use std::fmt; @@ -9,6 +13,9 @@ use crate::syntax::ast::node::StatementList; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +#[cfg(test)] +mod tests; + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Clone, Debug, Trace, Finalize, PartialEq)] pub struct Case { @@ -95,7 +102,11 @@ impl Switch { } /// Implements the display formatting with indentation. - pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indent: usize) -> fmt::Result { + pub(in crate::syntax::ast::node) fn display( + &self, + f: &mut fmt::Formatter<'_>, + indent: usize, + ) -> fmt::Result { writeln!(f, "switch ({}) {{", self.val())?; for e in self.cases().iter() { writeln!(f, "{}case {}:", indent, e.condition())?; @@ -110,6 +121,84 @@ impl Switch { } } +impl Executable for Switch { + fn run(&self, interpreter: &mut Context) -> Result { + let val = self.val().run(interpreter)?; + let mut result = Value::null(); + let mut matched = false; + interpreter + .executor() + .set_current_state(InterpreterState::Executing); + + // If a case block does not end with a break statement then subsequent cases will be run without + // checking their conditions until a break is encountered. + let mut fall_through: bool = false; + + for case in self.cases().iter() { + let cond = case.condition(); + let block = case.body(); + if fall_through || val.strict_equals(&cond.run(interpreter)?) { + matched = true; + let result = block.run(interpreter)?; + match interpreter.executor().get_current_state() { + InterpreterState::Return => { + // Early return. + return Ok(result); + } + InterpreterState::Break(_label) => { + // TODO, break to a label. + // Break statement encountered so therefore end switch statement. + interpreter + .executor() + .set_current_state(InterpreterState::Executing); + break; + } + InterpreterState::Continue(_label) => { + // TODO, continue to a label. + break; + } + InterpreterState::Executing => { + // Continuing execution / falling through to next case statement(s). + fall_through = true; + } + } + } + } + + if !matched { + if let Some(default) = self.default() { + interpreter + .executor() + .set_current_state(InterpreterState::Executing); + for (i, item) in default.iter().enumerate() { + let val = item.run(interpreter)?; + match interpreter.executor().get_current_state() { + InterpreterState::Return => { + // Early return. + result = val; + break; + } + InterpreterState::Break(_label) => { + // TODO, break to a label. + + // Early break. + break; + } + _ => { + // Continue execution + } + } + if i == default.len() - 1 { + result = val; + } + } + } + } + + Ok(result) + } +} + impl fmt::Display for Switch { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.display(f, 0) diff --git a/boa/src/exec/switch/tests.rs b/boa/src/syntax/ast/node/switch/tests.rs similarity index 100% rename from boa/src/exec/switch/tests.rs rename to boa/src/syntax/ast/node/switch/tests.rs diff --git a/boa/src/syntax/ast/node/throw.rs b/boa/src/syntax/ast/node/throw/mod.rs similarity index 85% rename from boa/src/syntax/ast/node/throw.rs rename to boa/src/syntax/ast/node/throw/mod.rs index 432616e2d3..38d04ce2bc 100644 --- a/boa/src/syntax/ast/node/throw.rs +++ b/boa/src/syntax/ast/node/throw/mod.rs @@ -1,4 +1,4 @@ -use super::Node; +use crate::{exec::Executable, syntax::ast::node::Node, Context, Result, Value}; use gc::{Finalize, Trace}; use std::fmt; @@ -41,6 +41,13 @@ impl Throw { } } +impl Executable for Throw { + #[inline] + fn run(&self, interpreter: &mut Context) -> Result { + Err(self.expr().run(interpreter)?) + } +} + impl fmt::Display for Throw { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "throw {}", self.expr) diff --git a/boa/src/syntax/ast/node/try_node.rs b/boa/src/syntax/ast/node/try_node/mod.rs similarity index 70% rename from boa/src/syntax/ast/node/try_node.rs rename to boa/src/syntax/ast/node/try_node/mod.rs index 125d9fe348..77cca0b4a5 100644 --- a/boa/src/syntax/ast/node/try_node.rs +++ b/boa/src/syntax/ast/node/try_node/mod.rs @@ -1,10 +1,18 @@ -use super::{Block, Identifier, Node}; +use crate::{ + environment::lexical_environment::{new_declarative_environment, VariableScope}, + exec::Executable, + syntax::ast::node::{Block, Identifier, Node}, + BoaProfiler, Context, Result, Value, +}; use gc::{Finalize, Trace}; use std::fmt; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +#[cfg(test)] +mod tests; + /// The `try...catch` statement marks a block of statements to try and specifies a response /// should an exception be thrown. /// @@ -64,7 +72,11 @@ impl Try { } /// Implements the display formatting with indentation. - pub(super) fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { + pub(in crate::syntax::ast::node) fn display( + &self, + f: &mut fmt::Formatter<'_>, + indentation: usize, + ) -> fmt::Result { write!(f, "{}try ", " ".repeat(indentation))?; self.block.display(f, indentation)?; @@ -79,6 +91,50 @@ impl Try { } } +impl Executable for Try { + fn run(&self, interpreter: &mut Context) -> Result { + let _timer = BoaProfiler::global().start_event("Try", "exec"); + let res = self.block().run(interpreter).map_or_else( + |err| { + if let Some(catch) = self.catch() { + { + let env = &mut interpreter.realm_mut().environment; + env.push(new_declarative_environment(Some( + env.get_current_environment_ref().clone(), + ))); + + if let Some(param) = catch.parameter() { + env.create_mutable_binding( + param.to_owned(), + false, + VariableScope::Block, + ); + + env.initialize_binding(param, err); + } + } + + let res = catch.block().run(interpreter); + + // pop the block env + let _ = interpreter.realm_mut().environment.pop(); + + res + } else { + Err(err) + } + }, + Ok, + ); + + if let Some(finally) = self.finally() { + finally.run(interpreter)?; + } + + res + } +} + impl fmt::Display for Try { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.display(f, 0) diff --git a/boa/src/exec/try_node/tests.rs b/boa/src/syntax/ast/node/try_node/tests.rs similarity index 100% rename from boa/src/exec/try_node/tests.rs rename to boa/src/syntax/ast/node/try_node/tests.rs