Rust编写的JavaScript引擎,该项目是一个试验性质的项目。
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

415 lines
16 KiB

//! 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)
}
}
}
}