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