mirror of https://github.com/boa-dev/boa.git
João Borges
4 years ago
committed by
GitHub
17 changed files with 1154 additions and 74 deletions
@ -0,0 +1,419 @@
|
||||
//! This module implements the global `Set` objest.
|
||||
//!
|
||||
//! The JavaScript `Set` class is a global object that is used in the construction of sets; which
|
||||
//! are high-level, collections of values.
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//! - [MDN documentation][mdn]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-set-objects
|
||||
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
|
||||
|
||||
use crate::{ |
||||
builtins::{iterable::get_iterator, BuiltIn}, |
||||
object::{ConstructorBuilder, FunctionBuilder, ObjectData, PROTOTYPE}, |
||||
property::Attribute, |
||||
symbol::WellKnownSymbols, |
||||
BoaProfiler, Context, Result, Value, |
||||
}; |
||||
use ordered_set::OrderedSet; |
||||
|
||||
pub mod set_iterator; |
||||
use set_iterator::{SetIterationKind, SetIterator}; |
||||
|
||||
pub mod ordered_set; |
||||
#[cfg(test)] |
||||
mod tests; |
||||
|
||||
#[derive(Debug, Clone)] |
||||
pub(crate) struct Set(OrderedSet<Value>); |
||||
|
||||
impl BuiltIn for Set { |
||||
const NAME: &'static str = "Set"; |
||||
|
||||
fn attribute() -> Attribute { |
||||
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE |
||||
} |
||||
|
||||
fn init(context: &mut Context) -> (&'static str, Value, Attribute) { |
||||
let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); |
||||
|
||||
let species = WellKnownSymbols::species(); |
||||
|
||||
let species_getter = FunctionBuilder::new(context, Self::species_getter) |
||||
.callable(true) |
||||
.constructable(false) |
||||
.name("get [Symbol.species]") |
||||
.build(); |
||||
|
||||
let size_getter = FunctionBuilder::new(context, Self::size_getter) |
||||
.callable(true) |
||||
.constructable(false) |
||||
.name("get size") |
||||
.build(); |
||||
|
||||
let iterator_symbol = WellKnownSymbols::iterator(); |
||||
|
||||
let to_string_tag = WellKnownSymbols::to_string_tag(); |
||||
|
||||
let values_function = FunctionBuilder::new(context, Self::values) |
||||
.name("values") |
||||
.length(0) |
||||
.callable(true) |
||||
.constructable(false) |
||||
.build(); |
||||
|
||||
let set_object = ConstructorBuilder::with_standard_object( |
||||
context, |
||||
Self::constructor, |
||||
context.standard_objects().set_object().clone(), |
||||
) |
||||
.name(Self::NAME) |
||||
.length(Self::LENGTH) |
||||
.static_accessor(species, Some(species_getter), None, Attribute::CONFIGURABLE) |
||||
.method(Self::add, "add", 1) |
||||
.method(Self::clear, "clear", 0) |
||||
.method(Self::delete, "delete", 1) |
||||
.method(Self::entries, "entries", 0) |
||||
.method(Self::for_each, "forEach", 1) |
||||
.method(Self::has, "has", 1) |
||||
.property( |
||||
"keys", |
||||
values_function.clone(), |
||||
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, |
||||
) |
||||
.accessor("size", Some(size_getter), None, Attribute::CONFIGURABLE) |
||||
.property( |
||||
"values", |
||||
values_function.clone(), |
||||
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, |
||||
) |
||||
.property( |
||||
iterator_symbol, |
||||
values_function, |
||||
Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, |
||||
) |
||||
.property(to_string_tag, "Set", Attribute::CONFIGURABLE) |
||||
.build(); |
||||
|
||||
(Self::NAME, set_object.into(), Self::attribute()) |
||||
} |
||||
} |
||||
|
||||
impl Set { |
||||
pub(crate) const LENGTH: usize = 0; |
||||
|
||||
/// Create a new set
|
||||
pub(crate) fn constructor( |
||||
new_target: &Value, |
||||
args: &[Value], |
||||
context: &mut Context, |
||||
) -> Result<Value> { |
||||
// 1
|
||||
if new_target.is_undefined() { |
||||
return context |
||||
.throw_type_error("calling a builtin Set constructor without new is forbidden"); |
||||
} |
||||
|
||||
// 2
|
||||
let set_prototype = context.standard_objects().set_object().prototype(); |
||||
let prototype = new_target |
||||
.as_object() |
||||
.and_then(|obj| { |
||||
obj.get(&PROTOTYPE.into(), obj.clone().into(), context) |
||||
.map(|o| o.as_object()) |
||||
.transpose() |
||||
}) |
||||
.transpose()? |
||||
.unwrap_or(set_prototype); |
||||
|
||||
let mut obj = context.construct_object(); |
||||
obj.set_prototype_instance(prototype.into()); |
||||
|
||||
let set = Value::from(obj); |
||||
// 3
|
||||
set.set_data(ObjectData::Set(OrderedSet::default())); |
||||
|
||||
let iterable = args.get(0).cloned().unwrap_or_default(); |
||||
// 4
|
||||
if iterable.is_null_or_undefined() { |
||||
return Ok(set); |
||||
} |
||||
|
||||
// 5
|
||||
let adder = set.get_field("add", context)?; |
||||
|
||||
// 6
|
||||
if !adder.is_function() { |
||||
return context.throw_type_error("'add' of 'newTarget' is not a function"); |
||||
} |
||||
|
||||
// 7
|
||||
let iterator_record = get_iterator(context, iterable)?; |
||||
|
||||
// 8.a
|
||||
let mut next = iterator_record.next(context)?; |
||||
|
||||
// 8
|
||||
while !next.is_done() { |
||||
// c
|
||||
let next_value = next.value(); |
||||
|
||||
// d, e
|
||||
if let Err(status) = context.call(&adder, &set, &[next_value]) { |
||||
return iterator_record.close(Err(status), context); |
||||
} |
||||
|
||||
next = iterator_record.next(context)? |
||||
} |
||||
|
||||
// 8.b
|
||||
Ok(set) |
||||
} |
||||
|
||||
/// `get Set [ @@species ]`
|
||||
///
|
||||
/// get accessor for the @@species property of Set
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-get-set-@@species
|
||||
fn species_getter(this: &Value, _: &[Value], _: &mut Context) -> Result<Value> { |
||||
Ok(this.clone()) |
||||
} |
||||
|
||||
/// `Set.prototype.add( value )`
|
||||
///
|
||||
/// This method adds an entry with value into the set. Returns the set object
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-set.prototype.add
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add
|
||||
pub(crate) fn add(this: &Value, args: &[Value], context: &mut Context) -> Result<Value> { |
||||
let mut value = args.get(0).cloned().unwrap_or_default(); |
||||
|
||||
if let Some(object) = this.as_object() { |
||||
if let Some(set) = object.borrow_mut().as_set_mut() { |
||||
if value.as_number().map(|n| n == -0f64).unwrap_or(false) { |
||||
value = Value::Integer(0); |
||||
} |
||||
set.add(value); |
||||
} else { |
||||
return context.throw_type_error("'this' is not a Set"); |
||||
} |
||||
} else { |
||||
return context.throw_type_error("'this' is not a Set"); |
||||
}; |
||||
|
||||
Ok(this.clone()) |
||||
} |
||||
|
||||
/// `Set.prototype.clear( )`
|
||||
///
|
||||
/// This method removes all entries from the set.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-set.prototype.clear
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear
|
||||
pub(crate) fn clear(this: &Value, _: &[Value], context: &mut Context) -> Result<Value> { |
||||
if let Some(object) = this.as_object() { |
||||
if object.borrow_mut().is_set() { |
||||
this.set_data(ObjectData::Set(OrderedSet::new())); |
||||
Ok(Value::Undefined) |
||||
} else { |
||||
context.throw_type_error("'this' is not a Set") |
||||
} |
||||
} else { |
||||
context.throw_type_error("'this' is not a Set") |
||||
} |
||||
} |
||||
|
||||
/// `Set.prototype.delete( value )`
|
||||
///
|
||||
/// This method removes the entry for the given value if it exists.
|
||||
/// Returns true if there was an element, false otherwise.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-set.prototype.delete
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete
|
||||
pub(crate) fn delete(this: &Value, args: &[Value], context: &mut Context) -> Result<Value> { |
||||
let value = args.get(0).cloned().unwrap_or_default(); |
||||
|
||||
let res = if let Some(object) = this.as_object() { |
||||
if let Some(set) = object.borrow_mut().as_set_mut() { |
||||
set.delete(&value) |
||||
} else { |
||||
return context.throw_type_error("'this' is not a Set"); |
||||
} |
||||
} else { |
||||
return context.throw_type_error("'this' is not a Set"); |
||||
}; |
||||
|
||||
Ok(res.into()) |
||||
} |
||||
|
||||
/// `Set.prototype.entries( )`
|
||||
///
|
||||
/// This method returns an iterator over the entries of the set
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-set.prototype.entries
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/entries
|
||||
pub(crate) fn entries(this: &Value, _: &[Value], context: &mut Context) -> Result<Value> { |
||||
if let Some(object) = this.as_object() { |
||||
let object = object.borrow(); |
||||
if !object.is_set() { |
||||
return context.throw_type_error( |
||||
"Method Set.prototype.entries called on incompatible receiver", |
||||
); |
||||
} |
||||
} else { |
||||
return context |
||||
.throw_type_error("Method Set.prototype.entries called on incompatible receiver"); |
||||
} |
||||
|
||||
Ok(SetIterator::create_set_iterator( |
||||
context, |
||||
this.clone(), |
||||
SetIterationKind::KeyAndValue, |
||||
)) |
||||
} |
||||
|
||||
/// `Set.prototype.forEach( callbackFn [ , thisArg ] )`
|
||||
///
|
||||
/// This method executes the provided callback function for each value in the set
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-set.prototype.foreach
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/foreach
|
||||
pub(crate) fn for_each(this: &Value, args: &[Value], context: &mut Context) -> Result<Value> { |
||||
if args.is_empty() { |
||||
return Err(Value::from("Missing argument for Set.prototype.forEach")); |
||||
} |
||||
|
||||
let callback_arg = &args[0]; |
||||
let this_arg = args.get(1).cloned().unwrap_or_else(Value::undefined); |
||||
// TODO: if condition should also check that we are not in strict mode
|
||||
let this_arg = if this_arg.is_undefined() { |
||||
Value::Object(context.global_object()) |
||||
} else { |
||||
this_arg |
||||
}; |
||||
|
||||
let mut index = 0; |
||||
|
||||
while index < Set::get_size(this, context)? { |
||||
let arguments = if let Value::Object(ref object) = this { |
||||
let object = object.borrow(); |
||||
if let Some(set) = object.as_set_ref() { |
||||
set.get_index(index) |
||||
.map(|value| [value.clone(), value.clone(), this.clone()]) |
||||
} else { |
||||
return context.throw_type_error("'this' is not a Set"); |
||||
} |
||||
} else { |
||||
return context.throw_type_error("'this' is not a Set"); |
||||
}; |
||||
|
||||
if let Some(arguments) = arguments { |
||||
context.call(callback_arg, &this_arg, &arguments)?; |
||||
} |
||||
|
||||
index += 1; |
||||
} |
||||
|
||||
Ok(Value::Undefined) |
||||
} |
||||
|
||||
/// `Map.prototype.has( key )`
|
||||
///
|
||||
/// This method checks if the map contains an entry with the given key.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-map.prototype.has
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has
|
||||
pub(crate) fn has(this: &Value, args: &[Value], context: &mut Context) -> Result<Value> { |
||||
let undefined = Value::Undefined; |
||||
let value = match args.len() { |
||||
0 => &undefined, |
||||
_ => &args[0], |
||||
}; |
||||
|
||||
if let Value::Object(ref object) = this { |
||||
let object = object.borrow(); |
||||
if let Some(set) = object.as_set_ref() { |
||||
return Ok(set.contains(value).into()); |
||||
} |
||||
} |
||||
|
||||
Err(context.construct_type_error("'this' is not a Set")) |
||||
} |
||||
|
||||
/// `Set.prototype.values( )`
|
||||
///
|
||||
/// This method returns an iterator over the values of the set
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
/// - [MDN documentation][mdn]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-set.prototype.values
|
||||
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/values
|
||||
pub(crate) fn values(this: &Value, _: &[Value], context: &mut Context) -> Result<Value> { |
||||
if let Some(object) = this.as_object() { |
||||
let object = object.borrow(); |
||||
if !object.is_set() { |
||||
return context.throw_type_error( |
||||
"Method Set.prototype.values called on incompatible receiver", |
||||
); |
||||
} |
||||
} else { |
||||
return context |
||||
.throw_type_error("Method Set.prototype.values called on incompatible receiver"); |
||||
} |
||||
|
||||
Ok(SetIterator::create_set_iterator( |
||||
context, |
||||
this.clone(), |
||||
SetIterationKind::Value, |
||||
)) |
||||
} |
||||
|
||||
fn size_getter(this: &Value, _: &[Value], context: &mut Context) -> Result<Value> { |
||||
Set::get_size(this, context).map(Value::from) |
||||
} |
||||
|
||||
/// Helper function to get the size of the set.
|
||||
fn get_size(set: &Value, context: &mut Context) -> Result<usize> { |
||||
if let Value::Object(ref object) = set { |
||||
let object = object.borrow(); |
||||
if let Some(set) = object.as_set_ref() { |
||||
Ok(set.size()) |
||||
} else { |
||||
Err(context.construct_type_error("'this' is not a Set")) |
||||
} |
||||
} else { |
||||
Err(context.construct_type_error("'this' is not a Set")) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,130 @@
|
||||
use crate::gc::{custom_trace, Finalize, Trace}; |
||||
use indexmap::{ |
||||
set::{IntoIter, Iter}, |
||||
IndexSet, |
||||
}; |
||||
use std::{ |
||||
collections::hash_map::RandomState, |
||||
fmt::Debug, |
||||
hash::{BuildHasher, Hash}, |
||||
}; |
||||
|
||||
/// A newtype wrapping indexmap::IndexSet
|
||||
#[derive(Clone)] |
||||
pub struct OrderedSet<V, S = RandomState>(IndexSet<V, S>) |
||||
where |
||||
V: Hash + Eq; |
||||
|
||||
impl<V: Eq + Hash + Trace, S: BuildHasher> Finalize for OrderedSet<V, S> {} |
||||
unsafe impl<V: Eq + Hash + Trace, S: BuildHasher> Trace for OrderedSet<V, S> { |
||||
custom_trace!(this, { |
||||
for v in this.0.iter() { |
||||
mark(v); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
impl<V: Hash + Eq + Debug> Debug for OrderedSet<V> { |
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { |
||||
self.0.fmt(formatter) |
||||
} |
||||
} |
||||
|
||||
impl<V: Hash + Eq> Default for OrderedSet<V> { |
||||
fn default() -> Self { |
||||
Self::new() |
||||
} |
||||
} |
||||
|
||||
impl<V> OrderedSet<V> |
||||
where |
||||
V: Hash + Eq, |
||||
{ |
||||
pub fn new() -> Self { |
||||
OrderedSet(IndexSet::new()) |
||||
} |
||||
|
||||
pub fn with_capacity(capacity: usize) -> Self { |
||||
OrderedSet(IndexSet::with_capacity(capacity)) |
||||
} |
||||
|
||||
/// Return the number of key-value pairs in the map.
|
||||
///
|
||||
/// Computes in **O(1)** time.
|
||||
pub fn size(&self) -> usize { |
||||
self.0.len() |
||||
} |
||||
|
||||
/// Returns true if the map contains no elements.
|
||||
///
|
||||
/// Computes in **O(1)** time.
|
||||
pub fn is_empty(&self) -> bool { |
||||
self.0.len() == 0 |
||||
} |
||||
|
||||
/// Insert a value pair in the set.
|
||||
///
|
||||
/// If an equivalent value already exists in the set: ???
|
||||
///
|
||||
/// If no equivalent value existed in the set: the new value is
|
||||
/// inserted, last in order, and false
|
||||
///
|
||||
/// Computes in **O(1)** time (amortized average).
|
||||
pub fn add(&mut self, value: V) -> bool { |
||||
self.0.insert(value) |
||||
} |
||||
|
||||
/// Delete the `value` from the set and return true if successful
|
||||
///
|
||||
/// Return `false` if `value` is not in map.
|
||||
///
|
||||
/// Computes in **O(n)** time (average).
|
||||
pub fn delete(&mut self, value: &V) -> bool { |
||||
self.0.shift_remove(value) |
||||
} |
||||
|
||||
/// Checks if a given value is present in the set
|
||||
///
|
||||
/// Return `true` if `value` is present in set, false otherwise.
|
||||
///
|
||||
/// Computes in **O(n)** time (average).
|
||||
pub fn contains(&self, value: &V) -> bool { |
||||
self.0.contains(value) |
||||
} |
||||
|
||||
/// Get a key-value pair by index
|
||||
/// Valid indices are 0 <= index < self.len()
|
||||
/// Computes in O(1) time.
|
||||
pub fn get_index(&self, index: usize) -> Option<&V> { |
||||
self.0.get_index(index) |
||||
} |
||||
|
||||
/// Return an iterator over the values of the set, in their order
|
||||
pub fn iter(&self) -> Iter<'_, V> { |
||||
self.0.iter() |
||||
} |
||||
} |
||||
|
||||
impl<'a, V, S> IntoIterator for &'a OrderedSet<V, S> |
||||
where |
||||
V: Hash + Eq, |
||||
S: BuildHasher, |
||||
{ |
||||
type Item = &'a V; |
||||
type IntoIter = Iter<'a, V>; |
||||
fn into_iter(self) -> Self::IntoIter { |
||||
self.0.iter() |
||||
} |
||||
} |
||||
|
||||
impl<V, S> IntoIterator for OrderedSet<V, S> |
||||
where |
||||
V: Hash + Eq, |
||||
S: BuildHasher, |
||||
{ |
||||
type Item = V; |
||||
type IntoIter = IntoIter<V>; |
||||
fn into_iter(self) -> IntoIter<V> { |
||||
self.0.into_iter() |
||||
} |
||||
} |
@ -0,0 +1,151 @@
|
||||
use crate::{ |
||||
builtins::function::make_builtin_fn, |
||||
builtins::iterable::create_iter_result_object, |
||||
builtins::Array, |
||||
builtins::Value, |
||||
object::{GcObject, ObjectData}, |
||||
property::{Attribute, DataDescriptor}, |
||||
symbol::WellKnownSymbols, |
||||
BoaProfiler, Context, Result, |
||||
}; |
||||
use gc::{Finalize, Trace}; |
||||
|
||||
#[derive(Debug, Clone, Finalize, Trace)] |
||||
pub enum SetIterationKind { |
||||
Value, |
||||
KeyAndValue, |
||||
} |
||||
|
||||
/// The Set Iterator object represents an iteration over a set. It implements the iterator protocol.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-set-iterator-objects
|
||||
#[derive(Debug, Clone, Finalize, Trace)] |
||||
pub struct SetIterator { |
||||
iterated_set: Value, |
||||
next_index: usize, |
||||
iteration_kind: SetIterationKind, |
||||
} |
||||
|
||||
impl SetIterator { |
||||
pub(crate) const NAME: &'static str = "SetIterator"; |
||||
|
||||
/// Constructs a new `SetIterator`, that will iterate over `set`, starting at index 0
|
||||
fn new(set: Value, kind: SetIterationKind) -> Self { |
||||
SetIterator { |
||||
iterated_set: set, |
||||
next_index: 0, |
||||
iteration_kind: kind, |
||||
} |
||||
} |
||||
|
||||
/// Abstract operation CreateSetIterator( set, kind )
|
||||
///
|
||||
/// Creates a new iterator over the given set.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://www.ecma-international.org/ecma-262/11.0/index.html#sec-createsetiterator
|
||||
pub(crate) fn create_set_iterator( |
||||
context: &Context, |
||||
set: Value, |
||||
kind: SetIterationKind, |
||||
) -> Value { |
||||
let set_iterator = Value::new_object(context); |
||||
set_iterator.set_data(ObjectData::SetIterator(Self::new(set, kind))); |
||||
set_iterator |
||||
.as_object() |
||||
.expect("set iterator object") |
||||
.set_prototype_instance(context.iterator_prototypes().set_iterator().into()); |
||||
set_iterator |
||||
} |
||||
|
||||
/// %SetIteratorPrototype%.next( )
|
||||
///
|
||||
/// Advances the iterator and gets the next result in the set.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-%setiteratorprototype%.next
|
||||
pub(crate) fn next(this: &Value, _: &[Value], context: &mut Context) -> Result<Value> { |
||||
if let Value::Object(ref object) = this { |
||||
let mut object = object.borrow_mut(); |
||||
if let Some(set_iterator) = object.as_set_iterator_mut() { |
||||
let m = &set_iterator.iterated_set; |
||||
let mut index = set_iterator.next_index; |
||||
let item_kind = &set_iterator.iteration_kind; |
||||
|
||||
if set_iterator.iterated_set.is_undefined() { |
||||
return Ok(create_iter_result_object(context, Value::undefined(), true)); |
||||
} |
||||
|
||||
if let Value::Object(ref object) = m { |
||||
if let Some(entries) = object.borrow().as_set_ref() { |
||||
let num_entries = entries.size(); |
||||
while index < num_entries { |
||||
let e = entries.get_index(index); |
||||
index += 1; |
||||
set_iterator.next_index = index; |
||||
if let Some(value) = e { |
||||
match item_kind { |
||||
SetIterationKind::Value => { |
||||
return Ok(create_iter_result_object( |
||||
context, |
||||
value.clone(), |
||||
false, |
||||
)); |
||||
} |
||||
SetIterationKind::KeyAndValue => { |
||||
let result = Array::construct_array( |
||||
&Array::new_array(context), |
||||
&[value.clone(), value.clone()], |
||||
context, |
||||
)?; |
||||
return Ok(create_iter_result_object( |
||||
context, result, false, |
||||
)); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} else { |
||||
return Err(context.construct_type_error("'this' is not a Set")); |
||||
} |
||||
} else { |
||||
return Err(context.construct_type_error("'this' is not a Set")); |
||||
} |
||||
|
||||
set_iterator.iterated_set = Value::undefined(); |
||||
Ok(create_iter_result_object(context, Value::undefined(), true)) |
||||
} else { |
||||
context.throw_type_error("`this` is not an SetIterator") |
||||
} |
||||
} else { |
||||
context.throw_type_error("`this` is not an SetIterator") |
||||
} |
||||
} |
||||
|
||||
/// Create the %SetIteratorPrototype% object
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMA reference][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-%setiteratorprototype%-object
|
||||
pub(crate) fn create_prototype(context: &mut Context, iterator_prototype: Value) -> GcObject { |
||||
let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); |
||||
|
||||
// Create prototype
|
||||
let mut set_iterator = context.construct_object(); |
||||
make_builtin_fn(Self::next, "next", &set_iterator, 0, context); |
||||
set_iterator.set_prototype_instance(iterator_prototype); |
||||
|
||||
let to_string_tag = WellKnownSymbols::to_string_tag(); |
||||
let to_string_tag_property = DataDescriptor::new("Set Iterator", Attribute::CONFIGURABLE); |
||||
set_iterator.insert(to_string_tag, to_string_tag_property); |
||||
set_iterator |
||||
} |
||||
} |
@ -0,0 +1,248 @@
|
||||
use crate::{forward, Context}; |
||||
|
||||
#[test] |
||||
fn construct_empty() { |
||||
let mut context = Context::new(); |
||||
let init = r#" |
||||
var empty = new Set(); |
||||
"#; |
||||
forward(&mut context, init); |
||||
let result = forward(&mut context, "empty.size"); |
||||
assert_eq!(result, "0"); |
||||
} |
||||
|
||||
#[test] |
||||
fn construct_from_array() { |
||||
let mut context = Context::new(); |
||||
let init = r#" |
||||
let set = new Set(["one", "two"]); |
||||
"#; |
||||
forward(&mut context, init); |
||||
let result = forward(&mut context, "set.size"); |
||||
assert_eq!(result, "2"); |
||||
} |
||||
|
||||
#[test] |
||||
fn clone() { |
||||
let mut context = Context::new(); |
||||
let init = r#" |
||||
let original = new Set(["one", "two"]); |
||||
let clone = new Set(original); |
||||
"#; |
||||
forward(&mut context, init); |
||||
let result = forward(&mut context, "clone.size"); |
||||
assert_eq!(result, "2"); |
||||
let result = forward( |
||||
&mut context, |
||||
r#" |
||||
original.add("three"); |
||||
original.size"#, |
||||
); |
||||
assert_eq!(result, "3"); |
||||
let result = forward(&mut context, "clone.size"); |
||||
assert_eq!(result, "2"); |
||||
} |
||||
|
||||
#[test] |
||||
fn symbol_iterator() { |
||||
let mut context = Context::new(); |
||||
let init = r#" |
||||
const set1 = new Set(); |
||||
set1.add('foo'); |
||||
set1.add('bar'); |
||||
const iterator = set1[Symbol.iterator](); |
||||
let item1 = iterator.next(); |
||||
let item2 = iterator.next(); |
||||
let item3 = iterator.next(); |
||||
"#; |
||||
forward(&mut context, init); |
||||
let result = forward(&mut context, "item1.value"); |
||||
assert_eq!(result, "\"foo\""); |
||||
let result = forward(&mut context, "item1.done"); |
||||
assert_eq!(result, "false"); |
||||
let result = forward(&mut context, "item2.value"); |
||||
assert_eq!(result, "\"bar\""); |
||||
let result = forward(&mut context, "item2.done"); |
||||
assert_eq!(result, "false"); |
||||
let result = forward(&mut context, "item3.value"); |
||||
assert_eq!(result, "undefined"); |
||||
let result = forward(&mut context, "item3.done"); |
||||
assert_eq!(result, "true"); |
||||
} |
||||
|
||||
#[test] |
||||
fn entries() { |
||||
let mut context = Context::new(); |
||||
let init = r#" |
||||
const set1 = new Set(); |
||||
set1.add('foo'); |
||||
set1.add('bar'); |
||||
const entriesIterator = set1.entries(); |
||||
let item1 = entriesIterator.next(); |
||||
let item2 = entriesIterator.next(); |
||||
let item3 = entriesIterator.next(); |
||||
"#; |
||||
forward(&mut context, init); |
||||
let result = forward(&mut context, "item1.value.length"); |
||||
assert_eq!(result, "2"); |
||||
let result = forward(&mut context, "item1.value[0]"); |
||||
assert_eq!(result, "\"foo\""); |
||||
let result = forward(&mut context, "item1.value[1]"); |
||||
assert_eq!(result, "\"foo\""); |
||||
let result = forward(&mut context, "item1.done"); |
||||
assert_eq!(result, "false"); |
||||
let result = forward(&mut context, "item2.value.length"); |
||||
assert_eq!(result, "2"); |
||||
let result = forward(&mut context, "item2.value[0]"); |
||||
assert_eq!(result, "\"bar\""); |
||||
let result = forward(&mut context, "item2.value[1]"); |
||||
assert_eq!(result, "\"bar\""); |
||||
let result = forward(&mut context, "item2.done"); |
||||
assert_eq!(result, "false"); |
||||
let result = forward(&mut context, "item3.value"); |
||||
assert_eq!(result, "undefined"); |
||||
let result = forward(&mut context, "item3.done"); |
||||
assert_eq!(result, "true"); |
||||
} |
||||
|
||||
#[test] |
||||
fn merge() { |
||||
let mut context = Context::new(); |
||||
let init = r#" |
||||
let first = new Set(["one", "two"]); |
||||
let second = new Set(["three", "four"]); |
||||
let third = new Set(["four", "five"]); |
||||
let merged1 = new Set([...first, ...second]); |
||||
let merged2 = new Set([...second, ...third]); |
||||
"#; |
||||
forward(&mut context, init); |
||||
let result = forward(&mut context, "merged1.size"); |
||||
assert_eq!(result, "4"); |
||||
let result = forward(&mut context, "merged2.size"); |
||||
assert_eq!(result, "3"); |
||||
} |
||||
|
||||
#[test] |
||||
fn clear() { |
||||
let mut context = Context::new(); |
||||
let init = r#" |
||||
let set = new Set(["one", "two"]); |
||||
set.clear(); |
||||
"#; |
||||
forward(&mut context, init); |
||||
let result = forward(&mut context, "set.size"); |
||||
assert_eq!(result, "0"); |
||||
} |
||||
|
||||
#[test] |
||||
fn delete() { |
||||
let mut context = Context::new(); |
||||
let init = r#" |
||||
let set = new Set(["one", "two"]); |
||||
"#; |
||||
forward(&mut context, init); |
||||
let result = forward(&mut context, "set.delete('one')"); |
||||
assert_eq!(result, "true"); |
||||
let result = forward(&mut context, "set.size"); |
||||
assert_eq!(result, "1"); |
||||
let result = forward(&mut context, "set.delete('one')"); |
||||
assert_eq!(result, "false"); |
||||
} |
||||
|
||||
#[test] |
||||
fn has() { |
||||
let mut context = Context::new(); |
||||
let init = r#" |
||||
let set = new Set(["one", "two"]); |
||||
"#; |
||||
forward(&mut context, init); |
||||
let result = forward(&mut context, "set.has('one')"); |
||||
assert_eq!(result, "true"); |
||||
let result = forward(&mut context, "set.has('two')"); |
||||
assert_eq!(result, "true"); |
||||
let result = forward(&mut context, "set.has('three')"); |
||||
assert_eq!(result, "false"); |
||||
let result = forward(&mut context, "set.has()"); |
||||
assert_eq!(result, "false"); |
||||
} |
||||
|
||||
#[test] |
||||
fn values_and_keys() { |
||||
let mut context = Context::new(); |
||||
let init = r#" |
||||
const set1 = new Set(); |
||||
set1.add('foo'); |
||||
set1.add('bar'); |
||||
const valuesIterator = set1.values(); |
||||
let item1 = valuesIterator.next(); |
||||
let item2 = valuesIterator.next(); |
||||
let item3 = valuesIterator.next(); |
||||
"#; |
||||
forward(&mut context, init); |
||||
let result = forward(&mut context, "item1.value"); |
||||
assert_eq!(result, "\"foo\""); |
||||
let result = forward(&mut context, "item1.done"); |
||||
assert_eq!(result, "false"); |
||||
let result = forward(&mut context, "item2.value"); |
||||
assert_eq!(result, "\"bar\""); |
||||
let result = forward(&mut context, "item2.done"); |
||||
assert_eq!(result, "false"); |
||||
let result = forward(&mut context, "item3.value"); |
||||
assert_eq!(result, "undefined"); |
||||
let result = forward(&mut context, "item3.done"); |
||||
assert_eq!(result, "true"); |
||||
let result = forward(&mut context, "set1.values == set1.keys"); |
||||
assert_eq!(result, "true"); |
||||
} |
||||
|
||||
#[test] |
||||
fn for_each() { |
||||
let mut context = Context::new(); |
||||
let init = r#" |
||||
let set = new Set([5, 10, 15]); |
||||
let value1Sum = 0; |
||||
let value2Sum = 0; |
||||
let sizeSum = 0; |
||||
function callingCallback(value1, value2, set) { |
||||
value1Sum += value1; |
||||
value2Sum += value2; |
||||
sizeSum += set.size; |
||||
} |
||||
set.forEach(callingCallback); |
||||
"#; |
||||
forward(&mut context, init); |
||||
assert_eq!(forward(&mut context, "value1Sum"), "30"); |
||||
assert_eq!(forward(&mut context, "value2Sum"), "30"); |
||||
assert_eq!(forward(&mut context, "sizeSum"), "9"); |
||||
} |
||||
|
||||
#[test] |
||||
fn recursive_display() { |
||||
let mut context = Context::new(); |
||||
let init = r#" |
||||
let set = new Set(); |
||||
let array = new Array([set]); |
||||
set.add(set); |
||||
"#; |
||||
forward(&mut context, init); |
||||
let result = forward(&mut context, "set"); |
||||
assert_eq!(result, "Set { Set(1) }"); |
||||
let result = forward(&mut context, "set.add(array)"); |
||||
assert_eq!(result, "Set { Set(2), Array(1) }"); |
||||
} |
||||
|
||||
#[test] |
||||
fn not_a_function() { |
||||
let mut context = Context::new(); |
||||
let init = r" |
||||
try { |
||||
let set = Set() |
||||
} catch(e) { |
||||
e.toString() |
||||
} |
||||
"; |
||||
assert_eq!( |
||||
forward(&mut context, init), |
||||
"\"TypeError: calling a builtin Set constructor without new is forbidden\"" |
||||
); |
||||
} |
Loading…
Reference in new issue