Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Implement Generator Function Constructor #2174

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion boa_engine/src/builtins/async_function/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ impl AsyncFunction {
context: &mut Context,
) -> JsResult<JsValue> {
crate::builtins::function::BuiltInFunctionObject::create_dynamic_function(
new_target, args, true, context,
new_target, args, true, false, context,
)
.map(Into::into)
}
Expand Down
109 changes: 69 additions & 40 deletions boa_engine/src/builtins/function/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,7 @@ impl BuiltInFunctionObject {
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
Self::create_dynamic_function(new_target, args, false, context).map(Into::into)
Self::create_dynamic_function(new_target, args, false, false, context).map(Into::into)
}

/// `CreateDynamicFunction ( constructor, newTarget, kind, args )`
Expand All @@ -473,40 +473,56 @@ impl BuiltInFunctionObject {
new_target: &JsValue,
args: &[JsValue],
r#async: bool,
generator: bool,
context: &mut Context,
) -> JsResult<JsObject> {
let prototype =
get_prototype_from_constructor(new_target, StandardConstructors::function, context)?;
let default = if r#async {
StandardConstructors::async_function
} else if generator {
StandardConstructors::generator_function
} else {
StandardConstructors::function
};

let prototype = get_prototype_from_constructor(new_target, default, context)?;
if let Some((body_arg, args)) = args.split_last() {
let parameters =
if args.is_empty() {
FormalParameterList::empty()
} else {
let mut parameters = Vec::with_capacity(args.len());
for arg in args {
parameters.push(arg.to_string(context)?);
let parameters = if args.is_empty() {
FormalParameterList::empty()
} else {
let mut parameters = Vec::with_capacity(args.len());
for arg in args {
parameters.push(arg.to_string(context)?);
}
let mut parameters = parameters.join(",");
Razican marked this conversation as resolved.
Show resolved Hide resolved
parameters.push(')');

let parameters = match Parser::new(parameters.as_bytes()).parse_formal_parameters(
context.interner_mut(),
generator,
r#async,
) {
Ok(parameters) => parameters,
Err(e) => {
return context.throw_syntax_error(format!(
"failed to parse function parameters: {e}"
))
}
let mut parameters = parameters.join(",");
parameters.push(')');

let parameters = match Parser::new(parameters.as_bytes())
.parse_formal_parameters(context.interner_mut(), false, r#async)
{
Ok(parameters) => parameters,
Err(e) => {
return context.throw_syntax_error(format!(
"failed to parse function parameters: {e}"
))
}
};
parameters
};

if generator && parameters.contains_yield_expression() {
return context.throw_syntax_error(
"yield expression is not allowed in formal parameter list of generator function",
);
}

parameters
};

let body_arg = body_arg.to_string(context)?;

let body = match Parser::new(body_arg.as_bytes()).parse_function_body(
context.interner_mut(),
false,
generator,
r#async,
) {
Ok(statement_list) => statement_list,
Expand Down Expand Up @@ -567,23 +583,45 @@ impl BuiltInFunctionObject {

let code = crate::bytecompiler::ByteCompiler::compile_function_code(
crate::bytecompiler::FunctionKind::Expression,
Some(Sym::EMPTY_STRING),
Some(Sym::ANONYMOUS),
&parameters,
&body,
generator,
false,
context,
)?;

let environments = context.realm.environments.pop_to_global();

let function_object = if generator {
crate::vm::create_generator_function_object(code, context)
} else {
crate::vm::create_function_object(code, r#async, Some(prototype), context)
};

context.realm.environments.extend(environments);

Ok(function_object)
} else if generator {
let code = crate::bytecompiler::ByteCompiler::compile_function_code(
crate::bytecompiler::FunctionKind::Expression,
Some(Sym::ANONYMOUS),
&FormalParameterList::empty(),
&StatementList::default(),
true,
false,
context,
)?;

let environments = context.realm.environments.pop_to_global();
let function_object = crate::vm::create_function_object(code, r#async, context);
let function_object = crate::vm::create_generator_function_object(code, context);
context.realm.environments.extend(environments);

Ok(function_object)
} else if r#async {
} else {
let code = crate::bytecompiler::ByteCompiler::compile_function_code(
crate::bytecompiler::FunctionKind::Expression,
Some(Sym::EMPTY_STRING),
Some(Sym::ANONYMOUS),
&FormalParameterList::empty(),
&StatementList::default(),
false,
Expand All @@ -592,20 +630,11 @@ impl BuiltInFunctionObject {
)?;

let environments = context.realm.environments.pop_to_global();
let function_object = crate::vm::create_function_object(code, r#async, context);
let function_object =
crate::vm::create_function_object(code, r#async, Some(prototype), context);
context.realm.environments.extend(environments);

Ok(function_object)
} else {
let this = JsObject::from_proto_and_data(
prototype,
ObjectData::function(Function::Native {
function: |_, _, _| Ok(JsValue::undefined()),
constructor: Some(ConstructorKind::Base),
}),
);

Ok(this)
}
}

Expand Down
35 changes: 14 additions & 21 deletions boa_engine/src/builtins/generator_function/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/GeneratorFunction

use crate::{
builtins::{function::Function, BuiltIn},
context::intrinsics::StandardConstructors,
object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData},
builtins::{
function::{BuiltInFunctionObject, ConstructorKind, Function},
BuiltIn,
},
object::ObjectData,
property::PropertyDescriptor,
symbol::WellKnownSymbols,
value::JsValue,
Context, JsResult,
};
use boa_profiler::Profiler;

use super::function::ConstructorKind;

/// The internal representation on a `Generator` object.
#[derive(Debug, Clone, Copy)]
pub struct GeneratorFunction;
Expand Down Expand Up @@ -111,25 +111,18 @@ impl BuiltIn for GeneratorFunction {
}

impl GeneratorFunction {
/// `GeneratorFunction ( p1, p2, … , pn, body )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-generatorfunction
pub(crate) fn constructor(
new_target: &JsValue,
_: &[JsValue],
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
let prototype = get_prototype_from_constructor(
new_target,
StandardConstructors::generator_function,
context,
)?;

let this = JsObject::from_proto_and_data(
prototype,
ObjectData::function(Function::Native {
function: |_, _, _| Ok(JsValue::undefined()),
constructor: Some(ConstructorKind::Base),
}),
);

Ok(this.into())
BuiltInFunctionObject::create_dynamic_function(new_target, args, false, true, context)
.map(Into::into)
}
}
5 changes: 2 additions & 3 deletions boa_engine/src/syntax/ast/node/yield/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,12 @@ impl Yield {
}

/// Creates a `Yield` AST node.
pub fn new<E, OE>(expr: OE, delegate: bool) -> Self
pub fn new<E>(expr: Option<E>, delegate: bool) -> Self
Razican marked this conversation as resolved.
Show resolved Hide resolved
where
E: Into<Node>,
OE: Into<Option<E>>,
{
Self {
expr: expr.into().map(E::into).map(Box::new),
expr: expr.map(Into::into).map(Box::new),
delegate,
}
}
Expand Down
21 changes: 10 additions & 11 deletions boa_engine/src/syntax/parser/expression/assignment/yield.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::syntax::{
Keyword, Punctuator,
},
lexer::TokenKind,
parser::{AllowAwait, AllowIn, Cursor, ParseError, ParseResult, TokenParser},
parser::{AllowAwait, AllowIn, Cursor, ParseResult, TokenParser},
};
use boa_interner::Interner;
use boa_profiler::Profiler;
Expand Down Expand Up @@ -63,16 +63,18 @@ where
interner,
)?;

let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?;
let token = if let Some(token) = cursor.peek(0, interner)? {
token
} else {
return Ok(Node::Yield(Yield::new::<Node>(None, false)));
};

match token.kind() {
TokenKind::Punctuator(Punctuator::Mul) => {
cursor.next(interner)?.expect("token disappeared");
let expr = AssignmentExpression::new(None, self.allow_in, true, self.allow_await)
.parse(cursor, interner)?;
Ok(Node::Yield(Yield::new::<Node, Option<Node>>(
Some(expr),
true,
)))
Ok(Node::Yield(Yield::new(Some(expr), true)))
}
TokenKind::Identifier(_)
| TokenKind::Punctuator(
Expand Down Expand Up @@ -109,12 +111,9 @@ where
| TokenKind::TemplateMiddle(_) => {
let expr = AssignmentExpression::new(None, self.allow_in, true, self.allow_await)
.parse(cursor, interner)?;
Ok(Node::Yield(Yield::new::<Node, Option<Node>>(
Some(expr),
false,
)))
Ok(Node::Yield(Yield::new(Some(expr), false)))
}
_ => Ok(Node::Yield(Yield::new::<Node, Option<Node>>(None, false))),
_ => Ok(Node::Yield(Yield::new::<Node>(None, false))),
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ fn check_generator_function_expression() {
GeneratorExpr::new::<_, _, StatementList>(
Some(gen),
FormalParameterList::default(),
vec![Yield::new(Const::from(1), false).into()].into(),
vec![Yield::new(Some(Const::from(1)), false).into()].into(),
)
.into(),
),
Expand Down Expand Up @@ -53,7 +53,7 @@ fn check_generator_function_delegate_yield_expression() {
GeneratorExpr::new::<_, _, StatementList>(
Some(gen),
FormalParameterList::default(),
vec![Yield::new(Const::from(1), true).into()].into(),
vec![Yield::new(Some(Const::from(1)), true).into()].into(),
)
.into(),
),
Expand Down
5 changes: 4 additions & 1 deletion boa_engine/src/vm/code_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -446,11 +446,14 @@ impl ToInternedString for CodeBlock {
pub(crate) fn create_function_object(
code: Gc<CodeBlock>,
r#async: bool,
prototype: Option<JsObject>,
context: &mut Context,
) -> JsObject {
let _timer = Profiler::global().start_event("JsVmFunction::new", "vm");

let function_prototype = if r#async {
let function_prototype = if let Some(prototype) = prototype {
prototype
} else if r#async {
context
.intrinsics()
.constructors()
Expand Down
8 changes: 4 additions & 4 deletions boa_engine/src/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::{
value::Numeric,
vm::{
call_frame::CatchAddresses,
code_block::{create_generator_function_object, initialize_instance_elements, Readable},
code_block::{initialize_instance_elements, Readable},
},
Context, JsBigInt, JsResult, JsString, JsValue,
};
Expand All @@ -30,7 +30,7 @@ pub use {call_frame::CallFrame, code_block::CodeBlock, opcode::Opcode};

pub(crate) use {
call_frame::{FinallyReturn, GeneratorResumeKind, TryStackEntry},
code_block::create_function_object,
code_block::{create_function_object, create_generator_function_object},
opcode::BindingOpcode,
};

Expand Down Expand Up @@ -1683,13 +1683,13 @@ impl Context {
Opcode::GetFunction => {
let index = self.vm.read::<u32>();
let code = self.vm.frame().code.functions[index as usize].clone();
let function = create_function_object(code, false, self);
let function = create_function_object(code, false, None, self);
self.vm.push(function);
}
Opcode::GetFunctionAsync => {
let index = self.vm.read::<u32>();
let code = self.vm.frame().code.functions[index as usize].clone();
let function = create_function_object(code, true, self);
let function = create_function_object(code, true, None, self);
self.vm.push(function);
}
Opcode::GetGenerator => {
Expand Down
4 changes: 4 additions & 0 deletions boa_interner/src/sym.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ impl Sym {
/// Symbol for the `"public"` string.
pub const PUBLIC: Self = unsafe { Self::new_unchecked(22) };

/// Symbol for the `"anonymous"` string.
pub const ANONYMOUS: Self = unsafe { Self::new_unchecked(23) };

/// Creates a new [`Sym`] from the provided `value`, or returns `None` if `index` is zero.
#[inline]
pub(super) fn new(value: usize) -> Option<Self> {
Expand Down Expand Up @@ -141,6 +144,7 @@ pub(super) static COMMON_STRINGS: phf::OrderedSet<&'static str> = {
"private",
"protected",
"public",
"anonymous",
};
// A `COMMON_STRINGS` of size `usize::MAX` would cause an overflow on our `Interner`
sa::const_assert!(COMMON_STRINGS.len() < usize::MAX);
Expand Down