mirror of https://github.com/boa-dev/boa.git
Browse Source
* 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>pull/1023/head
tofpie
4 years ago
committed by
GitHub
16 changed files with 678 additions and 82 deletions
@ -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 |
||||
} |
||||
} |
@ -0,0 +1,243 @@
|
||||
use crate::{ |
||||
builtins::{iterable::IteratorRecord, ForInIterator}, |
||||
environment::lexical_environment::{new_declarative_environment, VariableScope}, |
||||
exec::{Executable, InterpreterState}, |
||||
gc::{Finalize, Trace}, |
||||
syntax::ast::node::Node, |
||||
BoaProfiler, Context, Result, Value, |
||||
}; |
||||
use std::fmt; |
||||
|
||||
#[cfg(feature = "deser")] |
||||
use serde::{Deserialize, Serialize}; |
||||
|
||||
#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] |
||||
#[derive(Clone, Debug, Trace, Finalize, PartialEq)] |
||||
pub struct ForInLoop { |
||||
variable: Box<Node>, |
||||
expr: Box<Node>, |
||||
body: Box<Node>, |
||||
label: Option<Box<str>>, |
||||
} |
||||
|
||||
impl ForInLoop { |
||||
pub fn new<V, I, B>(variable: V, expr: I, body: B) -> Self |
||||
where |
||||
V: Into<Node>, |
||||
I: Into<Node>, |
||||
B: Into<Node>, |
||||
{ |
||||
Self { |
||||
variable: Box::new(variable.into()), |
||||
expr: Box::new(expr.into()), |
||||
body: Box::new(body.into()), |
||||
label: None, |
||||
} |
||||
} |
||||
|
||||
pub fn variable(&self) -> &Node { |
||||
&self.variable |
||||
} |
||||
|
||||
pub fn expr(&self) -> &Node { |
||||
&self.expr |
||||
} |
||||
|
||||
pub fn body(&self) -> &Node { |
||||
&self.body |
||||
} |
||||
|
||||
pub fn label(&self) -> Option<&str> { |
||||
self.label.as_ref().map(Box::as_ref) |
||||
} |
||||
|
||||
pub fn set_label(&mut self, label: Box<str>) { |
||||
self.label = Some(label); |
||||
} |
||||
|
||||
pub fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { |
||||
write!(f, "for ({} in {}) {{", self.variable, self.expr,)?; |
||||
self.body().display(f, indentation + 1)?; |
||||
f.write_str("}") |
||||
} |
||||
} |
||||
|
||||
impl fmt::Display for ForInLoop { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
self.display(f, 0) |
||||
} |
||||
} |
||||
|
||||
impl From<ForInLoop> for Node { |
||||
fn from(for_in: ForInLoop) -> Node { |
||||
Self::ForInLoop(for_in) |
||||
} |
||||
} |
||||
|
||||
impl Executable for ForInLoop { |
||||
fn run(&self, context: &mut Context) -> Result<Value> { |
||||
let _timer = BoaProfiler::global().start_event("ForIn", "exec"); |
||||
let object = self.expr().run(context)?; |
||||
let mut result = Value::undefined(); |
||||
|
||||
if object.is_null_or_undefined() { |
||||
return Ok(result); |
||||
} |
||||
let object = object.to_object(context)?; |
||||
let for_in_iterator = ForInIterator::create_for_in_iterator(context, Value::from(object))?; |
||||
let next_function = for_in_iterator |
||||
.get_property("next") |
||||
.map(|p| p.as_data_descriptor().unwrap().value()) |
||||
.ok_or_else(|| context.construct_type_error("Could not find property `next`"))?; |
||||
let iterator = IteratorRecord::new(for_in_iterator, next_function); |
||||
|
||||
loop { |
||||
{ |
||||
let env = &mut context.realm_mut().environment; |
||||
env.push(new_declarative_environment(Some( |
||||
env.get_current_environment_ref().clone(), |
||||
))); |
||||
} |
||||
let iterator_result = iterator.next(context)?; |
||||
if iterator_result.is_done() { |
||||
break; |
||||
} |
||||
let next_result = iterator_result.value(); |
||||
|
||||
match self.variable() { |
||||
Node::Identifier(ref name) => { |
||||
let environment = &mut context.realm_mut().environment; |
||||
|
||||
if environment.has_binding(name.as_ref()) { |
||||
// Binding already exists
|
||||
environment |
||||
.set_mutable_binding(name.as_ref(), next_result.clone(), true) |
||||
.map_err(|e| e.to_error(context))?; |
||||
} else { |
||||
environment |
||||
.create_mutable_binding( |
||||
name.as_ref().to_owned(), |
||||
true, |
||||
VariableScope::Function, |
||||
) |
||||
.map_err(|e| e.to_error(context))?; |
||||
let environment = &mut context.realm_mut().environment; |
||||
environment |
||||
.initialize_binding(name.as_ref(), next_result.clone()) |
||||
.map_err(|e| e.to_error(context))?; |
||||
} |
||||
} |
||||
Node::VarDeclList(ref list) => match list.as_ref() { |
||||
[var] => { |
||||
let environment = &mut context.realm_mut().environment; |
||||
|
||||
if var.init().is_some() { |
||||
return context.throw_syntax_error("a declaration in the head of a for-in loop can't have an initializer"); |
||||
} |
||||
|
||||
if environment.has_binding(var.name()) { |
||||
environment |
||||
.set_mutable_binding(var.name(), next_result, true) |
||||
.map_err(|e| e.to_error(context))?; |
||||
} else { |
||||
environment |
||||
.create_mutable_binding( |
||||
var.name().to_owned(), |
||||
false, |
||||
VariableScope::Function, |
||||
) |
||||
.map_err(|e| e.to_error(context))?; |
||||
let environment = &mut context.realm_mut().environment; |
||||
environment |
||||
.initialize_binding(var.name(), next_result) |
||||
.map_err(|e| e.to_error(context))?; |
||||
} |
||||
} |
||||
_ => { |
||||
return context.throw_syntax_error( |
||||
"only one variable can be declared in the head of a for-in loop", |
||||
) |
||||
} |
||||
}, |
||||
Node::LetDeclList(ref list) => match list.as_ref() { |
||||
[var] => { |
||||
let environment = &mut context.realm_mut().environment; |
||||
|
||||
if var.init().is_some() { |
||||
return context.throw_syntax_error("a declaration in the head of a for-in loop can't have an initializer"); |
||||
} |
||||
|
||||
environment |
||||
.create_mutable_binding( |
||||
var.name().to_owned(), |
||||
false, |
||||
VariableScope::Block, |
||||
) |
||||
.map_err(|e| e.to_error(context))?; |
||||
let environment = &mut context.realm_mut().environment; |
||||
environment |
||||
.initialize_binding(var.name(), next_result) |
||||
.map_err(|e| e.to_error(context))?; |
||||
} |
||||
_ => { |
||||
return context.throw_syntax_error( |
||||
"only one variable can be declared in the head of a for-in loop", |
||||
) |
||||
} |
||||
}, |
||||
Node::ConstDeclList(ref list) => match list.as_ref() { |
||||
[var] => { |
||||
let environment = &mut context.realm_mut().environment; |
||||
|
||||
if var.init().is_some() { |
||||
return context.throw_syntax_error("a declaration in the head of a for-in loop can't have an initializer"); |
||||
} |
||||
|
||||
environment |
||||
.create_immutable_binding( |
||||
var.name().to_owned(), |
||||
false, |
||||
VariableScope::Block, |
||||
) |
||||
.map_err(|e| e.to_error(context))?; |
||||
let environment = &mut context.realm_mut().environment; |
||||
environment |
||||
.initialize_binding(var.name(), next_result) |
||||
.map_err(|e| e.to_error(context))?; |
||||
} |
||||
_ => { |
||||
return context.throw_syntax_error( |
||||
"only one variable can be declared in the head of a for-in loop", |
||||
) |
||||
} |
||||
}, |
||||
Node::Assign(_) => { |
||||
return context.throw_syntax_error( |
||||
"a declaration in the head of a for-in loop can't have an initializer", |
||||
); |
||||
} |
||||
_ => { |
||||
return context |
||||
.throw_syntax_error("unknown left hand side in head of for-in loop") |
||||
} |
||||
} |
||||
|
||||
result = self.body().run(context)?; |
||||
match context.executor().get_current_state() { |
||||
InterpreterState::Break(label) => { |
||||
handle_state_with_labels!(self, label, context, break); |
||||
break; |
||||
} |
||||
InterpreterState::Continue(label) => { |
||||
handle_state_with_labels!(self, label, context, continue); |
||||
} |
||||
InterpreterState::Return => return Ok(result), |
||||
InterpreterState::Executing => { |
||||
// Continue execution.
|
||||
} |
||||
} |
||||
let _ = context.realm_mut().environment.pop(); |
||||
} |
||||
Ok(result) |
||||
} |
||||
} |
Loading…
Reference in new issue