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

Implement for..in #976

Merged
merged 14 commits into from
Jan 1, 2021
15 changes: 13 additions & 2 deletions boa/src/builtins/iterable/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::{
builtins::string::string_iterator::StringIterator,
builtins::ArrayIterator,
builtins::ForInIterator,
builtins::MapIterator,
object::{GcObject, ObjectInitializer},
property::{Attribute, DataDescriptor},
Expand All @@ -13,6 +14,7 @@ pub struct IteratorPrototypes {
array_iterator: GcObject,
string_iterator: GcObject,
map_iterator: GcObject,
for_in_iterator: GcObject,
}

impl IteratorPrototypes {
Expand All @@ -28,9 +30,12 @@ impl IteratorPrototypes {
string_iterator: StringIterator::create_prototype(context, iterator_prototype.clone())
.as_object()
.expect("String Iterator Prototype is not an object"),
map_iterator: MapIterator::create_prototype(context, iterator_prototype)
map_iterator: MapIterator::create_prototype(context, iterator_prototype.clone())
.as_object()
.expect("Map Iterator Prototype is not an object"),
for_in_iterator: ForInIterator::create_prototype(context, iterator_prototype)
.as_object()
.expect("For In Iterator Prototype is not an object"),
}
}

Expand All @@ -49,9 +54,15 @@ impl IteratorPrototypes {
self.string_iterator.clone()
}

#[inline]
pub fn map_iterator(&self) -> GcObject {
self.map_iterator.clone()
}

#[inline]
pub fn for_in_iterator(&self) -> GcObject {
self.for_in_iterator.clone()
}
}

/// CreateIterResultObject( value, done )
Expand Down Expand Up @@ -110,7 +121,7 @@ pub struct IteratorRecord {
}

impl IteratorRecord {
fn new(iterator_object: Value, next_function: Value) -> Self {
pub fn new(iterator_object: Value, next_function: Value) -> Self {
Self {
iterator_object,
next_function,
Expand Down
1 change: 1 addition & 0 deletions boa/src/builtins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pub(crate) use self::{
math::Math,
nan::NaN,
number::Number,
object::for_in_iterator::ForInIterator,
object::Object as BuiltInObjectObject,
regexp::RegExp,
string::String,
Expand Down
145 changes: 145 additions & 0 deletions boa/src/builtins/object/for_in_iterator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
use crate::property::PropertyKey;
use crate::value::RcString;
use crate::{
builtins::{function::make_builtin_fn, iterable::create_iter_result_object},
gc::{Finalize, Trace},
object::ObjectData,
property::{Attribute, DataDescriptor},
BoaProfiler, Context, Result, Value,
};
use rustc_hash::FxHashSet;
use std::collections::VecDeque;

/// The ForInIterator object represents an iteration over some specific object.
/// It implements the iterator protocol.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-for-in-iterator-objects
#[derive(Debug, Clone, Finalize, Trace)]
pub struct ForInIterator {
object: Value,
visited_keys: FxHashSet<RcString>,
remaining_keys: VecDeque<RcString>,
object_was_visited: bool,
}

impl ForInIterator {
pub(crate) const NAME: &'static str = "ForInIterator";

fn new(object: Value) -> Self {
ForInIterator {
object,
visited_keys: FxHashSet::default(),
remaining_keys: VecDeque::default(),
object_was_visited: false,
}
}

/// CreateForInIterator( object )
///
/// Creates a new iterator over the given object.
///
/// More information:
/// - [ECMA reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-createforiniterator
pub(crate) fn create_for_in_iterator(context: &Context, object: Value) -> Result<Value> {
let for_in_iterator = Value::new_object(context);
for_in_iterator.set_data(ObjectData::ForInIterator(Self::new(object)));
for_in_iterator
.as_object()
.expect("for in iterator object")
.set_prototype_instance(context.iterator_prototypes().for_in_iterator().into());
Ok(for_in_iterator)
}

/// %ForInIteratorPrototype%.next( )
///
/// Gets the next result in the object.
///
/// More information:
/// - [ECMA reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%foriniteratorprototype%.next
pub(crate) fn next(this: &Value, _: &[Value], context: &mut Context) -> Result<Value> {
if let Value::Object(ref o) = this {
let mut for_in_iterator = o.borrow_mut();
if let Some(iterator) = for_in_iterator.as_for_in_iterator_mut() {
let mut object = iterator.object.to_object(context)?;
loop {
if !iterator.object_was_visited {
let keys = object.own_property_keys();
for k in keys {
match k {
PropertyKey::String(ref k) => {
iterator.remaining_keys.push_back(k.clone());
}
PropertyKey::Index(i) => {
iterator.remaining_keys.push_back(i.to_string().into());
}
_ => {}
}
}
iterator.object_was_visited = true;
}
while let Some(r) = iterator.remaining_keys.pop_front() {
if !iterator.visited_keys.contains(&r) {
if let Some(desc) =
object.get_own_property(&PropertyKey::from(r.clone()))
{
iterator.visited_keys.insert(r.clone());
if desc.enumerable() {
return Ok(create_iter_result_object(
context,
Value::from(r.to_string()),
false,
));
}
}
}
}
match object.prototype_instance().to_object(context) {
Ok(o) => {
object = o;
}
_ => {
return Ok(create_iter_result_object(context, Value::undefined(), true))
}
}
iterator.object = Value::from(object.clone());
iterator.object_was_visited = false;
}
} else {
context.throw_type_error("`this` is not a ForInIterator")
}
} else {
context.throw_type_error("`this` is not an ForInIterator")
}
}

/// Create the %ArrayIteratorPrototype% object
///
/// More information:
/// - [ECMA reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-%foriniteratorprototype%-object
pub(crate) fn create_prototype(context: &mut Context, iterator_prototype: Value) -> Value {
let _timer = BoaProfiler::global().start_event(Self::NAME, "init");

// Create prototype
let for_in_iterator = Value::new_object(context);
make_builtin_fn(Self::next, "next", &for_in_iterator, 0, context);
for_in_iterator
.as_object()
.expect("for in iterator prototype object")
.set_prototype_instance(iterator_prototype);

let to_string_tag = context.well_known_symbols().to_string_tag_symbol();
let to_string_tag_property =
DataDescriptor::new("For In Iterator", Attribute::CONFIGURABLE);
for_in_iterator.set_property(to_string_tag, to_string_tag_property);
for_in_iterator
}
}
1 change: 1 addition & 0 deletions boa/src/builtins/object/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use crate::{
BoaProfiler, Context, Result,
};

pub mod for_in_iterator;
#[cfg(test)]
mod tests;

Expand Down
19 changes: 19 additions & 0 deletions boa/src/object/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ mod gcobject;
mod internal_methods;
mod iter;

use crate::builtins::object::for_in_iterator::ForInIterator;
pub use gcobject::{GcObject, RecursionLimiter, Ref, RefMut};
pub use iter::*;

Expand Down Expand Up @@ -84,6 +85,7 @@ pub enum ObjectData {
RegExp(Box<RegExp>),
BigInt(RcBigInt),
Boolean(bool),
ForInIterator(ForInIterator),
Function(Function),
String(RcString),
StringIterator(StringIterator),
Expand All @@ -104,6 +106,7 @@ impl Display for ObjectData {
match self {
Self::Array => "Array",
Self::ArrayIterator(_) => "ArrayIterator",
Self::ForInIterator(_) => "ForInIterator",
Self::Function(_) => "Function",
Self::RegExp(_) => "RegExp",
Self::Map(_) => "Map",
Expand Down Expand Up @@ -311,6 +314,22 @@ impl Object {
}
}

#[inline]
pub fn as_for_in_iterator(&self) -> Option<&ForInIterator> {
match &self.data {
ObjectData::ForInIterator(iter) => Some(iter),
_ => None,
}
}

#[inline]
pub fn as_for_in_iterator_mut(&mut self) -> Option<&mut ForInIterator> {
match &mut self.data {
ObjectData::ForInIterator(iter) => Some(iter),
_ => None,
}
}

/// Checks if it is a `Map` object.pub
#[inline]
pub fn is_map(&self) -> bool {
Expand Down
53 changes: 13 additions & 40 deletions boa/src/syntax/ast/node/iteration/do_while_loop/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ impl DoWhileLoop {
self.label.as_ref().map(Box::as_ref)
}

pub fn set_label(&mut self, label: Box<str>) {
self.label = Some(label);
}

/// Creates a `DoWhileLoop` AST node.
pub fn new<B, C>(body: B, condition: C) -> Self
where
Expand All @@ -68,50 +72,16 @@ impl DoWhileLoop {

impl Executable for DoWhileLoop {
fn run(&self, context: &mut Context) -> Result<Value> {
let mut result = self.body().run(context)?;
match context.executor().get_current_state() {
InterpreterState::Break(_label) => {
// TODO break to label.

// Loops 'consume' breaks.
context
.executor()
.set_current_state(InterpreterState::Executing);
return Ok(result);
}
InterpreterState::Continue(_label) => {
// TODO continue to label;
context
.executor()
.set_current_state(InterpreterState::Executing);
// after breaking out of the block, continue execution of the loop
}
InterpreterState::Return => {
return Ok(result);
}
InterpreterState::Executing => {
// Continue execution.
}
}

while self.cond().run(context)?.to_boolean() {
let mut result;
loop {
result = self.body().run(context)?;
match context.executor().get_current_state() {
InterpreterState::Break(_label) => {
// TODO break to label.

// Loops 'consume' breaks.
context
.executor()
.set_current_state(InterpreterState::Executing);
InterpreterState::Break(label) => {
handle_state_with_labels!(self, label, context, break);
break;
}
InterpreterState::Continue(_label) => {
// TODO continue to label.
context
.executor()
.set_current_state(InterpreterState::Executing);
// after breaking out of the block, continue execution of the loop
InterpreterState::Continue(label) => {
handle_state_with_labels!(self, label, context, continue);
}
InterpreterState::Return => {
return Ok(result);
Expand All @@ -120,6 +90,9 @@ impl Executable for DoWhileLoop {
// Continue execution.
}
}
if !self.cond().run(context)?.to_boolean() {
break;
}
}
Ok(result)
}
Expand Down
Loading