Browse Source

Implement generator execution (#1790)

This Pull Request fixes/closes #1559.

It changes the following:

- Implement GeneratorFunction Objects
- Implement Generator Objects
- Implement generator execution in vm
- Create `FormalParameterList` to remove duplicate checks on function parameters 
- Refactor  `MethodDefinition` on object literals
pull/1870/head
raskad 2 years ago
parent
commit
60e2294706
  1. 6
      boa_engine/src/builtins/function/arguments.rs
  2. 18
      boa_engine/src/builtins/function/mod.rs
  3. 415
      boa_engine/src/builtins/generator/mod.rs
  4. 131
      boa_engine/src/builtins/generator_function/mod.rs
  5. 9
      boa_engine/src/builtins/mod.rs
  6. 151
      boa_engine/src/bytecompiler.rs
  7. 22
      boa_engine/src/context.rs
  8. 11
      boa_engine/src/object/jsobject.rs
  9. 72
      boa_engine/src/object/mod.rs
  10. 10
      boa_engine/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs
  11. 11
      boa_engine/src/syntax/ast/node/declaration/async_function_decl/mod.rs
  12. 19
      boa_engine/src/syntax/ast/node/declaration/async_function_expr/mod.rs
  13. 10
      boa_engine/src/syntax/ast/node/declaration/async_generator_decl/mod.rs
  14. 10
      boa_engine/src/syntax/ast/node/declaration/async_generator_expr/mod.rs
  15. 10
      boa_engine/src/syntax/ast/node/declaration/function_decl/mod.rs
  16. 10
      boa_engine/src/syntax/ast/node/declaration/function_expr/mod.rs
  17. 16
      boa_engine/src/syntax/ast/node/declaration/generator_decl/mod.rs
  18. 10
      boa_engine/src/syntax/ast/node/declaration/generator_expr/mod.rs
  19. 320
      boa_engine/src/syntax/ast/node/mod.rs
  20. 282
      boa_engine/src/syntax/ast/node/object/mod.rs
  21. 153
      boa_engine/src/syntax/ast/node/parameters.rs
  22. 28
      boa_engine/src/syntax/parser/expression/assignment/arrow_function.rs
  23. 6
      boa_engine/src/syntax/parser/expression/primary/async_function_expression/mod.rs
  24. 22
      boa_engine/src/syntax/parser/expression/primary/async_function_expression/tests.rs
  25. 6
      boa_engine/src/syntax/parser/expression/primary/async_generator_expression/mod.rs
  26. 22
      boa_engine/src/syntax/parser/expression/primary/async_generator_expression/tests.rs
  27. 6
      boa_engine/src/syntax/parser/expression/primary/function_expression/mod.rs
  28. 21
      boa_engine/src/syntax/parser/expression/primary/function_expression/tests.rs
  29. 6
      boa_engine/src/syntax/parser/expression/primary/generator_expression/mod.rs
  30. 8
      boa_engine/src/syntax/parser/expression/primary/generator_expression/tests.rs
  31. 72
      boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs
  32. 89
      boa_engine/src/syntax/parser/expression/primary/object_initializer/tests.rs
  33. 47
      boa_engine/src/syntax/parser/function/mod.rs
  34. 458
      boa_engine/src/syntax/parser/function/tests.rs
  35. 2
      boa_engine/src/syntax/parser/mod.rs
  36. 8
      boa_engine/src/syntax/parser/statement/block/tests.rs
  37. 26
      boa_engine/src/syntax/parser/statement/declaration/hoistable/async_function_decl/tests.rs
  38. 12
      boa_engine/src/syntax/parser/statement/declaration/hoistable/async_generator_decl/tests.rs
  39. 26
      boa_engine/src/syntax/parser/statement/declaration/hoistable/function_decl/tests.rs
  40. 12
      boa_engine/src/syntax/parser/statement/declaration/hoistable/generator_decl/tests.rs
  41. 10
      boa_engine/src/syntax/parser/statement/declaration/hoistable/mod.rs
  42. 31
      boa_engine/src/syntax/parser/tests.rs
  43. 22
      boa_engine/src/vm/call_frame.rs
  44. 392
      boa_engine/src/vm/code_block.rs
  45. 183
      boa_engine/src/vm/mod.rs
  46. 32
      boa_engine/src/vm/opcode.rs
  47. 1
      test_ignore.txt

6
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 {
/// <https://tc39.es/ecma262/#sec-createmappedargumentsobject>
pub(crate) fn create_mapped_arguments_object(
func: &JsObject,
formals: &[FormalParameter],
formals: &FormalParameterList,
arguments_list: &[JsValue],
env: &Gc<DeclarativeEnvironment>,
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;

18
boa_engine/src/builtins/function/mod.rs

@ -175,7 +175,11 @@ pub enum Function {
constructor: bool,
captures: Captures,
},
VmOrdinary {
Ordinary {
code: Gc<crate::vm::CodeBlock>,
environments: DeclarativeEnvironmentStack,
},
Generator {
code: Gc<crate::vm::CodeBlock>,
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()),
}
}

415
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<JsValue>,
}
/// 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<Gc<Cell<GeneratorContext>>>,
}
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<JsValue> {
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<JsValue> {
// 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<JsValue> {
// 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<JsValue> {
// 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<JsValue> {
// 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<JsValue>,
context: &mut Context,
) -> JsResult<JsValue> {
// 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)
}
}
}
}

131
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<JsValue> {
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())
}
}

9
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::<console::Console>(context);
}

151
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, &[]);

22
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.

11
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

72
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<JsValue>),
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<JsValue>) -> 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
}

10
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<Sym>,
params: Box<[FormalParameter]>,
params: FormalParameterList,
body: StatementList,
}
@ -31,7 +31,7 @@ impl ArrowFunctionDecl {
pub(in crate::syntax) fn new<N, P, B>(name: N, params: P, body: B) -> Self
where
N: Into<Option<Sym>>,
P: Into<Box<[FormalParameter]>>,
P: Into<FormalParameterList>,
B: Into<StatementList>,
{
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 {

11
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<P, B>(name: Sym, parameters: P, body: B) -> Self
where
P: Into<Box<[FormalParameter]>>,
P: Into<FormalParameterList>,
B: Into<StatementList>,
{
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 {

19
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<Sym>,
parameters: Box<[FormalParameter]>,
parameters: FormalParameterList,
body: StatementList,
}
@ -29,7 +29,7 @@ impl AsyncFunctionExpr {
pub(in crate::syntax) fn new<N, P, B>(name: N, parameters: P, body: B) -> Self
where
N: Into<Option<Sym>>,
P: Into<Box<[FormalParameter]>>,
P: Into<FormalParameterList>,
B: Into<StatementList>,
{
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!(

10
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<P, B>(name: Sym, parameters: P, body: B) -> Self
where
P: Into<Box<[FormalParameter]>>,
P: Into<FormalParameterList>,
B: Into<StatementList>,
{
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(") {}");

10
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<Sym>,
parameters: Box<[FormalParameter]>,
parameters: FormalParameterList,
body: StatementList,
}
@ -28,7 +28,7 @@ impl AsyncGeneratorExpr {
pub(in crate::syntax) fn new<N, P, B>(name: N, parameters: P, body: B) -> Self
where
N: Into<Option<Sym>>,
P: Into<Box<[FormalParameter]>>,
P: Into<FormalParameterList>,
B: Into<StatementList>,
{
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)
));

10
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<P, B>(name: Sym, parameters: P, body: B) -> Self
where
P: Into<Box<[FormalParameter]>>,
P: Into<FormalParameterList>,
B: Into<StatementList>,
{
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(") {}");

10
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<Sym>,
parameters: Box<[FormalParameter]>,
parameters: FormalParameterList,
body: StatementList,
}
@ -36,7 +36,7 @@ impl FunctionExpr {
pub(in crate::syntax) fn new<N, P, B>(name: N, parameters: P, body: B) -> Self
where
N: Into<Option<Sym>>,
P: Into<Box<[FormalParameter]>>,
P: Into<FormalParameterList>,
B: Into<StatementList>,
{
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)
));

16
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<P, B>(name: Sym, parameters: P, body: B) -> Self
where
P: Into<Box<[FormalParameter]>>,
P: Into<FormalParameterList>,
B: Into<StatementList>,
{
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!(

10
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<Sym>,
parameters: Box<[FormalParameter]>,
parameters: FormalParameterList,
body: StatementList,
}
@ -28,7 +28,7 @@ impl GeneratorExpr {
pub(in crate::syntax) fn new<N, P, B>(name: N, parameters: P, body: B) -> Self
where
N: Into<Option<Sym>>,
P: Into<Box<[FormalParameter]>>,
P: Into<FormalParameterList>,
B: Into<StatementList>,
{
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)
));

320
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<D>(declaration: D, is_rest_param: bool) -> Self
where
D: Into<Declaration>,
{
Self {
declaration: declaration.into(),
is_rest_param,
}
}
/// Gets the name of the formal parameter.
pub fn names(&self) -> Vec<Sym> {
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<N, V>(name: N, value: V) -> Self
where
N: Into<PropertyName>,
V: Into<Node>,
{
Self::Property(name.into(), value.into())
}
/// Creates a `MethodDefinition`.
pub fn method_definition<N>(kind: MethodDefinitionKind, name: N, body: FunctionExpr) -> Self
where
N: Into<PropertyName>,
{
Self::MethodDefinition(kind, name.into(), body)
}
/// Creates a `SpreadObject`.
pub fn spread_object<O>(obj: O) -> Self
where
O: Into<Node>,
{
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<Sym> for PropertyName {
fn from(name: Sym) -> Self {
Self::Literal(name)
}
}
impl From<Node> 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

282
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<Object> 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<N, V>(name: N, value: V) -> Self
where
N: Into<PropertyName>,
V: Into<Node>,
{
Self::Property(name.into(), value.into())
}
/// Creates a `MethodDefinition`.
pub fn method_definition<N>(kind: MethodDefinition, name: N) -> Self
where
N: Into<PropertyName>,
{
Self::MethodDefinition(kind, name.into())
}
/// Creates a `SpreadObject`.
pub fn spread_object<O>(obj: O) -> Self
where
O: Into<Node>,
{
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<Sym> for PropertyName {
fn from(name: Sym) -> Self {
Self::Literal(name)
}
}
impl From<Node> for PropertyName {
fn from(name: Node) -> Self {
Self::Computed(name)
}
}
unsafe impl Trace for PropertyName {
unsafe_empty_trace!();
}

153
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<D>(declaration: D, is_rest_param: bool) -> Self
where
D: Into<Declaration>,
{
Self {
declaration: declaration.into(),
is_rest_param,
}
}
/// Gets the name of the formal parameter.
pub fn names(&self) -> Vec<Sym> {
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
}
}

28
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<I>(allow_in: I) -> Self
where
I: Into<AllowIn>,

6
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))
}
}

22
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<Sym>>(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<Sym>>(
Const::from(1),
None,
)
.into()]
.into(),
)
.into(),
),

6
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))
}
}

22
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<Sym>>(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<Sym>>(
Const::from(1),
None,
)
.into()]
.into(),
)
.into(),
),

6
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))
}
}

21
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<Sym>>(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<Sym>>(
Const::from(1),
None,
)
.into()]
.into(),
)
.into(),
),

6
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))
}
}

8
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(),

72
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<R> TokenParser<R> 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<R> TokenParser<R> for PropertyName
where
R: Read,
{
type Output = node::PropertyName;
type Output = object::PropertyName;
fn parse(
self,

89
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(

47
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(&param_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))
}
}

458
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::<Node, Option<Node>, 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::<Node, Option<Node>, 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::<Node, Option<_>, 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::<Node, Option<_>, 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::<Node, Option<_>, 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::<Node, Option<_>, 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::<Node, Option<_>, 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::<Node, Option<_>, 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::<Node, Option<_>, 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::<Node, Option<_>, 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::<Node, Option<_>, 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::<Node, Option<_>, Option<_>>(
Some(Identifier::new(interner.get_or_intern_static("a")).into()),
None,

2
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;

8
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(),

26
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,
);
}

12
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,
);
}

26
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,
);
}

12
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,
);
}

10
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<R: Read, C: CallableDeclaration>(
c: &C,
cursor: &mut Cursor<R>,
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<R: Read, C: CallableDeclaration>(
// 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<R: Read, C: CallableDeclaration>(
// 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<R: Read, C: CallableDeclaration>(
}
}
Ok((name, params.parameters, body))
Ok((name, params, body))
}

31
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::<Option<Sym>, Box<[FormalParameter]>, StatementList>(
None,
Box::new([FormalParameter::new(
Declaration::new_with_identifier::<_, Option<Node>>(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,
);
}

22
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<Box<Self>>,
pub(crate) code: Gc<CodeBlock>,
pub(crate) pc: usize,
pub(crate) this: JsValue,
#[unsafe_ignore_trace]
pub(crate) catch: Vec<CatchAddresses>,
#[unsafe_ignore_trace]
pub(crate) finally_return: FinallyReturn,
pub(crate) finally_jump: Vec<Option<u32>>,
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<TryStackEntry>,
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<u32>,
}
/// 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,
}

392
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<u8>,
@ -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::<u32>(*pc).to_string();
*pc += size_of::<u32>();
@ -201,7 +206,7 @@ impl CodeBlock {
*pc += size_of::<u32>();
format!("{operand1}, {operand2}")
}
Opcode::GetFunction => {
Opcode::GetFunction | Opcode::GetGenerator => {
let operand = self.read::<u32>(*pc);
*pc += size_of::<u32>();
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<CodeBlock>, 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<CodeBlock>, 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<CodeBlock>,
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<dyn ClosureFunctionSignature>,
captures: Captures,
},
Generator {
code: Gc<CodeBlock>,
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<JsValue> {
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")
}
}
}
}

183
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<bool> {
fn execute_instruction(&mut self) -> JsResult<ShouldExit> {
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::<u32>();
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::<u32>();
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::<u32>();
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<JsValue> {
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))
}
}

32
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",
}
}

1
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

Loading…
Cancel
Save