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