mirror of https://github.com/boa-dev/boa.git
Browse Source
This Pull Request fixes #1560. It changes the following: - Implement `AsyncGeneratorFunction` builtin object. - Implement `AsyncGenerator` builtin object. - Implement async generator execution. - Add some parse errors for async generators. The `AsyncGenerator.prototype.return()` method currently does not work correctly with `finally` blocks, but I think we should merge this first to not increase the complexity of the pr too much.pull/2213/head
raskad
2 years ago
20 changed files with 1836 additions and 301 deletions
@ -0,0 +1,754 @@
|
||||
//! This module implements the global `AsyncGenerator` object.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-asyncgenerator-objects
|
||||
|
||||
use crate::{ |
||||
builtins::{ |
||||
generator::GeneratorContext, iterable::create_iter_result_object, |
||||
promise::if_abrupt_reject_promise, promise::PromiseCapability, BuiltIn, JsArgs, Promise, |
||||
}, |
||||
object::{ConstructorBuilder, FunctionBuilder, JsObject, ObjectData}, |
||||
property::{Attribute, PropertyDescriptor}, |
||||
symbol::WellKnownSymbols, |
||||
value::JsValue, |
||||
vm::GeneratorResumeKind, |
||||
Context, JsResult, |
||||
}; |
||||
use boa_gc::{Cell, Finalize, Gc, Trace}; |
||||
use boa_profiler::Profiler; |
||||
use std::collections::VecDeque; |
||||
|
||||
/// Indicates the state of an async generator.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)] |
||||
pub(crate) enum AsyncGeneratorState { |
||||
Undefined, |
||||
SuspendedStart, |
||||
SuspendedYield, |
||||
Executing, |
||||
AwaitingReturn, |
||||
Completed, |
||||
} |
||||
|
||||
/// `AsyncGeneratorRequest Records`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorrequest-records
|
||||
#[derive(Debug, Clone, Finalize, Trace)] |
||||
pub(crate) struct AsyncGeneratorRequest { |
||||
/// The `[[Completion]]` slot.
|
||||
pub(crate) completion: (JsResult<JsValue>, bool), |
||||
|
||||
/// The `[[Capability]]` slot.
|
||||
capability: PromiseCapability, |
||||
} |
||||
|
||||
/// The internal representation on an `AsyncGenerator` object.
|
||||
#[derive(Debug, Clone, Finalize, Trace)] |
||||
pub struct AsyncGenerator { |
||||
/// The `[[AsyncGeneratorState]]` internal slot.
|
||||
#[unsafe_ignore_trace] |
||||
pub(crate) state: AsyncGeneratorState, |
||||
|
||||
/// The `[[AsyncGeneratorContext]]` internal slot.
|
||||
pub(crate) context: Option<Gc<Cell<GeneratorContext>>>, |
||||
|
||||
/// The `[[AsyncGeneratorQueue]]` internal slot.
|
||||
pub(crate) queue: VecDeque<AsyncGeneratorRequest>, |
||||
} |
||||
|
||||
impl BuiltIn for AsyncGenerator { |
||||
const NAME: &'static str = "AsyncGenerator"; |
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> { |
||||
let _timer = Profiler::global().start_event(Self::NAME, "init"); |
||||
|
||||
let iterator_prototype = context |
||||
.intrinsics() |
||||
.objects() |
||||
.iterator_prototypes() |
||||
.async_iterator_prototype(); |
||||
|
||||
let generator_function_prototype = context |
||||
.intrinsics() |
||||
.constructors() |
||||
.async_generator_function() |
||||
.prototype(); |
||||
|
||||
ConstructorBuilder::with_standard_constructor( |
||||
context, |
||||
Self::constructor, |
||||
context |
||||
.intrinsics() |
||||
.constructors() |
||||
.async_generator() |
||||
.clone(), |
||||
) |
||||
.name(Self::NAME) |
||||
.length(0) |
||||
.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 |
||||
.intrinsics() |
||||
.constructors() |
||||
.async_generator() |
||||
.prototype |
||||
.insert_property( |
||||
"constructor", |
||||
PropertyDescriptor::builder() |
||||
.value(generator_function_prototype) |
||||
.writable(false) |
||||
.enumerable(false) |
||||
.configurable(true), |
||||
); |
||||
|
||||
None |
||||
} |
||||
} |
||||
|
||||
impl AsyncGenerator { |
||||
#[allow(clippy::unnecessary_wraps)] |
||||
pub(crate) fn constructor( |
||||
_: &JsValue, |
||||
_: &[JsValue], |
||||
context: &mut Context, |
||||
) -> JsResult<JsValue> { |
||||
let prototype = context |
||||
.intrinsics() |
||||
.constructors() |
||||
.async_generator() |
||||
.prototype(); |
||||
|
||||
let this = JsObject::from_proto_and_data( |
||||
prototype, |
||||
ObjectData::async_generator(Self { |
||||
state: AsyncGeneratorState::Undefined, |
||||
context: None, |
||||
queue: VecDeque::new(), |
||||
}), |
||||
); |
||||
|
||||
Ok(this.into()) |
||||
} |
||||
|
||||
/// `AsyncGenerator.prototype.next ( value )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-asyncgenerator-prototype-next
|
||||
pub(crate) fn next( |
||||
this: &JsValue, |
||||
args: &[JsValue], |
||||
context: &mut Context, |
||||
) -> JsResult<JsValue> { |
||||
// 1. Let generator be the this value.
|
||||
let generator = this; |
||||
|
||||
// 2. Let promiseCapability be ! NewPromiseCapability(%Promise%).
|
||||
let promise_capability = PromiseCapability::new( |
||||
&context |
||||
.intrinsics() |
||||
.constructors() |
||||
.promise() |
||||
.constructor() |
||||
.into(), |
||||
context, |
||||
) |
||||
.expect("cannot fail with promise constructor"); |
||||
|
||||
// 3. Let result be Completion(AsyncGeneratorValidate(generator, empty)).
|
||||
// 4. IfAbruptRejectPromise(result, promiseCapability).
|
||||
let generator_object = generator.as_object().ok_or_else(|| { |
||||
context.construct_type_error("generator resumed on non generator object") |
||||
}); |
||||
if_abrupt_reject_promise!(generator_object, promise_capability, context); |
||||
let mut generator_obj_mut = generator_object.borrow_mut(); |
||||
let generator = generator_obj_mut.as_async_generator_mut().ok_or_else(|| { |
||||
context.construct_type_error("generator resumed on non generator object") |
||||
}); |
||||
if_abrupt_reject_promise!(generator, promise_capability, context); |
||||
|
||||
// 5. Let state be generator.[[AsyncGeneratorState]].
|
||||
let state = generator.state; |
||||
|
||||
// 6. If state is completed, then
|
||||
if state == AsyncGeneratorState::Completed { |
||||
drop(generator_obj_mut); |
||||
|
||||
// a. Let iteratorResult be CreateIterResultObject(undefined, true).
|
||||
let iterator_result = create_iter_result_object(JsValue::undefined(), true, context); |
||||
|
||||
// b. Perform ! Call(promiseCapability.[[Resolve]], undefined, « iteratorResult »).
|
||||
promise_capability |
||||
.resolve() |
||||
.call(&JsValue::undefined(), &[iterator_result], context) |
||||
.expect("cannot fail per spec"); |
||||
|
||||
// c. Return promiseCapability.[[Promise]].
|
||||
return Ok(promise_capability.promise().clone().into()); |
||||
} |
||||
|
||||
// 7. Let completion be NormalCompletion(value).
|
||||
let completion = (Ok(args.get_or_undefined(0).clone()), false); |
||||
|
||||
// 8. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
|
||||
generator.enqueue(completion.clone(), promise_capability.clone()); |
||||
|
||||
// 9. If state is either suspendedStart or suspendedYield, then
|
||||
if state == AsyncGeneratorState::SuspendedStart |
||||
|| state == AsyncGeneratorState::SuspendedYield |
||||
{ |
||||
// a. Perform AsyncGeneratorResume(generator, completion).
|
||||
let generator_context = generator |
||||
.context |
||||
.clone() |
||||
.expect("generator context cannot be empty here"); |
||||
|
||||
drop(generator_obj_mut); |
||||
|
||||
Self::resume( |
||||
generator_object, |
||||
state, |
||||
&generator_context, |
||||
completion, |
||||
context, |
||||
); |
||||
} |
||||
|
||||
// 11. Return promiseCapability.[[Promise]].
|
||||
Ok(promise_capability.promise().clone().into()) |
||||
} |
||||
|
||||
/// `AsyncGenerator.prototype.return ( value )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-asyncgenerator-prototype-return
|
||||
pub(crate) fn r#return( |
||||
this: &JsValue, |
||||
args: &[JsValue], |
||||
context: &mut Context, |
||||
) -> JsResult<JsValue> { |
||||
// 1. Let generator be the this value.
|
||||
let generator = this; |
||||
|
||||
// 2. Let promiseCapability be ! NewPromiseCapability(%Promise%).
|
||||
let promise_capability = PromiseCapability::new( |
||||
&context |
||||
.intrinsics() |
||||
.constructors() |
||||
.promise() |
||||
.constructor() |
||||
.into(), |
||||
context, |
||||
) |
||||
.expect("cannot fail with promise constructor"); |
||||
|
||||
// 3. Let result be Completion(AsyncGeneratorValidate(generator, empty)).
|
||||
// 4. IfAbruptRejectPromise(result, promiseCapability).
|
||||
let generator_object = generator.as_object().ok_or_else(|| { |
||||
context.construct_type_error("generator resumed on non generator object") |
||||
}); |
||||
if_abrupt_reject_promise!(generator_object, promise_capability, context); |
||||
let mut generator_obj_mut = generator_object.borrow_mut(); |
||||
let generator = generator_obj_mut.as_async_generator_mut().ok_or_else(|| { |
||||
context.construct_type_error("generator resumed on non generator object") |
||||
}); |
||||
if_abrupt_reject_promise!(generator, promise_capability, context); |
||||
|
||||
// 5. Let completion be Completion Record { [[Type]]: return, [[Value]]: value, [[Target]]: empty }.
|
||||
let completion = (Ok(args.get_or_undefined(0).clone()), true); |
||||
|
||||
// 6. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
|
||||
generator.enqueue(completion.clone(), promise_capability.clone()); |
||||
|
||||
// 7. Let state be generator.[[AsyncGeneratorState]].
|
||||
let state = generator.state; |
||||
|
||||
// 8. If state is either suspendedStart or completed, then
|
||||
if state == AsyncGeneratorState::SuspendedStart || state == AsyncGeneratorState::Completed { |
||||
// a. Set generator.[[AsyncGeneratorState]] to awaiting-return.
|
||||
generator.state = AsyncGeneratorState::AwaitingReturn; |
||||
|
||||
// b. Perform ! AsyncGeneratorAwaitReturn(generator).
|
||||
let next = generator |
||||
.queue |
||||
.front() |
||||
.cloned() |
||||
.expect("queue cannot be empty here"); |
||||
drop(generator_obj_mut); |
||||
let (completion, _) = &next.completion; |
||||
Self::await_return(generator_object.clone(), completion.clone(), context); |
||||
} |
||||
// 9. Else if state is suspendedYield, then
|
||||
else if state == AsyncGeneratorState::SuspendedYield { |
||||
// a. Perform AsyncGeneratorResume(generator, completion).
|
||||
let generator_context = generator |
||||
.context |
||||
.clone() |
||||
.expect("generator context cannot be empty here"); |
||||
|
||||
drop(generator_obj_mut); |
||||
Self::resume( |
||||
generator_object, |
||||
state, |
||||
&generator_context, |
||||
completion, |
||||
context, |
||||
); |
||||
} |
||||
|
||||
// 11. Return promiseCapability.[[Promise]].
|
||||
Ok(promise_capability.promise().clone().into()) |
||||
} |
||||
|
||||
/// `AsyncGenerator.prototype.throw ( exception )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-asyncgenerator-prototype-throw
|
||||
pub(crate) fn throw( |
||||
this: &JsValue, |
||||
args: &[JsValue], |
||||
context: &mut Context, |
||||
) -> JsResult<JsValue> { |
||||
// 1. Let generator be the this value.
|
||||
let generator = this; |
||||
|
||||
// 2. Let promiseCapability be ! NewPromiseCapability(%Promise%).
|
||||
let promise_capability = PromiseCapability::new( |
||||
&context |
||||
.intrinsics() |
||||
.constructors() |
||||
.promise() |
||||
.constructor() |
||||
.into(), |
||||
context, |
||||
) |
||||
.expect("cannot fail with promise constructor"); |
||||
|
||||
// 3. Let result be Completion(AsyncGeneratorValidate(generator, empty)).
|
||||
// 4. IfAbruptRejectPromise(result, promiseCapability).
|
||||
let generator_object = generator.as_object().ok_or_else(|| { |
||||
context.construct_type_error("generator resumed on non generator object") |
||||
}); |
||||
if_abrupt_reject_promise!(generator_object, promise_capability, context); |
||||
let mut generator_obj_mut = generator_object.borrow_mut(); |
||||
let generator = generator_obj_mut.as_async_generator_mut().ok_or_else(|| { |
||||
context.construct_type_error("generator resumed on non generator object") |
||||
}); |
||||
if_abrupt_reject_promise!(generator, promise_capability, context); |
||||
|
||||
// 5. Let state be generator.[[AsyncGeneratorState]].
|
||||
let mut state = generator.state; |
||||
|
||||
// 6. If state is suspendedStart, then
|
||||
if state == AsyncGeneratorState::SuspendedStart { |
||||
// a. Set generator.[[AsyncGeneratorState]] to completed.
|
||||
generator.state = AsyncGeneratorState::Completed; |
||||
|
||||
// b. Set state to completed.
|
||||
state = AsyncGeneratorState::Completed; |
||||
} |
||||
|
||||
// 7. If state is completed, then
|
||||
if state == AsyncGeneratorState::Completed { |
||||
drop(generator_obj_mut); |
||||
|
||||
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « exception »).
|
||||
promise_capability |
||||
.reject() |
||||
.call( |
||||
&JsValue::undefined(), |
||||
&[args.get_or_undefined(0).clone()], |
||||
context, |
||||
) |
||||
.expect("cannot fail per spec"); |
||||
|
||||
// b. Return promiseCapability.[[Promise]].
|
||||
return Ok(promise_capability.promise().clone().into()); |
||||
} |
||||
|
||||
// 8. Let completion be ThrowCompletion(exception).
|
||||
let completion = (Err(args.get_or_undefined(0).clone()), false); |
||||
|
||||
// 9. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
|
||||
generator.enqueue(completion.clone(), promise_capability.clone()); |
||||
|
||||
// 10. If state is suspendedYield, then
|
||||
if state == AsyncGeneratorState::SuspendedYield { |
||||
let generator_context = generator |
||||
.context |
||||
.clone() |
||||
.expect("generator context cannot be empty here"); |
||||
drop(generator_obj_mut); |
||||
|
||||
// a. Perform AsyncGeneratorResume(generator, completion).
|
||||
Self::resume( |
||||
generator_object, |
||||
state, |
||||
&generator_context, |
||||
completion, |
||||
context, |
||||
); |
||||
} |
||||
|
||||
// 12. Return promiseCapability.[[Promise]].
|
||||
Ok(promise_capability.promise().clone().into()) |
||||
} |
||||
|
||||
/// `AsyncGeneratorEnqueue ( generator, completion, promiseCapability )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorenqueue
|
||||
pub(crate) fn enqueue( |
||||
&mut self, |
||||
completion: (JsResult<JsValue>, bool), |
||||
promise_capability: PromiseCapability, |
||||
) { |
||||
// 1. Let request be AsyncGeneratorRequest { [[Completion]]: completion, [[Capability]]: promiseCapability }.
|
||||
let request = AsyncGeneratorRequest { |
||||
completion, |
||||
capability: promise_capability, |
||||
}; |
||||
|
||||
// 2. Append request to the end of generator.[[AsyncGeneratorQueue]].
|
||||
self.queue.push_back(request); |
||||
} |
||||
|
||||
/// `AsyncGeneratorCompleteStep ( generator, completion, done [ , realm ] )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorcompletestep
|
||||
pub(crate) fn complete_step( |
||||
next: &AsyncGeneratorRequest, |
||||
completion: JsResult<JsValue>, |
||||
done: bool, |
||||
context: &mut Context, |
||||
) { |
||||
// 1. Let queue be generator.[[AsyncGeneratorQueue]].
|
||||
// 2. Assert: queue is not empty.
|
||||
// 3. Let next be the first element of queue.
|
||||
// 4. Remove the first element from queue.
|
||||
// 5. Let promiseCapability be next.[[Capability]].
|
||||
let promise_capability = &next.capability; |
||||
|
||||
// 6. Let value be completion.[[Value]].
|
||||
match completion { |
||||
// 7. If completion.[[Type]] is throw, then
|
||||
Err(value) => { |
||||
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « value »).
|
||||
promise_capability |
||||
.reject() |
||||
.call(&JsValue::undefined(), &[value], context) |
||||
.expect("cannot fail per spec"); |
||||
} |
||||
// 8. Else,
|
||||
Ok(value) => { |
||||
// a. Assert: completion.[[Type]] is normal.
|
||||
|
||||
// TODO: Realm handling not implemented yet.
|
||||
// b. If realm is present, then
|
||||
// i. Let oldRealm be the running execution context's Realm.
|
||||
// ii. Set the running execution context's Realm to realm.
|
||||
// iii. Let iteratorResult be CreateIterResultObject(value, done).
|
||||
// iv. Set the running execution context's Realm to oldRealm.
|
||||
// c. Else,
|
||||
// i. Let iteratorResult be CreateIterResultObject(value, done).
|
||||
let iterator_result = create_iter_result_object(value, done, context); |
||||
|
||||
// d. Perform ! Call(promiseCapability.[[Resolve]], undefined, « iteratorResult »).
|
||||
promise_capability |
||||
.resolve() |
||||
.call(&JsValue::undefined(), &[iterator_result], context) |
||||
.expect("cannot fail per spec"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// `AsyncGeneratorResume ( generator, completion )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorresume
|
||||
pub(crate) fn resume( |
||||
generator: &JsObject, |
||||
state: AsyncGeneratorState, |
||||
generator_context: &Gc<Cell<GeneratorContext>>, |
||||
completion: (JsResult<JsValue>, bool), |
||||
context: &mut Context, |
||||
) { |
||||
// 1. Assert: generator.[[AsyncGeneratorState]] is either suspendedStart or suspendedYield.
|
||||
assert!( |
||||
state == AsyncGeneratorState::SuspendedStart |
||||
|| state == AsyncGeneratorState::SuspendedYield |
||||
); |
||||
|
||||
// 2. Let genContext be generator.[[AsyncGeneratorContext]].
|
||||
let mut generator_context_mut = generator_context.borrow_mut(); |
||||
|
||||
// 3. Let callerContext be the running execution context.
|
||||
// 4. Suspend callerContext.
|
||||
|
||||
// 5. Set generator.[[AsyncGeneratorState]] to executing.
|
||||
generator |
||||
.borrow_mut() |
||||
.as_async_generator_mut() |
||||
.expect("already checked before") |
||||
.state = AsyncGeneratorState::Executing; |
||||
|
||||
// 6. Push genContext onto the execution context stack; genContext is now the running execution context.
|
||||
std::mem::swap( |
||||
&mut context.realm.environments, |
||||
&mut generator_context_mut.environments, |
||||
); |
||||
std::mem::swap(&mut context.vm.stack, &mut generator_context_mut.stack); |
||||
context |
||||
.vm |
||||
.push_frame(generator_context_mut.call_frame.clone()); |
||||
|
||||
// 7. Resume the suspended evaluation of genContext using completion as the result of the operation that suspended it. Let result be the Completion Record returned by the resumed computation.
|
||||
match completion { |
||||
(Ok(value), r#return) => { |
||||
context.vm.push(value); |
||||
if r#return { |
||||
context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Return; |
||||
} else { |
||||
context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Normal; |
||||
} |
||||
} |
||||
(Err(value), _) => { |
||||
context.vm.push(value); |
||||
context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Throw; |
||||
} |
||||
} |
||||
drop(generator_context_mut); |
||||
let result = context.run(); |
||||
|
||||
let mut generator_context_mut = generator_context.borrow_mut(); |
||||
std::mem::swap( |
||||
&mut context.realm.environments, |
||||
&mut generator_context_mut.environments, |
||||
); |
||||
std::mem::swap(&mut context.vm.stack, &mut generator_context_mut.stack); |
||||
generator_context_mut.call_frame = |
||||
context.vm.pop_frame().expect("generator frame must exist"); |
||||
drop(generator_context_mut); |
||||
|
||||
// 8. Assert: result is never an abrupt completion.
|
||||
assert!(result.is_ok()); |
||||
|
||||
// 9. Assert: When we return here, genContext has already been removed from the execution context stack and callerContext is the currently running execution context.
|
||||
// 10. Return unused.
|
||||
} |
||||
|
||||
/// `AsyncGeneratorAwaitReturn ( generator )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorawaitreturn
|
||||
pub(crate) fn await_return( |
||||
generator: JsObject, |
||||
completion: JsResult<JsValue>, |
||||
context: &mut Context, |
||||
) { |
||||
// 1. Let queue be generator.[[AsyncGeneratorQueue]].
|
||||
// 2. Assert: queue is not empty.
|
||||
// 3. Let next be the first element of queue.
|
||||
// 4. Let completion be Completion(next.[[Completion]]).
|
||||
|
||||
// 5. Assert: completion.[[Type]] is return.
|
||||
let value = completion.expect("completion must be a return completion"); |
||||
|
||||
// Note: The spec is currently broken here.
|
||||
// See: https://github.com/tc39/ecma262/pull/2683
|
||||
|
||||
// 6. Let promise be ? PromiseResolve(%Promise%, completion.[[Value]]).
|
||||
let promise_completion = Promise::promise_resolve( |
||||
context.intrinsics().constructors().promise().constructor(), |
||||
value, |
||||
context, |
||||
); |
||||
|
||||
let promise = match promise_completion { |
||||
Ok(value) => value, |
||||
Err(value) => { |
||||
let mut generator_borrow_mut = generator.borrow_mut(); |
||||
let gen = generator_borrow_mut |
||||
.as_async_generator_mut() |
||||
.expect("already checked before"); |
||||
gen.state = AsyncGeneratorState::Completed; |
||||
let next = gen.queue.pop_front().expect("queue must not be empty"); |
||||
drop(generator_borrow_mut); |
||||
Self::complete_step(&next, Err(value), true, context); |
||||
Self::drain_queue(&generator, context); |
||||
return; |
||||
} |
||||
}; |
||||
|
||||
// 7. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures generator and performs the following steps when called:
|
||||
// 8. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »).
|
||||
let on_fulfilled = FunctionBuilder::closure_with_captures( |
||||
context, |
||||
|_this, args, generator, context| { |
||||
let mut generator_borrow_mut = generator.borrow_mut(); |
||||
let gen = generator_borrow_mut |
||||
.as_async_generator_mut() |
||||
.expect("already checked before"); |
||||
|
||||
// a. Set generator.[[AsyncGeneratorState]] to completed.
|
||||
gen.state = AsyncGeneratorState::Completed; |
||||
|
||||
// b. Let result be NormalCompletion(value).
|
||||
let result = Ok(args.get_or_undefined(0).clone()); |
||||
|
||||
// c. Perform AsyncGeneratorCompleteStep(generator, result, true).
|
||||
let next = gen.queue.pop_front().expect("must have one entry"); |
||||
drop(generator_borrow_mut); |
||||
Self::complete_step(&next, result, true, context); |
||||
|
||||
// d. Perform AsyncGeneratorDrainQueue(generator).
|
||||
Self::drain_queue(generator, context); |
||||
|
||||
// e. Return undefined.
|
||||
Ok(JsValue::undefined()) |
||||
}, |
||||
generator.clone(), |
||||
) |
||||
.name("") |
||||
.length(1) |
||||
.build(); |
||||
|
||||
// 9. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures generator and performs the following steps when called:
|
||||
// 10. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »).
|
||||
let on_rejected = FunctionBuilder::closure_with_captures( |
||||
context, |
||||
|_this, args, generator, context| { |
||||
let mut generator_borrow_mut = generator.borrow_mut(); |
||||
let gen = generator_borrow_mut |
||||
.as_async_generator_mut() |
||||
.expect("already checked before"); |
||||
|
||||
// a. Set generator.[[AsyncGeneratorState]] to completed.
|
||||
gen.state = AsyncGeneratorState::Completed; |
||||
|
||||
// b. Let result be ThrowCompletion(reason).
|
||||
let result = Err(args.get_or_undefined(0).clone()); |
||||
|
||||
// c. Perform AsyncGeneratorCompleteStep(generator, result, true).
|
||||
let next = gen.queue.pop_front().expect("must have one entry"); |
||||
drop(generator_borrow_mut); |
||||
Self::complete_step(&next, result, true, context); |
||||
|
||||
// d. Perform AsyncGeneratorDrainQueue(generator).
|
||||
Self::drain_queue(generator, context); |
||||
|
||||
// e. Return undefined.
|
||||
Ok(JsValue::undefined()) |
||||
}, |
||||
generator, |
||||
) |
||||
.name("") |
||||
.length(1) |
||||
.build(); |
||||
|
||||
// 11. Perform PerformPromiseThen(promise, onFulfilled, onRejected).
|
||||
let promise_obj = promise |
||||
.as_object() |
||||
.expect("constructed promise must be a promise"); |
||||
promise_obj |
||||
.borrow_mut() |
||||
.as_promise_mut() |
||||
.expect("constructed promise must be a promise") |
||||
.perform_promise_then(&on_fulfilled.into(), &on_rejected.into(), None, context); |
||||
} |
||||
|
||||
/// `AsyncGeneratorDrainQueue ( generator )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratordrainqueue
|
||||
pub(crate) fn drain_queue(generator: &JsObject, context: &mut Context) { |
||||
let mut generator_borrow_mut = generator.borrow_mut(); |
||||
let gen = generator_borrow_mut |
||||
.as_async_generator_mut() |
||||
.expect("already checked before"); |
||||
|
||||
// 1. Assert: generator.[[AsyncGeneratorState]] is completed.
|
||||
assert_eq!(gen.state, AsyncGeneratorState::Completed); |
||||
|
||||
// 2. Let queue be generator.[[AsyncGeneratorQueue]].
|
||||
let queue = &mut gen.queue; |
||||
|
||||
// 3. If queue is empty, return unused.
|
||||
if queue.is_empty() { |
||||
return; |
||||
} |
||||
|
||||
// 4. Let done be false.
|
||||
// 5. Repeat, while done is false,
|
||||
loop { |
||||
// a. Let next be the first element of queue.
|
||||
let next = queue.front().expect("must have entry"); |
||||
|
||||
// b. Let completion be Completion(next.[[Completion]]).
|
||||
match &next.completion { |
||||
// c. If completion.[[Type]] is return, then
|
||||
(completion, true) => { |
||||
// i. Set generator.[[AsyncGeneratorState]] to awaiting-return.
|
||||
gen.state = AsyncGeneratorState::AwaitingReturn; |
||||
|
||||
// ii. Perform ! AsyncGeneratorAwaitReturn(generator).
|
||||
let completion = completion.clone(); |
||||
drop(generator_borrow_mut); |
||||
Self::await_return(generator.clone(), completion, context); |
||||
|
||||
// iii. Set done to true.
|
||||
break; |
||||
} |
||||
// d. Else,
|
||||
(completion, false) => { |
||||
// i. If completion.[[Type]] is normal, then
|
||||
let completion = if completion.is_ok() { |
||||
// 1. Set completion to NormalCompletion(undefined).
|
||||
Ok(JsValue::undefined()) |
||||
} else { |
||||
completion.clone() |
||||
}; |
||||
|
||||
// ii. Perform AsyncGeneratorCompleteStep(generator, completion, true).
|
||||
let next = queue.pop_front().expect("must have entry"); |
||||
Self::complete_step(&next, completion, true, context); |
||||
|
||||
// iii. If queue is empty, set done to true.
|
||||
if queue.is_empty() { |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,129 @@
|
||||
//! This module implements the `AsyncGeneratorFunction` object.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorfunction-objects
|
||||
|
||||
use crate::{ |
||||
builtins::{ |
||||
function::{BuiltInFunctionObject, ConstructorKind, Function}, |
||||
BuiltIn, |
||||
}, |
||||
object::ObjectData, |
||||
property::PropertyDescriptor, |
||||
symbol::WellKnownSymbols, |
||||
value::JsValue, |
||||
Context, JsResult, |
||||
}; |
||||
use boa_profiler::Profiler; |
||||
|
||||
/// The internal representation on a `AsyncGeneratorFunction` object.
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub struct AsyncGeneratorFunction; |
||||
|
||||
impl BuiltIn for AsyncGeneratorFunction { |
||||
const NAME: &'static str = "AsyncGeneratorFunction"; |
||||
|
||||
fn init(context: &mut Context) -> Option<JsValue> { |
||||
let _timer = Profiler::global().start_event(Self::NAME, "init"); |
||||
|
||||
let prototype = &context |
||||
.intrinsics() |
||||
.constructors() |
||||
.async_generator_function() |
||||
.prototype; |
||||
let constructor = &context |
||||
.intrinsics() |
||||
.constructors() |
||||
.async_generator_function() |
||||
.constructor; |
||||
|
||||
constructor.set_prototype(Some( |
||||
context.intrinsics().constructors().function().constructor(), |
||||
)); |
||||
let property = PropertyDescriptor::builder() |
||||
.value(1) |
||||
.writable(false) |
||||
.enumerable(false) |
||||
.configurable(true); |
||||
constructor.borrow_mut().insert("length", property); |
||||
let property = PropertyDescriptor::builder() |
||||
.value(Self::NAME) |
||||
.writable(false) |
||||
.enumerable(false) |
||||
.configurable(true); |
||||
constructor.borrow_mut().insert("name", property); |
||||
let property = PropertyDescriptor::builder() |
||||
.value( |
||||
context |
||||
.intrinsics() |
||||
.constructors() |
||||
.async_generator_function() |
||||
.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: Some(ConstructorKind::Base), |
||||
}); |
||||
|
||||
prototype.set_prototype(Some( |
||||
context.intrinsics().constructors().function().prototype(), |
||||
)); |
||||
let property = PropertyDescriptor::builder() |
||||
.value( |
||||
context |
||||
.intrinsics() |
||||
.constructors() |
||||
.async_generator_function() |
||||
.constructor(), |
||||
) |
||||
.writable(false) |
||||
.enumerable(false) |
||||
.configurable(true); |
||||
prototype.borrow_mut().insert("constructor", property); |
||||
let property = PropertyDescriptor::builder() |
||||
.value( |
||||
context |
||||
.intrinsics() |
||||
.constructors() |
||||
.async_generator() |
||||
.prototype(), |
||||
) |
||||
.writable(false) |
||||
.enumerable(false) |
||||
.configurable(true); |
||||
prototype.borrow_mut().insert("prototype", property); |
||||
let property = PropertyDescriptor::builder() |
||||
.value("AsyncGeneratorFunction") |
||||
.writable(false) |
||||
.enumerable(false) |
||||
.configurable(true); |
||||
prototype |
||||
.borrow_mut() |
||||
.insert(WellKnownSymbols::to_string_tag(), property); |
||||
|
||||
None |
||||
} |
||||
} |
||||
|
||||
impl AsyncGeneratorFunction { |
||||
/// `AsyncGeneratorFunction ( p1, p2, … , pn, body )`
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorfunction
|
||||
pub(crate) fn constructor( |
||||
new_target: &JsValue, |
||||
args: &[JsValue], |
||||
context: &mut Context, |
||||
) -> JsResult<JsValue> { |
||||
BuiltInFunctionObject::create_dynamic_function(new_target, args, true, true, context) |
||||
.map(Into::into) |
||||
} |
||||
} |
@ -0,0 +1,194 @@
|
||||
use crate::{ |
||||
builtins::function::ThisMode, |
||||
bytecompiler::{ByteCompiler, FunctionKind}, |
||||
syntax::ast::node::{Declaration, FormalParameterList, StatementList}, |
||||
vm::{BindingOpcode, CodeBlock, Opcode}, |
||||
Context, JsResult, |
||||
}; |
||||
use boa_gc::Gc; |
||||
use boa_interner::Sym; |
||||
use rustc_hash::FxHashMap; |
||||
|
||||
/// `FunctionCompiler` is used to compile AST functions to bytecode.
|
||||
#[derive(Debug, Clone, Copy)] |
||||
pub(crate) struct FunctionCompiler { |
||||
name: Sym, |
||||
generator: bool, |
||||
r#async: bool, |
||||
strict: bool, |
||||
kind: FunctionKind, |
||||
} |
||||
|
||||
impl FunctionCompiler { |
||||
/// Create a new `FunctionCompiler`.
|
||||
#[inline] |
||||
pub(crate) fn new() -> Self { |
||||
Self { |
||||
name: Sym::EMPTY_STRING, |
||||
generator: false, |
||||
r#async: false, |
||||
strict: false, |
||||
kind: FunctionKind::Declaration, |
||||
} |
||||
} |
||||
|
||||
/// Set the name of the function.
|
||||
#[inline] |
||||
pub(crate) fn name<N>(mut self, name: N) -> Self |
||||
where |
||||
N: Into<Option<Sym>>, |
||||
{ |
||||
let name = name.into(); |
||||
if let Some(name) = name { |
||||
self.name = name; |
||||
} |
||||
self |
||||
} |
||||
|
||||
/// Indicate if the function is a generator function.
|
||||
#[inline] |
||||
pub(crate) fn generator(mut self, generator: bool) -> Self { |
||||
self.generator = generator; |
||||
self |
||||
} |
||||
|
||||
/// Indicate if the function is an async function.
|
||||
#[inline] |
||||
pub(crate) fn r#async(mut self, r#async: bool) -> Self { |
||||
self.r#async = r#async; |
||||
self |
||||
} |
||||
|
||||
/// Indicate if the function is in a strict context.
|
||||
#[inline] |
||||
pub(crate) fn strict(mut self, strict: bool) -> Self { |
||||
self.strict = strict; |
||||
self |
||||
} |
||||
|
||||
/// Indicate if the function is a declaration, expression or arrow function.
|
||||
#[inline] |
||||
pub(crate) fn kind(mut self, kind: FunctionKind) -> Self { |
||||
self.kind = kind; |
||||
self |
||||
} |
||||
|
||||
/// Compile a function statement list and it's parameters into bytecode.
|
||||
pub(crate) fn compile( |
||||
mut self, |
||||
parameters: &FormalParameterList, |
||||
body: &StatementList, |
||||
context: &mut Context, |
||||
) -> JsResult<Gc<CodeBlock>> { |
||||
self.strict = self.strict || body.strict(); |
||||
|
||||
let length = parameters.length(); |
||||
let mut code = CodeBlock::new(self.name, length, self.strict); |
||||
|
||||
if self.kind == FunctionKind::Arrow { |
||||
code.this_mode = ThisMode::Lexical; |
||||
} |
||||
|
||||
let mut compiler = ByteCompiler { |
||||
code_block: code, |
||||
literals_map: FxHashMap::default(), |
||||
names_map: FxHashMap::default(), |
||||
bindings_map: FxHashMap::default(), |
||||
jump_info: Vec::new(), |
||||
in_async_generator: self.generator && self.r#async, |
||||
context, |
||||
}; |
||||
|
||||
compiler.context.push_compile_time_environment(true); |
||||
|
||||
// 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 !(self.kind == FunctionKind::Arrow) && !parameters.has_arguments() { |
||||
compiler |
||||
.context |
||||
.create_mutable_binding(Sym::ARGUMENTS, false); |
||||
compiler.code_block.arguments_binding = Some( |
||||
compiler |
||||
.context |
||||
.initialize_mutable_binding(Sym::ARGUMENTS, false), |
||||
); |
||||
} |
||||
|
||||
for parameter in parameters.parameters.iter() { |
||||
if parameter.is_rest_param() { |
||||
compiler.emit_opcode(Opcode::RestParameterInit); |
||||
} |
||||
|
||||
match parameter.declaration() { |
||||
Declaration::Identifier { ident, .. } => { |
||||
compiler.context.create_mutable_binding(ident.sym(), false); |
||||
if let Some(init) = parameter.declaration().init() { |
||||
let skip = compiler.emit_opcode_with_operand(Opcode::JumpIfNotUndefined); |
||||
compiler.compile_expr(init, true)?; |
||||
compiler.patch_jump(skip); |
||||
} |
||||
compiler.emit_binding(BindingOpcode::InitArg, ident.sym()); |
||||
} |
||||
Declaration::Pattern(pattern) => { |
||||
for ident in pattern.idents() { |
||||
compiler.context.create_mutable_binding(ident, false); |
||||
} |
||||
compiler.compile_declaration_pattern(pattern, BindingOpcode::InitArg)?; |
||||
} |
||||
} |
||||
} |
||||
|
||||
if !parameters.has_rest_parameter() { |
||||
compiler.emit_opcode(Opcode::RestParameterPop); |
||||
} |
||||
|
||||
let env_label = if parameters.has_expressions() { |
||||
compiler.code_block.num_bindings = compiler.context.get_binding_number(); |
||||
compiler.context.push_compile_time_environment(true); |
||||
compiler.code_block.function_environment_push_location = |
||||
compiler.next_opcode_location(); |
||||
Some(compiler.emit_opcode_with_two_operands(Opcode::PushFunctionEnvironment)) |
||||
} else { |
||||
None |
||||
}; |
||||
|
||||
// When a generator object is created from a generator function, the generator executes until here to init parameters.
|
||||
if self.generator { |
||||
compiler.emit_opcode(Opcode::PushUndefined); |
||||
compiler.emit_opcode(Opcode::Yield); |
||||
} |
||||
|
||||
compiler.create_declarations(body.items())?; |
||||
compiler.compile_statement_list(body.items(), false)?; |
||||
|
||||
if let Some(env_label) = env_label { |
||||
let (num_bindings, compile_environment) = |
||||
compiler.context.pop_compile_time_environment(); |
||||
let index_compile_environment = compiler.push_compile_environment(compile_environment); |
||||
compiler.patch_jump_with_target(env_label.0, num_bindings as u32); |
||||
compiler.patch_jump_with_target(env_label.1, index_compile_environment as u32); |
||||
|
||||
let (_, compile_environment) = compiler.context.pop_compile_time_environment(); |
||||
compiler.push_compile_environment(compile_environment); |
||||
} else { |
||||
let (num_bindings, compile_environment) = |
||||
compiler.context.pop_compile_time_environment(); |
||||
compiler |
||||
.code_block |
||||
.compile_environments |
||||
.push(compile_environment); |
||||
compiler.code_block.num_bindings = num_bindings; |
||||
} |
||||
|
||||
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, &[]); |
||||
compiler.emit(Opcode::Return, &[]); |
||||
|
||||
Ok(Gc::new(compiler.finish())) |
||||
} |
||||
} |
Loading…
Reference in new issue