mirror of https://github.com/boa-dev/boa.git
Browse Source
This PR adds a safe wrapper around JavaScript `JsSet` from `builtins::set`, and is being tracked at #2098. Implements following methods - [x] `Set.prototype.size` - [x] `Set.prototype.add(value)` - [x] `Set.prototype.clear()` - [x] `Set.prototype.delete(value)` - [x] `Set.prototype.has(value)` - [x] `Set.prototype.forEach(callbackFn[, thisArg])` Implement wrapper for `builtins::set_iterator`, to be used by following. - [x] `Set.prototype.values()` - [x] `Set.prototype.keys()` - [x] `Set.prototype.entries()` *Note: Are there any other functions that should be added? Also adds `set_create()` and made `get_size()` public in `builtins::set`.pull/2173/head
Anuvrat Singh
2 years ago
32 changed files with 403 additions and 46 deletions
@ -0,0 +1,185 @@ |
|||||||
|
use std::ops::Deref; |
||||||
|
|
||||||
|
use boa_gc::{Finalize, Trace}; |
||||||
|
|
||||||
|
use crate::{ |
||||||
|
builtins::Set, |
||||||
|
object::{JsFunction, JsObject, JsObjectType, JsSetIterator}, |
||||||
|
Context, JsResult, JsValue, |
||||||
|
}; |
||||||
|
|
||||||
|
// This is an wrapper for `JsSet`
|
||||||
|
#[derive(Debug, Clone, Trace, Finalize)] |
||||||
|
pub struct JsSet { |
||||||
|
inner: JsObject, |
||||||
|
} |
||||||
|
|
||||||
|
impl JsSet { |
||||||
|
/// Create a new empty set.
|
||||||
|
///
|
||||||
|
/// Doesn't matches JavaScript `new Set()` as it doesn't takes an iterator
|
||||||
|
/// similar to Rust initialization.
|
||||||
|
#[inline] |
||||||
|
pub fn new(context: &mut Context) -> Self { |
||||||
|
let inner = Set::set_create(None, context); |
||||||
|
|
||||||
|
Self { inner } |
||||||
|
} |
||||||
|
|
||||||
|
/// Returns the size of the `Set` as an integer.
|
||||||
|
///
|
||||||
|
/// Same as JavaScript's `set.size`.
|
||||||
|
#[inline] |
||||||
|
pub fn size(&self, context: &mut Context) -> JsResult<usize> { |
||||||
|
Set::get_size(&self.inner.clone().into(), context) |
||||||
|
} |
||||||
|
|
||||||
|
/// Appends value to the Set object.
|
||||||
|
/// Returns the Set object with added value.
|
||||||
|
///
|
||||||
|
/// Same as JavaScript's `set.add(value)`.
|
||||||
|
#[inline] |
||||||
|
pub fn add<T>(&self, value: T, context: &mut Context) -> JsResult<JsValue> |
||||||
|
where |
||||||
|
T: Into<JsValue>, |
||||||
|
{ |
||||||
|
self.add_items(&[value.into()], context) |
||||||
|
} |
||||||
|
|
||||||
|
/// Adds slice as a single element.
|
||||||
|
/// Returns the Set object with added slice.
|
||||||
|
///
|
||||||
|
/// Same as JavaScript's `set.add(["one", "two", "three"])`
|
||||||
|
#[inline] |
||||||
|
pub fn add_items(&self, items: &[JsValue], context: &mut Context) -> JsResult<JsValue> { |
||||||
|
Set::add(&self.inner.clone().into(), items, context) |
||||||
|
} |
||||||
|
|
||||||
|
/// Removes all elements from the Set object.
|
||||||
|
/// Returns `Undefined`.
|
||||||
|
///
|
||||||
|
/// Same as JavaScript's `set.clear()`.
|
||||||
|
#[inline] |
||||||
|
pub fn clear(&self, context: &mut Context) -> JsResult<JsValue> { |
||||||
|
Set::clear(&self.inner.clone().into(), &[JsValue::Null], context) |
||||||
|
} |
||||||
|
|
||||||
|
/// Removes the element associated to the value.
|
||||||
|
/// Returns a boolean asserting whether an element was
|
||||||
|
/// successfully removed or not.
|
||||||
|
///
|
||||||
|
/// Same as JavaScript's `set.delete(value)`.
|
||||||
|
#[inline] |
||||||
|
pub fn delete<T>(&self, value: T, context: &mut Context) -> JsResult<bool> |
||||||
|
where |
||||||
|
T: Into<JsValue>, |
||||||
|
{ |
||||||
|
match Set::delete(&self.inner.clone().into(), &[value.into()], context)? { |
||||||
|
JsValue::Boolean(bool) => Ok(bool), |
||||||
|
_ => Err(JsValue::Undefined), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Returns a boolean asserting whether an element is present
|
||||||
|
/// with the given value in the Set object or not.
|
||||||
|
///
|
||||||
|
/// Same as JavaScript's `set.has(value)`.
|
||||||
|
#[inline] |
||||||
|
pub fn has<T>(&self, value: T, context: &mut Context) -> JsResult<bool> |
||||||
|
where |
||||||
|
T: Into<JsValue>, |
||||||
|
{ |
||||||
|
match Set::has(&self.inner.clone().into(), &[value.into()], context)? { |
||||||
|
JsValue::Boolean(bool) => Ok(bool), |
||||||
|
_ => Err(JsValue::Undefined), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Returns a new iterator object that yields the values
|
||||||
|
/// for each element in the Set object in insertion order.
|
||||||
|
///
|
||||||
|
/// Same as JavaScript's `set.values()`.
|
||||||
|
#[inline] |
||||||
|
pub fn values(&self, context: &mut Context) -> JsResult<JsSetIterator> { |
||||||
|
let iterator_object = Set::values(&self.inner.clone().into(), &[JsValue::Null], context)? |
||||||
|
.get_iterator(context, None, None)?; |
||||||
|
|
||||||
|
JsSetIterator::from_object(iterator_object.iterator().clone(), context) |
||||||
|
} |
||||||
|
|
||||||
|
/// Alias for `Set.prototype.values()`
|
||||||
|
/// Returns a new iterator object that yields the values
|
||||||
|
/// for each element in the Set object in insertion order.
|
||||||
|
///
|
||||||
|
/// Same as JavaScript's `set.keys()`.
|
||||||
|
#[inline] |
||||||
|
pub fn keys(&self, context: &mut Context) -> JsResult<JsSetIterator> { |
||||||
|
let iterator_object = Set::values(&self.inner.clone().into(), &[JsValue::Null], context)? |
||||||
|
.get_iterator(context, None, None)?; |
||||||
|
|
||||||
|
JsSetIterator::from_object(iterator_object.iterator().clone(), context) |
||||||
|
} |
||||||
|
|
||||||
|
/// Calls callbackFn once for each value present in the Set object,
|
||||||
|
/// in insertion order.
|
||||||
|
/// Returns `Undefined`.
|
||||||
|
///
|
||||||
|
/// Same as JavaScript's `set.forEach(values)`.
|
||||||
|
#[inline] |
||||||
|
pub fn for_each( |
||||||
|
&self, |
||||||
|
callback: JsFunction, |
||||||
|
this_arg: JsValue, |
||||||
|
context: &mut Context, |
||||||
|
) -> JsResult<JsValue> { |
||||||
|
Set::for_each( |
||||||
|
&self.inner.clone().into(), |
||||||
|
&[callback.into(), this_arg], |
||||||
|
context, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
/// Utility: Creates `JsSet` from `JsObject`, if not a Set throw `TypeError`.
|
||||||
|
#[inline] |
||||||
|
pub fn from_object(object: JsObject, context: &mut Context) -> JsResult<Self> { |
||||||
|
if object.borrow().is_set() { |
||||||
|
Ok(Self { inner: object }) |
||||||
|
} else { |
||||||
|
context.throw_error("Object is not a Set") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Utility: Creates a `JsSet` from a `<IntoIterator<Item = JsValue>` convertible object.
|
||||||
|
#[inline] |
||||||
|
pub fn from_iter<I>(elements: I, context: &mut Context) -> Self |
||||||
|
where |
||||||
|
I: IntoIterator<Item = JsValue>, |
||||||
|
{ |
||||||
|
let inner = Set::create_set_from_list(elements, context); |
||||||
|
Self { inner } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl From<JsSet> for JsObject { |
||||||
|
#[inline] |
||||||
|
fn from(o: JsSet) -> Self { |
||||||
|
o.inner.clone() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl From<JsSet> for JsValue { |
||||||
|
#[inline] |
||||||
|
fn from(o: JsSet) -> Self { |
||||||
|
o.inner.clone().into() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Deref for JsSet { |
||||||
|
type Target = JsObject; |
||||||
|
#[inline] |
||||||
|
fn deref(&self) -> &Self::Target { |
||||||
|
&self.inner |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl JsObjectType for JsSet {} |
@ -0,0 +1,56 @@ |
|||||||
|
use std::ops::Deref; |
||||||
|
|
||||||
|
use boa_gc::{Finalize, Trace}; |
||||||
|
|
||||||
|
use crate::{ |
||||||
|
builtins::SetIterator, |
||||||
|
object::{JsObject, JsObjectType}, |
||||||
|
Context, JsResult, JsValue, |
||||||
|
}; |
||||||
|
|
||||||
|
/// JavaScript `SetIterator` rust object
|
||||||
|
#[derive(Debug, Clone, Finalize, Trace)] |
||||||
|
pub struct JsSetIterator { |
||||||
|
inner: JsObject, |
||||||
|
} |
||||||
|
|
||||||
|
impl JsSetIterator { |
||||||
|
/// Create a `JsSetIterator` from a `JsObject`.
|
||||||
|
/// If object is not a `SetIterator`, throw `TypeError`.
|
||||||
|
pub fn from_object(object: JsObject, context: &mut Context) -> JsResult<Self> { |
||||||
|
if object.borrow().is_set_iterator() { |
||||||
|
Ok(Self { inner: object }) |
||||||
|
} else { |
||||||
|
context.throw_type_error("object is not a SetIterator") |
||||||
|
} |
||||||
|
} |
||||||
|
/// Advances the `JsSetIterator` and gets the next result in the `JsSet`.
|
||||||
|
pub fn next(&self, context: &mut Context) -> JsResult<JsValue> { |
||||||
|
SetIterator::next(&self.inner.clone().into(), &[JsValue::Null], context) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl From<JsSetIterator> for JsObject { |
||||||
|
#[inline] |
||||||
|
fn from(o: JsSetIterator) -> Self { |
||||||
|
o.inner.clone() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl From<JsSetIterator> for JsValue { |
||||||
|
#[inline] |
||||||
|
fn from(o: JsSetIterator) -> Self { |
||||||
|
o.inner.clone().into() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Deref for JsSetIterator { |
||||||
|
type Target = JsObject; |
||||||
|
|
||||||
|
#[inline] |
||||||
|
fn deref(&self) -> &Self::Target { |
||||||
|
&self.inner |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl JsObjectType for JsSetIterator {} |
@ -0,0 +1,53 @@ |
|||||||
|
// This example shows how to manipulate a Javascript Set using Rust code.
|
||||||
|
#![allow(clippy::bool_assert_comparison)] |
||||||
|
use boa_engine::{object::JsSet, Context, JsValue}; |
||||||
|
|
||||||
|
fn main() -> Result<(), JsValue> { |
||||||
|
// New `Context` for a new Javascript executor.
|
||||||
|
let context = &mut Context::default(); |
||||||
|
|
||||||
|
// Create an empty set.
|
||||||
|
let set = JsSet::new(context); |
||||||
|
|
||||||
|
assert_eq!(set.size(context)?, 0); |
||||||
|
set.add(5, context)?; |
||||||
|
assert_eq!(set.size(context)?, 1); |
||||||
|
set.add(10, context)?; |
||||||
|
assert_eq!(set.size(context)?, 2); |
||||||
|
set.clear(context)?; |
||||||
|
assert_eq!(set.size(context)?, 0); |
||||||
|
|
||||||
|
set.add("one", context)?; |
||||||
|
set.add("two", context)?; |
||||||
|
set.add("three", context)?; |
||||||
|
|
||||||
|
assert!(set.has("one", context)?); |
||||||
|
assert_eq!(set.has("One", context)?, false); |
||||||
|
|
||||||
|
set.delete("two", context)?; |
||||||
|
|
||||||
|
assert_eq!(set.has("two", context)?, false); |
||||||
|
|
||||||
|
set.clear(context)?; |
||||||
|
|
||||||
|
assert_eq!(set.has("one", context)?, false); |
||||||
|
assert_eq!(set.has("three", context)?, false); |
||||||
|
assert_eq!(set.size(context)?, 0); |
||||||
|
|
||||||
|
// Add a slice into a set;
|
||||||
|
set.add_items( |
||||||
|
&[JsValue::new(1), JsValue::new(2), JsValue::new(3)], |
||||||
|
context, |
||||||
|
)?; |
||||||
|
// Will return 1, as one slice was added.
|
||||||
|
assert_eq!(set.size(context)?, 1); |
||||||
|
|
||||||
|
// Make a new set from a slice
|
||||||
|
let slice_set = JsSet::from_iter([JsValue::new(1), JsValue::new(2), JsValue::new(3)], context); |
||||||
|
// Will return 3, as each element of slice was added into the set.
|
||||||
|
assert_eq!(slice_set.size(context)?, 3); |
||||||
|
|
||||||
|
set.clear(context)?; |
||||||
|
|
||||||
|
Ok(()) |
||||||
|
} |
Loading…
Reference in new issue