Skip to content

Commit

Permalink
Implement for..in (#976)
Browse files Browse the repository at this point in the history
* Implement for..in

* Fix code styling issue

* Add break and continue with label

* Add break and continue for while and do-while

* Add unit tests

* Fix formatting

* Split node ForInOfLoop into ForInLoop and ForOfLoop

* Fix issues in ForInOfLoop node splitting

* Merge with master branch

Co-authored-by: tofpie <tofpie@users.noreply.github.com>
  • Loading branch information
tofpie and tofpie authored Jan 1, 2021
1 parent a77c879 commit 57d5679
Show file tree
Hide file tree
Showing 16 changed files with 678 additions and 82 deletions.
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

0 comments on commit 57d5679

Please sign in to comment.