mirror of https://github.com/boa-dev/boa.git
Browse Source
* Initial implementation of for...of loop * Initial implementation of for...of loop * Extend for...of to support var, let, and const lhs * Use cached well known symbols * Nest use statements * Nest use statements * Add tests. * Initial implementation of for...of loop * Extend for...of to support var, let, and const lhs * Use cached well known symbols * Nest use statements * Nest use statements * Add tests. * Add tests, cache iterator prototypes, pull common iterator functions out into their own file to allow for repeated use. * Initial implementation of for...of loop * Extend for...of to support var, let, and const lhs * Use cached well known symbols * Nest use statements * Nest use statements * Add tests. * Add tests, cache iterator prototypes, pull common iterator functions out into their own file to allow for repeated use. * Initial implementation of for...of loop * Extend for...of to support var, let, and const lhs * Use cached well known symbols * Initial implementation of for...of loop * Nest use statements * Add string iterator * Clean up merge * Use ctx.global_iterator() * Merge upstream * Use u32 as array next index * Use into * Use boa::Result * Tidy up use statement * Tidy up use statementpull/722/head
joshwd36
4 years ago
committed by
GitHub
24 changed files with 1291 additions and 73 deletions
@ -0,0 +1,139 @@
|
||||
use crate::{ |
||||
builtins::{function::make_builtin_fn, iterable::create_iter_result_object, Array, Value}, |
||||
object::ObjectData, |
||||
property::{Attribute, Property}, |
||||
BoaProfiler, Context, Result, |
||||
}; |
||||
use gc::{Finalize, Trace}; |
||||
|
||||
#[derive(Debug, Clone, Finalize, Trace)] |
||||
pub enum ArrayIterationKind { |
||||
Key, |
||||
Value, |
||||
KeyAndValue, |
||||
} |
||||
|
||||
/// The Array Iterator object represents an iteration over an array. It implements the iterator protocol.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-array-iterator-objects
|
||||
#[derive(Debug, Clone, Finalize, Trace)] |
||||
pub struct ArrayIterator { |
||||
array: Value, |
||||
next_index: u32, |
||||
kind: ArrayIterationKind, |
||||
} |
||||
|
||||
impl ArrayIterator { |
||||
pub(crate) const NAME: &'static str = "ArrayIterator"; |
||||
|
||||
fn new(array: Value, kind: ArrayIterationKind) -> Self { |
||||
ArrayIterator { |
||||
array, |
||||
kind, |
||||
next_index: 0, |
||||
} |
||||
} |
||||
|
||||
/// CreateArrayIterator( array, kind )
|
||||
///
|
||||
/// Creates a new iterator over the given array.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-createarrayiterator
|
||||
pub(crate) fn create_array_iterator( |
||||
ctx: &Context, |
||||
array: Value, |
||||
kind: ArrayIterationKind, |
||||
) -> Result<Value> { |
||||
let array_iterator = Value::new_object(Some(ctx.global_object())); |
||||
array_iterator.set_data(ObjectData::ArrayIterator(Self::new(array, kind))); |
||||
array_iterator |
||||
.as_object_mut() |
||||
.expect("array iterator object") |
||||
.set_prototype_instance(ctx.iterator_prototypes().array_iterator().into()); |
||||
Ok(array_iterator) |
||||
} |
||||
|
||||
/// %ArrayIteratorPrototype%.next( )
|
||||
///
|
||||
/// Gets the next result in the array.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%.next
|
||||
pub(crate) fn next(this: &Value, _args: &[Value], ctx: &mut Context) -> Result<Value> { |
||||
if let Value::Object(ref object) = this { |
||||
let mut object = object.borrow_mut(); |
||||
if let Some(array_iterator) = object.as_array_iterator_mut() { |
||||
let index = array_iterator.next_index; |
||||
if array_iterator.array.is_undefined() { |
||||
return Ok(create_iter_result_object(ctx, Value::undefined(), true)); |
||||
} |
||||
let len = array_iterator |
||||
.array |
||||
.get_field("length") |
||||
.as_number() |
||||
.ok_or_else(|| ctx.construct_type_error("Not an array"))? |
||||
as u32; |
||||
if array_iterator.next_index >= len { |
||||
array_iterator.array = Value::undefined(); |
||||
return Ok(create_iter_result_object(ctx, Value::undefined(), true)); |
||||
} |
||||
array_iterator.next_index = index + 1; |
||||
match array_iterator.kind { |
||||
ArrayIterationKind::Key => { |
||||
Ok(create_iter_result_object(ctx, index.into(), false)) |
||||
} |
||||
ArrayIterationKind::Value => { |
||||
let element_value = array_iterator.array.get_field(index); |
||||
Ok(create_iter_result_object(ctx, element_value, false)) |
||||
} |
||||
ArrayIterationKind::KeyAndValue => { |
||||
let element_value = array_iterator.array.get_field(index); |
||||
let result = Array::make_array( |
||||
&Value::new_object(Some(ctx.global_object())), |
||||
&[index.into(), element_value], |
||||
ctx, |
||||
)?; |
||||
Ok(create_iter_result_object(ctx, result, false)) |
||||
} |
||||
} |
||||
} else { |
||||
ctx.throw_type_error("`this` is not an ArrayIterator") |
||||
} |
||||
} else { |
||||
ctx.throw_type_error("`this` is not an ArrayIterator") |
||||
} |
||||
} |
||||
|
||||
/// Create the %ArrayIteratorPrototype% object
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%-object
|
||||
pub(crate) fn create_prototype(ctx: &mut Context, iterator_prototype: Value) -> Value { |
||||
let global = ctx.global_object(); |
||||
let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); |
||||
|
||||
// Create prototype
|
||||
let array_iterator = Value::new_object(Some(global)); |
||||
make_builtin_fn(Self::next, "next", &array_iterator, 0, ctx); |
||||
array_iterator |
||||
.as_object_mut() |
||||
.expect("array iterator prototype object") |
||||
.set_prototype_instance(iterator_prototype); |
||||
|
||||
let to_string_tag = ctx.well_known_symbols().to_string_tag_symbol(); |
||||
let to_string_tag_property = |
||||
Property::data_descriptor(Value::string("Array Iterator"), Attribute::CONFIGURABLE); |
||||
array_iterator.set_property(to_string_tag, to_string_tag_property); |
||||
array_iterator |
||||
} |
||||
} |
@ -0,0 +1,154 @@
|
||||
use crate::{ |
||||
builtins::string::string_iterator::StringIterator, |
||||
builtins::{ |
||||
function::{BuiltInFunction, Function, FunctionFlags}, |
||||
ArrayIterator, |
||||
}, |
||||
object::GcObject, |
||||
object::{Object, PROTOTYPE}, |
||||
property::Property, |
||||
BoaProfiler, Context, Result, Value, |
||||
}; |
||||
|
||||
#[derive(Debug, Default)] |
||||
pub struct IteratorPrototypes { |
||||
iterator_prototype: GcObject, |
||||
array_iterator: GcObject, |
||||
string_iterator: GcObject, |
||||
} |
||||
|
||||
impl IteratorPrototypes { |
||||
pub fn init(ctx: &mut Context) -> Self { |
||||
let iterator_prototype = create_iterator_prototype(ctx); |
||||
Self { |
||||
iterator_prototype: iterator_prototype |
||||
.as_gc_object() |
||||
.expect("Iterator prototype is not an object"), |
||||
array_iterator: ArrayIterator::create_prototype(ctx, iterator_prototype.clone()) |
||||
.as_gc_object() |
||||
.expect("Array Iterator Prototype is not an object"), |
||||
string_iterator: StringIterator::create_prototype(ctx, iterator_prototype) |
||||
.as_gc_object() |
||||
.expect("String Iterator Prototype is not an object"), |
||||
} |
||||
} |
||||
|
||||
pub fn array_iterator(&self) -> GcObject { |
||||
self.array_iterator.clone() |
||||
} |
||||
|
||||
pub fn iterator_prototype(&self) -> GcObject { |
||||
self.iterator_prototype.clone() |
||||
} |
||||
|
||||
pub fn string_iterator(&self) -> GcObject { |
||||
self.string_iterator.clone() |
||||
} |
||||
} |
||||
|
||||
/// CreateIterResultObject( value, done )
|
||||
///
|
||||
/// Generates an object supporting the IteratorResult interface.
|
||||
pub fn create_iter_result_object(ctx: &mut Context, value: Value, done: bool) -> Value { |
||||
let object = Value::new_object(Some(ctx.global_object())); |
||||
let value_property = Property::default().value(value); |
||||
let done_property = Property::default().value(Value::boolean(done)); |
||||
object.set_property("value", value_property); |
||||
object.set_property("done", done_property); |
||||
object |
||||
} |
||||
|
||||
/// Get an iterator record
|
||||
pub fn get_iterator(ctx: &mut Context, iterable: Value) -> Result<IteratorRecord> { |
||||
let iterator_function = iterable |
||||
.get_property(ctx.well_known_symbols().iterator_symbol()) |
||||
.and_then(|mut p| p.value.take()) |
||||
.ok_or_else(|| ctx.construct_type_error("Not an iterable"))?; |
||||
let iterator_object = ctx.call(&iterator_function, &iterable, &[])?; |
||||
let next_function = iterator_object |
||||
.get_property("next") |
||||
.and_then(|mut p| p.value.take()) |
||||
.ok_or_else(|| ctx.construct_type_error("Could not find property `next`"))?; |
||||
Ok(IteratorRecord::new(iterator_object, next_function)) |
||||
} |
||||
|
||||
/// Create the %IteratorPrototype% object
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-%iteratorprototype%-object
|
||||
fn create_iterator_prototype(ctx: &mut Context) -> Value { |
||||
let global = ctx.global_object(); |
||||
let _timer = BoaProfiler::global().start_event("Iterator Prototype", "init"); |
||||
|
||||
let iterator_prototype = Value::new_object(Some(global)); |
||||
let mut function = Object::function( |
||||
Function::BuiltIn( |
||||
BuiltInFunction(|v, _, _| Ok(v.clone())), |
||||
FunctionFlags::CALLABLE, |
||||
), |
||||
global.get_field("Function").get_field(PROTOTYPE), |
||||
); |
||||
function.insert_field("length", Value::from(0)); |
||||
function.insert_field("name", Value::string("[Symbol.iterator]")); |
||||
|
||||
let symbol_iterator = ctx.well_known_symbols().iterator_symbol(); |
||||
iterator_prototype.set_field(symbol_iterator, Value::from(function)); |
||||
iterator_prototype |
||||
} |
||||
|
||||
#[derive(Debug)] |
||||
pub struct IteratorRecord { |
||||
iterator_object: Value, |
||||
next_function: Value, |
||||
} |
||||
|
||||
impl IteratorRecord { |
||||
fn new(iterator_object: Value, next_function: Value) -> Self { |
||||
Self { |
||||
iterator_object, |
||||
next_function, |
||||
} |
||||
} |
||||
|
||||
/// Get the next value in the iterator
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-iteratornext
|
||||
pub(crate) fn next(&self, ctx: &mut Context) -> Result<IteratorResult> { |
||||
let next = ctx.call(&self.next_function, &self.iterator_object, &[])?; |
||||
let done = next |
||||
.get_property("done") |
||||
.and_then(|mut p| p.value.take()) |
||||
.and_then(|v| v.as_boolean()) |
||||
.ok_or_else(|| ctx.construct_type_error("Could not find property `done`"))?; |
||||
let next_result = next |
||||
.get_property("value") |
||||
.and_then(|mut p| p.value.take()) |
||||
.unwrap_or_default(); |
||||
Ok(IteratorResult::new(next_result, done)) |
||||
} |
||||
} |
||||
|
||||
#[derive(Debug)] |
||||
pub struct IteratorResult { |
||||
value: Value, |
||||
done: bool, |
||||
} |
||||
|
||||
impl IteratorResult { |
||||
fn new(value: Value, done: bool) -> Self { |
||||
Self { value, done } |
||||
} |
||||
|
||||
pub fn is_done(&self) -> bool { |
||||
self.done |
||||
} |
||||
|
||||
pub fn value(self) -> Value { |
||||
self.value |
||||
} |
||||
} |
@ -0,0 +1,89 @@
|
||||
use crate::builtins::string::code_point_at; |
||||
use crate::{ |
||||
builtins::{function::make_builtin_fn, iterable::create_iter_result_object}, |
||||
object::ObjectData, |
||||
property::{Attribute, Property}, |
||||
BoaProfiler, Context, Result, Value, |
||||
}; |
||||
use gc::{Finalize, Trace}; |
||||
|
||||
#[derive(Debug, Clone, Finalize, Trace)] |
||||
pub struct StringIterator { |
||||
string: Value, |
||||
next_index: i32, |
||||
} |
||||
|
||||
impl StringIterator { |
||||
fn new(string: Value) -> Self { |
||||
Self { |
||||
string, |
||||
next_index: 0, |
||||
} |
||||
} |
||||
|
||||
pub fn create_string_iterator(ctx: &mut Context, string: Value) -> Result<Value> { |
||||
let string_iterator = Value::new_object(Some(ctx.global_object())); |
||||
string_iterator.set_data(ObjectData::StringIterator(Self::new(string))); |
||||
string_iterator |
||||
.as_object_mut() |
||||
.expect("array iterator object") |
||||
.set_prototype_instance(ctx.iterator_prototypes().string_iterator().into()); |
||||
Ok(string_iterator) |
||||
} |
||||
|
||||
pub fn next(this: &Value, _args: &[Value], ctx: &mut Context) -> Result<Value> { |
||||
if let Value::Object(ref object) = this { |
||||
let mut object = object.borrow_mut(); |
||||
if let Some(string_iterator) = object.as_string_iterator_mut() { |
||||
if string_iterator.string.is_undefined() { |
||||
return Ok(create_iter_result_object(ctx, Value::undefined(), true)); |
||||
} |
||||
let native_string = string_iterator.string.to_string(ctx)?; |
||||
let len = native_string.encode_utf16().count() as i32; |
||||
let position = string_iterator.next_index; |
||||
if position >= len { |
||||
string_iterator.string = Value::undefined(); |
||||
return Ok(create_iter_result_object(ctx, Value::undefined(), true)); |
||||
} |
||||
let (_, code_unit_count, _) = |
||||
code_point_at(native_string, position).expect("Invalid code point position"); |
||||
string_iterator.next_index += code_unit_count as i32; |
||||
let result_string = crate::builtins::string::String::substring( |
||||
&string_iterator.string, |
||||
&[position.into(), string_iterator.next_index.into()], |
||||
ctx, |
||||
)?; |
||||
Ok(create_iter_result_object(ctx, result_string, false)) |
||||
} else { |
||||
ctx.throw_type_error("`this` is not an ArrayIterator") |
||||
} |
||||
} else { |
||||
ctx.throw_type_error("`this` is not an ArrayIterator") |
||||
} |
||||
} |
||||
|
||||
/// Create the %ArrayIteratorPrototype% object
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%-object
|
||||
pub(crate) fn create_prototype(ctx: &mut Context, iterator_prototype: Value) -> Value { |
||||
let global = ctx.global_object(); |
||||
let _timer = BoaProfiler::global().start_event("String Iterator", "init"); |
||||
|
||||
// Create prototype
|
||||
let array_iterator = Value::new_object(Some(global)); |
||||
make_builtin_fn(Self::next, "next", &array_iterator, 0, ctx); |
||||
array_iterator |
||||
.as_object_mut() |
||||
.expect("array iterator prototype object") |
||||
.set_prototype_instance(iterator_prototype); |
||||
|
||||
let to_string_tag = ctx.well_known_symbols().to_string_tag_symbol(); |
||||
let to_string_tag_property = |
||||
Property::data_descriptor(Value::string("String Iterator"), Attribute::CONFIGURABLE); |
||||
array_iterator.set_property(to_string_tag, to_string_tag_property); |
||||
array_iterator |
||||
} |
||||
} |
@ -0,0 +1,208 @@
|
||||
use crate::{ |
||||
builtins::iterable::get_iterator, |
||||
environment::lexical_environment::{new_declarative_environment, VariableScope}, |
||||
exec::{Executable, InterpreterState}, |
||||
syntax::ast::node::Node, |
||||
BoaProfiler, Context, Result, Value, |
||||
}; |
||||
use gc::{Finalize, Trace}; |
||||
use std::fmt; |
||||
|
||||
#[cfg(feature = "serde")] |
||||
use serde::{Deserialize, Serialize}; |
||||
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] |
||||
#[derive(Clone, Debug, Trace, Finalize, PartialEq)] |
||||
pub struct ForOfLoop { |
||||
variable: Box<Node>, |
||||
iterable: Box<Node>, |
||||
body: Box<Node>, |
||||
} |
||||
|
||||
impl ForOfLoop { |
||||
pub fn new<V, I, B>(variable: V, iterable: I, body: B) -> Self |
||||
where |
||||
V: Into<Node>, |
||||
I: Into<Node>, |
||||
B: Into<Node>, |
||||
{ |
||||
Self { |
||||
variable: Box::new(variable.into()), |
||||
iterable: Box::new(iterable.into()), |
||||
body: Box::new(body.into()), |
||||
} |
||||
} |
||||
|
||||
pub fn variable(&self) -> &Node { |
||||
&self.variable |
||||
} |
||||
|
||||
pub fn iterable(&self) -> &Node { |
||||
&self.iterable |
||||
} |
||||
|
||||
pub fn body(&self) -> &Node { |
||||
&self.body |
||||
} |
||||
|
||||
pub fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result { |
||||
write!(f, "for ({} of {}) {{", self.variable, self.iterable)?; |
||||
self.body().display(f, indentation + 1)?; |
||||
f.write_str("}") |
||||
} |
||||
} |
||||
|
||||
impl fmt::Display for ForOfLoop { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
self.display(f, 0) |
||||
} |
||||
} |
||||
|
||||
impl From<ForOfLoop> for Node { |
||||
fn from(for_of: ForOfLoop) -> Node { |
||||
Self::ForOfLoop(for_of) |
||||
} |
||||
} |
||||
|
||||
impl Executable for ForOfLoop { |
||||
fn run(&self, interpreter: &mut Context) -> Result<Value> { |
||||
let _timer = BoaProfiler::global().start_event("ForOf", "exec"); |
||||
let iterable = self.iterable().run(interpreter)?; |
||||
let iterator = get_iterator(interpreter, iterable)?; |
||||
let mut result = Value::undefined(); |
||||
|
||||
loop { |
||||
{ |
||||
let env = &mut interpreter.realm_mut().environment; |
||||
env.push(new_declarative_environment(Some( |
||||
env.get_current_environment_ref().clone(), |
||||
))); |
||||
} |
||||
let iterator_result = iterator.next(interpreter)?; |
||||
if iterator_result.is_done() { |
||||
break; |
||||
} |
||||
let next_result = iterator_result.value(); |
||||
|
||||
match self.variable() { |
||||
Node::Identifier(ref name) => { |
||||
let environment = &mut interpreter.realm_mut().environment; |
||||
|
||||
if environment.has_binding(name.as_ref()) { |
||||
// Binding already exists
|
||||
environment.set_mutable_binding(name.as_ref(), next_result.clone(), true); |
||||
} else { |
||||
environment.create_mutable_binding( |
||||
name.as_ref().to_owned(), |
||||
true, |
||||
VariableScope::Function, |
||||
); |
||||
environment.initialize_binding(name.as_ref(), next_result.clone()); |
||||
} |
||||
} |
||||
Node::VarDeclList(ref list) => match list.as_ref() { |
||||
[var] => { |
||||
let environment = &mut interpreter.realm_mut().environment; |
||||
|
||||
if var.init().is_some() { |
||||
return interpreter.throw_syntax_error("a declaration in the head of a for-of loop can't have an initializer"); |
||||
} |
||||
|
||||
if environment.has_binding(var.name()) { |
||||
environment.set_mutable_binding(var.name(), next_result, true); |
||||
} else { |
||||
environment.create_mutable_binding( |
||||
var.name().to_owned(), |
||||
false, |
||||
VariableScope::Function, |
||||
); |
||||
environment.initialize_binding(var.name(), next_result); |
||||
} |
||||
} |
||||
_ => { |
||||
return interpreter.throw_syntax_error( |
||||
"only one variable can be declared in the head of a for-of loop", |
||||
) |
||||
} |
||||
}, |
||||
Node::LetDeclList(ref list) => match list.as_ref() { |
||||
[var] => { |
||||
let environment = &mut interpreter.realm_mut().environment; |
||||
|
||||
if var.init().is_some() { |
||||
return interpreter.throw_syntax_error("a declaration in the head of a for-of loop can't have an initializer"); |
||||
} |
||||
|
||||
environment.create_mutable_binding( |
||||
var.name().to_owned(), |
||||
false, |
||||
VariableScope::Block, |
||||
); |
||||
environment.initialize_binding(var.name(), next_result); |
||||
} |
||||
_ => { |
||||
return interpreter.throw_syntax_error( |
||||
"only one variable can be declared in the head of a for-of loop", |
||||
) |
||||
} |
||||
}, |
||||
Node::ConstDeclList(ref list) => match list.as_ref() { |
||||
[var] => { |
||||
let environment = &mut interpreter.realm_mut().environment; |
||||
|
||||
if var.init().is_some() { |
||||
return interpreter.throw_syntax_error("a declaration in the head of a for-of loop can't have an initializer"); |
||||
} |
||||
|
||||
environment.create_immutable_binding( |
||||
var.name().to_owned(), |
||||
false, |
||||
VariableScope::Block, |
||||
); |
||||
environment.initialize_binding(var.name(), next_result); |
||||
} |
||||
_ => { |
||||
return interpreter.throw_syntax_error( |
||||
"only one variable can be declared in the head of a for-of loop", |
||||
) |
||||
} |
||||
}, |
||||
Node::Assign(_) => { |
||||
return interpreter.throw_syntax_error( |
||||
"a declaration in the head of a for-of loop can't have an initializer", |
||||
); |
||||
} |
||||
_ => { |
||||
return interpreter |
||||
.throw_syntax_error("unknown left hand side in head of for-of loop") |
||||
} |
||||
} |
||||
|
||||
result = self.body().run(interpreter)?; |
||||
match interpreter.executor().get_current_state() { |
||||
InterpreterState::Break(_label) => { |
||||
// TODO break to label.
|
||||
|
||||
// Loops 'consume' breaks.
|
||||
interpreter |
||||
.executor() |
||||
.set_current_state(InterpreterState::Executing); |
||||
break; |
||||
} |
||||
InterpreterState::Continue(_label) => { |
||||
// TODO continue to label.
|
||||
interpreter |
||||
.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.
|
||||
} |
||||
} |
||||
let _ = interpreter.realm_mut().environment.pop(); |
||||
} |
||||
Ok(result) |
||||
} |
||||
} |
Loading…
Reference in new issue