Browse Source

Make iterator spec complaint (#1889)

This PR is also related to #577 

Changes:
- Implements `IteratorValue` (`IteratorResult::value()`)
- Implements `IteratorComplete` (`IteratorResult::complete()`)
- Implements `IteratorStep` (`IteratorRecord::step()`)
- Makes  `IteratorNext` (`IteratorRecord::next()`) spec compliant
- Deprecates/removes `JsValue::get_field()`.
pull/1888/head
Halid Odat 3 years ago
parent
commit
a9a82a7381
  1. 124
      boa_engine/src/builtins/iterable/mod.rs
  2. 17
      boa_engine/src/builtins/map/mod.rs
  3. 40
      boa_engine/src/builtins/set/mod.rs
  4. 17
      boa_engine/src/value/mod.rs
  5. 66
      boa_engine/src/vm/mod.rs

124
boa_engine/src/builtins/iterable/mod.rs

@ -23,6 +23,8 @@ pub struct IteratorPrototypes {
impl IteratorPrototypes { impl IteratorPrototypes {
pub(crate) fn init(context: &mut Context) -> Self { pub(crate) fn init(context: &mut Context) -> Self {
let _timer = Profiler::global().start_event("IteratorPrototypes::init", "init");
let iterator_prototype = create_iterator_prototype(context); let iterator_prototype = create_iterator_prototype(context);
Self { Self {
array_iterator: ArrayIterator::create_prototype(iterator_prototype.clone(), context), array_iterator: ArrayIterator::create_prototype(iterator_prototype.clone(), context),
@ -77,7 +79,10 @@ impl IteratorPrototypes {
/// `CreateIterResultObject( value, done )` /// `CreateIterResultObject( value, done )`
/// ///
/// Generates an object supporting the `IteratorResult` interface. /// Generates an object supporting the `IteratorResult` interface.
#[inline]
pub fn create_iter_result_object(value: JsValue, done: bool, context: &mut Context) -> JsValue { pub fn create_iter_result_object(value: JsValue, done: bool, context: &mut Context) -> JsValue {
let _timer = Profiler::global().start_event("create_iter_result_object", "init");
// 1. Assert: Type(done) is Boolean. // 1. Assert: Type(done) is Boolean.
// 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%). // 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%).
let obj = context.construct_object(); let obj = context.construct_object();
@ -106,6 +111,7 @@ impl JsValue {
/// - [ECMA reference][spec] /// - [ECMA reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-getiterator /// [spec]: https://tc39.es/ecma262/#sec-getiterator
#[inline]
pub fn get_iterator( pub fn get_iterator(
&self, &self,
context: &mut Context, context: &mut Context,
@ -168,6 +174,7 @@ impl JsValue {
/// - [ECMA reference][spec] /// - [ECMA reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-%iteratorprototype%-object /// [spec]: https://tc39.es/ecma262/#sec-%iteratorprototype%-object
#[inline]
fn create_iterator_prototype(context: &mut Context) -> JsObject { fn create_iterator_prototype(context: &mut Context) -> JsObject {
let _timer = Profiler::global().start_event("Iterator Prototype", "init"); let _timer = Profiler::global().start_event("Iterator Prototype", "init");
@ -182,13 +189,58 @@ fn create_iterator_prototype(context: &mut Context) -> JsObject {
iterator_prototype iterator_prototype
} }
#[derive(Debug)]
pub struct IteratorResult {
object: JsObject,
}
impl IteratorResult {
/// Get `done` property of iterator result object.
///
/// More information:
/// - [ECMA reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-iteratorclose
#[inline]
pub fn complete(&self, context: &mut Context) -> JsResult<bool> {
// 1. Return ToBoolean(? Get(iterResult, "done")).
Ok(self.object.get("done", context)?.to_boolean())
}
/// Get `value` property of iterator result object.
///
/// More information:
/// - [ECMA reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-iteratorvalue
#[inline]
pub fn value(&self, context: &mut Context) -> JsResult<JsValue> {
// 1. Return ? Get(iterResult, "value").
self.object.get("value", context)
}
}
/// An Iterator Record is a Record value used to encapsulate an
/// `Iterator` or `AsyncIterator` along with the next method.
///
/// More information:
/// - [ECMA reference][spec]
///
/// [spec]:https://tc39.es/ecma262/#table-iterator-record-fields
#[derive(Debug)] #[derive(Debug)]
pub struct IteratorRecord { pub struct IteratorRecord {
/// `[[Iterator]]`
///
/// An object that conforms to the Iterator or AsyncIterator interface.
iterator_object: JsValue, iterator_object: JsValue,
/// `[[NextMethod]]`
///
/// The next method of the `[[Iterator]]` object.
next_function: JsValue, next_function: JsValue,
} }
impl IteratorRecord { impl IteratorRecord {
#[inline]
pub fn new(iterator_object: JsValue, next_function: JsValue) -> Self { pub fn new(iterator_object: JsValue, next_function: JsValue) -> Self {
Self { Self {
iterator_object, iterator_object,
@ -196,10 +248,12 @@ impl IteratorRecord {
} }
} }
#[inline]
pub(crate) fn iterator_object(&self) -> &JsValue { pub(crate) fn iterator_object(&self) -> &JsValue {
&self.iterator_object &self.iterator_object
} }
#[inline]
pub(crate) fn next_function(&self) -> &JsValue { pub(crate) fn next_function(&self) -> &JsValue {
&self.next_function &self.next_function
} }
@ -210,12 +264,50 @@ impl IteratorRecord {
/// - [ECMA reference][spec] /// - [ECMA reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-iteratornext /// [spec]: https://tc39.es/ecma262/#sec-iteratornext
pub(crate) fn next(&self, context: &mut Context) -> JsResult<IteratorResult> { #[inline]
let next = context.call(&self.next_function, &self.iterator_object, &[])?; pub(crate) fn next(
let done = next.get_field("done", context)?.to_boolean(); &self,
value: Option<JsValue>,
context: &mut Context,
) -> JsResult<IteratorResult> {
let _timer = Profiler::global().start_event("IteratorRecord::next", "iterator");
let value = next.get_field("value", context)?; // 1. If value is not present, then
Ok(IteratorResult { value, done }) // a. Let result be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]]).
// 2. Else,
// a. Let result be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]], « value »).
let result = if let Some(value) = value {
context.call(&self.next_function, &self.iterator_object, &[value])?
} else {
context.call(&self.next_function, &self.iterator_object, &[])?
};
// 3. If Type(result) is not Object, throw a TypeError exception.
// 4. Return result.
if let Some(o) = result.as_object() {
Ok(IteratorResult { object: o.clone() })
} else {
context.throw_type_error("next value should be an object")
}
}
#[inline]
pub(crate) fn step(&self, context: &mut Context) -> JsResult<Option<IteratorResult>> {
let _timer = Profiler::global().start_event("IteratorRecord::step", "iterator");
// 1. Let result be ? IteratorNext(iteratorRecord).
let result = self.next(None, context)?;
// 2. Let done be ? IteratorComplete(result).
let done = result.complete(context)?;
// 3. If done is true, return false.
if done {
return Ok(None);
}
// 4. Return result.
Ok(Some(result))
} }
/// Cleanup the iterator /// Cleanup the iterator
@ -224,16 +316,18 @@ impl IteratorRecord {
/// - [ECMA reference][spec] /// - [ECMA reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-iteratorclose /// [spec]: https://tc39.es/ecma262/#sec-iteratorclose
#[inline]
pub(crate) fn close( pub(crate) fn close(
&self, &self,
completion: JsResult<JsValue>, completion: JsResult<JsValue>,
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let _timer = Profiler::global().start_event("IteratorRecord::close", "iterator");
// 1. Assert: Type(iteratorRecord.[[Iterator]]) is Object. // 1. Assert: Type(iteratorRecord.[[Iterator]]) is Object.
// 2. Let iterator be iteratorRecord.[[Iterator]]. // 2. Let iterator be iteratorRecord.[[Iterator]].
// 3. Let innerResult be GetMethod(iterator, "return"). // 3. Let innerResult be GetMethod(iterator, "return").
let inner_result = self.iterator_object.get_method("return", context); let inner_result = self.iterator_object.get_method("return", context);
//let mut inner_result = self.iterator_object.get_field("return", context);
// 4. If innerResult.[[Type]] is normal, then // 4. If innerResult.[[Type]] is normal, then
if let Ok(inner_value) = inner_result { if let Ok(inner_value) = inner_result {
@ -281,6 +375,8 @@ pub(crate) fn iterable_to_list(
items: &JsValue, items: &JsValue,
method: Option<JsValue>, method: Option<JsValue>,
) -> JsResult<Vec<JsValue>> { ) -> JsResult<Vec<JsValue>> {
let _timer = Profiler::global().start_event("iterable_to_list", "iterator");
// 1. If method is present, then // 1. If method is present, then
let iterator_record = if let Some(method) = method { let iterator_record = if let Some(method) = method {
// a. Let iteratorRecord be ? GetIterator(items, sync, method). // a. Let iteratorRecord be ? GetIterator(items, sync, method).
@ -300,21 +396,11 @@ pub(crate) fn iterable_to_list(
// b. If next is not false, then // b. If next is not false, then
// i. Let nextValue be ? IteratorValue(next). // i. Let nextValue be ? IteratorValue(next).
// ii. Append nextValue to the end of the List values. // ii. Append nextValue to the end of the List values.
loop { while let Some(next) = iterator_record.step(context)? {
let next = iterator_record.next(context)?; let next_value = next.value(context)?;
if next.done { values.push(next_value);
break;
}
values.push(next.value);
} }
// 6. Return values. // 6. Return values.
Ok(values) Ok(values)
} }
#[derive(Debug)]
pub struct IteratorResult {
pub value: JsValue,
pub done: bool,
}

17
boa_engine/src/builtins/map/mod.rs

@ -15,7 +15,7 @@
use self::{map_iterator::MapIterator, ordered_map::OrderedMap}; use self::{map_iterator::MapIterator, ordered_map::OrderedMap};
use super::JsArgs; use super::JsArgs;
use crate::{ use crate::{
builtins::{iterable::IteratorResult, BuiltIn}, builtins::BuiltIn,
context::StandardObjects, context::StandardObjects,
object::{ object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder,
@ -547,19 +547,20 @@ pub(crate) fn add_entries_from_iterable(
// 3. Repeat, // 3. Repeat,
loop { loop {
// a. Let next be ? IteratorStep(iteratorRecord). // a. Let next be ? IteratorStep(iteratorRecord).
// c. Let nextItem be ? IteratorValue(next). let next = iterator_record.step(context)?;
let IteratorResult { value, done } = iterator_record.next(context)?;
// b. If next is false, return target. // b. If next is false, return target.
if done { // c. Let nextItem be ? IteratorValue(next).
let next_item = if let Some(next) = next {
next.value(context)?
} else {
return Ok(target.clone().into()); return Ok(target.clone().into());
} };
let next_item = if let Some(obj) = value.as_object() { let next_item = if let Some(obj) = next_item.as_object() {
obj obj
}
// d. If Type(nextItem) is not Object, then // d. If Type(nextItem) is not Object, then
else { } else {
// i. Let error be ThrowCompletion(a newly created TypeError object). // i. Let error be ThrowCompletion(a newly created TypeError object).
let err = context let err = context
.throw_type_error("cannot get key and value from primitive item of `iterable`"); .throw_type_error("cannot get key and value from primitive item of `iterable`");

40
boa_engine/src/builtins/set/mod.rs

@ -118,53 +118,53 @@ impl Set {
args: &[JsValue], args: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
// 1 // 1. If NewTarget is undefined, throw a TypeError exception.
if new_target.is_undefined() { if new_target.is_undefined() {
return context return context
.throw_type_error("calling a builtin Set constructor without new is forbidden"); .throw_type_error("calling a builtin Set constructor without new is forbidden");
} }
// 2 // 2. Let set be ? OrdinaryCreateFromConstructor(NewTarget, "%Set.prototype%", « [[SetData]] »).
// 3. Set set.[[SetData]] to a new empty List.
let prototype = let prototype =
get_prototype_from_constructor(new_target, StandardObjects::set_object, context)?; get_prototype_from_constructor(new_target, StandardObjects::set_object, context)?;
let set = JsObject::from_proto_and_data(prototype, ObjectData::set(OrderedSet::default()));
let obj = JsObject::from_proto_and_data(prototype, ObjectData::set(OrderedSet::default())); // 4. If iterable is either undefined or null, return set.
let iterable = args.get_or_undefined(0); let iterable = args.get_or_undefined(0);
// 4
if iterable.is_null_or_undefined() { if iterable.is_null_or_undefined() {
return Ok(obj.into()); return Ok(set.into());
} }
// 5 // 5. Let adder be ? Get(set, "add").
let adder = obj.get("add", context)?; let adder = set.get("add", context)?;
// 6 // 6. If IsCallable(adder) is false, throw a TypeError exception.
let adder = adder.as_callable().ok_or_else(|| { let adder = adder.as_callable().ok_or_else(|| {
context.construct_type_error("'add' of 'newTarget' is not a function") context.construct_type_error("'add' of 'newTarget' is not a function")
})?; })?;
// 7 // 7. Let iteratorRecord be ? GetIterator(iterable).
let iterator_record = iterable.clone().get_iterator(context, None, None)?; let iterator_record = iterable.clone().get_iterator(context, None, None)?;
// 8.a // 8. Repeat,
let mut next = iterator_record.next(context)?; // a. Let next be ? IteratorStep(iteratorRecord).
// b. If next is false, return set.
// 8 // c. Let nextValue be ? IteratorValue(next).
while !next.done { // d. Let status be Completion(Call(adder, set, « nextValue »)).
// e. IfAbruptCloseIterator(status, iteratorRecord).
while let Some(next) = iterator_record.step(context)? {
// c // c
let next_value = next.value; let next_value = next.value(context)?;
// d, e // d, e
if let Err(status) = adder.call(&obj.clone().into(), &[next_value], context) { if let Err(status) = adder.call(&set.clone().into(), &[next_value], context) {
return iterator_record.close(Err(status), context); return iterator_record.close(Err(status), context);
} }
next = iterator_record.next(context)?;
} }
// 8.b // 8.b
Ok(obj.into()) Ok(set.into())
} }
/// `get Set [ @@species ]` /// `get Set [ @@species ]`

17
boa_engine/src/value/mod.rs

@ -316,23 +316,6 @@ impl JsValue {
} }
} }
/**
Resolve the property in the object and get its value, or undefined if this is not an object or the field doesn't exist
`get_field` receives a Property from get_prop(). It should then return the `[[Get]]` result value if that's set, otherwise fall back to `[[Value]]`
*/
pub(crate) fn get_field<K>(&self, key: K, context: &mut Context) -> JsResult<Self>
where
K: Into<PropertyKey>,
{
let _timer = Profiler::global().start_event("Value::get_field", "value");
if let Self::Object(ref obj) = *self {
obj.clone()
.__get__(&key.into(), obj.clone().into(), context)
} else {
Ok(Self::undefined())
}
}
/// Set the kind of an object. /// Set the kind of an object.
#[inline] #[inline]
pub fn set_data(&self, data: ObjectData) { pub fn set_data(&self, data: ObjectData) {

66
boa_engine/src/vm/mod.rs

@ -220,13 +220,8 @@ impl Context {
let array = self.vm.pop(); let array = self.vm.pop();
let iterator = IteratorRecord::new(iterator, next_function); let iterator = IteratorRecord::new(iterator, next_function);
loop { while let Some(next) = iterator.step(self)? {
let next = iterator.next(self)?; Array::push(&array, &[next.value(self)?], self)?;
if next.done {
break;
}
Array::push(&array, &[next.value], self)?;
} }
self.vm.push(array); self.vm.push(array);
@ -980,12 +975,8 @@ impl Context {
let iterator_record = rest_argument.get_iterator(self, None, None)?; let iterator_record = rest_argument.get_iterator(self, None, None)?;
let mut rest_arguments = Vec::new(); let mut rest_arguments = Vec::new();
loop { while let Some(next) = iterator_record.step(self)? {
let next = iterator_record.next(self)?; rest_arguments.push(next.value(self)?);
if next.done {
break;
}
rest_arguments.push(next.value);
} }
arguments.append(&mut rest_arguments); arguments.append(&mut rest_arguments);
@ -1036,12 +1027,8 @@ impl Context {
let iterator_record = rest_argument.get_iterator(self, None, None)?; let iterator_record = rest_argument.get_iterator(self, None, None)?;
let mut rest_arguments = Vec::new(); let mut rest_arguments = Vec::new();
loop { while let Some(next) = iterator_record.step(self)? {
let next = iterator_record.next(self)?; rest_arguments.push(next.value(self)?);
if next.done {
break;
}
rest_arguments.push(next.value);
} }
arguments.append(&mut rest_arguments); arguments.append(&mut rest_arguments);
@ -1171,23 +1158,34 @@ impl Context {
let iterator = self.vm.pop(); let iterator = self.vm.pop();
let iterator_record = IteratorRecord::new(iterator.clone(), next_function.clone()); let iterator_record = IteratorRecord::new(iterator.clone(), next_function.clone());
let iterator_result = iterator_record.next(self)?; let next = iterator_record.step(self)?;
self.vm.push(iterator); self.vm.push(iterator);
self.vm.push(next_function); self.vm.push(next_function);
self.vm.push(iterator_result.value); if let Some(next) = next {
let value = next.value(self)?;
self.vm.push(value);
} else {
self.vm.push(JsValue::undefined());
}
} }
Opcode::IteratorNextFull => { Opcode::IteratorNextFull => {
let next_function = self.vm.pop(); let next_function = self.vm.pop();
let iterator = self.vm.pop(); let iterator = self.vm.pop();
let iterator_record = IteratorRecord::new(iterator.clone(), next_function.clone()); let iterator_record = IteratorRecord::new(iterator.clone(), next_function.clone());
let iterator_result = iterator_record.next(self)?; let next = iterator_record.step(self)?;
self.vm.push(iterator); self.vm.push(iterator);
self.vm.push(next_function); self.vm.push(next_function);
self.vm.push(iterator_result.done); if let Some(next) = next {
self.vm.push(iterator_result.value); let value = next.value(self)?;
self.vm.push(false);
self.vm.push(value);
} else {
self.vm.push(true);
self.vm.push(JsValue::undefined());
}
} }
Opcode::IteratorClose => { Opcode::IteratorClose => {
let done = self.vm.pop(); let done = self.vm.pop();
@ -1205,12 +1203,8 @@ impl Context {
let iterator_record = IteratorRecord::new(iterator.clone(), next_function.clone()); let iterator_record = IteratorRecord::new(iterator.clone(), next_function.clone());
let mut values = Vec::new(); let mut values = Vec::new();
loop { while let Some(result) = iterator_record.step(self)? {
let next = iterator_record.next(self)?; values.push(result.value(self)?);
if next.done {
break;
}
values.push(next.value);
} }
let array = Array::create_array_from_list(values, self); let array = Array::create_array_from_list(values, self);
@ -1226,18 +1220,18 @@ impl Context {
let iterator = self.vm.pop(); let iterator = self.vm.pop();
let iterator_record = IteratorRecord::new(iterator.clone(), next_function.clone()); let iterator_record = IteratorRecord::new(iterator.clone(), next_function.clone());
let iterator_result = iterator_record.next(self)?; if let Some(next) = iterator_record.step(self)? {
if iterator_result.done { self.vm.push(iterator);
self.vm.push(next_function);
let value = next.value(self)?;
self.vm.push(value);
} else {
self.vm.frame_mut().pc = address as usize; self.vm.frame_mut().pc = address as usize;
self.vm.frame_mut().loop_env_stack_dec(); self.vm.frame_mut().loop_env_stack_dec();
self.vm.frame_mut().try_env_stack_dec(); self.vm.frame_mut().try_env_stack_dec();
self.realm.environments.pop(); self.realm.environments.pop();
self.vm.push(iterator); self.vm.push(iterator);
self.vm.push(next_function); self.vm.push(next_function);
} else {
self.vm.push(iterator);
self.vm.push(next_function);
self.vm.push(iterator_result.value);
} }
} }
Opcode::ConcatToString => { Opcode::ConcatToString => {

Loading…
Cancel
Save