diff --git a/boa_engine/src/builtins/function/arguments.rs b/boa_engine/src/builtins/function/arguments.rs index 81b9d3a3ec6..b96cdc70a46 100644 --- a/boa_engine/src/builtins/function/arguments.rs +++ b/boa_engine/src/builtins/function/arguments.rs @@ -4,7 +4,7 @@ use crate::{ object::{JsObject, ObjectData}, property::PropertyDescriptor, symbol::{self, WellKnownSymbols}, - syntax::ast::node::FormalParameter, + syntax::ast::node::FormalParameterList, Context, JsValue, }; use boa_gc::{Finalize, Gc, Trace}; @@ -147,7 +147,7 @@ impl Arguments { /// pub(crate) fn create_mapped_arguments_object( func: &JsObject, - formals: &[FormalParameter], + formals: &FormalParameterList, arguments_list: &[JsValue], env: &Gc, context: &mut Context, @@ -199,7 +199,7 @@ impl Arguments { let mut bindings = FxHashMap::default(); let mut property_index = 0; - 'outer: for formal in formals { + 'outer: for formal in formals.parameters.iter() { for name in formal.names() { if property_index >= len { break 'outer; diff --git a/boa_engine/src/builtins/function/mod.rs b/boa_engine/src/builtins/function/mod.rs index be7ef7028ec..c4c35c24948 100644 --- a/boa_engine/src/builtins/function/mod.rs +++ b/boa_engine/src/builtins/function/mod.rs @@ -175,7 +175,11 @@ pub enum Function { constructor: bool, captures: Captures, }, - VmOrdinary { + Ordinary { + code: Gc, + environments: DeclarativeEnvironmentStack, + }, + Generator { code: Gc, environments: DeclarativeEnvironmentStack, }, @@ -192,7 +196,7 @@ impl Function { pub fn is_constructor(&self) -> bool { match self { Self::Native { constructor, .. } | Self::Closure { constructor, .. } => *constructor, - Self::VmOrdinary { code, .. } => code.constructor, + Self::Ordinary { code, .. } | Self::Generator { code, .. } => code.constructor, } } } @@ -454,11 +458,15 @@ impl BuiltInFunctionObject { }, Some(name), ) => Ok(format!("function {name}() {{\n [native Code]\n}}").into()), - (Function::VmOrdinary { .. }, Some(name)) if name.is_empty() => { + (Function::Ordinary { .. }, Some(name)) if name.is_empty() => { Ok("[Function (anonymous)]".into()) } - (Function::VmOrdinary { .. }, Some(name)) => Ok(format!("[Function: {name}]").into()), - (Function::VmOrdinary { .. }, None) => Ok("[Function (anonymous)]".into()), + (Function::Ordinary { .. }, Some(name)) => Ok(format!("[Function: {name}]").into()), + (Function::Ordinary { .. }, None) => Ok("[Function (anonymous)]".into()), + (Function::Generator { .. }, Some(name)) => { + Ok(format!("[Function*: {}]", &name).into()) + } + (Function::Generator { .. }, None) => Ok("[Function* (anonymous)]".into()), _ => Ok("TODO".into()), } } diff --git a/boa_engine/src/builtins/generator/mod.rs b/boa_engine/src/builtins/generator/mod.rs new file mode 100644 index 00000000000..9801bc231f9 --- /dev/null +++ b/boa_engine/src/builtins/generator/mod.rs @@ -0,0 +1,415 @@ +//! This module implements the global `Generator` object. +//! +//! A Generator is an instance of a generator function and conforms to both the Iterator and Iterable interfaces. +//! +//! More information: +//! - [ECMAScript reference][spec] +//! - [MDN documentation][mdn] +//! +//! [spec]: https://tc39.es/ecma262/#sec-generator-objects +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator + +use crate::{ + builtins::{iterable::create_iter_result_object, BuiltIn, JsArgs}, + environments::DeclarativeEnvironmentStack, + object::{ConstructorBuilder, JsObject, ObjectData}, + property::{Attribute, PropertyDescriptor}, + symbol::WellKnownSymbols, + value::JsValue, + vm::{CallFrame, GeneratorResumeKind, ReturnType}, + Context, JsResult, +}; +use boa_gc::{Cell, Finalize, Gc, Trace}; +use boa_profiler::Profiler; + +/// Indicates the state of a generator. +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) enum GeneratorState { + Undefined, + SuspendedStart, + SuspendedYield, + Executing, + Completed, +} + +/// Holds all information that a generator needs to continue it's execution. +/// +/// All of the fields must be changed with those that are currently present in the +/// context/vm before the generator execution starts/resumes and after it has ended/yielded. +#[derive(Debug, Clone, Finalize, Trace)] +pub(crate) struct GeneratorContext { + pub(crate) environments: DeclarativeEnvironmentStack, + pub(crate) call_frame: CallFrame, + pub(crate) stack: Vec, +} + +/// The internal representation on a `Generator` object. +#[derive(Debug, Clone, Finalize, Trace)] +pub struct Generator { + /// The `[[GeneratorState]]` internal slot. + #[unsafe_ignore_trace] + pub(crate) state: GeneratorState, + + /// The `[[GeneratorContext]]` internal slot. + pub(crate) context: Option>>, +} + +impl BuiltIn for Generator { + const NAME: &'static str = "Generator"; + + const ATTRIBUTE: Attribute = Attribute::NON_ENUMERABLE.union(Attribute::CONFIGURABLE); + + fn init(context: &mut Context) -> JsValue { + let _timer = Profiler::global().start_event(Self::NAME, "init"); + + let iterator_prototype = context.iterator_prototypes().iterator_prototype(); + + let generator_function_prototype = context + .standard_objects() + .generator_function_object() + .prototype(); + + let obj = ConstructorBuilder::with_standard_object( + context, + Self::constructor, + context.standard_objects().generator_object().clone(), + ) + .name(Self::NAME) + .length(Self::LENGTH) + .property( + WellKnownSymbols::to_string_tag(), + Self::NAME, + Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, + ) + .method(Self::next, "next", 1) + .method(Self::r#return, "return", 1) + .method(Self::throw, "throw", 1) + .inherit(iterator_prototype) + .build(); + + context + .standard_objects() + .generator_object() + .prototype + .insert_property( + "constructor", + PropertyDescriptor::builder() + .value(generator_function_prototype) + .writable(false) + .enumerable(false) + .configurable(true), + ); + + obj.into() + } +} + +impl Generator { + pub(crate) const LENGTH: usize = 0; + + #[allow(clippy::unnecessary_wraps)] + pub(crate) fn constructor( + _: &JsValue, + _: &[JsValue], + context: &mut Context, + ) -> JsResult { + let prototype = context.standard_objects().generator_object().prototype(); + + let this = JsObject::from_proto_and_data( + prototype, + ObjectData::generator(Self { + state: GeneratorState::Undefined, + context: None, + }), + ); + + Ok(this.into()) + } + + /// `Generator.prototype.next ( value )` + /// + /// The `next()` method returns an object with two properties done and value. + /// You can also provide a parameter to the next method to send a value to the generator. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-generator.prototype.next + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/next + pub(crate) fn next( + this: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + // 1. Return ? GeneratorResume(this value, value, empty). + match this.as_object() { + Some(obj) if obj.is_generator() => { + Self::generator_resume(obj, args.get_or_undefined(0), context) + } + _ => context.throw_type_error("Generator.prototype.next called on non generator"), + } + } + + /// `Generator.prototype.return ( value )` + /// + /// The `return()` method returns the given value and finishes the generator. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-generator.prototype.return + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/return + pub(crate) fn r#return( + this: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + // 1. Let g be the this value. + // 2. Let C be Completion { [[Type]]: return, [[Value]]: value, [[Target]]: empty }. + // 3. Return ? GeneratorResumeAbrupt(g, C, empty). + Self::generator_resume_abrupt(this, Ok(args.get_or_undefined(0).clone()), context) + } + + /// `Generator.prototype.throw ( exception )` + /// + /// The `throw()` method resumes the execution of a generator by throwing an error into it + /// and returns an object with two properties done and value. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-generator.prototype.throw + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator/throw + pub(crate) fn throw( + this: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + // 1. Let g be the this value. + // 2. Let C be ThrowCompletion(exception). + // 3. Return ? GeneratorResumeAbrupt(g, C, empty). + Self::generator_resume_abrupt(this, Err(args.get_or_undefined(0).clone()), context) + } + + /// `27.5.3.3 GeneratorResume ( generator, value, generatorBrand )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-generatorresume + pub(crate) fn generator_resume( + generator_obj: &JsObject, + value: &JsValue, + context: &mut Context, + ) -> JsResult { + // 1. Let state be ? GeneratorValidate(generator, generatorBrand). + let mut generator_obj_mut = generator_obj.borrow_mut(); + let generator = generator_obj_mut.as_generator_mut().ok_or_else(|| { + context.construct_type_error("generator resumed on non generator object") + })?; + let state = generator.state; + + if state == GeneratorState::Executing { + return Err(context.construct_type_error("Generator should not be executing")); + } + + // 2. If state is completed, return CreateIterResultObject(undefined, true). + if state == GeneratorState::Completed { + return Ok(create_iter_result_object( + JsValue::undefined(), + true, + context, + )); + } + + // 3. Assert: state is either suspendedStart or suspendedYield. + assert!(matches!( + state, + GeneratorState::SuspendedStart | GeneratorState::SuspendedYield + )); + + // 4. Let genContext be generator.[[GeneratorContext]]. + // 5. Let methodContext be the running execution context. + // 6. Suspend methodContext. + // 7. Set generator.[[GeneratorState]] to executing. + generator.state = GeneratorState::Executing; + let first_execution = matches!(state, GeneratorState::SuspendedStart); + + let generator_context_cell = generator + .context + .take() + .expect("generator context cannot be empty here"); + let mut generator_context = generator_context_cell.borrow_mut(); + drop(generator_obj_mut); + + std::mem::swap( + &mut context.realm.environments, + &mut generator_context.environments, + ); + std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); + context.vm.push_frame(generator_context.call_frame.clone()); + if !first_execution { + context.vm.push(value); + } + + context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Normal; + + let result = context.run(); + + generator_context.call_frame = *context + .vm + .pop_frame() + .expect("generator call frame must exist"); + std::mem::swap( + &mut context.realm.environments, + &mut generator_context.environments, + ); + std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); + + let mut generator_obj_mut = generator_obj.borrow_mut(); + let generator = generator_obj_mut + .as_generator_mut() + .expect("already checked this object type"); + + match result { + Ok((value, ReturnType::Yield)) => { + generator.state = GeneratorState::SuspendedYield; + drop(generator_context); + generator.context = Some(generator_context_cell); + Ok(create_iter_result_object(value, false, context)) + } + Ok((value, _)) => { + generator.state = GeneratorState::Completed; + Ok(create_iter_result_object(value, true, context)) + } + Err(value) => { + generator.state = GeneratorState::Completed; + Err(value) + } + } + + // 8. Push genContext onto the execution context stack; genContext is now the running execution context. + // 9. Resume the suspended evaluation of genContext using NormalCompletion(value) as the result of the operation that suspended it. Let result be the value returned by the resumed computation. + // 10. Assert: When we return here, genContext has already been removed from the execution context stack and methodContext is the currently running execution context. + // 11. Return Completion(result). + } + + /// `27.5.3.4 GeneratorResumeAbrupt ( generator, abruptCompletion, generatorBrand )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-generatorresumeabrupt + pub(crate) fn generator_resume_abrupt( + this: &JsValue, + abrupt_completion: JsResult, + context: &mut Context, + ) -> JsResult { + // 1. Let state be ? GeneratorValidate(generator, generatorBrand). + let generator_obj = this.as_object().ok_or_else(|| { + context.construct_type_error("generator resumed on non generator object") + })?; + let mut generator_obj_mut = generator_obj.borrow_mut(); + let generator = generator_obj_mut.as_generator_mut().ok_or_else(|| { + context.construct_type_error("generator resumed on non generator object") + })?; + let mut state = generator.state; + + if state == GeneratorState::Executing { + return Err(context.construct_type_error("Generator should not be executing")); + } + + // 2. If state is suspendedStart, then + if state == GeneratorState::SuspendedStart { + // a. Set generator.[[GeneratorState]] to completed. + generator.state = GeneratorState::Completed; + // b. Once a generator enters the completed state it never leaves it and its associated execution context is never resumed. Any execution state associated with generator can be discarded at this point. + generator.context = None; + // c. Set state to completed. + state = GeneratorState::Completed; + } + + // 3. If state is completed, then + if state == GeneratorState::Completed { + // a. If abruptCompletion.[[Type]] is return, then + if let Ok(value) = abrupt_completion { + // i. Return CreateIterResultObject(abruptCompletion.[[Value]], true). + return Ok(create_iter_result_object(value, true, context)); + } + // b. Return Completion(abruptCompletion). + return abrupt_completion; + } + + // 4. Assert: state is suspendedYield. + // 5. Let genContext be generator.[[GeneratorContext]]. + // 6. Let methodContext be the running execution context. + // 7. Suspend methodContext. + // 8. Set generator.[[GeneratorState]] to executing. + // 9. Push genContext onto the execution context stack; genContext is now the running execution context. + // 10. Resume the suspended evaluation of genContext using abruptCompletion as the result of the operation that suspended it. Let result be the completion record returned by the resumed computation. + // 11. Assert: When we return here, genContext has already been removed from the execution context stack and methodContext is the currently running execution context. + // 12. Return Completion(result). + let generator_context_cell = generator + .context + .take() + .expect("generator context cannot be empty here"); + let mut generator_context = generator_context_cell.borrow_mut(); + + generator.state = GeneratorState::Executing; + drop(generator_obj_mut); + + std::mem::swap( + &mut context.realm.environments, + &mut generator_context.environments, + ); + std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); + context.vm.push_frame(generator_context.call_frame.clone()); + + let result = match abrupt_completion { + Ok(value) => { + context.vm.push(value); + context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Return; + context.run() + } + Err(value) => { + context.vm.push(value); + context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Throw; + context.run() + } + }; + generator_context.call_frame = *context + .vm + .pop_frame() + .expect("generator call frame must exist"); + std::mem::swap( + &mut context.realm.environments, + &mut generator_context.environments, + ); + std::mem::swap(&mut context.vm.stack, &mut generator_context.stack); + + let mut generator_obj_mut = generator_obj.borrow_mut(); + let generator = generator_obj_mut + .as_generator_mut() + .expect("already checked this object type"); + + match result { + Ok((value, ReturnType::Yield)) => { + generator.state = GeneratorState::SuspendedYield; + drop(generator_context); + generator.context = Some(generator_context_cell); + Ok(create_iter_result_object(value, false, context)) + } + Ok((value, _)) => { + generator.state = GeneratorState::Completed; + Ok(create_iter_result_object(value, true, context)) + } + Err(value) => { + generator.state = GeneratorState::Completed; + Err(value) + } + } + } +} diff --git a/boa_engine/src/builtins/generator_function/mod.rs b/boa_engine/src/builtins/generator_function/mod.rs new file mode 100644 index 00000000000..d3dcfdcead8 --- /dev/null +++ b/boa_engine/src/builtins/generator_function/mod.rs @@ -0,0 +1,131 @@ +//! This module implements the global `GeneratorFunction` object. +//! +//! The `GeneratorFunction` constructor creates a new generator function object. +//! In JavaScript, every generator function is actually a `GeneratorFunction` object. +//! +//! More information: +//! - [ECMAScript reference][spec] +//! - [MDN documentation][mdn] +//! +//! [spec]: https://tc39.es/ecma262/#sec-generatorfunction-objects +//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/GeneratorFunction + +use crate::{ + builtins::{function::Function, BuiltIn}, + context::StandardObjects, + object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, + property::{Attribute, PropertyDescriptor}, + symbol::WellKnownSymbols, + value::JsValue, + Context, JsResult, +}; +use boa_profiler::Profiler; + +/// The internal representation on a `Generator` object. +#[derive(Debug, Clone, Copy)] +pub struct GeneratorFunction; + +impl BuiltIn for GeneratorFunction { + const NAME: &'static str = "GeneratorFunction"; + + const ATTRIBUTE: Attribute = Attribute::NON_ENUMERABLE.union(Attribute::WRITABLE); + + fn init(context: &mut Context) -> JsValue { + let _timer = Profiler::global().start_event(Self::NAME, "init"); + + let prototype = &context + .standard_objects() + .generator_function_object() + .prototype; + let constructor = &context + .standard_objects() + .generator_function_object() + .constructor; + + constructor.set_prototype(Some( + context.standard_objects().function_object().constructor(), + )); + let property = PropertyDescriptor::builder() + .value(1) + .writable(false) + .enumerable(false) + .configurable(true); + constructor.borrow_mut().insert("length", property); + let property = PropertyDescriptor::builder() + .value("GeneratorFunction") + .writable(false) + .enumerable(false) + .configurable(true); + constructor.borrow_mut().insert("name", property); + let property = PropertyDescriptor::builder() + .value( + context + .standard_objects() + .generator_function_object() + .prototype(), + ) + .writable(false) + .enumerable(false) + .configurable(false); + constructor.borrow_mut().insert("prototype", property); + constructor.borrow_mut().data = ObjectData::function(Function::Native { + function: Self::constructor, + constructor: true, + }); + + prototype.set_prototype(Some( + context.standard_objects().function_object().prototype(), + )); + let property = PropertyDescriptor::builder() + .value( + context + .standard_objects() + .generator_function_object() + .constructor(), + ) + .writable(false) + .enumerable(false) + .configurable(true); + prototype.borrow_mut().insert("constructor", property); + let property = PropertyDescriptor::builder() + .value(context.standard_objects().generator_object().prototype()) + .writable(false) + .enumerable(false) + .configurable(true); + prototype.borrow_mut().insert("prototype", property); + let property = PropertyDescriptor::builder() + .value("GeneratorFunction") + .writable(false) + .enumerable(false) + .configurable(true); + prototype + .borrow_mut() + .insert(WellKnownSymbols::to_string_tag(), property); + + JsValue::Null + } +} + +impl GeneratorFunction { + pub(crate) fn constructor( + new_target: &JsValue, + _: &[JsValue], + context: &mut Context, + ) -> JsResult { + let prototype = get_prototype_from_constructor( + new_target, + StandardObjects::generator_function_object, + context, + )?; + + let this = JsObject::from_proto_and_data( + prototype, + ObjectData::function(Function::Native { + function: |_, _, _| Ok(JsValue::undefined()), + constructor: true, + }), + ); + + Ok(this.into()) + } +} diff --git a/boa_engine/src/builtins/mod.rs b/boa_engine/src/builtins/mod.rs index de272641a21..65795897ed4 100644 --- a/boa_engine/src/builtins/mod.rs +++ b/boa_engine/src/builtins/mod.rs @@ -10,6 +10,8 @@ pub mod dataview; pub mod date; pub mod error; pub mod function; +pub mod generator; +pub mod generator_function; pub mod global_this; pub mod infinity; pub mod intl; @@ -64,7 +66,9 @@ pub(crate) use self::{ }; use crate::{ - builtins::array_buffer::ArrayBuffer, + builtins::{ + array_buffer::ArrayBuffer, generator::Generator, generator_function::GeneratorFunction, + }, property::{Attribute, PropertyDescriptor}, Context, JsValue, }; @@ -162,6 +166,9 @@ pub fn init(context: &mut Context) { Reflect }; + Generator::init(context); + GeneratorFunction::init(context); + #[cfg(feature = "console")] init_builtin::(context); } diff --git a/boa_engine/src/bytecompiler.rs b/boa_engine/src/bytecompiler.rs index 2aca2a1c292..1f4550a98da 100644 --- a/boa_engine/src/bytecompiler.rs +++ b/boa_engine/src/bytecompiler.rs @@ -5,9 +5,9 @@ use crate::{ node::{ declaration::{BindingPatternTypeArray, BindingPatternTypeObject, DeclarationPattern}, iteration::IterableLoopInitializer, + object::{MethodDefinition, PropertyDefinition, PropertyName}, template::TemplateElement, - Declaration, GetConstField, GetField, MethodDefinitionKind, PropertyDefinition, - PropertyName, StatementList, + Declaration, GetConstField, GetField, StatementList, }, op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp, UnaryOp}, Const, Node, @@ -806,53 +806,65 @@ impl<'b> ByteCompiler<'b> { self.emit_opcode(Opcode::DefineOwnPropertyByValue); } }, - PropertyDefinition::MethodDefinition(kind, name, func) => { + PropertyDefinition::MethodDefinition(kind, name) => { match kind { - MethodDefinitionKind::Get => match name { + MethodDefinition::Get(expr) => match name { PropertyName::Literal(name) => { - self.compile_stmt(&func.clone().into(), true)?; + self.function(&expr.clone().into(), true)?; self.emit_opcode(Opcode::Swap); let index = self.get_or_insert_name(*name); self.emit(Opcode::SetPropertyGetterByName, &[index]); } PropertyName::Computed(name_node) => { self.compile_stmt(name_node, true)?; - self.compile_stmt(&func.clone().into(), true)?; + self.function(&expr.clone().into(), true)?; self.emit_opcode(Opcode::SetPropertyGetterByValue); } }, - MethodDefinitionKind::Set => match name { + MethodDefinition::Set(expr) => match name { PropertyName::Literal(name) => { - self.compile_stmt(&func.clone().into(), true)?; + self.function(&expr.clone().into(), true)?; self.emit_opcode(Opcode::Swap); let index = self.get_or_insert_name(*name); self.emit(Opcode::SetPropertySetterByName, &[index]); } PropertyName::Computed(name_node) => { self.compile_stmt(name_node, true)?; - self.compile_stmt(&func.clone().into(), true)?; + self.function(&expr.clone().into(), true)?; self.emit_opcode(Opcode::SetPropertySetterByValue); } }, - MethodDefinitionKind::Ordinary => match name { + MethodDefinition::Ordinary(expr) => match name { PropertyName::Literal(name) => { - self.compile_stmt(&func.clone().into(), true)?; + self.function(&expr.clone().into(), true)?; self.emit_opcode(Opcode::Swap); let index = self.get_or_insert_name(*name); self.emit(Opcode::DefineOwnPropertyByName, &[index]); } PropertyName::Computed(name_node) => { self.compile_stmt(name_node, true)?; - self.compile_stmt(&func.clone().into(), true)?; + self.function(&expr.clone().into(), true)?; self.emit_opcode(Opcode::DefineOwnPropertyByValue); } }, - MethodDefinitionKind::Generator - | MethodDefinitionKind::Async - | MethodDefinitionKind::AsyncGenerator => { - // TODO: Implement generators + MethodDefinition::Generator(expr) => match name { + PropertyName::Literal(name) => { + self.function(&expr.clone().into(), true)?; + self.emit_opcode(Opcode::Swap); + let index = self.get_or_insert_name(*name); + self.emit(Opcode::DefineOwnPropertyByName, &[index]); + } + PropertyName::Computed(name_node) => { + self.compile_stmt(name_node, true)?; + self.function(&expr.clone().into(), true)?; + self.emit_opcode(Opcode::DefineOwnPropertyByValue); + } + }, + // TODO: Implement async + // TODO: Implement async generators + MethodDefinition::Async(_) + | MethodDefinition::AsyncGenerator(_) => { // TODO: Implement async - // TODO: Implement async generators match name { PropertyName::Literal(name) => { self.emit_opcode(Opcode::PushUndefined); @@ -965,16 +977,34 @@ impl<'b> ByteCompiler<'b> { } // TODO: implement AsyncFunctionExpr // TODO: implement AwaitExpr - // TODO: implement GeneratorExpr // TODO: implement AsyncGeneratorExpr - // TODO: implement Yield - Node::AsyncFunctionExpr(_) - | Node::AwaitExpr(_) - | Node::GeneratorExpr(_) - | Node::AsyncGeneratorExpr(_) - | Node::Yield(_) => { + Node::AsyncFunctionExpr(_) | Node::AwaitExpr(_) | Node::AsyncGeneratorExpr(_) => { self.emit_opcode(Opcode::PushUndefined); } + Node::GeneratorExpr(_) => self.function(expr, use_expr)?, + Node::Yield(r#yield) => { + if let Some(expr) = r#yield.expr() { + self.compile_expr(expr, true)?; + } else { + self.emit_opcode(Opcode::PushUndefined); + } + + if r#yield.delegate() { + self.emit_opcode(Opcode::InitIterator); + self.emit_opcode(Opcode::PushUndefined); + let start_address = self.next_opcode_location(); + let start = self.jump_with_custom_opcode(Opcode::GeneratorNextDelegate); + self.emit(Opcode::Jump, &[start_address]); + self.patch_jump(start); + } else { + self.emit_opcode(Opcode::Yield); + self.emit_opcode(Opcode::GeneratorNext); + } + + if !use_expr { + self.emit_opcode(Opcode::Pop); + } + } Node::TaggedTemplate(template) => { match template.tag() { Node::GetConstField(field) => { @@ -1651,9 +1681,9 @@ impl<'b> ByteCompiler<'b> { } } // TODO: implement AsyncFunctionDecl - // TODO: implement GeneratorDecl + Node::GeneratorDecl(_) => self.function(node, false)?, // TODO: implement AsyncGeneratorDecl - Node::AsyncFunctionDecl(_) | Node::GeneratorDecl(_) | Node::AsyncGeneratorDecl(_) => { + Node::AsyncFunctionDecl(_) | Node::AsyncGeneratorDecl(_) => { self.emit_opcode(Opcode::PushUndefined); } Node::Empty => {} @@ -1670,30 +1700,47 @@ impl<'b> ByteCompiler<'b> { Arrow, } - let (kind, name, parameters, body) = match function { + let (kind, name, parameters, body, generator) = match function { Node::FunctionDecl(function) => ( FunctionKind::Declaration, Some(function.name()), function.parameters(), function.body(), + false, + ), + Node::GeneratorDecl(generator) => ( + FunctionKind::Declaration, + Some(generator.name()), + generator.parameters(), + generator.body(), + true, ), Node::FunctionExpr(function) => ( FunctionKind::Expression, function.name(), function.parameters(), function.body(), + false, + ), + Node::GeneratorExpr(generator) => ( + FunctionKind::Expression, + generator.name(), + generator.parameters(), + generator.body(), + true, ), Node::ArrowFunctionDecl(function) => ( FunctionKind::Arrow, function.name(), function.params(), function.body(), + false, ), _ => unreachable!(), }; let strict = body.strict() || self.code_block.strict; - let length = parameters.len() as u32; + let length = parameters.parameters.len() as u32; let mut code = CodeBlock::new(name.unwrap_or(Sym::EMPTY_STRING), length, strict, true); if let FunctionKind::Arrow = kind { @@ -1701,6 +1748,10 @@ impl<'b> ByteCompiler<'b> { code.this_mode = ThisMode::Lexical; } + if generator { + code.constructor = false; + } + let mut compiler = ByteCompiler { code_block: code, literals_map: FxHashMap::default(), @@ -1712,18 +1763,12 @@ impl<'b> ByteCompiler<'b> { compiler.context.push_compile_time_environment(true); - let mut arguments_in_parameter_names = false; - for parameter in parameters { - arguments_in_parameter_names = - arguments_in_parameter_names || parameter.names().contains(&Sym::ARGUMENTS); - } - // An arguments object is added when all of the following conditions are met // - If not in an arrow function (10.2.11.16) // - If the parameter list does not contain `arguments` (10.2.11.17) // Note: This following just means, that we add an extra environment for the arguments. // - If there are default parameters or if lexical names and function names do not contain `arguments` (10.2.11.18) - if !(kind == FunctionKind::Arrow) && !arguments_in_parameter_names { + if !(kind == FunctionKind::Arrow) && !parameters.has_arguments() { compiler .context .create_mutable_binding(Sym::ARGUMENTS, false, true)?; @@ -1734,16 +1779,8 @@ impl<'b> ByteCompiler<'b> { ); } - let mut has_rest_parameter = false; - let mut has_parameter_expressions = false; - - for parameter in parameters { - has_parameter_expressions = has_parameter_expressions || parameter.init().is_some(); - arguments_in_parameter_names = - arguments_in_parameter_names || parameter.names().contains(&Sym::ARGUMENTS); - + for parameter in parameters.parameters.iter() { if parameter.is_rest_param() { - has_rest_parameter = true; compiler.emit_opcode(Opcode::RestParameterInit); } @@ -1770,11 +1807,11 @@ impl<'b> ByteCompiler<'b> { } } - if !has_rest_parameter { + if !parameters.has_rest_parameter() { compiler.emit_opcode(Opcode::RestParameterPop); } - let env_label = if has_parameter_expressions { + let env_label = if parameters.has_expressions() { compiler.code_block.num_bindings = compiler.context.get_binding_number(); compiler.context.push_compile_time_environment(true); Some(compiler.jump_with_custom_opcode(Opcode::PushFunctionEnvironment)) @@ -1782,6 +1819,12 @@ impl<'b> ByteCompiler<'b> { None }; + // When a generator object is created from a generator function, the generator executes until here to init parameters. + if generator { + compiler.emit_opcode(Opcode::PushUndefined); + compiler.emit_opcode(Opcode::Yield); + } + for node in body.items() { compiler.create_declarations(node)?; } @@ -1802,7 +1845,7 @@ impl<'b> ByteCompiler<'b> { .num_bindings(); } - compiler.code_block.params = parameters.to_owned().into_boxed_slice(); + compiler.code_block.params = parameters.clone(); // TODO These are redundant if a function returns so may need to check if a function returns and adding these if it doesn't compiler.emit(Opcode::PushUndefined, &[]); @@ -1813,7 +1856,11 @@ impl<'b> ByteCompiler<'b> { let index = self.code_block.functions.len() as u32; self.code_block.functions.push(code); - self.emit(Opcode::GetFunction, &[index]); + if generator { + self.emit(Opcode::GetGenerator, &[index]); + } else { + self.emit(Opcode::GetFunction, &[index]); + } match kind { FunctionKind::Declaration => { @@ -1848,13 +1895,17 @@ impl<'b> ByteCompiler<'b> { match call.expr() { Node::GetConstField(field) => { self.compile_expr(field.obj(), true)?; - self.emit(Opcode::Dup, &[]); + if kind == CallKind::Call { + self.emit(Opcode::Dup, &[]); + } let index = self.get_or_insert_name(field.field()); self.emit(Opcode::GetPropertyByName, &[index]); } Node::GetField(field) => { self.compile_expr(field.obj(), true)?; - self.emit(Opcode::Dup, &[]); + if kind == CallKind::Call { + self.emit(Opcode::Dup, &[]); + } self.compile_expr(field.field(), true)?; self.emit(Opcode::Swap, &[]); self.emit(Opcode::GetPropertyByValue, &[]); diff --git a/boa_engine/src/context.rs b/boa_engine/src/context.rs index a29bfb3a4a5..042a11972c4 100644 --- a/boa_engine/src/context.rs +++ b/boa_engine/src/context.rs @@ -11,7 +11,7 @@ use crate::{ property::{Attribute, PropertyDescriptor, PropertyKey}, realm::Realm, syntax::{ast::node::StatementList, parser::ParseError, Parser}, - vm::{CallFrame, CodeBlock, FinallyReturn, Vm}, + vm::{CallFrame, CodeBlock, FinallyReturn, GeneratorResumeKind, Vm}, JsResult, JsValue, }; use boa_gc::Gc; @@ -69,6 +69,8 @@ pub struct StandardObjects { object: StandardConstructor, proxy: StandardConstructor, function: StandardConstructor, + generator: StandardConstructor, + generator_function: StandardConstructor, array: StandardConstructor, bigint: StandardConstructor, number: StandardConstructor, @@ -107,6 +109,8 @@ impl Default for StandardObjects { object: StandardConstructor::default(), proxy: StandardConstructor::default(), function: StandardConstructor::default(), + generator: StandardConstructor::default(), + generator_function: StandardConstructor::default(), array: StandardConstructor::default(), bigint: StandardConstructor::default(), number: StandardConstructor::with_prototype(JsObject::from_proto_and_data( @@ -166,6 +170,16 @@ impl StandardObjects { &self.function } + #[inline] + pub fn generator_object(&self) -> &StandardConstructor { + &self.generator + } + + #[inline] + pub fn generator_function_object(&self) -> &StandardConstructor { + &self.generator_function + } + #[inline] pub fn array_object(&self) -> &StandardConstructor { &self.array @@ -424,8 +438,8 @@ impl Default for Context { .clone(); context.typed_array_constructor.constructor = typed_array_constructor_constructor; context.typed_array_constructor.prototype = typed_array_constructor_prototype; - context.create_intrinsics(); context.iterator_prototypes = IteratorPrototypes::init(&mut context); + context.create_intrinsics(); context.intrinsic_objects = IntrinsicObjects::init(&mut context); context } @@ -1011,12 +1025,14 @@ impl Context { }], param_count: 0, arg_count: 0, + generator_resume_kind: GeneratorResumeKind::Normal, }); self.realm.set_global_binding_number(); let result = self.run(); self.vm.pop_frame(); - result + let (result, _) = result?; + Ok(result) } /// Return the cached iterator prototypes. diff --git a/boa_engine/src/object/jsobject.rs b/boa_engine/src/object/jsobject.rs index 8a6a995bbe8..1568db961ec 100644 --- a/boa_engine/src/object/jsobject.rs +++ b/boa_engine/src/object/jsobject.rs @@ -340,6 +340,17 @@ impl JsObject { self.borrow().is_function() } + /// Checks if it's a `Generator` object. + /// + /// # Panics + /// + /// Panics if the object is currently mutably borrowed. + #[inline] + #[track_caller] + pub fn is_generator(&self) -> bool { + self.borrow().is_generator() + } + /// Checks if it's a `Symbol` object. /// /// # Panics diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index ff3ef040cbc..09735b9c733 100644 --- a/boa_engine/src/object/mod.rs +++ b/boa_engine/src/object/mod.rs @@ -28,6 +28,7 @@ use crate::{ function::{ arguments::ParameterMap, BoundFunction, Captures, Function, NativeFunctionSignature, }, + generator::Generator, map::map_iterator::MapIterator, map::ordered_map::OrderedMap, object::for_in_iterator::ForInIterator, @@ -130,6 +131,8 @@ pub enum ObjectKind { ForInIterator(ForInIterator), Function(Function), BoundFunction(BoundFunction), + Generator(Generator), + GeneratorFunction(Function), Set(OrderedSet), SetIterator(SetIterator), String(JsString), @@ -259,6 +262,26 @@ impl ObjectData { } } + /// Create the `Generator` object data + pub fn generator(generator: Generator) -> Self { + Self { + kind: ObjectKind::Generator(generator), + internal_methods: &ORDINARY_INTERNAL_METHODS, + } + } + + /// Create the `GeneratorFunction` object data + pub fn generator_function(function: Function) -> Self { + Self { + internal_methods: if function.is_constructor() { + &CONSTRUCTOR_INTERNAL_METHODS + } else { + &FUNCTION_INTERNAL_METHODS + }, + kind: ObjectKind::GeneratorFunction(function), + } + } + /// Create the `Set` object data pub fn set(set: OrderedSet) -> Self { Self { @@ -391,6 +414,8 @@ impl Display for ObjectKind { Self::ForInIterator(_) => "ForInIterator", Self::Function(_) => "Function", Self::BoundFunction(_) => "BoundFunction", + Self::Generator(_) => "Generator", + Self::GeneratorFunction(_) => "GeneratorFunction", Self::RegExp(_) => "RegExp", Self::RegExpStringIterator(_) => "RegExpStringIterator", Self::Map(_) => "Map", @@ -718,7 +743,8 @@ impl Object { pub fn as_function(&self) -> Option<&Function> { match self.data { ObjectData { - kind: ObjectKind::Function(ref function), + kind: + ObjectKind::Function(ref function) | ObjectKind::GeneratorFunction(ref function), .. } => Some(function), _ => None, @@ -729,7 +755,9 @@ impl Object { pub fn as_function_mut(&mut self) -> Option<&mut Function> { match self.data { ObjectData { - kind: ObjectKind::Function(ref mut function), + kind: + ObjectKind::Function(ref mut function) + | ObjectKind::GeneratorFunction(ref mut function), .. } => Some(function), _ => None, @@ -747,6 +775,42 @@ impl Object { } } + /// Checks if it's a `Generator` object. + #[inline] + pub fn is_generator(&self) -> bool { + matches!( + self.data, + ObjectData { + kind: ObjectKind::Generator(_), + .. + } + ) + } + + /// Returns a reference to the generator data on the object. + #[inline] + pub fn as_generator(&self) -> Option<&Generator> { + match self.data { + ObjectData { + kind: ObjectKind::Generator(ref generator), + .. + } => Some(generator), + _ => None, + } + } + + /// Returns a mutable reference to the generator data on the object. + #[inline] + pub fn as_generator_mut(&mut self) -> Option<&mut Generator> { + match self.data { + ObjectData { + kind: ObjectKind::Generator(ref mut generator), + .. + } => Some(generator), + _ => None, + } + } + /// Checks if it a Symbol object. #[inline] pub fn is_symbol(&self) -> bool { @@ -1342,7 +1406,9 @@ impl<'context> FunctionBuilder<'context> { } => { *constructor = yes; } - Function::VmOrdinary { .. } => unreachable!("function must be native or closure"), + Function::Ordinary { .. } | Function::Generator { .. } => { + unreachable!("function must be native or closure"); + } } self } diff --git a/boa_engine/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs b/boa_engine/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs index 674b109ba4c..5514b3b5cfe 100644 --- a/boa_engine/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs +++ b/boa_engine/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs @@ -1,4 +1,4 @@ -use crate::syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList}; +use crate::syntax::ast::node::{join_nodes, FormalParameterList, Node, StatementList}; use boa_gc::{Finalize, Trace}; use boa_interner::{Interner, Sym, ToInternedString}; @@ -22,7 +22,7 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Trace, Finalize, PartialEq)] pub struct ArrowFunctionDecl { name: Option, - params: Box<[FormalParameter]>, + params: FormalParameterList, body: StatementList, } @@ -31,7 +31,7 @@ impl ArrowFunctionDecl { pub(in crate::syntax) fn new(name: N, params: P, body: B) -> Self where N: Into>, - P: Into>, + P: Into, B: Into, { Self { @@ -52,7 +52,7 @@ impl ArrowFunctionDecl { } /// Gets the list of parameters of the arrow function. - pub(crate) fn params(&self) -> &[FormalParameter] { + pub(crate) fn params(&self) -> &FormalParameterList { &self.params } @@ -67,7 +67,7 @@ impl ArrowFunctionDecl { interner: &Interner, indentation: usize, ) -> String { - let mut buf = format!("({}", join_nodes(interner, &self.params)); + let mut buf = format!("({}", join_nodes(interner, &self.params.parameters)); if self.body().items().is_empty() { buf.push_str(") => {}"); } else { diff --git a/boa_engine/src/syntax/ast/node/declaration/async_function_decl/mod.rs b/boa_engine/src/syntax/ast/node/declaration/async_function_decl/mod.rs index fe628733449..27e59f29f01 100644 --- a/boa_engine/src/syntax/ast/node/declaration/async_function_decl/mod.rs +++ b/boa_engine/src/syntax/ast/node/declaration/async_function_decl/mod.rs @@ -1,6 +1,6 @@ //! Async Function Declaration. -use crate::syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList}; +use crate::syntax::ast::node::{join_nodes, FormalParameterList, Node, StatementList}; use boa_gc::{Finalize, Trace}; use boa_interner::{Interner, Sym, ToInternedString}; @@ -19,7 +19,7 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Trace, Finalize, PartialEq)] pub struct AsyncFunctionDecl { name: Sym, - parameters: Box<[FormalParameter]>, + parameters: FormalParameterList, body: StatementList, } @@ -27,7 +27,7 @@ impl AsyncFunctionDecl { /// Creates a new async function declaration. pub(in crate::syntax) fn new(name: Sym, parameters: P, body: B) -> Self where - P: Into>, + P: Into, B: Into, { Self { @@ -43,7 +43,7 @@ impl AsyncFunctionDecl { } /// Gets the list of parameters of the async function declaration. - pub fn parameters(&self) -> &[FormalParameter] { + pub fn parameters(&self) -> &FormalParameterList { &self.parameters } @@ -61,9 +61,8 @@ impl AsyncFunctionDecl { let mut buf = format!( "async function {}({}", interner.resolve_expect(self.name), - join_nodes(interner, &self.parameters) + join_nodes(interner, &self.parameters.parameters) ); - if self.body().is_empty() { buf.push_str(") {}"); } else { diff --git a/boa_engine/src/syntax/ast/node/declaration/async_function_expr/mod.rs b/boa_engine/src/syntax/ast/node/declaration/async_function_expr/mod.rs index d508778ea6b..38012856fc3 100644 --- a/boa_engine/src/syntax/ast/node/declaration/async_function_expr/mod.rs +++ b/boa_engine/src/syntax/ast/node/declaration/async_function_expr/mod.rs @@ -1,6 +1,6 @@ //! Async Function Expression. -use crate::syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList}; +use crate::syntax::ast::node::{join_nodes, FormalParameterList, Node, StatementList}; use boa_gc::{Finalize, Trace}; use boa_interner::{Interner, Sym, ToInternedString}; @@ -20,7 +20,7 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Trace, Finalize, PartialEq)] pub struct AsyncFunctionExpr { name: Option, - parameters: Box<[FormalParameter]>, + parameters: FormalParameterList, body: StatementList, } @@ -29,7 +29,7 @@ impl AsyncFunctionExpr { pub(in crate::syntax) fn new(name: N, parameters: P, body: B) -> Self where N: Into>, - P: Into>, + P: Into, B: Into, { Self { @@ -45,13 +45,13 @@ impl AsyncFunctionExpr { } /// Gets the list of parameters of the function declaration. - pub fn parameters(&self) -> &[FormalParameter] { + pub fn parameters(&self) -> &FormalParameterList { &self.parameters } /// Gets the body of the function declaration. - pub fn body(&self) -> &[Node] { - self.body.items() + pub fn body(&self) -> &StatementList { + &self.body } /// Implements the display formatting with indentation. @@ -64,8 +64,11 @@ impl AsyncFunctionExpr { if let Some(name) = self.name { buf.push_str(&format!(" {}", interner.resolve_expect(name))); } - buf.push_str(&format!("({}", join_nodes(interner, &self.parameters))); - if self.body().is_empty() { + buf.push_str(&format!( + "({}", + join_nodes(interner, &self.parameters.parameters) + )); + if self.body().items().is_empty() { buf.push_str(") {}"); } else { buf.push_str(&format!( diff --git a/boa_engine/src/syntax/ast/node/declaration/async_generator_decl/mod.rs b/boa_engine/src/syntax/ast/node/declaration/async_generator_decl/mod.rs index 279e31fa2f7..cb127f7d830 100644 --- a/boa_engine/src/syntax/ast/node/declaration/async_generator_decl/mod.rs +++ b/boa_engine/src/syntax/ast/node/declaration/async_generator_decl/mod.rs @@ -1,6 +1,6 @@ //! Async Generator Declaration -use crate::syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList}; +use crate::syntax::ast::node::{join_nodes, FormalParameterList, Node, StatementList}; use boa_gc::{Finalize, Trace}; use boa_interner::{Interner, Sym, ToInternedString}; @@ -17,7 +17,7 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Trace, Finalize, PartialEq)] pub struct AsyncGeneratorDecl { name: Sym, - parameters: Box<[FormalParameter]>, + parameters: FormalParameterList, body: StatementList, } @@ -25,7 +25,7 @@ impl AsyncGeneratorDecl { /// Creates a new async generator declaration. pub(in crate::syntax) fn new(name: Sym, parameters: P, body: B) -> Self where - P: Into>, + P: Into, B: Into, { Self { @@ -41,7 +41,7 @@ impl AsyncGeneratorDecl { } /// Gets the list of parameters of the async function declaration. - pub fn parameters(&self) -> &[FormalParameter] { + pub fn parameters(&self) -> &FormalParameterList { &self.parameters } @@ -59,7 +59,7 @@ impl AsyncGeneratorDecl { let mut buf = format!( "async function* {}({}", interner.resolve_expect(self.name), - join_nodes(interner, &self.parameters) + join_nodes(interner, &self.parameters.parameters) ); if self.body().is_empty() { buf.push_str(") {}"); diff --git a/boa_engine/src/syntax/ast/node/declaration/async_generator_expr/mod.rs b/boa_engine/src/syntax/ast/node/declaration/async_generator_expr/mod.rs index fee25b76f1e..f41aa6b7574 100644 --- a/boa_engine/src/syntax/ast/node/declaration/async_generator_expr/mod.rs +++ b/boa_engine/src/syntax/ast/node/declaration/async_generator_expr/mod.rs @@ -1,6 +1,6 @@ //! Async Generator Expression -use crate::syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList}; +use crate::syntax::ast::node::{join_nodes, FormalParameterList, Node, StatementList}; use boa_gc::{Finalize, Trace}; use boa_interner::{Interner, Sym, ToInternedString}; @@ -19,7 +19,7 @@ use super::block_to_string; #[derive(Clone, Debug, Trace, Finalize, PartialEq)] pub struct AsyncGeneratorExpr { name: Option, - parameters: Box<[FormalParameter]>, + parameters: FormalParameterList, body: StatementList, } @@ -28,7 +28,7 @@ impl AsyncGeneratorExpr { pub(in crate::syntax) fn new(name: N, parameters: P, body: B) -> Self where N: Into>, - P: Into>, + P: Into, B: Into, { Self { @@ -44,7 +44,7 @@ impl AsyncGeneratorExpr { } /// Gets the list of parameters of the async generator expression - pub fn parameters(&self) -> &[FormalParameter] { + pub fn parameters(&self) -> &FormalParameterList { &self.parameters } @@ -64,7 +64,7 @@ impl AsyncGeneratorExpr { } buf.push_str(&format!( "({}) {}", - join_nodes(interner, &self.parameters), + join_nodes(interner, &self.parameters.parameters), block_to_string(&self.body, interner, indentation) )); diff --git a/boa_engine/src/syntax/ast/node/declaration/function_decl/mod.rs b/boa_engine/src/syntax/ast/node/declaration/function_decl/mod.rs index b97eb285047..41d3d2baa89 100644 --- a/boa_engine/src/syntax/ast/node/declaration/function_decl/mod.rs +++ b/boa_engine/src/syntax/ast/node/declaration/function_decl/mod.rs @@ -1,4 +1,4 @@ -use crate::syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList}; +use crate::syntax::ast::node::{join_nodes, FormalParameterList, Node, StatementList}; use boa_gc::{Finalize, Trace}; use boa_interner::{Interner, Sym, ToInternedString}; @@ -27,7 +27,7 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Trace, Finalize, PartialEq)] pub struct FunctionDecl { name: Sym, - parameters: Box<[FormalParameter]>, + parameters: FormalParameterList, body: StatementList, } @@ -35,7 +35,7 @@ impl FunctionDecl { /// Creates a new function declaration. pub(in crate::syntax) fn new(name: Sym, parameters: P, body: B) -> Self where - P: Into>, + P: Into, B: Into, { Self { @@ -51,7 +51,7 @@ impl FunctionDecl { } /// Gets the list of parameters of the function declaration. - pub fn parameters(&self) -> &[FormalParameter] { + pub fn parameters(&self) -> &FormalParameterList { &self.parameters } @@ -69,7 +69,7 @@ impl FunctionDecl { let mut buf = format!( "function {}({}", interner.resolve_expect(self.name), - join_nodes(interner, &self.parameters) + join_nodes(interner, &self.parameters.parameters) ); if self.body().items().is_empty() { buf.push_str(") {}"); diff --git a/boa_engine/src/syntax/ast/node/declaration/function_expr/mod.rs b/boa_engine/src/syntax/ast/node/declaration/function_expr/mod.rs index 2905f906068..73e65a983a8 100644 --- a/boa_engine/src/syntax/ast/node/declaration/function_expr/mod.rs +++ b/boa_engine/src/syntax/ast/node/declaration/function_expr/mod.rs @@ -1,4 +1,4 @@ -use crate::syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList}; +use crate::syntax::ast::node::{join_nodes, FormalParameterList, Node, StatementList}; use boa_gc::{Finalize, Trace}; use boa_interner::{Interner, Sym, ToInternedString}; @@ -27,7 +27,7 @@ use super::block_to_string; #[derive(Clone, Debug, Trace, Finalize, PartialEq)] pub struct FunctionExpr { name: Option, - parameters: Box<[FormalParameter]>, + parameters: FormalParameterList, body: StatementList, } @@ -36,7 +36,7 @@ impl FunctionExpr { pub(in crate::syntax) fn new(name: N, parameters: P, body: B) -> Self where N: Into>, - P: Into>, + P: Into, B: Into, { Self { @@ -52,7 +52,7 @@ impl FunctionExpr { } /// Gets the list of parameters of the function declaration. - pub fn parameters(&self) -> &[FormalParameter] { + pub fn parameters(&self) -> &FormalParameterList { &self.parameters } @@ -73,7 +73,7 @@ impl FunctionExpr { } buf.push_str(&format!( "({}) {}", - join_nodes(interner, &self.parameters), + join_nodes(interner, &self.parameters.parameters), block_to_string(&self.body, interner, indentation) )); diff --git a/boa_engine/src/syntax/ast/node/declaration/generator_decl/mod.rs b/boa_engine/src/syntax/ast/node/declaration/generator_decl/mod.rs index d1ca3d2d83c..6dbd0d66417 100644 --- a/boa_engine/src/syntax/ast/node/declaration/generator_decl/mod.rs +++ b/boa_engine/src/syntax/ast/node/declaration/generator_decl/mod.rs @@ -1,4 +1,4 @@ -use crate::syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList}; +use crate::syntax::ast::node::{join_nodes, FormalParameterList, Node, StatementList}; use boa_gc::{Finalize, Trace}; use boa_interner::{Interner, Sym, ToInternedString}; @@ -18,7 +18,7 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Trace, Finalize, PartialEq)] pub struct GeneratorDecl { name: Sym, - parameters: Box<[FormalParameter]>, + parameters: FormalParameterList, body: StatementList, } @@ -26,7 +26,7 @@ impl GeneratorDecl { /// Creates a new generator declaration. pub(in crate::syntax) fn new(name: Sym, parameters: P, body: B) -> Self where - P: Into>, + P: Into, B: Into, { Self { @@ -42,13 +42,13 @@ impl GeneratorDecl { } /// Gets the list of parameters of the generator declaration. - pub fn parameters(&self) -> &[FormalParameter] { + pub fn parameters(&self) -> &FormalParameterList { &self.parameters } /// Gets the body of the generator declaration. - pub fn body(&self) -> &[Node] { - self.body.items() + pub fn body(&self) -> &StatementList { + &self.body } /// Implements the display formatting with indentation. @@ -60,9 +60,9 @@ impl GeneratorDecl { let mut buf = format!( "function* {}({}", interner.resolve_expect(self.name), - join_nodes(interner, &self.parameters) + join_nodes(interner, &self.parameters.parameters) ); - if self.body().is_empty() { + if self.body().items().is_empty() { buf.push_str(") {}"); } else { buf.push_str(&format!( diff --git a/boa_engine/src/syntax/ast/node/declaration/generator_expr/mod.rs b/boa_engine/src/syntax/ast/node/declaration/generator_expr/mod.rs index 05fbac78e34..28783b72c85 100644 --- a/boa_engine/src/syntax/ast/node/declaration/generator_expr/mod.rs +++ b/boa_engine/src/syntax/ast/node/declaration/generator_expr/mod.rs @@ -1,4 +1,4 @@ -use crate::syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList}; +use crate::syntax::ast::node::{join_nodes, FormalParameterList, Node, StatementList}; use boa_gc::{Finalize, Trace}; use boa_interner::{Interner, Sym, ToInternedString}; @@ -19,7 +19,7 @@ use super::block_to_string; #[derive(Clone, Debug, Trace, Finalize, PartialEq)] pub struct GeneratorExpr { name: Option, - parameters: Box<[FormalParameter]>, + parameters: FormalParameterList, body: StatementList, } @@ -28,7 +28,7 @@ impl GeneratorExpr { pub(in crate::syntax) fn new(name: N, parameters: P, body: B) -> Self where N: Into>, - P: Into>, + P: Into, B: Into, { Self { @@ -44,7 +44,7 @@ impl GeneratorExpr { } /// Gets the list of parameters of the generator declaration. - pub fn parameters(&self) -> &[FormalParameter] { + pub fn parameters(&self) -> &FormalParameterList { &self.parameters } @@ -65,7 +65,7 @@ impl GeneratorExpr { } buf.push_str(&format!( "({}) {}", - join_nodes(interner, &self.parameters), + join_nodes(interner, &self.parameters.parameters), block_to_string(&self.body, interner, indentation) )); diff --git a/boa_engine/src/syntax/ast/node/mod.rs b/boa_engine/src/syntax/ast/node/mod.rs index d1e0049ad9f..8d36bd5d8d4 100644 --- a/boa_engine/src/syntax/ast/node/mod.rs +++ b/boa_engine/src/syntax/ast/node/mod.rs @@ -1,5 +1,7 @@ //! This module implements the `Node` structure, which composes the AST. +mod parameters; + pub mod array; pub mod await_expr; pub mod block; @@ -39,6 +41,7 @@ pub use self::{ new::New, object::Object, operator::{Assign, BinOp, UnaryOp}, + parameters::{FormalParameter, FormalParameterList}, r#yield::Yield, return_smt::Return, spread::Spread, @@ -48,9 +51,12 @@ pub use self::{ throw::Throw, try_node::{Catch, Finally, Try}, }; + +pub(crate) use self::parameters::FormalParameterListFlags; + use super::Const; -use boa_gc::{unsafe_empty_trace, Finalize, Trace}; -use boa_interner::{Interner, Sym, ToInternedString}; +use boa_gc::{Finalize, Trace}; +use boa_interner::{Interner, ToInternedString}; use std::cmp::Ordering; #[cfg(feature = "deser")] @@ -348,316 +354,6 @@ where buf } -/// "Formal parameter" is a fancy way of saying "function parameter". -/// -/// In the declaration of a function, the parameters must be identifiers, -/// not any value like numbers, strings, or objects. -///```text -///function foo(formalParameter1, formalParameter2) { -///} -///``` -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#prod-FormalParameter -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Missing_formal_parameter -#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, PartialEq, Trace, Finalize)] -pub struct FormalParameter { - declaration: Declaration, - is_rest_param: bool, -} - -impl FormalParameter { - /// Creates a new formal parameter. - pub(in crate::syntax) fn new(declaration: D, is_rest_param: bool) -> Self - where - D: Into, - { - Self { - declaration: declaration.into(), - is_rest_param, - } - } - - /// Gets the name of the formal parameter. - pub fn names(&self) -> Vec { - match &self.declaration { - Declaration::Identifier { ident, .. } => vec![ident.sym()], - Declaration::Pattern(pattern) => match pattern { - DeclarationPattern::Object(object_pattern) => object_pattern.idents(), - - DeclarationPattern::Array(array_pattern) => array_pattern.idents(), - }, - } - } - - /// Get the declaration of the formal parameter - pub fn declaration(&self) -> &Declaration { - &self.declaration - } - - /// Gets the initialization node of the formal parameter, if any. - pub fn init(&self) -> Option<&Node> { - self.declaration.init() - } - - /// Gets wether the parameter is a rest parameter. - pub fn is_rest_param(&self) -> bool { - self.is_rest_param - } - - pub fn is_identifier(&self) -> bool { - matches!(&self.declaration, Declaration::Identifier { .. }) - } -} - -impl ToInternedString for FormalParameter { - fn to_interned_string(&self, interner: &Interner) -> String { - let mut buf = if self.is_rest_param { - "...".to_owned() - } else { - String::new() - }; - buf.push_str(&self.declaration.to_interned_string(interner)); - buf - } -} - -/// A JavaScript property is a characteristic of an object, often describing attributes associated with a data structure. -/// -/// A property has a name (a string) and a value (primitive, method, or object reference). -/// Note that when we say that "a property holds an object", that is shorthand for "a property holds an object reference". -/// This distinction matters because the original referenced object remains unchanged when you change the property's value. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#prod-PropertyDefinition -/// [mdn]: https://developer.mozilla.org/en-US/docs/Glossary/property/JavaScript -// TODO: Support all features: https://tc39.es/ecma262/#prod-PropertyDefinition -#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, PartialEq, Trace, Finalize)] -pub enum PropertyDefinition { - /// Puts a variable into an object. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - [MDN documentation][mdn] - /// - /// [spec]: https://tc39.es/ecma262/#prod-IdentifierReference - /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Property_definitions - IdentifierReference(Sym), - - /// Binds a property name to a JavaScript value. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - [MDN documentation][mdn] - /// - /// [spec]: https://tc39.es/ecma262/#prod-PropertyDefinition - /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Property_definitions - Property(PropertyName, Node), - - /// A property of an object can also refer to a function or a getter or setter method. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - [MDN documentation][mdn] - /// - /// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition - /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Method_definitions - MethodDefinition(MethodDefinitionKind, PropertyName, FunctionExpr), - - /// The Rest/Spread Properties for ECMAScript proposal (stage 4) adds spread properties to object literals. - /// It copies own enumerable properties from a provided object onto a new object. - /// - /// Shallow-cloning (excluding `prototype`) or merging objects is now possible using a shorter syntax than `Object.assign()`. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - [MDN documentation][mdn] - /// - /// [spec]: https://tc39.es/ecma262/#prod-PropertyDefinition - /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Spread_properties - SpreadObject(Node), -} - -impl PropertyDefinition { - /// Creates an `IdentifierReference` property definition. - pub fn identifier_reference(ident: Sym) -> Self { - Self::IdentifierReference(ident) - } - - /// Creates a `Property` definition. - pub fn property(name: N, value: V) -> Self - where - N: Into, - V: Into, - { - Self::Property(name.into(), value.into()) - } - - /// Creates a `MethodDefinition`. - pub fn method_definition(kind: MethodDefinitionKind, name: N, body: FunctionExpr) -> Self - where - N: Into, - { - Self::MethodDefinition(kind, name.into(), body) - } - - /// Creates a `SpreadObject`. - pub fn spread_object(obj: O) -> Self - where - O: Into, - { - Self::SpreadObject(obj.into()) - } -} - -/// Method definition kinds. -/// -/// Starting with ECMAScript 2015, a shorter syntax for method definitions on objects initializers is introduced. -/// It is a shorthand for a function assigned to the method's name. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions -#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, PartialEq, Copy, Finalize)] -pub enum MethodDefinitionKind { - /// The `get` syntax binds an object property to a function that will be called when that property is looked up. - /// - /// Sometimes it is desirable to allow access to a property that returns a dynamically computed value, - /// or you may want to reflect the status of an internal variable without requiring the use of explicit method calls. - /// In JavaScript, this can be accomplished with the use of a getter. - /// - /// It is not possible to simultaneously have a getter bound to a property and have that property actually hold a value, - /// although it is possible to use a getter and a setter in conjunction to create a type of pseudo-property. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - [MDN documentation][mdn] - /// - /// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition - /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get - Get, - - /// The `set` syntax binds an object property to a function to be called when there is an attempt to set that property. - /// - /// In JavaScript, a setter can be used to execute a function whenever a specified property is attempted to be changed. - /// Setters are most often used in conjunction with getters to create a type of pseudo-property. - /// It is not possible to simultaneously have a setter on a property that holds an actual value. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - [MDN documentation][mdn] - /// - /// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition - /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set - Set, - - /// Starting with ECMAScript 2015, you are able to define own methods in a shorter syntax, similar to the getters and setters. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - [MDN documentation][mdn] - /// - /// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition - /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions#Method_definition_syntax - Ordinary, - - /// Starting with ECMAScript 2015, you are able to define own methods in a shorter syntax, similar to the getters and setters. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - [MDN documentation][mdn] - /// - /// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition - /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions#generator_methods - Generator, - - /// Async generators can be used to define a method - /// - /// More information - /// - [ECMAScript reference][spec] - /// - [MDN documentation][mdn] - /// - /// [spec]: https://tc39.es/ecma262/#prod-AsyncGeneratorMethod - /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions#async_generator_methods - AsyncGenerator, - - /// Async function can be used to define a method - /// - /// More information - /// - [ECMAScript reference][spec] - /// - [MDN documentation][mdn] - /// - /// [spec]: https://tc39.es/ecma262/#prod-AsyncMethod - /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions#async_methods - Async, -} - -unsafe impl Trace for MethodDefinitionKind { - unsafe_empty_trace!(); -} - -/// `PropertyName` can be either a literal or computed. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#prod-PropertyName -#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, PartialEq, Finalize)] -pub enum PropertyName { - /// A `Literal` property name can be either an identifier, a string or a numeric literal. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#prod-LiteralPropertyName - Literal(Sym), - /// A `Computed` property name is an expression that gets evaluated and converted into a property name. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#prod-ComputedPropertyName - Computed(Node), -} - -impl ToInternedString for PropertyName { - fn to_interned_string(&self, interner: &Interner) -> String { - match self { - PropertyName::Literal(key) => interner.resolve_expect(*key).to_owned(), - PropertyName::Computed(key) => key.to_interned_string(interner), - } - } -} - -impl From for PropertyName { - fn from(name: Sym) -> Self { - Self::Literal(name) - } -} - -impl From for PropertyName { - fn from(name: Node) -> Self { - Self::Computed(name) - } -} - -unsafe impl Trace for PropertyName { - unsafe_empty_trace!(); -} - /// This parses the given source code, and then makes sure that /// the resulting `StatementList` is formatted in the same manner /// as the source code. This is expected to have a preceding diff --git a/boa_engine/src/syntax/ast/node/object/mod.rs b/boa_engine/src/syntax/ast/node/object/mod.rs index 4fc64660be2..f5dc0a30225 100644 --- a/boa_engine/src/syntax/ast/node/object/mod.rs +++ b/boa_engine/src/syntax/ast/node/object/mod.rs @@ -1,10 +1,11 @@ //! Object node. use crate::syntax::ast::node::{ - declaration::block_to_string, join_nodes, MethodDefinitionKind, Node, PropertyDefinition, + declaration::block_to_string, join_nodes, AsyncFunctionExpr, AsyncGeneratorExpr, FunctionExpr, + GeneratorExpr, Node, }; -use boa_gc::{Finalize, Trace}; -use boa_interner::{Interner, ToInternedString}; +use boa_gc::{unsafe_empty_trace, Finalize, Trace}; +use boa_interner::{Interner, Sym, ToInternedString}; #[cfg(feature = "deser")] use serde::{Deserialize, Serialize}; @@ -66,20 +67,47 @@ impl Object { PropertyDefinition::SpreadObject(key) => { format!("{indentation}...{},\n", key.to_interned_string(interner)) } - PropertyDefinition::MethodDefinition(kind, key, node) => { + PropertyDefinition::MethodDefinition(method, key) => { format!( "{indentation}{}{}({}) {},\n", - match &kind { - MethodDefinitionKind::Get => "get ", - MethodDefinitionKind::Set => "set ", - MethodDefinitionKind::Ordinary - | MethodDefinitionKind::Generator - | MethodDefinitionKind::Async - | MethodDefinitionKind::AsyncGenerator => "", + match &method { + MethodDefinition::Get(_) => "get ", + MethodDefinition::Set(_) => "set ", + _ => "", }, key.to_interned_string(interner), - join_nodes(interner, node.parameters()), - block_to_string(node.body(), interner, indent_n + 1) + match &method { + MethodDefinition::Get(node) + | MethodDefinition::Set(node) + | MethodDefinition::Ordinary(node) => { + join_nodes(interner, &node.parameters().parameters) + } + MethodDefinition::Generator(node) => { + join_nodes(interner, &node.parameters().parameters) + } + MethodDefinition::AsyncGenerator(node) => { + join_nodes(interner, &node.parameters().parameters) + } + MethodDefinition::Async(node) => { + join_nodes(interner, &node.parameters().parameters) + } + }, + match &method { + MethodDefinition::Get(node) + | MethodDefinition::Set(node) + | MethodDefinition::Ordinary(node) => { + block_to_string(node.body(), interner, indent_n + 1) + } + MethodDefinition::Generator(node) => { + block_to_string(node.body(), interner, indent_n + 1) + } + MethodDefinition::AsyncGenerator(node) => { + block_to_string(node.body(), interner, indent_n + 1) + } + MethodDefinition::Async(node) => { + block_to_string(node.body(), interner, indent_n + 1) + } + }, ) } }); @@ -112,3 +140,231 @@ impl From for Node { Self::Object(obj) } } + +/// A JavaScript property is a characteristic of an object, often describing attributes associated with a data structure. +/// +/// A property has a name (a string) and a value (primitive, method, or object reference). +/// Note that when we say that "a property holds an object", that is shorthand for "a property holds an object reference". +/// This distinction matters because the original referenced object remains unchanged when you change the property's value. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#prod-PropertyDefinition +/// [mdn]: https://developer.mozilla.org/en-US/docs/Glossary/property/JavaScript +// TODO: Support all features: https://tc39.es/ecma262/#prod-PropertyDefinition +#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, PartialEq, Trace, Finalize)] +pub enum PropertyDefinition { + /// Puts a variable into an object. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#prod-IdentifierReference + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Property_definitions + IdentifierReference(Sym), + + /// Binds a property name to a JavaScript value. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#prod-PropertyDefinition + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Property_definitions + Property(PropertyName, Node), + + /// A property of an object can also refer to a function or a getter or setter method. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Method_definitions + MethodDefinition(MethodDefinition, PropertyName), + + /// The Rest/Spread Properties for ECMAScript proposal (stage 4) adds spread properties to object literals. + /// It copies own enumerable properties from a provided object onto a new object. + /// + /// Shallow-cloning (excluding `prototype`) or merging objects is now possible using a shorter syntax than `Object.assign()`. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#prod-PropertyDefinition + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Spread_properties + SpreadObject(Node), +} + +impl PropertyDefinition { + /// Creates an `IdentifierReference` property definition. + pub fn identifier_reference(ident: Sym) -> Self { + Self::IdentifierReference(ident) + } + + /// Creates a `Property` definition. + pub fn property(name: N, value: V) -> Self + where + N: Into, + V: Into, + { + Self::Property(name.into(), value.into()) + } + + /// Creates a `MethodDefinition`. + pub fn method_definition(kind: MethodDefinition, name: N) -> Self + where + N: Into, + { + Self::MethodDefinition(kind, name.into()) + } + + /// Creates a `SpreadObject`. + pub fn spread_object(obj: O) -> Self + where + O: Into, + { + Self::SpreadObject(obj.into()) + } +} + +/// Method definition. +/// +/// Starting with ECMAScript 2015, a shorter syntax for method definitions on objects initializers is introduced. +/// It is a shorthand for a function assigned to the method's name. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions +#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, PartialEq, Finalize, Trace)] +pub enum MethodDefinition { + /// The `get` syntax binds an object property to a function that will be called when that property is looked up. + /// + /// Sometimes it is desirable to allow access to a property that returns a dynamically computed value, + /// or you may want to reflect the status of an internal variable without requiring the use of explicit method calls. + /// In JavaScript, this can be accomplished with the use of a getter. + /// + /// It is not possible to simultaneously have a getter bound to a property and have that property actually hold a value, + /// although it is possible to use a getter and a setter in conjunction to create a type of pseudo-property. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get + Get(FunctionExpr), + + /// The `set` syntax binds an object property to a function to be called when there is an attempt to set that property. + /// + /// In JavaScript, a setter can be used to execute a function whenever a specified property is attempted to be changed. + /// Setters are most often used in conjunction with getters to create a type of pseudo-property. + /// It is not possible to simultaneously have a setter on a property that holds an actual value. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set + Set(FunctionExpr), + + /// Starting with ECMAScript 2015, you are able to define own methods in a shorter syntax, similar to the getters and setters. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions#Method_definition_syntax + Ordinary(FunctionExpr), + + /// Starting with ECMAScript 2015, you are able to define own methods in a shorter syntax, similar to the getters and setters. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#prod-MethodDefinition + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions#generator_methods + Generator(GeneratorExpr), + + /// Async generators can be used to define a method + /// + /// More information + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#prod-AsyncGeneratorMethod + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions#async_generator_methods + AsyncGenerator(AsyncGeneratorExpr), + + /// Async function can be used to define a method + /// + /// More information + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#prod-AsyncMethod + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions#async_methods + Async(AsyncFunctionExpr), +} + +/// `PropertyName` can be either a literal or computed. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-PropertyName +#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, PartialEq, Finalize)] +pub enum PropertyName { + /// A `Literal` property name can be either an identifier, a string or a numeric literal. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#prod-LiteralPropertyName + Literal(Sym), + /// A `Computed` property name is an expression that gets evaluated and converted into a property name. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#prod-ComputedPropertyName + Computed(Node), +} + +impl ToInternedString for PropertyName { + fn to_interned_string(&self, interner: &Interner) -> String { + match self { + PropertyName::Literal(key) => interner.resolve_expect(*key).to_owned(), + PropertyName::Computed(key) => key.to_interned_string(interner), + } + } +} + +impl From for PropertyName { + fn from(name: Sym) -> Self { + Self::Literal(name) + } +} + +impl From for PropertyName { + fn from(name: Node) -> Self { + Self::Computed(name) + } +} + +unsafe impl Trace for PropertyName { + unsafe_empty_trace!(); +} diff --git a/boa_engine/src/syntax/ast/node/parameters.rs b/boa_engine/src/syntax/ast/node/parameters.rs new file mode 100644 index 00000000000..40fb50a41b5 --- /dev/null +++ b/boa_engine/src/syntax/ast/node/parameters.rs @@ -0,0 +1,153 @@ +use super::{Declaration, DeclarationPattern, Node}; +use bitflags::bitflags; +use boa_gc::{Finalize, Trace}; +use boa_interner::{Interner, Sym, ToInternedString}; + +#[cfg(feature = "deser")] +use serde::{Deserialize, Serialize}; + +/// `FormalParameterList` is a list of `FormalParameter`s that describes the parameters of a function. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-FormalParameterList +#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Default, PartialEq, Trace, Finalize)] +pub struct FormalParameterList { + pub(crate) parameters: Box<[FormalParameter]>, + #[unsafe_ignore_trace] + pub(crate) flags: FormalParameterListFlags, +} + +impl FormalParameterList { + /// Creates a new formal parameter list. + pub(crate) fn new(parameters: Box<[FormalParameter]>, flags: FormalParameterListFlags) -> Self { + Self { parameters, flags } + } + + /// Indicates if the parameter list is simple. + pub(crate) fn is_simple(&self) -> bool { + self.flags.contains(FormalParameterListFlags::IS_SIMPLE) + } + + /// Indicates if the parameter list has duplicate parameters. + pub(crate) fn has_duplicates(&self) -> bool { + self.flags + .contains(FormalParameterListFlags::HAS_DUPLICATES) + } + + /// Indicates if the parameter list has a rest parameter. + pub(crate) fn has_rest_parameter(&self) -> bool { + self.flags + .contains(FormalParameterListFlags::HAS_REST_PARAMETER) + } + + /// Indicates if the parameter list has expressions in it's parameters. + pub(crate) fn has_expressions(&self) -> bool { + self.flags + .contains(FormalParameterListFlags::HAS_EXPRESSIONS) + } + + /// Indicates if the parameter list has parameters named 'arguments'. + pub(crate) fn has_arguments(&self) -> bool { + self.flags.contains(FormalParameterListFlags::HAS_ARGUMENTS) + } +} + +bitflags! { + /// Flags for a [`FormalParameterList`]. + #[allow(clippy::unsafe_derive_deserialize)] + #[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] + pub(crate) struct FormalParameterListFlags: u8 { + const IS_SIMPLE = 0b0000_0001; + const HAS_DUPLICATES = 0b0000_0010; + const HAS_REST_PARAMETER = 0b0000_0100; + const HAS_EXPRESSIONS = 0b0000_1000; + const HAS_ARGUMENTS = 0b0001_0000; + } +} + +impl Default for FormalParameterListFlags { + fn default() -> Self { + Self::empty().union(Self::IS_SIMPLE) + } +} + +/// "Formal parameter" is a fancy way of saying "function parameter". +/// +/// In the declaration of a function, the parameters must be identifiers, +/// not any value like numbers, strings, or objects. +///```text +///function foo(formalParameter1, formalParameter2) { +///} +///``` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// - [MDN documentation][mdn] +/// +/// [spec]: https://tc39.es/ecma262/#prod-FormalParameter +/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Missing_formal_parameter +#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, PartialEq, Trace, Finalize)] +pub struct FormalParameter { + declaration: Declaration, + is_rest_param: bool, +} + +impl FormalParameter { + /// Creates a new formal parameter. + pub(in crate::syntax) fn new(declaration: D, is_rest_param: bool) -> Self + where + D: Into, + { + Self { + declaration: declaration.into(), + is_rest_param, + } + } + + /// Gets the name of the formal parameter. + pub fn names(&self) -> Vec { + match &self.declaration { + Declaration::Identifier { ident, .. } => vec![ident.sym()], + Declaration::Pattern(pattern) => match pattern { + DeclarationPattern::Object(object_pattern) => object_pattern.idents(), + + DeclarationPattern::Array(array_pattern) => array_pattern.idents(), + }, + } + } + + /// Get the declaration of the formal parameter + pub fn declaration(&self) -> &Declaration { + &self.declaration + } + + /// Gets the initialization node of the formal parameter, if any. + pub fn init(&self) -> Option<&Node> { + self.declaration.init() + } + + /// Gets wether the parameter is a rest parameter. + pub fn is_rest_param(&self) -> bool { + self.is_rest_param + } + + pub fn is_identifier(&self) -> bool { + matches!(&self.declaration, Declaration::Identifier { .. }) + } +} + +impl ToInternedString for FormalParameter { + fn to_interned_string(&self, interner: &Interner) -> String { + let mut buf = if self.is_rest_param { + "...".to_owned() + } else { + String::new() + }; + buf.push_str(&self.declaration.to_interned_string(interner)); + buf + } +} diff --git a/boa_engine/src/syntax/parser/expression/assignment/arrow_function.rs b/boa_engine/src/syntax/parser/expression/assignment/arrow_function.rs index a7a66c66061..9e111fbf1a7 100644 --- a/boa_engine/src/syntax/parser/expression/assignment/arrow_function.rs +++ b/boa_engine/src/syntax/parser/expression/assignment/arrow_function.rs @@ -11,15 +11,15 @@ use super::AssignmentExpression; use crate::syntax::{ ast::{ node::{ - declaration::Declaration, ArrowFunctionDecl, FormalParameter, Node, Return, - StatementList, + declaration::Declaration, ArrowFunctionDecl, FormalParameter, FormalParameterList, + FormalParameterListFlags, Node, Return, StatementList, }, Position, Punctuator, }, lexer::{Error as LexError, TokenKind}, parser::{ error::{ErrorContext, ParseError, ParseResult}, - function::{FormalParameterList, FormalParameters, FunctionBody}, + function::{FormalParameters, FunctionBody}, statement::BindingIdentifier, AllowAwait, AllowIn, AllowYield, Cursor, TokenParser, }, @@ -98,15 +98,19 @@ where let param = BindingIdentifier::new(self.allow_yield, self.allow_await) .parse(cursor, interner) .context("arrow function")?; + let has_arguments = param == Sym::ARGUMENTS; + let mut flags = FormalParameterListFlags::IS_SIMPLE; + if has_arguments { + flags |= FormalParameterListFlags::HAS_ARGUMENTS; + } ( - FormalParameterList { - parameters: Box::new([FormalParameter::new( + FormalParameterList::new( + Box::new([FormalParameter::new( Declaration::new_with_identifier(param, None), false, )]), - is_simple: true, - has_duplicates: false, - }, + flags, + ), params_start_position, ) }; @@ -121,7 +125,7 @@ where let body = ConciseBody::new(self.allow_in).parse(cursor, interner)?; // Early Error: ArrowFormalParameters are UniqueFormalParameters. - if params.has_duplicates { + if params.has_duplicates() { return Err(ParseError::lex(LexError::Syntax( "Duplicate parameter name not allowed in this context".into(), params_start_position, @@ -130,7 +134,7 @@ where // Early Error: It is a Syntax Error if ConciseBodyContainsUseStrict of ConciseBody is true // and IsSimpleParameterList of ArrowParameters is false. - if body.strict() && !params.is_simple { + if body.strict() && !params.is_simple() { return Err(ParseError::lex(LexError::Syntax( "Illegal 'use strict' directive in function with non-simple parameter list".into(), params_start_position, @@ -161,7 +165,7 @@ where } } - Ok(ArrowFunctionDecl::new(self.name, params.parameters, body)) + Ok(ArrowFunctionDecl::new(self.name, params, body)) } } @@ -172,7 +176,7 @@ struct ConciseBody { } impl ConciseBody { - /// Creates a new `ConcideBody` parser. + /// Creates a new `ConciseBody` parser. fn new(allow_in: I) -> Self where I: Into, diff --git a/boa_engine/src/syntax/parser/expression/primary/async_function_expression/mod.rs b/boa_engine/src/syntax/parser/expression/primary/async_function_expression/mod.rs index 047bff9cc4d..9de8bcc78ad 100644 --- a/boa_engine/src/syntax/parser/expression/primary/async_function_expression/mod.rs +++ b/boa_engine/src/syntax/parser/expression/primary/async_function_expression/mod.rs @@ -103,7 +103,7 @@ where // Early Error: If the source code matching FormalParameters is strict mode code, // the Early Error rules for UniqueFormalParameters : FormalParameters are applied. - if (cursor.strict_mode() || body.strict()) && params.has_duplicates { + if (cursor.strict_mode() || body.strict()) && params.has_duplicates() { return Err(ParseError::lex(LexError::Syntax( "Duplicate parameter name not allowed in this context".into(), params_start_position, @@ -112,7 +112,7 @@ where // Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of AsyncFunctionBody is true // and IsSimpleParameterList of FormalParameters is false. - if body.strict() && !params.is_simple { + if body.strict() && !params.is_simple() { return Err(ParseError::lex(LexError::Syntax( "Illegal 'use strict' directive in function with non-simple parameter list".into(), params_start_position, @@ -143,6 +143,6 @@ where } } - Ok(AsyncFunctionExpr::new(name, params.parameters, body)) + Ok(AsyncFunctionExpr::new(name, params, body)) } } diff --git a/boa_engine/src/syntax/parser/expression/primary/async_function_expression/tests.rs b/boa_engine/src/syntax/parser/expression/primary/async_function_expression/tests.rs index fdfd3cf0b54..9bc0cf82670 100644 --- a/boa_engine/src/syntax/parser/expression/primary/async_function_expression/tests.rs +++ b/boa_engine/src/syntax/parser/expression/primary/async_function_expression/tests.rs @@ -1,11 +1,14 @@ use crate::syntax::{ ast::{ - node::{AsyncFunctionExpr, Declaration, DeclarationList, Return, StatementList}, + node::{ + AsyncFunctionExpr, Declaration, DeclarationList, FormalParameterList, Return, + StatementList, + }, Const, }, parser::tests::check_parser, }; -use boa_interner::Interner; +use boa_interner::{Interner, Sym}; /// Checks async expression parsing. #[test] @@ -22,8 +25,8 @@ fn check_async_expression() { Some( AsyncFunctionExpr::new::<_, _, StatementList>( None, - [], - vec![Return::new(Const::from(1), None).into()].into(), + FormalParameterList::default(), + vec![Return::new::<_, _, Option>(Const::from(1), None).into()].into(), ) .into(), ), @@ -51,15 +54,20 @@ fn check_nested_async_expression() { Some( AsyncFunctionExpr::new::<_, _, StatementList>( None, - [], + FormalParameterList::default(), vec![DeclarationList::Const( vec![Declaration::new_with_identifier( interner.get_or_intern_static("b"), Some( AsyncFunctionExpr::new::<_, _, StatementList>( None, - [], - vec![Return::new(Const::from(1), None).into()].into(), + FormalParameterList::default(), + vec![Return::new::<_, _, Option>( + Const::from(1), + None, + ) + .into()] + .into(), ) .into(), ), diff --git a/boa_engine/src/syntax/parser/expression/primary/async_generator_expression/mod.rs b/boa_engine/src/syntax/parser/expression/primary/async_generator_expression/mod.rs index efb43cdafe6..56db0cb0c73 100644 --- a/boa_engine/src/syntax/parser/expression/primary/async_generator_expression/mod.rs +++ b/boa_engine/src/syntax/parser/expression/primary/async_generator_expression/mod.rs @@ -109,7 +109,7 @@ where // Early Error: If the source code matching FormalParameters is strict mode code, // the Early Error rules for UniqueFormalParameters : FormalParameters are applied. - if (cursor.strict_mode() || body.strict()) && params.has_duplicates { + if (cursor.strict_mode() || body.strict()) && params.has_duplicates() { return Err(ParseError::lex(LexError::Syntax( "Duplicate parameter name not allowed in this context".into(), params_start_position, @@ -118,7 +118,7 @@ where // Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of GeneratorBody is true // and IsSimpleParameterList of FormalParameters is false. - if body.strict() && !params.is_simple { + if body.strict() && !params.is_simple() { return Err(ParseError::lex(LexError::Syntax( "Illegal 'use strict' directive in function with non-simple parameter list".into(), params_start_position, @@ -149,6 +149,6 @@ where } //implement the below AsyncGeneratorExpr in ast::node - Ok(AsyncGeneratorExpr::new(name, params.parameters, body)) + Ok(AsyncGeneratorExpr::new(name, params, body)) } } diff --git a/boa_engine/src/syntax/parser/expression/primary/async_generator_expression/tests.rs b/boa_engine/src/syntax/parser/expression/primary/async_generator_expression/tests.rs index 67091d90b29..727c331d9ae 100644 --- a/boa_engine/src/syntax/parser/expression/primary/async_generator_expression/tests.rs +++ b/boa_engine/src/syntax/parser/expression/primary/async_generator_expression/tests.rs @@ -1,11 +1,14 @@ use crate::syntax::{ ast::{ - node::{AsyncGeneratorExpr, Declaration, DeclarationList, Return, StatementList}, + node::{ + AsyncGeneratorExpr, Declaration, DeclarationList, FormalParameterList, Return, + StatementList, + }, Const, }, parser::tests::check_parser, }; -use boa_interner::Interner; +use boa_interner::{Interner, Sym}; ///checks async generator expression parsing @@ -23,8 +26,8 @@ fn check_async_generator_expr() { Some( AsyncGeneratorExpr::new::<_, _, StatementList>( None, - [], - vec![Return::new(Const::from(1), None).into()].into(), + FormalParameterList::default(), + vec![Return::new::<_, _, Option>(Const::from(1), None).into()].into(), ) .into(), ), @@ -52,15 +55,20 @@ fn check_nested_async_generator_expr() { Some( AsyncGeneratorExpr::new::<_, _, StatementList>( None, - [], + FormalParameterList::default(), vec![DeclarationList::Const( vec![Declaration::new_with_identifier( interner.get_or_intern_static("b"), Some( AsyncGeneratorExpr::new::<_, _, StatementList>( None, - [], - vec![Return::new(Const::from(1), None).into()].into(), + FormalParameterList::default(), + vec![Return::new::<_, _, Option>( + Const::from(1), + None, + ) + .into()] + .into(), ) .into(), ), diff --git a/boa_engine/src/syntax/parser/expression/primary/function_expression/mod.rs b/boa_engine/src/syntax/parser/expression/primary/function_expression/mod.rs index 027fe790176..641f86745e0 100644 --- a/boa_engine/src/syntax/parser/expression/primary/function_expression/mod.rs +++ b/boa_engine/src/syntax/parser/expression/primary/function_expression/mod.rs @@ -88,7 +88,7 @@ where // Early Error: If the source code matching FormalParameters is strict mode code, // the Early Error rules for UniqueFormalParameters : FormalParameters are applied. - if (cursor.strict_mode() || body.strict()) && params.has_duplicates { + if (cursor.strict_mode() || body.strict()) && params.has_duplicates() { return Err(ParseError::lex(LexError::Syntax( "Duplicate parameter name not allowed in this context".into(), params_start_position, @@ -97,7 +97,7 @@ where // Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of GeneratorBody is true // and IsSimpleParameterList of FormalParameters is false. - if body.strict() && !params.is_simple { + if body.strict() && !params.is_simple() { return Err(ParseError::lex(LexError::Syntax( "Illegal 'use strict' directive in function with non-simple parameter list".into(), params_start_position, @@ -128,6 +128,6 @@ where } } - Ok(FunctionExpr::new(name, params.parameters, body)) + Ok(FunctionExpr::new(name, params, body)) } } diff --git a/boa_engine/src/syntax/parser/expression/primary/function_expression/tests.rs b/boa_engine/src/syntax/parser/expression/primary/function_expression/tests.rs index e60f4ee108e..6d53f0a3e8a 100644 --- a/boa_engine/src/syntax/parser/expression/primary/function_expression/tests.rs +++ b/boa_engine/src/syntax/parser/expression/primary/function_expression/tests.rs @@ -1,11 +1,13 @@ use crate::syntax::{ ast::{ - node::{Declaration, DeclarationList, FunctionExpr, Return, StatementList}, + node::{ + Declaration, DeclarationList, FormalParameterList, FunctionExpr, Return, StatementList, + }, Const, }, parser::tests::check_parser, }; -use boa_interner::Interner; +use boa_interner::{Interner, Sym}; /// Checks async expression parsing. #[test] @@ -22,8 +24,8 @@ fn check_function_expression() { Some( FunctionExpr::new::<_, _, StatementList>( None, - [], - vec![Return::new(Const::from(1), None).into()].into(), + FormalParameterList::default(), + vec![Return::new::<_, _, Option>(Const::from(1), None).into()].into(), ) .into(), ), @@ -51,15 +53,20 @@ fn check_nested_function_expression() { Some( FunctionExpr::new::<_, _, StatementList>( None, - [], + FormalParameterList::default(), vec![DeclarationList::Const( vec![Declaration::new_with_identifier( interner.get_or_intern_static("b"), Some( FunctionExpr::new::<_, _, StatementList>( None, - [], - vec![Return::new(Const::from(1), None).into()].into(), + FormalParameterList::default(), + vec![Return::new::<_, _, Option>( + Const::from(1), + None, + ) + .into()] + .into(), ) .into(), ), diff --git a/boa_engine/src/syntax/parser/expression/primary/generator_expression/mod.rs b/boa_engine/src/syntax/parser/expression/primary/generator_expression/mod.rs index ffb7d32ff2c..34859d7b9e5 100644 --- a/boa_engine/src/syntax/parser/expression/primary/generator_expression/mod.rs +++ b/boa_engine/src/syntax/parser/expression/primary/generator_expression/mod.rs @@ -92,7 +92,7 @@ where // Early Error: If the source code matching FormalParameters is strict mode code, // the Early Error rules for UniqueFormalParameters : FormalParameters are applied. - if (cursor.strict_mode() || body.strict()) && params.has_duplicates { + if (cursor.strict_mode() || body.strict()) && params.has_duplicates() { return Err(ParseError::lex(LexError::Syntax( "Duplicate parameter name not allowed in this context".into(), params_start_position, @@ -101,7 +101,7 @@ where // Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of GeneratorBody is true // and IsSimpleParameterList of FormalParameters is false. - if body.strict() && !params.is_simple { + if body.strict() && !params.is_simple() { return Err(ParseError::lex(LexError::Syntax( "Illegal 'use strict' directive in function with non-simple parameter list".into(), params_start_position, @@ -132,6 +132,6 @@ where } } - Ok(GeneratorExpr::new(name, params.parameters, body)) + Ok(GeneratorExpr::new(name, params, body)) } } diff --git a/boa_engine/src/syntax/parser/expression/primary/generator_expression/tests.rs b/boa_engine/src/syntax/parser/expression/primary/generator_expression/tests.rs index c688991d752..809a3eef081 100644 --- a/boa_engine/src/syntax/parser/expression/primary/generator_expression/tests.rs +++ b/boa_engine/src/syntax/parser/expression/primary/generator_expression/tests.rs @@ -1,6 +1,8 @@ use crate::syntax::{ ast::{ - node::{Declaration, DeclarationList, GeneratorExpr, StatementList, Yield}, + node::{ + Declaration, DeclarationList, FormalParameterList, GeneratorExpr, StatementList, Yield, + }, Const, }, parser::tests::check_parser, @@ -21,7 +23,7 @@ fn check_generator_function_expression() { Some( GeneratorExpr::new::<_, _, StatementList>( None, - [], + FormalParameterList::default(), vec![Yield::new(Const::from(1), false).into()].into(), ) .into(), @@ -48,7 +50,7 @@ fn check_generator_function_delegate_yield_expression() { Some( GeneratorExpr::new::<_, _, StatementList>( None, - [], + FormalParameterList::default(), vec![Yield::new(Const::from(1), true).into()].into(), ) .into(), diff --git a/boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs b/boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs index 890794a7843..387ccf040cc 100644 --- a/boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs +++ b/boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs @@ -12,7 +12,11 @@ mod tests; use crate::syntax::{ ast::{ - node::{self, FunctionExpr, Identifier, MethodDefinitionKind, Node, Object}, + node::{ + object::{self, MethodDefinition}, + AsyncFunctionExpr, AsyncGeneratorExpr, FormalParameterList, FunctionExpr, + GeneratorExpr, Identifier, Node, Object, + }, Keyword, Position, Punctuator, }, lexer::{Error as LexError, TokenKind}, @@ -127,7 +131,7 @@ impl TokenParser for PropertyDefinition where R: Read, { - type Output = node::PropertyDefinition; + type Output = object::PropertyDefinition; fn parse( self, @@ -187,7 +191,7 @@ where )); } }; - return Ok(node::PropertyDefinition::property(ident.sym(), ident)); + return Ok(object::PropertyDefinition::property(ident.sym(), ident)); } } @@ -195,7 +199,7 @@ where if cursor.next_if(Punctuator::Spread, interner)?.is_some() { let node = AssignmentExpression::new(None, true, self.allow_yield, self.allow_await) .parse(cursor, interner)?; - return Ok(node::PropertyDefinition::SpreadObject(node)); + return Ok(object::PropertyDefinition::SpreadObject(node)); } //Async [AsyncMethod, AsyncGeneratorMethod] object methods @@ -226,7 +230,7 @@ where // Early Error: UniqueFormalParameters : FormalParameters // NOTE: does not appear to formally be in ECMAScript specs for method - if params.has_duplicates { + if params.has_duplicates() { return Err(ParseError::lex(LexError::Syntax( "Duplicate parameter name not allowed in this context".into(), params_start_position, @@ -247,7 +251,7 @@ where // Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of FunctionBody is true // and IsSimpleParameterList of UniqueFormalParameters is false. - if body.strict() && !params.is_simple { + if body.strict() && !params.is_simple() { return Err(ParseError::lex(LexError::Syntax( "Illegal 'use strict' directive in function with non-simple parameter list" .into(), @@ -278,10 +282,9 @@ where } } - return Ok(node::PropertyDefinition::method_definition( - MethodDefinitionKind::AsyncGenerator, + return Ok(object::PropertyDefinition::method_definition( + MethodDefinition::AsyncGenerator(AsyncGeneratorExpr::new(None, params, body)), property_name, - FunctionExpr::new(None, params.parameters, body), )); } // MethodDefinition[?Yield, ?Await] -> AsyncMethod[?Yield, ?Await] @@ -295,7 +298,7 @@ where // Early Error: UniqueFormalParameters : FormalParameters // NOTE: does not appear to be in ECMAScript specs - if params.has_duplicates { + if params.has_duplicates() { return Err(ParseError::lex(LexError::Syntax( "Duplicate parameter name not allowed in this context".into(), params_start_position, @@ -316,7 +319,7 @@ where // Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of FunctionBody is true // and IsSimpleParameterList of UniqueFormalParameters is false. - if body.strict() && !params.is_simple { + if body.strict() && !params.is_simple() { return Err(ParseError::lex(LexError::Syntax( "Illegal 'use strict' directive in function with non-simple parameter list" .into(), @@ -346,10 +349,9 @@ where } } } - return Ok(node::PropertyDefinition::method_definition( - MethodDefinitionKind::Async, + return Ok(object::PropertyDefinition::method_definition( + MethodDefinition::Async(AsyncFunctionExpr::new(None, params, body)), property_name, - FunctionExpr::new(None, params.parameters, body), )); } @@ -375,7 +377,7 @@ where // Early Error: UniqueFormalParameters : FormalParameters // NOTE: does not appear to be in ECMAScript specs for GeneratorMethod - if params.has_duplicates { + if params.has_duplicates() { return Err(ParseError::lex(LexError::Syntax( "Duplicate parameter name not allowed in this context".into(), params_start_position, @@ -396,7 +398,7 @@ where // Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of FunctionBody is true // and IsSimpleParameterList of UniqueFormalParameters is false. - if body.strict() && !params.is_simple { + if body.strict() && !params.is_simple() { return Err(ParseError::lex(LexError::Syntax( "Illegal 'use strict' directive in function with non-simple parameter list" .into(), @@ -427,10 +429,9 @@ where } } - return Ok(node::PropertyDefinition::method_definition( - MethodDefinitionKind::Generator, + return Ok(object::PropertyDefinition::method_definition( + MethodDefinition::Generator(GeneratorExpr::new(None, params, body)), property_name, - FunctionExpr::new(None, params.parameters, body), )); } @@ -441,7 +442,7 @@ where if cursor.next_if(Punctuator::Colon, interner)?.is_some() { let value = AssignmentExpression::new(None, true, self.allow_yield, self.allow_await) .parse(cursor, interner)?; - return Ok(node::PropertyDefinition::property(property_name, value)); + return Ok(object::PropertyDefinition::property(property_name, value)); } let ordinary_method = cursor @@ -452,7 +453,7 @@ where match property_name { // MethodDefinition[?Yield, ?Await] -> get ClassElementName[?Yield, ?Await] ( ) { FunctionBody[~Yield, ~Await] } - node::PropertyName::Literal(str) if str == Sym::GET && !ordinary_method => { + object::PropertyName::Literal(str) if str == Sym::GET && !ordinary_method => { property_name = PropertyName::new(self.allow_yield, self.allow_await) .parse(cursor, interner)?; @@ -479,14 +480,17 @@ where interner, )?; - Ok(node::PropertyDefinition::method_definition( - MethodDefinitionKind::Get, + Ok(object::PropertyDefinition::method_definition( + MethodDefinition::Get(FunctionExpr::new( + None, + FormalParameterList::default(), + body, + )), property_name, - FunctionExpr::new(None, [], body), )) } // MethodDefinition[?Yield, ?Await] -> set ClassElementName[?Yield, ?Await] ( PropertySetParameterList ) { FunctionBody[~Yield, ~Await] } - node::PropertyName::Literal(str) if str == Sym::SET && !ordinary_method => { + object::PropertyName::Literal(str) if str == Sym::SET && !ordinary_method => { property_name = PropertyName::new(self.allow_yield, self.allow_await) .parse(cursor, interner)?; @@ -525,7 +529,7 @@ where // Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of FunctionBody is true // and IsSimpleParameterList of PropertySetParameterList is false. - if body.strict() && !params.is_simple { + if body.strict() && !params.is_simple() { return Err(ParseError::lex(LexError::Syntax( "Illegal 'use strict' directive in function with non-simple parameter list" .into(), @@ -533,10 +537,9 @@ where ))); } - Ok(node::PropertyDefinition::method_definition( - MethodDefinitionKind::Set, + Ok(object::PropertyDefinition::method_definition( + MethodDefinition::Set(FunctionExpr::new(None, params, body)), property_name, - FunctionExpr::new(None, params.parameters, body), )) } // MethodDefinition[?Yield, ?Await] -> ClassElementName[?Yield, ?Await] ( UniqueFormalParameters[~Yield, ~Await] ) { FunctionBody[~Yield, ~Await] } @@ -557,7 +560,7 @@ where )?; // Early Error: UniqueFormalParameters : FormalParameters - if params.has_duplicates { + if params.has_duplicates() { return Err(ParseError::lex(LexError::Syntax( "Duplicate parameter name not allowed in this context".into(), params_start_position, @@ -578,7 +581,7 @@ where // Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of FunctionBody is true // and IsSimpleParameterList of UniqueFormalParameters is false. - if body.strict() && !params.is_simple { + if body.strict() && !params.is_simple() { return Err(ParseError::lex(LexError::Syntax( "Illegal 'use strict' directive in function with non-simple parameter list" .into(), @@ -586,10 +589,9 @@ where ))); } - Ok(node::PropertyDefinition::method_definition( - MethodDefinitionKind::Ordinary, + Ok(object::PropertyDefinition::method_definition( + MethodDefinition::Ordinary(FunctionExpr::new(None, params, body)), property_name, - FunctionExpr::new(None, params.parameters, body), )) } } @@ -626,7 +628,7 @@ impl TokenParser for PropertyName where R: Read, { - type Output = node::PropertyName; + type Output = object::PropertyName; fn parse( self, diff --git a/boa_engine/src/syntax/parser/expression/primary/object_initializer/tests.rs b/boa_engine/src/syntax/parser/expression/primary/object_initializer/tests.rs index 60681cfb774..f9f39bf4939 100644 --- a/boa_engine/src/syntax/parser/expression/primary/object_initializer/tests.rs +++ b/boa_engine/src/syntax/parser/expression/primary/object_initializer/tests.rs @@ -1,8 +1,9 @@ use crate::syntax::{ ast::{ node::{ - Declaration, DeclarationList, FormalParameter, FunctionExpr, Identifier, - MethodDefinitionKind, Object, PropertyDefinition, + object::{MethodDefinition, PropertyDefinition}, + AsyncFunctionExpr, AsyncGeneratorExpr, Declaration, DeclarationList, FormalParameter, + FormalParameterList, FormalParameterListFlags, FunctionExpr, Identifier, Object, }, Const, }, @@ -46,9 +47,12 @@ fn check_object_short_function() { let object_properties = vec![ PropertyDefinition::property(interner.get_or_intern_static("a"), Const::from(true)), PropertyDefinition::method_definition( - MethodDefinitionKind::Ordinary, + MethodDefinition::Ordinary(FunctionExpr::new( + None, + FormalParameterList::default(), + vec![], + )), interner.get_or_intern_static("b"), - FunctionExpr::new(None, vec![], vec![]), ), ]; @@ -78,16 +82,21 @@ fn check_object_short_function_arguments() { let object_properties = vec![ PropertyDefinition::property(interner.get_or_intern_static("a"), Const::from(true)), PropertyDefinition::method_definition( - MethodDefinitionKind::Ordinary, - interner.get_or_intern_static("b"), - FunctionExpr::new( + MethodDefinition::Ordinary(FunctionExpr::new( None, - vec![FormalParameter::new( - Declaration::new_with_identifier(interner.get_or_intern_static("test"), None), - false, - )], + FormalParameterList { + parameters: Box::new([FormalParameter::new( + Declaration::new_with_identifier( + interner.get_or_intern_static("test"), + None, + ), + false, + )]), + flags: FormalParameterListFlags::default(), + }, vec![], - ), + )), + interner.get_or_intern_static("b"), ), ]; @@ -116,9 +125,12 @@ fn check_object_getter() { let object_properties = vec![ PropertyDefinition::property(interner.get_or_intern_static("a"), Const::from(true)), PropertyDefinition::method_definition( - MethodDefinitionKind::Get, + MethodDefinition::Get(FunctionExpr::new( + None, + FormalParameterList::default(), + vec![], + )), interner.get_or_intern_static("b"), - FunctionExpr::new(None, vec![], vec![]), ), ]; @@ -147,16 +159,21 @@ fn check_object_setter() { let object_properties = vec![ PropertyDefinition::property(interner.get_or_intern_static("a"), Const::from(true)), PropertyDefinition::method_definition( - MethodDefinitionKind::Set, - interner.get_or_intern_static("b"), - FunctionExpr::new( + MethodDefinition::Set(FunctionExpr::new( None, - vec![FormalParameter::new( - Declaration::new_with_identifier(interner.get_or_intern_static("test"), None), - false, - )], + FormalParameterList { + parameters: Box::new([FormalParameter::new( + Declaration::new_with_identifier( + interner.get_or_intern_static("test"), + None, + ), + false, + )]), + flags: FormalParameterListFlags::default(), + }, vec![], - ), + )), + interner.get_or_intern_static("b"), ), ]; @@ -183,9 +200,12 @@ fn check_object_short_function_get() { let mut interner = Interner::default(); let object_properties = vec![PropertyDefinition::method_definition( - MethodDefinitionKind::Ordinary, + MethodDefinition::Ordinary(FunctionExpr::new( + None, + FormalParameterList::default(), + vec![], + )), interner.get_or_intern_static("get"), - FunctionExpr::new(None, vec![], vec![]), )]; check_parser( @@ -210,9 +230,12 @@ fn check_object_short_function_set() { let mut interner = Interner::default(); let object_properties = vec![PropertyDefinition::method_definition( - MethodDefinitionKind::Ordinary, + MethodDefinition::Ordinary(FunctionExpr::new( + None, + FormalParameterList::default(), + vec![], + )), interner.get_or_intern_static("set"), - FunctionExpr::new(None, vec![], vec![]), )]; check_parser( @@ -346,9 +369,12 @@ fn check_async_method() { let mut interner = Interner::default(); let object_properties = vec![PropertyDefinition::method_definition( - MethodDefinitionKind::Async, + MethodDefinition::Async(AsyncFunctionExpr::new( + None, + FormalParameterList::default(), + vec![], + )), interner.get_or_intern_static("dive"), - FunctionExpr::new(None, vec![], vec![]), )]; check_parser( @@ -373,9 +399,12 @@ fn check_async_generator_method() { let mut interner = Interner::default(); let object_properties = vec![PropertyDefinition::method_definition( - MethodDefinitionKind::AsyncGenerator, + MethodDefinition::AsyncGenerator(AsyncGeneratorExpr::new( + None, + FormalParameterList::default(), + vec![], + )), interner.get_or_intern_static("vroom"), - FunctionExpr::new(None, vec![], vec![]), )]; check_parser( diff --git a/boa_engine/src/syntax/parser/function/mod.rs b/boa_engine/src/syntax/parser/function/mod.rs index ba84c874195..20a7a2b9d44 100644 --- a/boa_engine/src/syntax/parser/function/mod.rs +++ b/boa_engine/src/syntax/parser/function/mod.rs @@ -11,7 +11,11 @@ mod tests; use crate::syntax::{ - ast::{node, node::declaration::Declaration, Punctuator}, + ast::{ + node::{self, FormalParameterList}, + node::{declaration::Declaration, FormalParameterListFlags}, + Punctuator, + }, lexer::{Error as LexError, InputElement, TokenKind}, parser::{ expression::Initializer, @@ -24,13 +28,6 @@ use boa_profiler::Profiler; use rustc_hash::FxHashSet; use std::io::Read; -/// Intermediate type for a list of `FormalParameters` with some meta information. -pub(in crate::syntax::parser) struct FormalParameterList { - pub(in crate::syntax::parser) parameters: Box<[node::FormalParameter]>, - pub(in crate::syntax::parser) is_simple: bool, - pub(in crate::syntax::parser) has_duplicates: bool, -} - /// Formal parameters parsing. /// /// More information: @@ -73,17 +70,15 @@ where let _timer = Profiler::global().start_event("FormalParameters", "Parsing"); cursor.set_goal(InputElement::RegExp); + let mut flags = FormalParameterListFlags::default(); let mut params = Vec::new(); - let mut is_simple = true; - let mut has_duplicates = false; let next_token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; if next_token.kind() == &TokenKind::Punctuator(Punctuator::CloseParen) { - return Ok(FormalParameterList { - parameters: params.into_boxed_slice(), - is_simple, - has_duplicates, - }); + return Ok(FormalParameterList::new( + params.into_boxed_slice(), + FormalParameterListFlags::IS_SIMPLE, + )); } let start_position = next_token.span().start(); @@ -95,6 +90,7 @@ where let next_param = match cursor.peek(0, interner)? { Some(tok) if tok.kind() == &TokenKind::Punctuator(Punctuator::Spread) => { rest_param = true; + flags |= FormalParameterListFlags::HAS_REST_PARAMETER; FunctionRestParameter::new(self.allow_yield, self.allow_await) .parse(cursor, interner)? } @@ -109,15 +105,22 @@ where ))); } + if next_param.init().is_some() { + flags |= FormalParameterListFlags::HAS_EXPRESSIONS; + } + if next_param.names().contains(&Sym::ARGUMENTS) { + flags |= FormalParameterListFlags::HAS_ARGUMENTS; + } + if next_param.is_rest_param() || next_param.init().is_some() || !next_param.is_identifier() { - is_simple = false; + flags.remove(FormalParameterListFlags::IS_SIMPLE); } for param_name in next_param.names() { if parameter_names.contains(¶m_name) { - has_duplicates = true; + flags |= FormalParameterListFlags::HAS_DUPLICATES; } parameter_names.insert(Box::from(param_name)); } @@ -146,18 +149,16 @@ where // Early Error: It is a Syntax Error if IsSimpleParameterList of FormalParameterList is false // and BoundNames of FormalParameterList contains any duplicate elements. - if !is_simple && has_duplicates { + if !flags.contains(FormalParameterListFlags::IS_SIMPLE) + && flags.contains(FormalParameterListFlags::HAS_DUPLICATES) + { return Err(ParseError::lex(LexError::Syntax( "Duplicate parameter name not allowed in this context".into(), start_position, ))); } - Ok(FormalParameterList { - parameters: params.into_boxed_slice(), - is_simple, - has_duplicates, - }) + Ok(FormalParameterList::new(params.into_boxed_slice(), flags)) } } diff --git a/boa_engine/src/syntax/parser/function/tests.rs b/boa_engine/src/syntax/parser/function/tests.rs index b58d5fa0c31..8ed7475d0e5 100644 --- a/boa_engine/src/syntax/parser/function/tests.rs +++ b/boa_engine/src/syntax/parser/function/tests.rs @@ -1,9 +1,9 @@ use crate::syntax::{ ast::node::{ - ArrowFunctionDecl, BinOp, Declaration, DeclarationList, FormalParameter, FunctionDecl, - Identifier, Node, Return, + ArrowFunctionDecl, BinOp, Declaration, DeclarationList, FormalParameter, + FormalParameterList, FunctionDecl, Identifier, Node, Return, }, - ast::op::NumOp, + ast::{node::FormalParameterListFlags, op::NumOp}, parser::{tests::check_parser, Parser}, }; use boa_interner::Interner; @@ -16,11 +16,14 @@ fn check_basic() { "function foo(a) { return a; }", vec![FunctionDecl::new( interner.get_or_intern_static("foo"), - vec![FormalParameter::new( - Declaration::new_with_identifier(interner.get_or_intern_static("a"), None), - false, - )], - vec![Return::new(Identifier::new(interner.get_or_intern_static("a")), None).into()], + FormalParameterList { + parameters: Box::new([FormalParameter::new( + Declaration::new_with_identifier(interner.get_or_intern_static("a"), None), + false, + )]), + flags: FormalParameterListFlags::default(), + }, + vec![Return::new(Identifier::from(interner.get_or_intern_static("a")), None).into()], ) .into()], &mut interner, @@ -35,17 +38,21 @@ fn check_duplicates_strict_off() { "function foo(a, a) { return a; }", vec![FunctionDecl::new( interner.get_or_intern_static("foo"), - vec![ - FormalParameter::new( - Declaration::new_with_identifier(interner.get_or_intern_static("a"), None), - false, - ), - FormalParameter::new( - Declaration::new_with_identifier(interner.get_or_intern_static("a"), None), - false, - ), - ], - vec![Return::new(Identifier::new(interner.get_or_intern_static("a")), None).into()], + FormalParameterList { + parameters: Box::new([ + FormalParameter::new( + Declaration::new_with_identifier(interner.get_or_intern_static("a"), None), + false, + ), + FormalParameter::new( + Declaration::new_with_identifier(interner.get_or_intern_static("a"), None), + false, + ), + ]), + flags: FormalParameterListFlags::default() + .union(FormalParameterListFlags::HAS_DUPLICATES), + }, + vec![Return::new(Identifier::from(interner.get_or_intern_static("a")), None).into()], ) .into()], &mut interner, @@ -71,11 +78,14 @@ fn check_basic_semicolon_insertion() { "function foo(a) { return a }", vec![FunctionDecl::new( interner.get_or_intern_static("foo"), - vec![FormalParameter::new( - Declaration::new_with_identifier(interner.get_or_intern_static("a"), None), - false, - )], - vec![Return::new(Identifier::new(interner.get_or_intern_static("a")), None).into()], + FormalParameterList { + parameters: Box::new([FormalParameter::new( + Declaration::new_with_identifier(interner.get_or_intern_static("a"), None), + false, + )]), + flags: FormalParameterListFlags::default(), + }, + vec![Return::new(Identifier::from(interner.get_or_intern_static("a")), None).into()], ) .into()], &mut interner, @@ -90,10 +100,13 @@ fn check_empty_return() { "function foo(a) { return; }", vec![FunctionDecl::new( interner.get_or_intern_static("foo"), - vec![FormalParameter::new( - Declaration::new_with_identifier(interner.get_or_intern_static("a"), None), - false, - )], + FormalParameterList { + parameters: Box::new([FormalParameter::new( + Declaration::new_with_identifier(interner.get_or_intern_static("a"), None), + false, + )]), + flags: FormalParameterListFlags::default(), + }, vec![Return::new::, Option<_>>(None, None).into()], ) .into()], @@ -109,10 +122,13 @@ fn check_empty_return_semicolon_insertion() { "function foo(a) { return }", vec![FunctionDecl::new( interner.get_or_intern_static("foo"), - vec![FormalParameter::new( - Declaration::new_with_identifier(interner.get_or_intern_static("a"), None), - false, - )], + FormalParameterList { + parameters: Box::new([FormalParameter::new( + Declaration::new_with_identifier(interner.get_or_intern_static("a"), None), + false, + )]), + flags: FormalParameterListFlags::default(), + }, vec![Return::new::, Option<_>>(None, None).into()], ) .into()], @@ -128,16 +144,20 @@ fn check_rest_operator() { "function foo(a, ...b) {}", vec![FunctionDecl::new( interner.get_or_intern_static("foo"), - vec![ - FormalParameter::new( - Declaration::new_with_identifier(interner.get_or_intern_static("a"), None), - false, - ), - FormalParameter::new( - Declaration::new_with_identifier(interner.get_or_intern_static("b"), None), - true, - ), - ], + FormalParameterList { + parameters: Box::new([ + FormalParameter::new( + Declaration::new_with_identifier(interner.get_or_intern_static("a"), None), + false, + ), + FormalParameter::new( + Declaration::new_with_identifier(interner.get_or_intern_static("b"), None), + true, + ), + ]), + flags: FormalParameterListFlags::empty() + .union(FormalParameterListFlags::HAS_REST_PARAMETER), + }, vec![], ) .into()], @@ -153,10 +173,14 @@ fn check_arrow_only_rest() { "(...a) => {}", vec![ArrowFunctionDecl::new( None, - vec![FormalParameter::new( - Declaration::new_with_identifier(interner.get_or_intern_static("a"), None), - true, - )], + FormalParameterList { + parameters: Box::new([FormalParameter::new( + Declaration::new_with_identifier(interner.get_or_intern_static("a"), None), + true, + )]), + flags: FormalParameterListFlags::empty() + .union(FormalParameterListFlags::HAS_REST_PARAMETER), + }, vec![], ) .into()], @@ -172,20 +196,24 @@ fn check_arrow_rest() { "(a, b, ...c) => {}", vec![ArrowFunctionDecl::new( None, - vec![ - FormalParameter::new( - Declaration::new_with_identifier(interner.get_or_intern_static("a"), None), - false, - ), - FormalParameter::new( - Declaration::new_with_identifier(interner.get_or_intern_static("b"), None), - false, - ), - FormalParameter::new( - Declaration::new_with_identifier(interner.get_or_intern_static("c"), None), - true, - ), - ], + FormalParameterList { + parameters: Box::new([ + FormalParameter::new( + Declaration::new_with_identifier(interner.get_or_intern_static("a"), None), + false, + ), + FormalParameter::new( + Declaration::new_with_identifier(interner.get_or_intern_static("b"), None), + false, + ), + FormalParameter::new( + Declaration::new_with_identifier(interner.get_or_intern_static("c"), None), + true, + ), + ]), + flags: FormalParameterListFlags::empty() + .union(FormalParameterListFlags::HAS_REST_PARAMETER), + }, vec![], ) .into()], @@ -201,16 +229,19 @@ fn check_arrow() { "(a, b) => { return a + b; }", vec![ArrowFunctionDecl::new( None, - vec![ - FormalParameter::new( - Declaration::new_with_identifier(interner.get_or_intern_static("a"), None), - false, - ), - FormalParameter::new( - Declaration::new_with_identifier(interner.get_or_intern_static("b"), None), - false, - ), - ], + FormalParameterList { + parameters: Box::new([ + FormalParameter::new( + Declaration::new_with_identifier(interner.get_or_intern_static("a"), None), + false, + ), + FormalParameter::new( + Declaration::new_with_identifier(interner.get_or_intern_static("b"), None), + false, + ), + ]), + flags: FormalParameterListFlags::default(), + }, vec![Return::new( BinOp::new( NumOp::Add, @@ -234,16 +265,19 @@ fn check_arrow_semicolon_insertion() { "(a, b) => { return a + b }", vec![ArrowFunctionDecl::new( None, - vec![ - FormalParameter::new( - Declaration::new_with_identifier(interner.get_or_intern_static("a"), None), - false, - ), - FormalParameter::new( - Declaration::new_with_identifier(interner.get_or_intern_static("b"), None), - false, - ), - ], + FormalParameterList { + parameters: Box::new([ + FormalParameter::new( + Declaration::new_with_identifier(interner.get_or_intern_static("a"), None), + false, + ), + FormalParameter::new( + Declaration::new_with_identifier(interner.get_or_intern_static("b"), None), + false, + ), + ]), + flags: FormalParameterListFlags::default(), + }, vec![Return::new( BinOp::new( NumOp::Add, @@ -267,16 +301,19 @@ fn check_arrow_epty_return() { "(a, b) => { return; }", vec![ArrowFunctionDecl::new( None, - vec![ - FormalParameter::new( - Declaration::new_with_identifier(interner.get_or_intern_static("a"), None), - false, - ), - FormalParameter::new( - Declaration::new_with_identifier(interner.get_or_intern_static("b"), None), - false, - ), - ], + FormalParameterList { + parameters: Box::new([ + FormalParameter::new( + Declaration::new_with_identifier(interner.get_or_intern_static("a"), None), + false, + ), + FormalParameter::new( + Declaration::new_with_identifier(interner.get_or_intern_static("b"), None), + false, + ), + ]), + flags: FormalParameterListFlags::default(), + }, vec![Return::new::, Option<_>>(None, None).into()], ) .into()], @@ -292,16 +329,19 @@ fn check_arrow_empty_return_semicolon_insertion() { "(a, b) => { return }", vec![ArrowFunctionDecl::new( None, - vec![ - FormalParameter::new( - Declaration::new_with_identifier(interner.get_or_intern_static("a"), None), - false, - ), - FormalParameter::new( - Declaration::new_with_identifier(interner.get_or_intern_static("b"), None), - false, - ), - ], + FormalParameterList { + parameters: Box::new([ + FormalParameter::new( + Declaration::new_with_identifier(interner.get_or_intern_static("a"), None), + false, + ), + FormalParameter::new( + Declaration::new_with_identifier(interner.get_or_intern_static("b"), None), + false, + ), + ]), + flags: FormalParameterListFlags::default(), + }, vec![Return::new::, Option<_>>(None, None).into()], ) .into()], @@ -320,13 +360,16 @@ fn check_arrow_assignment() { Some( ArrowFunctionDecl::new( Some(interner.get_or_intern_static("foo")), - vec![FormalParameter::new( - Declaration::new_with_identifier( - interner.get_or_intern_static("a"), - None, - ), - false, - )], + FormalParameterList { + parameters: Box::new([FormalParameter::new( + Declaration::new_with_identifier( + interner.get_or_intern_static("a"), + None, + ), + false, + )]), + flags: FormalParameterListFlags::default(), + }, vec![Return::new::, Option<_>>( Some(Identifier::new(interner.get_or_intern_static("a")).into()), None, @@ -353,14 +396,17 @@ fn check_arrow_assignment_nobrackets() { interner.get_or_intern_static("foo"), Some( ArrowFunctionDecl::new( - Some(interner.get_or_intern_static("foo")), - vec![FormalParameter::new( - Declaration::new_with_identifier( - interner.get_or_intern_static("a"), - None, - ), - false, - )], + interner.get_or_intern_static("foo"), + FormalParameterList { + parameters: Box::new([FormalParameter::new( + Declaration::new_with_identifier( + interner.get_or_intern_static("a"), + None, + ), + false, + )]), + flags: FormalParameterListFlags::default(), + }, vec![Return::new::, Option<_>>( Some(Identifier::new(interner.get_or_intern_static("a")).into()), None, @@ -388,13 +434,16 @@ fn check_arrow_assignment_noparenthesis() { Some( ArrowFunctionDecl::new( Some(interner.get_or_intern_static("foo")), - vec![FormalParameter::new( - Declaration::new_with_identifier( - interner.get_or_intern_static("a"), - None, - ), - false, - )], + FormalParameterList { + parameters: Box::new([FormalParameter::new( + Declaration::new_with_identifier( + interner.get_or_intern_static("a"), + None, + ), + false, + )]), + flags: FormalParameterListFlags::default(), + }, vec![Return::new::, Option<_>>( Some(Identifier::new(interner.get_or_intern_static("a")).into()), None, @@ -422,13 +471,16 @@ fn check_arrow_assignment_noparenthesis_nobrackets() { Some( ArrowFunctionDecl::new( Some(interner.get_or_intern_static("foo")), - vec![FormalParameter::new( - Declaration::new_with_identifier( - interner.get_or_intern_static("a"), - None, - ), - false, - )], + FormalParameterList { + parameters: Box::new([FormalParameter::new( + Declaration::new_with_identifier( + interner.get_or_intern_static("a"), + None, + ), + false, + )]), + flags: FormalParameterListFlags::default(), + }, vec![Return::new::, Option<_>>( Some(Identifier::new(interner.get_or_intern_static("a")).into()), None, @@ -456,22 +508,25 @@ fn check_arrow_assignment_2arg() { Some( ArrowFunctionDecl::new( Some(interner.get_or_intern_static("foo")), - vec![ - FormalParameter::new( - Declaration::new_with_identifier( - interner.get_or_intern_static("a"), - None, + FormalParameterList { + parameters: Box::new([ + FormalParameter::new( + Declaration::new_with_identifier( + interner.get_or_intern_static("a"), + None, + ), + false, ), - false, - ), - FormalParameter::new( - Declaration::new_with_identifier( - interner.get_or_intern_static("b"), - None, + FormalParameter::new( + Declaration::new_with_identifier( + interner.get_or_intern_static("b"), + None, + ), + false, ), - false, - ), - ], + ]), + flags: FormalParameterListFlags::default(), + }, vec![Return::new::, Option<_>>( Some(Identifier::new(interner.get_or_intern_static("a")).into()), None, @@ -499,22 +554,25 @@ fn check_arrow_assignment_2arg_nobrackets() { Some( ArrowFunctionDecl::new( Some(interner.get_or_intern_static("foo")), - vec![ - FormalParameter::new( - Declaration::new_with_identifier( - interner.get_or_intern_static("a"), - None, + FormalParameterList { + parameters: Box::new([ + FormalParameter::new( + Declaration::new_with_identifier( + interner.get_or_intern_static("a"), + None, + ), + false, ), - false, - ), - FormalParameter::new( - Declaration::new_with_identifier( - interner.get_or_intern_static("b"), - None, + FormalParameter::new( + Declaration::new_with_identifier( + interner.get_or_intern_static("b"), + None, + ), + false, ), - false, - ), - ], + ]), + flags: FormalParameterListFlags::default(), + }, vec![Return::new::, Option<_>>( Some(Identifier::new(interner.get_or_intern_static("a")).into()), None, @@ -542,29 +600,32 @@ fn check_arrow_assignment_3arg() { Some( ArrowFunctionDecl::new( Some(interner.get_or_intern_static("foo")), - vec![ - FormalParameter::new( - Declaration::new_with_identifier( - interner.get_or_intern_static("a"), - None, + FormalParameterList { + parameters: Box::new([ + FormalParameter::new( + Declaration::new_with_identifier( + interner.get_or_intern_static("a"), + None, + ), + false, ), - false, - ), - FormalParameter::new( - Declaration::new_with_identifier( - interner.get_or_intern_static("b"), - None, + FormalParameter::new( + Declaration::new_with_identifier( + interner.get_or_intern_static("b"), + None, + ), + false, ), - false, - ), - FormalParameter::new( - Declaration::new_with_identifier( - interner.get_or_intern_static("c"), - None, + FormalParameter::new( + Declaration::new_with_identifier( + interner.get_or_intern_static("c"), + None, + ), + false, ), - false, - ), - ], + ]), + flags: FormalParameterListFlags::default(), + }, vec![Return::new::, Option<_>>( Some(Identifier::new(interner.get_or_intern_static("a")).into()), None, @@ -592,29 +653,32 @@ fn check_arrow_assignment_3arg_nobrackets() { Some( ArrowFunctionDecl::new( Some(interner.get_or_intern_static("foo")), - vec![ - FormalParameter::new( - Declaration::new_with_identifier( - interner.get_or_intern_static("a"), - None, + FormalParameterList { + parameters: Box::new([ + FormalParameter::new( + Declaration::new_with_identifier( + interner.get_or_intern_static("a"), + None, + ), + false, ), - false, - ), - FormalParameter::new( - Declaration::new_with_identifier( - interner.get_or_intern_static("b"), - None, + FormalParameter::new( + Declaration::new_with_identifier( + interner.get_or_intern_static("b"), + None, + ), + false, ), - false, - ), - FormalParameter::new( - Declaration::new_with_identifier( - interner.get_or_intern_static("c"), - None, + FormalParameter::new( + Declaration::new_with_identifier( + interner.get_or_intern_static("c"), + None, + ), + false, ), - false, - ), - ], + ]), + flags: FormalParameterListFlags::default(), + }, vec![Return::new::, Option<_>>( Some(Identifier::new(interner.get_or_intern_static("a")).into()), None, diff --git a/boa_engine/src/syntax/parser/mod.rs b/boa_engine/src/syntax/parser/mod.rs index 7674f9d2e5c..a8bf60b6bd4 100644 --- a/boa_engine/src/syntax/parser/mod.rs +++ b/boa_engine/src/syntax/parser/mod.rs @@ -3,7 +3,7 @@ mod cursor; pub mod error; mod expression; -mod function; +pub(crate) mod function; mod statement; #[cfg(test)] mod tests; diff --git a/boa_engine/src/syntax/parser/statement/block/tests.rs b/boa_engine/src/syntax/parser/statement/block/tests.rs index a499a9e12c2..6f55549356d 100644 --- a/boa_engine/src/syntax/parser/statement/block/tests.rs +++ b/boa_engine/src/syntax/parser/statement/block/tests.rs @@ -3,8 +3,8 @@ use crate::syntax::{ ast::{ node::{ - Assign, Block, Call, Declaration, DeclarationList, FunctionDecl, Identifier, Node, - Return, UnaryOp, + Assign, Block, Call, Declaration, DeclarationList, FormalParameterList, FunctionDecl, + Identifier, Node, Return, UnaryOp, }, op, Const, }, @@ -65,7 +65,7 @@ fn non_empty() { vec![ FunctionDecl::new( hello, - vec![], + FormalParameterList::default(), vec![Return::new(Const::from(10), None).into()], ) .into(), @@ -98,7 +98,7 @@ fn hoisting() { vec![ FunctionDecl::new( hello, - vec![], + FormalParameterList::default(), vec![Return::new(Const::from(10), None).into()], ) .into(), diff --git a/boa_engine/src/syntax/parser/statement/declaration/hoistable/async_function_decl/tests.rs b/boa_engine/src/syntax/parser/statement/declaration/hoistable/async_function_decl/tests.rs index 2446bcd0b51..5e671994da3 100644 --- a/boa_engine/src/syntax/parser/statement/declaration/hoistable/async_function_decl/tests.rs +++ b/boa_engine/src/syntax/parser/statement/declaration/hoistable/async_function_decl/tests.rs @@ -1,4 +1,7 @@ -use crate::syntax::{ast::node::AsyncFunctionDecl, parser::tests::check_parser}; +use crate::syntax::{ + ast::node::{AsyncFunctionDecl, FormalParameterList}, + parser::tests::check_parser, +}; use boa_interner::Interner; /// Async function declaration parsing. @@ -7,7 +10,12 @@ fn async_function_declaration() { let mut interner = Interner::default(); check_parser( "async function hello() {}", - vec![AsyncFunctionDecl::new(interner.get_or_intern_static("hello"), vec![], vec![]).into()], + vec![AsyncFunctionDecl::new( + interner.get_or_intern_static("hello"), + FormalParameterList::default(), + vec![], + ) + .into()], &mut interner, ); } @@ -18,14 +26,24 @@ fn async_function_declaration_keywords() { let mut interner = Interner::default(); check_parser( "async function yield() {}", - vec![AsyncFunctionDecl::new(interner.get_or_intern_static("yield"), vec![], vec![]).into()], + vec![AsyncFunctionDecl::new( + interner.get_or_intern_static("yield"), + FormalParameterList::default(), + vec![], + ) + .into()], &mut interner, ); let mut interner = Interner::default(); check_parser( "async function await() {}", - vec![AsyncFunctionDecl::new(interner.get_or_intern_static("await"), vec![], vec![]).into()], + vec![AsyncFunctionDecl::new( + interner.get_or_intern_static("await"), + FormalParameterList::default(), + vec![], + ) + .into()], &mut interner, ); } diff --git a/boa_engine/src/syntax/parser/statement/declaration/hoistable/async_generator_decl/tests.rs b/boa_engine/src/syntax/parser/statement/declaration/hoistable/async_generator_decl/tests.rs index 23864263ec2..46c6c6056d9 100644 --- a/boa_engine/src/syntax/parser/statement/declaration/hoistable/async_generator_decl/tests.rs +++ b/boa_engine/src/syntax/parser/statement/declaration/hoistable/async_generator_decl/tests.rs @@ -1,4 +1,7 @@ -use crate::syntax::{ast::node::AsyncGeneratorDecl, parser::tests::check_parser}; +use crate::syntax::{ + ast::node::{AsyncGeneratorDecl, FormalParameterList}, + parser::tests::check_parser, +}; use boa_interner::Interner; #[test] @@ -6,7 +9,12 @@ fn async_generator_function_declaration() { let mut interner = Interner::default(); check_parser( "async function* gen() {}", - vec![AsyncGeneratorDecl::new(interner.get_or_intern_static("gen"), vec![], vec![]).into()], + vec![AsyncGeneratorDecl::new( + interner.get_or_intern_static("gen"), + FormalParameterList::default(), + vec![], + ) + .into()], &mut interner, ); } diff --git a/boa_engine/src/syntax/parser/statement/declaration/hoistable/function_decl/tests.rs b/boa_engine/src/syntax/parser/statement/declaration/hoistable/function_decl/tests.rs index 1c556b9517a..efd8091f65b 100644 --- a/boa_engine/src/syntax/parser/statement/declaration/hoistable/function_decl/tests.rs +++ b/boa_engine/src/syntax/parser/statement/declaration/hoistable/function_decl/tests.rs @@ -1,4 +1,7 @@ -use crate::syntax::{ast::node::FunctionDecl, parser::tests::check_parser}; +use crate::syntax::{ + ast::node::{FormalParameterList, FunctionDecl}, + parser::tests::check_parser, +}; use boa_interner::Interner; /// Function declaration parsing. @@ -7,7 +10,12 @@ fn function_declaration() { let mut interner = Interner::default(); check_parser( "function hello() {}", - vec![FunctionDecl::new(interner.get_or_intern_static("hello"), vec![], vec![]).into()], + vec![FunctionDecl::new( + interner.get_or_intern_static("hello"), + FormalParameterList::default(), + vec![], + ) + .into()], &mut interner, ); } @@ -18,14 +26,24 @@ fn function_declaration_keywords() { let mut interner = Interner::default(); check_parser( "function yield() {}", - vec![FunctionDecl::new(interner.get_or_intern_static("yield"), vec![], vec![]).into()], + vec![FunctionDecl::new( + interner.get_or_intern_static("yield"), + FormalParameterList::default(), + vec![], + ) + .into()], &mut interner, ); let mut interner = Interner::default(); check_parser( "function await() {}", - vec![FunctionDecl::new(interner.get_or_intern_static("await"), vec![], vec![]).into()], + vec![FunctionDecl::new( + interner.get_or_intern_static("await"), + FormalParameterList::default(), + vec![], + ) + .into()], &mut interner, ); } diff --git a/boa_engine/src/syntax/parser/statement/declaration/hoistable/generator_decl/tests.rs b/boa_engine/src/syntax/parser/statement/declaration/hoistable/generator_decl/tests.rs index 01332fbe62d..73e065d1bbe 100644 --- a/boa_engine/src/syntax/parser/statement/declaration/hoistable/generator_decl/tests.rs +++ b/boa_engine/src/syntax/parser/statement/declaration/hoistable/generator_decl/tests.rs @@ -1,4 +1,7 @@ -use crate::syntax::{ast::node::GeneratorDecl, parser::tests::check_parser}; +use crate::syntax::{ + ast::node::{FormalParameterList, GeneratorDecl}, + parser::tests::check_parser, +}; use boa_interner::Interner; #[test] @@ -6,7 +9,12 @@ fn generator_function_declaration() { let mut interner = Interner::default(); check_parser( "function* gen() {}", - vec![GeneratorDecl::new(interner.get_or_intern_static("gen"), vec![], vec![]).into()], + vec![GeneratorDecl::new( + interner.get_or_intern_static("gen"), + FormalParameterList::default(), + vec![], + ) + .into()], &mut interner, ); } diff --git a/boa_engine/src/syntax/parser/statement/declaration/hoistable/mod.rs b/boa_engine/src/syntax/parser/statement/declaration/hoistable/mod.rs index febf8ad3863..a641c3247d9 100644 --- a/boa_engine/src/syntax/parser/statement/declaration/hoistable/mod.rs +++ b/boa_engine/src/syntax/parser/statement/declaration/hoistable/mod.rs @@ -18,7 +18,7 @@ use self::{ generator_decl::GeneratorDeclaration, }; use crate::syntax::{ - ast::node::{FormalParameter, StatementList}, + ast::node::{FormalParameterList, StatementList}, ast::{Keyword, Node, Position, Punctuator}, lexer::TokenKind, parser::{ @@ -124,7 +124,7 @@ fn parse_callable_declaration( c: &C, cursor: &mut Cursor, interner: &mut Interner, -) -> Result<(Sym, Box<[FormalParameter]>, StatementList), ParseError> { +) -> Result<(Sym, FormalParameterList, StatementList), ParseError> { let next_token = cursor.peek(0, interner)?; let name = if let Some(token) = next_token { match token.kind() { @@ -175,7 +175,7 @@ fn parse_callable_declaration( // Early Error: If the source code matching FormalParameters is strict mode code, // the Early Error rules for UniqueFormalParameters : FormalParameters are applied. - if (cursor.strict_mode() || body.strict()) && params.has_duplicates { + if (cursor.strict_mode() || body.strict()) && params.has_duplicates() { return Err(ParseError::lex(LexError::Syntax( "Duplicate parameter name not allowed in this context".into(), params_start_position, @@ -184,7 +184,7 @@ fn parse_callable_declaration( // Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of FunctionBody is true // and IsSimpleParameterList of FormalParameters is false. - if body.strict() && !params.is_simple { + if body.strict() && !params.is_simple() { return Err(ParseError::lex(LexError::Syntax( "Illegal 'use strict' directive in function with non-simple parameter list".into(), params_start_position, @@ -215,5 +215,5 @@ fn parse_callable_declaration( } } - Ok((name, params.parameters, body)) + Ok((name, params, body)) } diff --git a/boa_engine/src/syntax/parser/tests.rs b/boa_engine/src/syntax/parser/tests.rs index f2d264b87fa..672872f653d 100644 --- a/boa_engine/src/syntax/parser/tests.rs +++ b/boa_engine/src/syntax/parser/tests.rs @@ -3,14 +3,15 @@ use super::Parser; use crate::syntax::ast::{ node::{ - field::GetConstField, ArrowFunctionDecl, Assign, BinOp, Call, Declaration, DeclarationList, - FormalParameter, FunctionDecl, Identifier, If, New, Node, Object, PropertyDefinition, - Return, StatementList, UnaryOp, + field::GetConstField, object::PropertyDefinition, ArrowFunctionDecl, Assign, BinOp, Call, + Declaration, DeclarationList, FormalParameter, FormalParameterList, + FormalParameterListFlags, FunctionDecl, Identifier, If, New, Node, Object, Return, + StatementList, UnaryOp, }, op::{self, CompOp, LogOp, NumOp}, Const, }; -use boa_interner::{Interner, Sym}; +use boa_interner::Interner; /// Checks that the given JavaScript string gives the expected expression. #[allow(clippy::unwrap_used)] @@ -88,7 +89,7 @@ fn hoisting() { vec![ FunctionDecl::new( hello, - vec![], + FormalParameterList::default(), vec![Return::new(Const::from(10), None).into()], ) .into(), @@ -404,17 +405,19 @@ fn spread_in_arrow_function() { let b = interner.get_or_intern_static("b"); check_parser( s, - vec![ - ArrowFunctionDecl::new::, Box<[FormalParameter]>, StatementList>( - None, - Box::new([FormalParameter::new( - Declaration::new_with_identifier::<_, Option>(b, None), + vec![ArrowFunctionDecl::new( + None, + FormalParameterList { + parameters: Box::new([FormalParameter::new( + Declaration::new_with_identifier(b, None), true, )]), - vec![Identifier::new(b).into()].into(), - ) - .into(), - ], + flags: FormalParameterListFlags::empty() + .union(FormalParameterListFlags::HAS_REST_PARAMETER), + }, + vec![Identifier::from(b).into()], + ) + .into()], &mut interner, ); } diff --git a/boa_engine/src/vm/call_frame.rs b/boa_engine/src/vm/call_frame.rs index 205ea3cdf4c..34e29a348ef 100644 --- a/boa_engine/src/vm/call_frame.rs +++ b/boa_engine/src/vm/call_frame.rs @@ -4,15 +4,17 @@ use super::CodeBlock; use crate::JsValue; -use boa_gc::Gc; +use boa_gc::{Finalize, Gc, Trace}; -#[derive(Debug)] +#[derive(Clone, Debug, Finalize, Trace)] pub struct CallFrame { pub(crate) prev: Option>, pub(crate) code: Gc, pub(crate) pc: usize, pub(crate) this: JsValue, + #[unsafe_ignore_trace] pub(crate) catch: Vec, + #[unsafe_ignore_trace] pub(crate) finally_return: FinallyReturn, pub(crate) finally_jump: Vec>, pub(crate) pop_on_return: usize, @@ -23,10 +25,13 @@ pub struct CallFrame { // Tracks the number of environments in the current try-catch-finally block. // On abrupt returns this is used to decide how many environments need to be pop'ed. + #[unsafe_ignore_trace] pub(crate) try_env_stack: Vec, pub(crate) param_count: usize, pub(crate) arg_count: usize, + #[unsafe_ignore_trace] + pub(crate) generator_resume_kind: GeneratorResumeKind, } impl CallFrame { @@ -89,15 +94,26 @@ pub(crate) struct TryStackEntry { pub(crate) num_loop_stack_entries: usize, } -#[derive(Debug)] +/// Tracks the address that should be jumped to when an error is caught. +/// Additionally the address of a finally block is tracked, to allow for special handling if it exists. +#[derive(Copy, Clone, Debug)] pub(crate) struct CatchAddresses { pub(crate) next: u32, pub(crate) finally: Option, } +/// Indicates if a function should return or throw at the end of a finally block. #[derive(Copy, Clone, Debug, PartialEq)] pub(crate) enum FinallyReturn { None, Ok, Err, } + +/// Indicates how a generator function that has been called/resumed should return. +#[derive(Copy, Clone, Debug)] +pub(crate) enum GeneratorResumeKind { + Normal, + Throw, + Return, +} diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index cb45df1c3fd..58ec989037b 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -3,19 +3,23 @@ //! This module is for the `CodeBlock` which implements a function representation in the VM use crate::{ - builtins::function::{ - arguments::Arguments, Captures, ClosureFunctionSignature, Function, - NativeFunctionSignature, ThisMode, + builtins::{ + function::{ + arguments::Arguments, Captures, ClosureFunctionSignature, Function, + NativeFunctionSignature, ThisMode, + }, + generator::{Generator, GeneratorContext, GeneratorState}, }, context::StandardObjects, environments::{BindingLocator, DeclarativeEnvironmentStack}, object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, property::PropertyDescriptor, - syntax::ast::node::FormalParameter, + syntax::ast::node::FormalParameterList, + vm::call_frame::GeneratorResumeKind, vm::{call_frame::FinallyReturn, CallFrame, Opcode}, Context, JsResult, JsValue, }; -use boa_gc::{Finalize, Gc, Trace}; +use boa_gc::{Cell, Finalize, Gc, Trace}; use boa_interner::{Interner, Sym, ToInternedString}; use boa_profiler::Profiler; use std::{convert::TryInto, mem::size_of}; @@ -65,7 +69,7 @@ pub struct CodeBlock { pub(crate) this_mode: ThisMode, /// Parameters passed to this function. - pub(crate) params: Box<[FormalParameter]>, + pub(crate) params: FormalParameterList, /// Bytecode pub(crate) code: Vec, @@ -109,7 +113,7 @@ impl CodeBlock { strict, constructor, this_mode: ThisMode::Global, - params: Vec::new().into_boxed_slice(), + params: FormalParameterList::default(), lexical_name_argument: false, arguments_binding: None, } @@ -189,6 +193,7 @@ impl CodeBlock { | Opcode::ForInLoopNext | Opcode::ConcatToString | Opcode::CopyDataProperties + | Opcode::GeneratorNextDelegate | Opcode::PushDeclarativeEnvironment => { let result = self.read::(*pc).to_string(); *pc += size_of::(); @@ -201,7 +206,7 @@ impl CodeBlock { *pc += size_of::(); format!("{operand1}, {operand2}") } - Opcode::GetFunction => { + Opcode::GetFunction | Opcode::GetGenerator => { let operand = self.read::(*pc); *pc += size_of::(); format!( @@ -317,6 +322,8 @@ impl CodeBlock { | Opcode::PushNewArray | Opcode::PopOnReturnAdd | Opcode::PopOnReturnSub + | Opcode::Yield + | Opcode::GeneratorNext | Opcode::Nop => String::new(), } } @@ -391,71 +398,122 @@ impl ToInternedString for CodeBlock { } } -#[derive(Debug)] -#[allow(missing_copy_implementations)] -pub struct JsVmFunction {} - -impl JsVmFunction { - #[allow(clippy::new_ret_no_self)] - pub fn new(code: Gc, context: &mut Context) -> JsObject { - let _timer = Profiler::global().start_event("JsVmFunction::new", "vm"); - - let function_prototype = context.standard_objects().function_object().prototype(); - - let prototype = context.construct_object(); - - let name_property = PropertyDescriptor::builder() - .value(context.interner().resolve_expect(code.name)) - .writable(false) - .enumerable(false) - .configurable(true) - .build(); - - let length_property = PropertyDescriptor::builder() - .value(code.length) - .writable(false) - .enumerable(false) - .configurable(true) - .build(); - - let function = Function::VmOrdinary { - code, - environments: context.realm.environments.clone(), - }; +/// Creates a new function object. +pub(crate) fn create_function_object(code: Gc, context: &mut Context) -> JsObject { + let _timer = Profiler::global().start_event("JsVmFunction::new", "vm"); + + let function_prototype = context.standard_objects().function_object().prototype(); + + let prototype = context.construct_object(); + + let name_property = PropertyDescriptor::builder() + .value(context.interner().resolve_expect(code.name)) + .writable(false) + .enumerable(false) + .configurable(true) + .build(); + + let length_property = PropertyDescriptor::builder() + .value(code.length) + .writable(false) + .enumerable(false) + .configurable(true) + .build(); + + let function = Function::Ordinary { + code, + environments: context.realm.environments.clone(), + }; + + let constructor = + JsObject::from_proto_and_data(function_prototype, ObjectData::function(function)); + + let constructor_property = PropertyDescriptor::builder() + .value(constructor.clone()) + .writable(true) + .enumerable(false) + .configurable(true) + .build(); + + prototype + .define_property_or_throw("constructor", constructor_property, context) + .expect("failed to define the constructor property of the function"); + + let prototype_property = PropertyDescriptor::builder() + .value(prototype) + .writable(true) + .enumerable(false) + .configurable(false) + .build(); + + constructor + .define_property_or_throw("prototype", prototype_property, context) + .expect("failed to define the prototype property of the function"); + constructor + .define_property_or_throw("name", name_property, context) + .expect("failed to define the name property of the function"); + constructor + .define_property_or_throw("length", length_property, context) + .expect("failed to define the length property of the function"); + + constructor +} - let constructor = - JsObject::from_proto_and_data(function_prototype, ObjectData::function(function)); - - let constructor_property = PropertyDescriptor::builder() - .value(constructor.clone()) - .writable(true) - .enumerable(false) - .configurable(true) - .build(); - - prototype - .define_property_or_throw("constructor", constructor_property, context) - .expect("failed to define the constructor property of the function"); - - let prototype_property = PropertyDescriptor::builder() - .value(prototype) - .writable(true) - .enumerable(false) - .configurable(false) - .build(); - - constructor - .define_property_or_throw("prototype", prototype_property, context) - .expect("failed to define the prototype property of the function"); - constructor - .define_property_or_throw("name", name_property, context) - .expect("failed to define the name property of the function"); - constructor - .define_property_or_throw("length", length_property, context) - .expect("failed to define the length property of the function"); - - constructor - } +/// Creates a new generator function object. +pub(crate) fn create_generator_function_object( + code: Gc, + context: &mut Context, +) -> JsObject { + let function_prototype = context + .standard_objects() + .generator_function_object() + .prototype(); + + let name_property = PropertyDescriptor::builder() + .value(context.interner().resolve_expect(code.name)) + .writable(false) + .enumerable(false) + .configurable(true) + .build(); + + let length_property = PropertyDescriptor::builder() + .value(code.length) + .writable(false) + .enumerable(false) + .configurable(true) + .build(); + + let prototype = JsObject::from_proto_and_data( + context.standard_objects().generator_object().prototype(), + ObjectData::ordinary(), + ); + + let function = Function::Generator { + code, + environments: context.realm.environments.clone(), + }; + + let constructor = + JsObject::from_proto_and_data(function_prototype, ObjectData::generator_function(function)); + + let prototype_property = PropertyDescriptor::builder() + .value(prototype) + .writable(true) + .enumerable(false) + .configurable(false) + .build(); + + constructor + .define_property_or_throw("prototype", prototype_property, context) + .expect("failed to define the prototype property of the generator function"); + constructor + .define_property_or_throw("name", name_property, context) + .expect("failed to define the name property of the generator function"); + constructor + .define_property_or_throw("length", length_property, context) + .expect("failed to define the length property of the generator function"); + + constructor } pub(crate) enum FunctionBody { @@ -470,9 +528,12 @@ pub(crate) enum FunctionBody { function: Box, captures: Captures, }, + Generator { + code: Gc, + environments: DeclarativeEnvironmentStack, + }, } -// TODO: this should be modified to not take `exit_on_return` and then moved to `internal_methods` impl JsObject { pub(crate) fn call_internal( &self, @@ -481,7 +542,6 @@ impl JsObject { context: &mut Context, ) -> JsResult { let this_function_object = self.clone(); - // let mut has_parameter_expressions = false; if !self.is_callable() { return context.throw_type_error("not a callable function"); @@ -512,7 +572,11 @@ impl JsObject { function: function.clone(), captures: captures.clone(), }, - Function::VmOrdinary { code, environments } => FunctionBody::Ordinary { + Function::Ordinary { code, environments } => FunctionBody::Ordinary { + code: code.clone(), + environments: environments.clone(), + }, + Function::Generator { code, environments } => FunctionBody::Generator { code: code.clone(), environments: environments.clone(), }, @@ -552,23 +616,9 @@ impl JsObject { .environments .push_function(code.num_bindings, this.clone()); - let mut arguments_in_parameter_names = false; - let mut is_simple_parameter_list = true; - let mut has_parameter_expressions = false; - - for param in code.params.iter() { - has_parameter_expressions = has_parameter_expressions || param.init().is_some(); - arguments_in_parameter_names = - arguments_in_parameter_names || param.names().contains(&Sym::ARGUMENTS); - is_simple_parameter_list = is_simple_parameter_list - && !param.is_rest_param() - && param.is_identifier() - && param.init().is_none(); - } - if let Some(binding) = code.arguments_binding { let arguments_obj = - if context.strict() || code.strict || !is_simple_parameter_list { + if context.strict() || code.strict || !code.params.is_simple() { Arguments::create_unmapped_arguments_object(args, context) } else { let env = context.realm.environments.current(); @@ -590,9 +640,12 @@ impl JsObject { let arg_count = args.len(); // Push function arguments to the stack. - let args = if code.params.len() > args.len() { + let args = if code.params.parameters.len() > args.len() { let mut v = args.to_vec(); - v.extend(vec![JsValue::Undefined; code.params.len() - args.len()]); + v.extend(vec![ + JsValue::Undefined; + code.params.parameters.len() - args.len() + ]); v } else { args.to_vec() @@ -602,7 +655,8 @@ impl JsObject { context.vm.push(arg); } - let param_count = code.params.len(); + let param_count = code.params.parameters.len(); + let has_expressions = code.params.has_expressions(); context.vm.push_frame(CallFrame { prev: None, @@ -620,19 +674,139 @@ impl JsObject { }], param_count, arg_count, + generator_resume_kind: GeneratorResumeKind::Normal, }); let result = context.run(); context.vm.pop_frame().expect("must have frame"); context.realm.environments.pop(); - if has_parameter_expressions { + if has_expressions { context.realm.environments.pop(); } std::mem::swap(&mut environments, &mut context.realm.environments); - result + let (result, _) = result?; + Ok(result) + } + FunctionBody::Generator { + code, + mut environments, + } => { + std::mem::swap(&mut environments, &mut context.realm.environments); + + let lexical_this_mode = code.this_mode == ThisMode::Lexical; + + let this = if lexical_this_mode { + if let Some(this) = context.realm.environments.get_last_this() { + this + } else { + context.global_object().clone().into() + } + } else if (!code.strict && !context.strict()) && this.is_null_or_undefined() { + context.global_object().clone().into() + } else { + this.clone() + }; + + context + .realm + .environments + .push_function(code.num_bindings, this.clone()); + + if let Some(binding) = code.arguments_binding { + let arguments_obj = + if context.strict() || code.strict || !code.params.is_simple() { + Arguments::create_unmapped_arguments_object(args, context) + } else { + let env = context.realm.environments.current(); + Arguments::create_mapped_arguments_object( + &this_function_object, + &code.params, + args, + &env, + context, + ) + }; + context.realm.environments.put_value( + binding.environment_index(), + binding.binding_index(), + arguments_obj.into(), + ); + } + + let arg_count = args.len(); + + // Push function arguments to the stack. + let mut args = if code.params.parameters.len() > args.len() { + let mut v = args.to_vec(); + v.extend(vec![ + JsValue::Undefined; + code.params.parameters.len() - args.len() + ]); + v + } else { + args.to_vec() + }; + args.reverse(); + + let param_count = code.params.parameters.len(); + + let call_frame = CallFrame { + prev: None, + code, + this, + pc: 0, + catch: Vec::new(), + finally_return: FinallyReturn::None, + finally_jump: Vec::new(), + pop_on_return: 0, + loop_env_stack: vec![0], + try_env_stack: vec![crate::vm::TryStackEntry { + num_env: 0, + num_loop_stack_entries: 0, + }], + param_count, + arg_count, + generator_resume_kind: GeneratorResumeKind::Normal, + }; + let mut stack = args; + + std::mem::swap(&mut context.vm.stack, &mut stack); + context.vm.push_frame(call_frame); + + let init_result = context.run(); + + let call_frame = context.vm.pop_frame().expect("frame must exist"); + std::mem::swap(&mut environments, &mut context.realm.environments); + std::mem::swap(&mut context.vm.stack, &mut stack); + + let prototype = if let Some(prototype) = this_function_object + .get("prototype", context) + .expect("GeneratorFunction must have a prototype property") + .as_object() + { + prototype.clone() + } else { + context.standard_objects().generator_object().prototype() + }; + + let generator = Self::from_proto_and_data( + prototype, + ObjectData::generator(Generator { + state: GeneratorState::SuspendedStart, + context: Some(Gc::new(Cell::new(GeneratorContext { + environments, + call_frame: *call_frame, + stack, + }))), + }), + ); + + init_result?; + + Ok(generator.into()) } } } @@ -664,10 +838,13 @@ impl JsObject { function: function.clone(), captures: captures.clone(), }, - Function::VmOrdinary { code, environments } => FunctionBody::Ordinary { + Function::Ordinary { code, environments } => FunctionBody::Ordinary { code: code.clone(), environments: environments.clone(), }, + Function::Generator { .. } => { + unreachable!("generator function cannot be a constructor") + } } }; @@ -704,7 +881,7 @@ impl JsObject { let mut is_simple_parameter_list = true; let mut has_parameter_expressions = false; - for param in code.params.iter() { + for param in code.params.parameters.iter() { has_parameter_expressions = has_parameter_expressions || param.init().is_some(); arguments_in_parameter_names = arguments_in_parameter_names || param.names().contains(&Sym::ARGUMENTS); @@ -716,7 +893,7 @@ impl JsObject { if let Some(binding) = code.arguments_binding { let arguments_obj = - if context.strict() || code.strict || !is_simple_parameter_list { + if context.strict() || code.strict || !code.params.is_simple() { Arguments::create_unmapped_arguments_object(args, context) } else { let env = context.realm.environments.current(); @@ -738,9 +915,12 @@ impl JsObject { let arg_count = args.len(); // Push function arguments to the stack. - let args = if code.params.len() > args.len() { + let args = if code.params.parameters.len() > args.len() { let mut v = args.to_vec(); - v.extend(vec![JsValue::Undefined; code.params.len() - args.len()]); + v.extend(vec![ + JsValue::Undefined; + code.params.parameters.len() - args.len() + ]); v } else { args.to_vec() @@ -750,7 +930,7 @@ impl JsObject { context.vm.push(arg); } - let param_count = code.params.len(); + let param_count = code.params.parameters.len(); let this = if (!code.strict && !context.strict()) && this.is_null_or_undefined() { context.global_object().clone().into() @@ -774,14 +954,13 @@ impl JsObject { }], param_count, arg_count, + generator_resume_kind: GeneratorResumeKind::Normal, }); let result = context.run(); let frame = context.vm.pop_frame().expect("must have frame"); - let this = frame.this; - context.realm.environments.pop(); if has_parameter_expressions { context.realm.environments.pop(); @@ -789,14 +968,17 @@ impl JsObject { std::mem::swap(&mut environments, &mut context.realm.environments); - let result = result?; + let (result, _) = result?; if result.is_object() { Ok(result) } else { - Ok(this) + Ok(frame.this.clone()) } } + FunctionBody::Generator { .. } => { + unreachable!("generator function cannot be a constructor") + } } } } diff --git a/boa_engine/src/vm/mod.rs b/boa_engine/src/vm/mod.rs index 98c57ff642a..6205e37c5a3 100644 --- a/boa_engine/src/vm/mod.rs +++ b/boa_engine/src/vm/mod.rs @@ -6,7 +6,10 @@ use crate::{ builtins::{iterable::IteratorRecord, Array, ForInIterator, Number}, property::{DescriptorKind, PropertyDescriptor, PropertyKey}, value::Numeric, - vm::{call_frame::CatchAddresses, code_block::Readable}, + vm::{ + call_frame::CatchAddresses, + code_block::{create_function_object, create_generator_function_object, Readable}, + }, Context, JsBigInt, JsResult, JsString, JsValue, }; use boa_interner::ToInternedString; @@ -17,11 +20,12 @@ mod call_frame; mod code_block; mod opcode; -pub use call_frame::CallFrame; -pub(crate) use call_frame::{FinallyReturn, TryStackEntry}; -pub use code_block::{CodeBlock, JsVmFunction}; -pub(crate) use opcode::BindingOpcode; -pub use opcode::Opcode; +pub use {call_frame::CallFrame, code_block::CodeBlock, opcode::Opcode}; + +pub(crate) use { + call_frame::{FinallyReturn, GeneratorResumeKind, TryStackEntry}, + opcode::BindingOpcode, +}; #[cfg(test)] mod tests; @@ -98,8 +102,23 @@ impl Vm { } } +/// Indicates if the execution should continue, exit or yield. +#[derive(Debug, Clone, Copy)] +enum ShouldExit { + True, + False, + Yield, +} + +/// Indicates if the execution of a codeblock has ended normally or has been yielded. +#[derive(Debug, Clone, Copy)] +pub(crate) enum ReturnType { + Normal, + Yield, +} + impl Context { - fn execute_instruction(&mut self) -> JsResult { + fn execute_instruction(&mut self) -> JsResult { macro_rules! bin_op { ($op:ident) => {{ let rhs = self.vm.pop(); @@ -832,7 +851,10 @@ impl Context { }); } Opcode::CatchEnd2 => { - self.vm.frame_mut().finally_return = FinallyReturn::None; + let frame = self.vm.frame_mut(); + if frame.finally_return == FinallyReturn::Err { + frame.finally_return = FinallyReturn::None; + } } Opcode::FinallyStart => { *self @@ -856,7 +878,7 @@ impl Context { } } FinallyReturn::Ok => { - return Ok(true); + return Ok(ShouldExit::True); } FinallyReturn::Err => { return Err(self.vm.pop()); @@ -895,7 +917,13 @@ impl Context { Opcode::GetFunction => { let index = self.vm.read::(); let code = self.vm.frame().code.functions[index as usize].clone(); - let function = JsVmFunction::new(code, self); + let function = create_function_object(code, self); + self.vm.push(function); + } + Opcode::GetGenerator => { + let index = self.vm.read::(); + let code = self.vm.frame().code.functions[index as usize].clone(); + let function = create_generator_function_object(code, self); self.vm.push(function); } Opcode::Call => { @@ -1041,7 +1069,7 @@ impl Context { .last_mut() .expect("must exist") -= num_env; } else { - return Ok(true); + return Ok(ShouldExit::True); } } Opcode::PushDeclarativeEnvironment => { @@ -1263,12 +1291,123 @@ impl Context { Opcode::PopOnReturnSub => { self.vm.frame_mut().pop_on_return -= 1; } + Opcode::Yield => return Ok(ShouldExit::Yield), + Opcode::GeneratorNext => match self.vm.frame().generator_resume_kind { + GeneratorResumeKind::Normal => return Ok(ShouldExit::False), + GeneratorResumeKind::Throw => { + let received = self.vm.pop(); + return Err(received); + } + GeneratorResumeKind::Return => { + let mut finally_left = false; + + while let Some(catch_addresses) = self.vm.frame().catch.last() { + if let Some(finally_address) = catch_addresses.finally { + let frame = self.vm.frame_mut(); + frame.pc = finally_address as usize; + frame.finally_return = FinallyReturn::Ok; + frame.catch.pop(); + finally_left = true; + break; + } + self.vm.frame_mut().catch.pop(); + } + + if finally_left { + return Ok(ShouldExit::False); + } + return Ok(ShouldExit::True); + } + }, + Opcode::GeneratorNextDelegate => { + let done_address = self.vm.read::(); + let received = self.vm.pop(); + let next_function = self.vm.pop(); + let iterator = self.vm.pop(); + + match self.vm.frame().generator_resume_kind { + GeneratorResumeKind::Normal => { + let result = self.call(&next_function, &iterator, &[received])?; + let result_object = result.as_object().ok_or_else(|| { + self.construct_type_error("generator next method returned non-object") + })?; + let done = result_object.get("done", self)?.to_boolean(); + if done { + self.vm.frame_mut().pc = done_address as usize; + let value = result_object.get("value", self)?; + self.vm.push(value); + return Ok(ShouldExit::False); + } + let value = result_object.get("value", self)?; + self.vm.push(iterator); + self.vm.push(next_function); + self.vm.push(value); + return Ok(ShouldExit::Yield); + } + GeneratorResumeKind::Throw => { + let throw = iterator.get_method("throw", self)?; + if let Some(throw) = throw { + let result = throw.call(&iterator, &[received], self)?; + let result_object = result.as_object().ok_or_else(|| { + self.construct_type_error( + "generator throw method returned non-object", + ) + })?; + let done = result_object.get("done", self)?.to_boolean(); + if done { + self.vm.frame_mut().pc = done_address as usize; + let value = result_object.get("value", self)?; + self.vm.push(value); + return Ok(ShouldExit::False); + } + let value = result_object.get("value", self)?; + self.vm.push(iterator); + self.vm.push(next_function); + self.vm.push(value); + return Ok(ShouldExit::Yield); + } + self.vm.frame_mut().pc = done_address as usize; + let iterator_record = + IteratorRecord::new(iterator.clone(), next_function.clone()); + iterator_record.close(Ok(JsValue::Undefined), self)?; + let error = + self.construct_type_error("iterator does not have a throw method"); + return Err(error); + } + GeneratorResumeKind::Return => { + let r#return = iterator.get_method("return", self)?; + if let Some(r#return) = r#return { + let result = r#return.call(&iterator, &[received], self)?; + let result_object = result.as_object().ok_or_else(|| { + self.construct_type_error( + "generator return method returned non-object", + ) + })?; + let done = result_object.get("done", self)?.to_boolean(); + if done { + self.vm.frame_mut().pc = done_address as usize; + let value = result_object.get("value", self)?; + self.vm.push(value); + return Ok(ShouldExit::True); + } + let value = result_object.get("value", self)?; + self.vm.push(iterator); + self.vm.push(next_function); + self.vm.push(value); + return Ok(ShouldExit::Yield); + } + self.vm.frame_mut().pc = done_address as usize; + self.vm.push(received); + return Ok(ShouldExit::True); + } + } + } } - Ok(false) + Ok(ShouldExit::False) } - pub(crate) fn run(&mut self) -> JsResult { + pub(crate) fn run(&mut self) -> JsResult<(JsValue, ReturnType)> { const COLUMN_WIDTH: usize = 26; const TIME_COLUMN_WIDTH: usize = COLUMN_WIDTH / 2; const OPCODE_COLUMN_WIDTH: usize = COLUMN_WIDTH; @@ -1300,7 +1439,6 @@ impl Context { ); } - self.vm.frame_mut().pc = 0; while self.vm.frame().pc < self.vm.frame().code.code.len() { let result = if self.vm.trace { let mut pc = self.vm.frame().pc; @@ -1345,11 +1483,14 @@ impl Context { }; match result { - Ok(should_exit) => { - if should_exit { - let result = self.vm.pop(); - return Ok(result); - } + Ok(ShouldExit::True) => { + let result = self.vm.pop(); + return Ok((result, ReturnType::Normal)); + } + Ok(ShouldExit::False) => {} + Ok(ShouldExit::Yield) => { + let result = self.vm.stack.pop().unwrap_or(JsValue::Undefined); + return Ok((result, ReturnType::Yield)); } Err(e) => { if let Some(address) = self.vm.frame().catch.last() { @@ -1421,9 +1562,9 @@ impl Context { } if self.vm.stack.is_empty() { - return Ok(JsValue::undefined()); + return Ok((JsValue::undefined(), ReturnType::Normal)); } - Ok(self.vm.pop()) + Ok((self.vm.pop(), ReturnType::Normal)) } } diff --git a/boa_engine/src/vm/opcode.rs b/boa_engine/src/vm/opcode.rs index cd96a17aa1a..e051811a832 100644 --- a/boa_engine/src/vm/opcode.rs +++ b/boa_engine/src/vm/opcode.rs @@ -690,6 +690,13 @@ pub enum Opcode { /// Stack: **=>** func GetFunction, + /// Get generator function from the pre-compiled inner functions. + /// + /// Operands: address: `u32` + /// + /// Stack: **=>** func + GetGenerator, + /// Call a function. /// /// Operands: argument_count: `u32` @@ -867,6 +874,27 @@ pub enum Opcode { /// Stack: **=>** PopOnReturnSub, + /// Yield from the current execution. + /// + /// Operands: + /// + /// Stack: value **=>** + Yield, + + /// Resumes the current generator function. + /// + /// Operands: + /// + /// Stack: received **=>** + GeneratorNext, + + /// Delegates the current generator function another generator. + /// + /// Operands: done_address: `u32` + /// + /// Stack: iterator, next_function, received **=>** iterator, next_function + GeneratorNextDelegate, + /// No-operation instruction, does nothing. /// /// Operands: @@ -982,6 +1010,7 @@ impl Opcode { Opcode::Case => "Case", Opcode::Default => "Default", Opcode::GetFunction => "GetFunction", + Opcode::GetGenerator => "GetGenerator", Opcode::Call => "Call", Opcode::CallWithRest => "CallWithRest", Opcode::New => "New", @@ -1007,6 +1036,9 @@ impl Opcode { Opcode::RestParameterPop => "RestParameterPop", Opcode::PopOnReturnAdd => "PopOnReturnAdd", Opcode::PopOnReturnSub => "PopOnReturnSub", + Opcode::Yield => "Yield", + Opcode::GeneratorNext => "GeneratorNext", + Opcode::GeneratorNextDelegate => "GeneratorNextDelegate", Opcode::Nop => "Nop", } } diff --git a/test_ignore.txt b/test_ignore.txt index d6de3606e82..585fbc55cdd 100644 --- a/test_ignore.txt +++ b/test_ignore.txt @@ -8,7 +8,6 @@ feature:SharedArrayBuffer feature:resizable-arraybuffer feature:Temporal feature:tail-call-optimization -//feature:generators //feature:async-iteration //feature:class