Skip to content

Commit

Permalink
Implement Generator Function Constructor (#2174)
Browse files Browse the repository at this point in the history
This Pull Request changes the following:

- Modify `CreateDynamicFunction` to work with generator functions.
- Add the name `anonymus` to functions created via `CreateDynamicFunction` to comply with the spec. 
- Fix a bug in the `Yield` parser where the parser would expect a token when no token is a legal case.
- Change the `Yield::new` function to require less turbofishes.
  • Loading branch information
raskad committed Jul 15, 2022
1 parent 556f3ce commit 6e377d6
Show file tree
Hide file tree
Showing 9 changed files with 110 additions and 83 deletions.
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(",");
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
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

0 comments on commit 6e377d6

Please sign in to comment.